[ANN] HTTP.jl 2.0 release and new package Reseau.jl

Hello! I’m excited to announce two related networking package updates: Reseau.jl and HTTP.jl 2.0.

The short version: HTTP.jl 2.0 is a pretty substantial internals rewrite, now built on top of Reseau.jl: a new lower-level networking package for libuv-free TCP/TLS IO primitives. On top of that, HTTP.jl now has rewritten pure-Julia HTTP/1.1 and HTTP/2 client/server support.

Most of the high-level interface for HTTP remains the same: HTTP.request, HTTP.get, HTTP.post, HTTP.serve, routing, streaming, SSE, and WebSocket flows should remain largely the same. Some deprecated/old keyword arguments have been removed or updated in new/better ways. HTTP.download was also removed in favor of Downloads.download. Because of the amount of internal churn, I felt a 2.0 release was appropriate, but I’m also very open to “unbreaking” anything that may have broken unintentionally. If you run into issues, or find something that seems like it should work, open an issue and we can probably add compat/fix.

A few notable pieces:

  • HTTP/1.1 client/server support was rewritten on top of the new Reseau-based transport layer.
  • HTTP/2 client/server support is now first-class.
  • Reseau provides the new network IO foundation: native TCP/TLS, a Go-inspired IO readiness poller, and a clean wait/notify boundary at the lowest levels that doesn’t require a global IO lock.
  • HTTP.jl 2.0 now requires Julia 1.10+.
  • Both Reseau.jl and HTTP.jl now maintain broad trim-compiled workloads as part of their test suites (though see some discussion/forth-coming Julia fixes for true HTTP trim-compiled workloads to fully work).

One personal goal I’ve had for a while was getting HTTP.jl to the point where it could actually saturate large cloud network links. HTTP.jl 1.x, because of libuv’s global IO lock around network operations, would top out around 10-12 Gbps no matter how much I tuned multithreading. With the Reseau-backed stack, I was able to hit around 46 Gbps from a single Julia process on a machine advertised with 40 Gbps network capacity, using concurrent cloud-store downloads. That’s probably not going to matter for most day-to-day HTTP.jl users, but it removes a ceiling that had been bothering me for a long time and opens up some more serious networking use cases.

Another major goal was making the stack friendlier to trim compilation / juliac. I spent a while pursuing an AWS CRT-based version of this. It got pretty far: cross-platform, very fast, and able to hit the same kind of throughput numbers. But the architecture ended up being a rough fit for trim compilation. CRT wants per-thread event loops where IO work and callbacks run on network threads, which meant a lot of Julia callback/function shuffling across thread boundaries. I kept finding myself needing hack-upon-hack to make that world compile cleanly.

The approach Reseau ended up taking is closer to Go’s network poller model. Julia threads can optimistically do their own IO; when they need to wait, they register the fd with a single readiness poller thread, park, and get woken back up when the IO event is ready. That ended up being much simpler, easier to reason about, and much easier to make trim-compileable while still hitting the performance goals.

There is still one known Julia Task wrinkle: trim-compiled background Tasks currently need a Julia fix. There’s an open proposal/patch for that, and with that patched Julia I can run fully trim-compiled HTTP client/server workloads. The important change in this release is that trim-compileability is now something the packages test and preserve and will maintain going forward.

For breaking changes, I’m hoping the actual surface area is fairly manageable. Since this is a major version, downstream packages need to opt in via compat, so it should not silently break existing environments. Some of the more visible changes:

  • HTTP.download is gone; use Downloads.download / Base.download, or HTTP.request / HTTP.open when you need HTTP-specific control.
  • RequestContext is now typed request state rather than just a plain dictionary, though migration helpers exist.
  • Client retry/timeout/pooling configuration has been cleaned up around the new Client / Transport APIs.
  • Old undocumented layer/parser/connection internals have been completely rewritten, so migrating will be case-by-case.

There is a migration guide in the HTTP.jl docs with more detail.

I’ve had a few personal projects running on this branch for a while without hiccups, and I’ll be working through popular downstream packages to help with compat updates where needed. I’d love to hear about any issues people run into, especially from package maintainers, folks using HTTP.jl in servers, and anyone experimenting with juliac / trim compilation.

Huge thanks to everyone who has tested, reviewed, asked good questions, or filed issues along the way. This was a lot of work over a long period, and it feels great to keep pushing things forward.

Super cool! Have you tested anything related to io_uring? It seems that the performance with epoll is close to optimal so not sure if it’s even worth to explore at this point.