Net::HTTP in depth

A GET and its status

Net::HTTP is in the standard library, so a request needs no gem. The shortest form takes a URI and returns a response object whose code decides what happened:

ruby -e 'require "net/http"; puts Net::HTTP.get_response(URI("https://example.com/")).code'

200

The one-liner is fine for a health check. It has no timeout of its own that a caller can see, no retry and no chance to read the body before the connection closes, which is why the next section opens the connection by hand.

Timeouts, headers and SSL

A client that can hang is not a client. open_timeout bounds the connect, read_timeout bounds each read, and use_ssl is what makes an https URI work:

require "net/http" uri = URI("https://example.com/") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.open_timeout = 5 http.read_timeout = 5 res = http.get(uri.path) p res.code p res["content-type"] p res.body.bytesize p res.is_a?(Net::HTTPSuccess)

ruby http_get.rb

"200" "text/html; charset=utf-8" 577 true

res[...] reads a response header; Net::HTTPSuccess is the class for a 2xx, so the check does not compare strings. A request that sends headers passes them as a hash, and a header key is written the way the API expects it.

Redirects

Net::HTTP does not follow a redirect by default. A 3xx is a response with the next URL in the location header, and following it is a decision the client makes:

ruby -e 'require "net/http"; res = Net::HTTP.get_response(URI("https://httpbin.org/redirect/1")); p res.code; p res["location"]'

"302" "/get"

Following is a loop with a limit: resolve the location against the original URI, count the hops, refuse after five, and drop the body before the next request. A redirect that changes the host is also where a credential would leak, so a client that carries an authorization header does not follow one.

A POST

A body and a content type are the two extra arguments. The response is read the same way as a GET, and for JSON the body is parsed after the status check:

ruby -e 'require "net/http"; require "json"; uri = URI("https://httpbin.org/post"); res = Net::HTTP.post(uri, "name=Ada", "Content-Type" => "application/x-www-form-urlencoded"); p res.code; p JSON.parse(res.body)["form"]'

"200" {"name" => "Ada"}

Net::HTTP.post opens and closes the connection for one request. A series of requests to one host is faster through Net::HTTP.start with a block, which keeps the connection open for the whole block.

What to watch for

A 4xx is not an exception in Net::HTTP; it is a response. Check res.is_a?(Net::HTTPSuccess) before parsing the body, or a JSON parse error replaces the useful "401 unauthorized".

A read timeout applies to one read, not to the whole response. A slow server can stream forever under it, so a client with a total budget also checks the clock between chunks.

Next: A REST client that survives

Article author: Arthur Isaev