skip to content

Outbound HTTP Calls

Calling services with Net::HTTP.get and Net::HTTP.start blocks, request objects with headers, open_timeout and read_timeout, and URI.open. Interviewers probe timeouts and why teams add a client gem.

on this pageshow

explore

questions

6

In Ruby's Net::HTTP, what does Net::HTTP.get return compared with Net::HTTP.get_response, and how do you check the status code?

level: juniorimportance: must knowfreq 58%

answer

  1. one returns a String, one an object
  2. get is get_response(...).body
  3. no exception on a 404
  4. is_a?(Net::HTTPSuccess)
  5. res.code is the String "200"

basics

~10 s

Net::HTTP.get returns only the response body as a String, whatever the status, so a 404 page arrives without an exception. Net::HTTP.get_response returns a Net::HTTPResponse subclass whose class and String code carry the status.

solid answer

~30 s

`Net::HTTP.get(uri)` is literally `get_response(uri).body`: it opens a connection, sends one GET, closes it and hands back the body String. It never raises for a 4xx or 5xx status, so an error page looks like data. `Net::HTTP.get_response(uri)` returns the response object instead, an instance of a subclass such as `Net::HTTPOK` or `Net::HTTPNotFound`. You branch on it with `res.is_a?(Net::HTTPSuccess)` or `case res when Net::HTTPSuccess`, read `res.code` (the String `"200"`, not an Integer), headers with `res["Content-Type"]` and the body with `res.body`. `res.value` returns nil for a 2xx and raises `Net::HTTPClientException` for a 4xx or `Net::HTTPFatalError` for a 5xx. Neither helper follows redirects.

code

ruby · 13 lines
ruby
require "net/http"

uri = URI("https://api.example.com/v1/forecast?city=Oslo")
res = Net::HTTP.get_response(uri)

case res
when Net::HTTPSuccess
  puts res.body
when Net::HTTPRedirection
  warn "moved to #{res["Location"]}"
else
  warn "forecast failed: #{res.code} #{res.message}"
end

go deeper

for a junior

Know that Net::HTTP.get gives you only a body String and that get_response gives you an object with code, headers and body; show one status check.

for a middle

Explain the response class hierarchy, why is_a?(Net::HTTPSuccess) beats listing codes, that code is a String, and what value raises for each family.

for a senior

Point out that silently parsing an error body is a data-quality bug, insist on status checks at every call site, and handle redirects with a hop limit.

for a principal

Argue for one shared wrapper that turns non-2xx responses into typed errors, so call sites cannot forget the check and failures are logged consistently.

## Two one-shot helpers Ruby's standard library ships **`Net::HTTP`** (the `net-http` default gem, 0.9.1 in Ruby 4.0). `require "net/http"` also loads `uri`, so `URI("https://...")` is available straight away. Two class methods cover the simplest case, one GET to one URL: - **`Net::HTTP.get(uri)`** returns a **String**: the response body and nothing else. - **`Net::HTTP.get_response(uri)`** returns a **`Net::HTTPResponse`** object: status, headers and body together. The source makes the relationship plain: `get` is implemented as `get_response(uri_or_host, path_or_headers, port).body`. Both open a TCP connection (with TLS when the URI's scheme is `https`), send a single request, close the connection and return. Both accept either a URI plus an optional headers Hash, or a host, a path and an optional port. ## Why get hides failures Because `get` throws the response object away, it has **no way to tell you the status**. A weather API that answers `404 Not Found` with an HTML error page, or `503 Service Unavailable` with a JSON error document, still gives you a String. Code that then parses that String as a forecast fails somewhere far from the real cause, or worse, stores the error text as data. `Net::HTTP.get` does not raise for any status code; exceptions come only from the transport (a refused connection, a timeout, a TLS failure). ## What a response object carries `get_response` returns an instance of a class chosen by the status code. The classes form a hierarchy you can test with `is_a?` or `case`/`when`: | Status family | Parent class | Example subclass | `res.value` raises | |---|---|---|---| | 1xx | `Net::HTTPInformation` | `Net::HTTPContinue` | `Net::HTTPError` | | 2xx | `Net::HTTPSuccess` | `Net::HTTPOK`, `Net::HTTPCreated` | nothing, returns nil | | 3xx | `Net::HTTPRedirection` | `Net::HTTPFound` | `Net::HTTPRetriableError` | | 4xx | `Net::HTTPClientError` | `Net::HTTPNotFound` | `Net::HTTPClientException` | | 5xx | `Net::HTTPServerError` | `Net::HTTPServiceUnavailable` | `Net::HTTPFatalError` | Useful readers on the object: - **`res.code`** is a **String** such as `"404"`; comparing it with the Integer `404` is always false. - **`res.message`** is the reason phrase, for example `"Not Found"`. - **`res["Content-Type"]`** reads a header case-insensitively. - **`res.body`** is the body String. - **`res.value`** turns a non-2xx response into an exception, which suits code that treats any failure as fatal. ## Checking status idiomatically 1. Prefer the class test: `res.is_a?(Net::HTTPSuccess)` accepts 200, 201, 204 and the rest of the 2xx family without listing codes. 2. Use `case res` with `when Net::HTTPSuccess`, `when Net::HTTPRedirection` and `else` when each family needs different handling. 3. Reach for `res.code` when a specific code matters, and compare it with a String (`res.code == "429"`). 4. Call `res.value` when you would rather raise than branch; rescue `Net::HTTPClientException` or `Net::HTTPFatalError` where you can recover. ## The host-and-path form is plain HTTP Both helpers also accept a host and a path instead of a URI: `Net::HTTP.get("api.example.com", "/v1/forecast")`. That form connects to port 80 (or the port you pass third) **without TLS**, because only the URI form reads the scheme and turns on `use_ssl` for `https`. Code migrated from an http endpoint to an https one keeps working on the surface while sending traffic in clear text, or fails when the server only listens on 443. Passing a `URI` object is the safer habit: - the scheme decides TLS; - the port comes from the URI, defaulting to 443 for https; - the second argument becomes a headers Hash, for example `{"Accept" => "application/json"}`. ## Redirects are yours to follow Neither helper follows a redirect. A `301` or `302` comes back as a `Net::HTTPRedirection` subclass whose `Location` header names the new URL; following it (with a limit on hops) is your code's job. That is one visible difference from `URI.open` in open-uri, which follows redirects for you. ## Common mistakes - Treating the String from `Net::HTTP.get` as proof of success. - Comparing `res.code` with an Integer. - Expecting `get_response` itself to raise on a 500; only `value` does that. - Forgetting that each call pays for its own connection, which matters once a script makes more than a handful of requests.

  • What does Net::HTTPResponse#value return for a 200 response, and what does it raise for a 503?
    For any 2xx response `value` returns nil and does nothing else. For a 5xx such as 503 it raises `Net::HTTPFatalError`, carrying the response so a rescuer can inspect it; a 4xx raises `Net::HTTPClientException` and a 3xx `Net::HTTPRetriableError`.
  • Net::HTTP.get_response returns a Net::HTTPMovedPermanently. How do you get the new URL, and does Net::HTTP retry there?
    Read `res["Location"]`, which may be absolute or relative, and issue a new request yourself. Net::HTTP never follows redirects, so a loop with a hop limit, as the Net::HTTP documentation sketches, is the usual pattern.

saying these in an interview costs you the question

  • Net::HTTP.get raises an exception when the server answers 404
  • res.code returns an Integer such as 200
  • Net::HTTP.get_response follows redirects to the final page
  • A non-empty body from Net::HTTP.get means the call succeeded
  • get_response raises Net::HTTPFatalError on a 500 without calling value
open as a page

In Ruby's Net::HTTP, what are the default open_timeout and read_timeout, how do you set them, and which exceptions signal them?

level: middleimportance: must knowfreq 52%

basics

~10 s

Both default to 60 seconds. Set them as options to Net::HTTP.start or through http.open_timeout= and read_timeout= before sending; expiry raises Net::OpenTimeout or Net::ReadTimeout, both subclasses of Timeout::Error.

open as a page

In Ruby's Net::HTTP, why use a Net::HTTP.start block with Net::HTTP::Get and Net::HTTP::Post objects instead of repeated Net::HTTP.get calls?

level: middleimportance: should knowfreq 45%

basics

~20 s

Net::HTTP.start opens one connection, reuses it for every request in the block and closes it when the block exits. Request objects such as Net::HTTP::Post carry headers and a body; in Ruby 4.0 you set Content-Type yourself.

open as a page

In Ruby, how do URI.parse, URI.join and URI.encode_www_form help build a request URL, and what trap hides in URI.join?

level: middleimportance: should knowfreq 36%

basics

~20 s

URI.parse turns a String into a URI::HTTP or URI::HTTPS object, URI.join resolves a relative path against a base, and URI.encode_www_form builds an escaped query. URI.join drops the base's last path segment unless it ends in a slash.

open as a page

A Ruby service calls a weather API through Net::HTTP GET with read_timeout set to 5, yet a stalled call takes about 10 seconds to raise Net::ReadTimeout. Why, and what would you change?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Net::HTTP retries an idempotent request such as GET once by default (max_retries is 1) after Net::ReadTimeout, so two 5-second waits elapse. Set max_retries to 0 and handle retries yourself, and remember read_timeout bounds each read, not the total.

open as a page

In Ruby, what does URI.open from open-uri return for an https URL, and how does it treat redirects and error statuses differently from Net::HTTP?

level: juniorimportance: nice to knowfreq 22%

basics

~10 s

URI.open returns a file-like object, a StringIO or a Tempfile for larger bodies, extended with status and content_type readers. Unlike Net::HTTP it follows redirects and raises OpenURI::HTTPError for any non-2xx status.

open as a page