Migration From HTTP.jl 1.x
HTTP.jl 2.0 is a breaking release with a new HTTP stack underneath. Code that calls HTTP.get, HTTP.post, HTTP.request, HTTP.serve!, HTTP.Router, and HTTP.WebSockets usually needs only small changes. Code that used parser, connection-pool, layer-stack, or other internal APIs has to move to the public API. HTTP.jl 2.0 requires Julia 1.10 or later.
The snippets below assume using HTTP and an existing url.
Checklist
- Read data written with
response_stream = iofromio;resp.bodyisnothing. - Replace
HTTP.downloadwithDownloads.downloador aresponse_streamfile. - Read text from a server request with
String(req.body). - Use
HTTP.get_request_context(req)for cancellation and deadlines. - Replace
poolwith a long-livedHTTP.Client. - Replace
retry_delaysandretry_checkwithretries,retry_if, andHTTP.RetryBucket. - Replace
readtimeoutwith the timeout keyword you need. - Replace
sslconfigwith aTransport'stls_config. - Replace
HTTP.Exceptions.StatusErrorand friends with the top-level names, andHTTP.RequestErrorwith the underlying exception. - Replace
Sockets.getpeername(stream)withHTTP.peeraddr(stream). - Start servers with an interactive thread, such as
julia --threads=4,1. - Re-test redirects, retries, proxies, cookies, streaming, WebSockets, server-sent events, and HTTP/2.
Response Bodies
HTTP.request and the verb helpers still return the whole body as a Vector{UInt8} in resp.body. With response_stream = io, 1.x also stored io in resp.body. In 2.0 resp.body is nothing; read the data from the io you passed:
open("payload.bin", "w") do io
resp = HTTP.get(url; response_stream = io)
@assert resp.body === nothing
endHTTP.download
HTTP.jl 2.0 has no download function. HTTP.download(url, path) still runs, because every module can see Base.download, but it calls Base.download, which uses the Downloads standard library. It accepts none of HTTP.jl's keywords; passing one throws a MethodError.
Use Downloads.download to copy a URL to a file:
using Downloads
Downloads.download(url, "payload.bin")Stream the response into a file when you need HTTP.jl options such as headers, retries, proxies, or a Client:
open("payload.bin", "w") do io
HTTP.get(url; response_stream = io)
endRequests, Responses, and Headers
The 1.x positional constructors still work:
req = HTTP.Request("POST", "/widgets", ["Content-Type" => "text/plain"], "hello")
resp = HTTP.Response(201, ["Location" => "/widgets/1"], "created")The keyword forms take the same values plus trailers, protocol version, and a context:
req = HTTP.Request("POST", "/widgets"; headers = ["Content-Type" => "text/plain"], body = "hello")req.headers and resp.headers are HTTP.Headers, an ordered vector of String => String pairs; in 1.x they were a Vector of SubString pairs. Header names keep the spelling you give them, and lookups such as HTTP.header(resp, "content-type") ignore case. Headers received from the network use Content-Type spelling, while 1.x kept the spelling from the wire. Pass canonicalize_headers = true to send every request header name in that form, as in 1.x. HTTP/2, which 2.0 uses for HTTPS when the server supports it, always sends names in lowercase; pass protocol = :h1 to a server that needs exact spelling. Places that take headers still accept vectors of pairs.
Request Context
req.context is still a Dict{Symbol,Any} for application data:
req.context[:request_id] = request_id
request_id = get(req.context, :request_id, nothing)Cancellation and deadlines live on the HTTP.RequestContext returned by HTTP.get_request_context(req):
ctx = HTTP.get_request_context(req)
HTTP.set_deadline!(ctx, time_ns() + 5_000_000_000)
HTTP.canceled(ctx) && throw(HTTP.CanceledError("request canceled"))Clients, Proxies, and TLS
The pool keyword is ignored. Create a long-lived HTTP.Client instead; it keeps connections, cookies, and settings across requests:
client = HTTP.Client(transport = HTTP.Transport(max_idle_per_host = 4))
try
resp = HTTP.get(url; client = client)
finally
close(client)
endAs in 1.x, requests without a client use proxies from the HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and NO_PROXY environment variables. 2.0 reads them once, when the first such request runs, instead of on every request. The proxy keyword accepts a proxy URL or an HTTP.ProxyConfig, and socks5:// and socks5h:// proxies are supported:
HTTP.get(url; proxy = HTTP.ProxyURL("http://proxy.internal:8080"; no_proxy = "localhost"))
HTTP.get(url; proxy = HTTP.ProxyURL("socks5h://proxy.internal:1080"))
HTTP.get(url; proxy = nothing) # connect directlyThe sslconfig and socket_type_tls keywords are ignored. Configure TLS with a Reseau.TLS.Config on the Transport:
using Reseau
client = HTTP.Client(transport = HTTP.Transport(tls_config = Reseau.TLS.Config(ca_file = "ca.pem")))require_ssl_verification = false still turns off certificate checks for one call.
Retries
The retry_delays and retry_check keywords are ignored. A retry_if function replaces retry_check. Return true to retry and nothing to fall back to the default rules:
Before:
resp = HTTP.get(url; retries = 3, retry_delays = [0.1, 0.5, 1.0],
retry_check = (s, ex, req, resp, resp_body) -> resp !== nothing && resp.status == 409)After:
resp = HTTP.get(url; retries = 3, retry_if = (attempt, err, req, resp) ->
resp !== nothing && resp.status == 409 ? true : nothing)There is no list of delays. Retries wait a random, exponentially growing delay; pass retry_bucket = HTTP.RetryBucket(backoff_scale_factor_ms = 100) to change its scale. 2.0 retries 408, 429, 500, 502, 503, and 504 responses by default, where 1.x also retried 403, 409, and every other 5xx status. See Retries in the client guide for the full default policy.
Timeouts
readtimeout still works but is deprecated. It sets read_idle_timeout, the longest wait between reads. Pick the timeout you mean:
resp = HTTP.get(url; connect_timeout = 5, request_timeout = 60, read_idle_timeout = 30)Timeouts in the client guide describes each keyword. A timeout throws HTTP.TimeoutError, also available as HTTP.HTTPTimeoutError.
Exceptions
The exception types live at the top level: HTTP.StatusError, HTTP.ConnectError, HTTP.TimeoutError, and their supertype HTTP.HTTPError. The 1.x names HTTP.Exceptions.StatusError, ConnectError, TimeoutError, and HTTPError still resolve to them, with a deprecation warning under --depwarn=yes. @try and current_exceptions_to_string are gone.
HTTP.RequestError is gone. Failures throw the underlying exception instead, and HTTP.isrecoverable tells you whether a failure is worth retrying:
try
HTTP.get(url)
catch e
HTTP.isrecoverable(e) || rethrow()
# a transient network failure; retry later
endHTTP.@client
HTTP.@client accepts one middleware or a tuple of them in each position. The first middleware in a tuple is the outermost:
HTTP.@client request_middleware
HTTP.@client request_middleware stream_middleware
HTTP.@client (outer_request, inner_request) (outer_stream, inner_stream)Request middleware wraps request and the verb helpers. Stream middleware wraps open.
Servers
HTTP.serve!, HTTP.serve, HTTP.listen!, HTTP.listen, and HTTP.Router keep their 1.x shapes. Some details changed:
- Server tasks run on Julia's
:interactivethread pool. Start Julia with at least one interactive thread; see the server guide. - In a
serve!handler,req.bodyis a body object rather than aVector{UInt8}. UseString(req.body)to read it as text. HTTP.peeraddr(stream)replacesSockets.getpeername(stream). It returns the client address, ornothing;string(addr)gives"ip:port".- The server
readtimeoutkeyword still works and setsread_timeout. Each server timeout has a seconds keyword (read_timeout) and a nanoseconds keyword (read_timeout_ns).
Before:
HTTP.listen("127.0.0.1", 8080) do stream
ip, port = Sockets.getpeername(stream)
endAfter:
HTTP.listen("127.0.0.1", 8080) do stream
addr = HTTP.peeraddr(stream)
endWebSockets
The HTTP.WebSockets API keeps its 1.x shape: open, listen!, listen, upgrade, isupgrade, send, receive, and iteration over a socket. Two differences:
maxframesizedefaults to 16 MiB instead of no limit. Pass a larger value if your messages are bigger.HTTP.WebSockets.upgradeworks only on HTTP/1.1 connections.
Server-Sent Events
Clients still use the sse_callback keyword:
events = HTTP.SSEEvent[]
HTTP.request("GET", url; sse_callback = event -> push!(events, event))Servers return the response that HTTP.sse_stream builds and write events in its do block:
HTTP.serve!("127.0.0.1", 8080) do req
return HTTP.sse_stream(200) do stream
write(stream, HTTP.SSEEvent("ready"; event = "status", id = "1"))
end
endIgnored 1.x Keywords
The client accepts these 1.x keywords so old calls do not fail, but they have no effect and log a warning: pool, retry_delays, retry_check, sslconfig, socket_type_tls, detect_content_type, observelayers, logerrors, and logtag. readtimeout sets read_idle_timeout, and canonicalize_headers keeps its 1.x meaning.
Internal APIs
The 1.x layer stack, connection pool, parser, and socket/TLS extension points have no 2.0 equivalent. Move that code to Client, Transport, Stream, the server functions, Router, HTTP.WebSockets, or the SSE functions. If a use case cannot be expressed with the public API, open an issue that describes it instead of depending on 2.0 internals.