HTTPX
abstract all the network/application connection level gory details from the user, so that, from the user’s perspective, it’s all HTTP. It’s a design decision, therefore a “feature not a bug”.
This document explains what’s the default behaviour, how to enable variants of it (and what are the implications), and alternative usages.
HTTPX
is optimized for concurrent request handling, which means, it will try to reuse resources as much as possible (sockets, TLS sessions) for the maximum number of requests, and will interleave them as much as the underlying protocol allows.
You can just perform requests using IPs in the URLs:
HTTPX.get("http://[::1]/get")
HTTPX.get("http://127.0.0.1/get")
This will use the IPs as the authority. Sometimes, you want to set the authority, but bypass name resolving (because you know the IP of the server or you have an SSH tunnel on a local IP, or DNS is down). You can achieve that by using the :addresses
option:
google_ip = "163.164.165.166"
HTTPX.get("https://google.com", addresses: [google_ip])
:addresses
must be an array of IPs, and in case connecting to the first fails, it’ll try on the next one and so on.
httpx
uses TCP sockets. The sockets will be reused:
HTTPX.get("https://example.com/1", "https://example.com/2")
;HTTPX::Session.wrap
block:
HTTPX.wrap do |http|
r1 = http.get("https://example.com/1")
r2 = http.get("https://example.com/2")
end
:persistent
plugin (sockets will be kept open):
http = HTTPX.plugin(:persistent)
r1 = http.get("https://example.com/1")
r2 = http.get("https://example.com/2")
You can also open the tcp socket and pass it yourself, via the :io
option:
google_sock = TCPSocket.new("163.164.165.166")
HTTPX.get("https://google.com", io: google_sock)
# if socket was created externally, httpx won't close it either!
google_sock.closed? #=> false
# or if you're connecting to multiple domains:
google_sock = TCPSocket.new("163.164.165.166")
amazon_sock = TCPSocket.new("184.24.16.9")
HTTPX.get(
"https://google.com", "https://www.amazon.com",
io: {
"google.com" => google_sock,
"www.amazon.com" => amazon_sock
}
)
google_sock.closed? #=> false
amazon_sock.closed? #=> false
You can use unix sockets by using the :transport
option and setting it to “unix”.
Providing the UNIX socket location can be done using the :addresses
optionn:
server_socket = "/path/to/socket"
HTTPX.get("https://myapp", transport: "unix", addresses: [server_socket])
However, just like in the section above, you can just initialize the socket yourself, and pass it to the httpx
session:
io = UNIXSocket.new("/path/to/socket")
HTTPX.get("https://myapp", transport: "unix", io: { "myapp" => io })
If the request is done using an “https://” URI, TLS will be used, and ALPN negotiation will privilege “h2” if the server supports it. If the server doesn’t support “h2” or ALPN negotiation at all, it will fallback to HTTP/1.1 over TLS.
If the request is done using an “http://” URI, it will use “HTTP/1.1” by default.
If the server supports “cleartext HTTP/2”, you can set the :fallback_protocol
option to "h2"
:
http = HTTPX.get(fallback_protocol: "h2")
http.get("http://nghttp2.org") #=> will use HTTP/2
If you use the h2c or upgrade/h2 plugins, and the server supports upgrading a connection to HTTP/2, it’ll be done using HTTP/2.
If the selected protocol is HTTP/2, requests will be interleaved in the same connection (and socket). Requests will use the same connection if:
(This technique is called “unsharding”, which I picked up from cURL’s blog).
If the selected protocol is HTTP/1, HTTPX
will try to use HTTP pipelining to send multiple requests to the server (the number of requests is controlled by the :max_concurrent_requests
option). If the server doesn’t support pipelining, it will fallback to sending one request at a time in the same connection; if the peer closes the connection however, it falls back again, this time to one request per connection.
Caveat: Some servers/proxies may not fail gracefully when receiving the pipelined requests and may abruptly abort the connection. There is no recovery strategy implemented for that. However, you can disable pipelining by setting :max_concurrent_requests
to 1.
TODO: fill this up when this is possible
Next: Connection Pools