Class: Mailkube::NetHttpAdapter
- Inherits:
-
Object
- Object
- Mailkube::NetHttpAdapter
- Defined in:
- lib/mailkube/net_http_adapter.rb,
sig/mailkube/net_http_adapter.rbs
Overview
The default HTTP adapter, and the only file in this gem that touches Net::HTTP.
The adapter contract
An adapter responds to #call(method:, url:, headers:, body:) and returns an
HttpResponse. It raises ConnectionError when the request never produced a response, and
raises nothing else: mapping an HTTP status to an exception is Transport's job, so a
replacement adapter never has to know the API's error envelope.
Pass your own through Client.new(http:) to route through a proxy, add instrumentation, or
drive the client from a test.
Why a fresh connection per request
Net::HTTP.start opens a connection, yields, and closes it. Sharing one Net::HTTP instance
across callers is not a crash bug, it is response cross-talk: two threads interleave on
one socket and one receives the other's body. Inside a single process that is a
confidentiality bug, and it is exactly what spec/concurrency_spec.rb exists to catch.
Connection reuse is therefore left to whoever needs it, with the reason stated: a correct pool has to be safe under threads and under a fiber scheduler, which is more than a scaffold should assume on a caller's behalf.
Constant Summary collapse
- METHODS =
The HTTP verbs this adapter can issue, mapped to their
Net::HTTPrequest classes.A frozen table rather than
const_geton a caller-supplied string, which would turn a method name into an arbitrary constant lookup. { "GET" => Net::HTTP::Get, "POST" => Net::HTTP::Post, "PATCH" => Net::HTTP::Patch, "DELETE" => Net::HTTP::Delete }.freeze
- TRANSPORT_ERRORS =
Errors
Net::HTTPraises when no response was produced. Each becomes a ConnectionError. [ IOError, SocketError, SystemCallError, Timeout::Error, OpenSSL::SSL::SSLError, Net::HTTPBadResponse, Net::ProtocolError ].freeze
Instance Method Summary collapse
-
#build_request(method, uri, headers, body) ⇒ Net::HTTPRequest
The request object for a method, URL, headers and body.
-
#call(method:, url:, headers:, body: nil) ⇒ HttpResponse
Perform one HTTP round trip.
-
#initialize(timeout: Config::DEFAULT_TIMEOUT) ⇒ NetHttpAdapter
constructor
A new instance of NetHttpAdapter.
-
#parse_url(url) ⇒ URI::Generic
Parse the URL, keeping
URI's own exception out of the caller's rescue clauses. -
#to_response(raw) ⇒ HttpResponse
The adapter-neutral view of a
Net::HTTPResponse.
Constructor Details
#initialize(timeout: Config::DEFAULT_TIMEOUT) ⇒ NetHttpAdapter
Returns a new instance of NetHttpAdapter.
56 57 58 59 |
# File 'lib/mailkube/net_http_adapter.rb', line 56 def initialize(timeout: Config::DEFAULT_TIMEOUT) @timeout = timeout freeze end |
Instance Method Details
#build_request(method, uri, headers, body) ⇒ Net::HTTPRequest
Returns the request object for a method, URL, headers and body.
107 108 109 110 111 112 113 114 115 |
# File 'lib/mailkube/net_http_adapter.rb', line 107 def build_request(method, uri, headers, body) request_class = METHODS.fetch(method.upcase) do raise ConfigurationError, "unsupported HTTP method #{method.inspect}" end request = request_class.new(uri) headers.each { |name, value| request[name] = value } request.body = body unless body.nil? request end |
#call(method:, url:, headers:, body: nil) ⇒ HttpResponse
Perform one HTTP round trip.
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/mailkube/net_http_adapter.rb', line 69 def call(method:, url:, headers:, body: nil) uri = parse_url(url) # `Config#build_url` only ever produces a URL with a host, but this adapter is public and # can be driven directly, so the guard is real rather than defensive noise. host = uri.hostname raise ConfigurationError, "URL has no host: #{url.inspect}" if host.nil? request = build_request(method, uri, headers, body) Net::HTTP.start(host, uri.port, use_ssl: uri.scheme == "https", open_timeout: @timeout, read_timeout: @timeout) do |http| to_response(http.request(request)) end rescue *TRANSPORT_ERRORS => e raise ConnectionError, "#{e.class}: #{e.}" end |
#parse_url(url) ⇒ URI::Generic
Parse the URL, keeping URI's own exception out of the caller's rescue clauses.
Everything this adapter can refuse already raises ConfigurationError — an unsupported verb,
a URL with no host — and a malformed URL was the one case where a foreign type escaped
instead. rescue Mailkube::Error is the documented way to catch anything this gem raises,
so a bare URI::InvalidURIError reaching a caller made that promise false. The adapter is
public and can be driven directly, so this is reachable without going through
Config#build_url, which already maps the same two exceptions the same way.
100 101 102 103 104 |
# File 'lib/mailkube/net_http_adapter.rb', line 100 def parse_url(url) URI.parse(url) rescue URI::InvalidURIError, URI::InvalidComponentError => e raise ConfigurationError, "invalid URL #{url.inspect}: #{e.}" end |
#to_response(raw) ⇒ HttpResponse
Returns the adapter-neutral view of a Net::HTTPResponse.
118 119 120 121 |
# File 'lib/mailkube/net_http_adapter.rb', line 118 def to_response(raw) headers = raw.each_header.to_h { |name, value| [name.downcase, value] } HttpResponse.new(status: raw.code.to_i, headers: headers, body: raw.body.to_s) end |