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?
answer
- one handshake, many requests
- block value returned, session closed in ensure
- req["Authorization"] = ...
- keep_alive_timeout defaults to 2
- Ruby 4.0: no default Content-Type
basics
~20 sNet::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.
solid answer
~40 sEach `Net::HTTP.get` call opens a TCP connection (plus a TLS handshake for https), sends one request and closes it. `Net::HTTP.start(host, port, use_ssl: true) { |http| ... }` connects once, lets every `http.request(req)` in the block reuse that keep-alive connection, finishes the session in an `ensure` when the block exits and returns the block's value. Request objects give you control: `Net::HTTP::Get.new(uri)` takes the path and query from the URI and sets `Host`; you add headers with `req["Authorization"] = "Bearer #{token}"` and a body with `req.body = payload`. In Ruby 4.0 (net-http 0.9.1) a request with a body no longer gets `application/x-www-form-urlencoded` by default, so set `req["Content-Type"] = "application/json"` explicitly, or use `set_form_data`, which still sets the form type.
code
ruby · 15 linesrequire "net/http"
require "json"
base = URI("https://api.example.com")
Net::HTTP.start(base.host, base.port, use_ssl: true) do |http|
get = Net::HTTP::Get.new(URI("https://api.example.com/v1/forecast?city=Oslo"))
get["Authorization"] = "Bearer #{ENV.fetch("WEATHER_TOKEN")}"
puts http.request(get).code
post = Net::HTTP::Post.new(URI("https://api.example.com/v1/alerts"))
post["Content-Type"] = "application/json"
post.body = JSON.generate({city: "Oslo", below_c: -10})
puts http.request(post).code
endgo deeper
Recall that Net::HTTP.start with a block opens one connection for several requests and closes it at the end, and that headers are set with req["Name"] = value.
Walk through start's lifecycle (options, connect, yield, ensure finish, block value), the request classes, default headers, and the Ruby 4.0 Content-Type change.
Show you would audit POST call sites after a Ruby 4.0 upgrade for missing Content-Type, and group calls per host into sessions to cut handshake latency.
Decide whether per-call sessions are good enough or whether pooled, shared connections justify adopting a client gem, weighing latency against dependency cost.
## One-shot helpers pay for a connection each time `Net::HTTP.get`, `Net::HTTP.get_response`, `Net::HTTP.post` and `Net::HTTP.put` are convenience class methods. Each one internally calls `Net::HTTP.start`, sends **one** request and closes the connection. For an https URL that means a TCP handshake and a TLS handshake per call. Calling a weather API ten times in a loop through `Net::HTTP.get` therefore opens and tears down ten connections, and the helpers give you no place to set headers beyond a Hash, no timeouts and no status object (with `get`). ## A session with Net::HTTP.start **`Net::HTTP.start`** creates a `Net::HTTP` object and opens a **session**. With a block it: 1. builds the object (`Net::HTTP.new(address, port)`) and applies any options you passed, such as `use_ssl: true`, `open_timeout:` or `read_timeout:`, by calling the matching setter; 2. connects, once; 3. yields the object, so every `http.request(req)`, `http.get(path)` or `http.post(path, data)` inside the block travels over the same **keep-alive** connection; 4. finishes the session in an `ensure`, even when the block raises; 5. returns **the block's value**. Without a block, `start` returns the open object and you must call `finish` yourself. With `use_ssl: true` passed to `Net::HTTP.start`, certificate verification defaults to `OpenSSL::SSL::VERIFY_PEER`. On an object you build with `Net::HTTP.new`, call `http.use_ssl = true` before starting; changing it once the session is open raises `IOError`. Inside a session Net::HTTP tracks idle time. **`keep_alive_timeout`** defaults to **2** seconds: if more than that has passed since the last exchange, the next request closes the socket and reconnects rather than trusting an idle connection. ## Request objects and headers Request classes live under `Net::HTTP` and are all subclasses of `Net::HTTPRequest`: | Class | Body | Typical use | |---|---|---| | `Net::HTTP::Get` | no | read a forecast | | `Net::HTTP::Head` | no | check headers only | | `Net::HTTP::Post` | yes | create a resource, submit a form | | `Net::HTTP::Put` / `Net::HTTP::Patch` | yes | replace or update | | `Net::HTTP::Delete` | no | remove | Building one from a URI (`Net::HTTP::Get.new(uri)`) takes `uri.request_uri`, the path plus query, and sets the `Host` header. Headers are case-insensitive and are set with `req["Name"] = value`, or passed as a Hash in the second argument to `new`. `req.basic_auth(user, pass)` builds a Basic `Authorization` header; `req.body = string` sets the payload; `req.set_form_data(hash)` URL-encodes a Hash and sets the form Content-Type. You send it with `http.request(req)`, which returns a `Net::HTTPResponse`. Net::HTTP fills a few headers for you when you leave them out: - `Accept: */*` and `User-Agent: Ruby`; - `Accept-Encoding` asking for gzip or deflate, with the response body decompressed transparently, unless you set `Accept-Encoding` (or `Range`) yourself. ## The Ruby 4.0 Content-Type change Before Ruby 4.0, a POST or PUT whose request set a body but no `Content-Type` was sent as `application/x-www-form-urlencoded`. **net-http 0.9.1, shipped with Ruby 4.0, removed that default.** Such a request now goes out with a `Content-Length` and **no `Content-Type` at all**. An API that validates the media type may answer `415 Unsupported Media Type` or read the body wrongly. The fix is to say what you send: - `req["Content-Type"] = "application/json"` (or `req.content_type = "application/json"`) for JSON; - `req.set_form_data(params)` for a form, which sets the form type itself; - a headers Hash as the third argument to `Net::HTTP.post(uri, data, headers)`. ## Instance shortcuts versus request objects Inside a session the `Net::HTTP` object also offers shortcuts: `http.get(path, headers)`, `http.head(path)`, `http.post(path, data, headers)`, `http.put(path, data, headers)` and `http.patch(path, data, headers)`. They build the matching request object for you and return the response. They are fine for simple calls; a request object is clearer when you need to set several headers, reuse a prepared request, attach Basic credentials or stream a body with `body_stream`. Both styles share the session's connection, timeouts and TLS settings, so choosing between them is about readability, not performance. ## Rules of thumb - Several calls to one host: one `start` block, many request objects. - Calling `http.request` on an object that was never started still works, but Net::HTTP opens a session for that single request, marks it `Connection: close` and closes it again. - Set timeouts and TLS options as `start` options so they apply before the connection opens. - Always set `Content-Type` on requests with a body.
- What happens when http.request is called on a Net::HTTP object that was never started?`Net::HTTP#request` starts a session itself, sets `Connection: close` on the request unless you already set that header, sends it and finishes the session afterwards. It works, but every call pays for a new TCP and TLS setup, which is what a `start` block avoids.
- What is keep_alive_timeout on a Net::HTTP object, and what is its default?It is how many idle seconds Net::HTTP trusts an open connection inside a session; the default is 2. Before each request it compares the time since the last exchange with that value and, once it has passed, closes the socket and reconnects instead of writing on a connection the server may already have dropped.
- How does req.set_form_data differ from assigning req.body a URL-encoded String?`set_form_data(hash)` encodes the Hash with `URI.encode_www_form` and also sets `Content-Type: application/x-www-form-urlencoded`. Assigning `req.body` only sets the payload; in Ruby 4.0 nothing adds a Content-Type for you, so you must set it yourself.
saying these in an interview costs you the question
- Repeated Net::HTTP.get calls share one connection automatically
- The session stays open after the start block returns and must be closed by hand
- Net::HTTP sets application/json when the body looks like JSON
- Ruby 4.0 still sends POST bodies as form-urlencoded when Content-Type is missing
- use_ssl can be switched on mid-session and applies to the next request