Add HTTP.peeraddr(::Stream) for the client socket address#1317
Conversation
HTTP 2.x had no public way to read the client IP from a server stream; callers had to reach through transport internals (stream.tracked.conn -> .tcp/.tls -> raddr), and the naive TCP path throws on TLS connections. Add `HTTP.peeraddr(stream)`, which returns the remote `SocketAddr` of a server stream via the public `remote_addr` accessor on both the TCP and TLS transports. Live server streams carry their connection in `tracked` for both HTTP/1 and HTTP/2 (an h2 stream is constructed with `tracked` and a shared `h2_conn`), so `tracked.conn` is the normal path and `h2_conn` is a defensive fallback. Returns `nothing` when the peer endpoint is unavailable, and throws `ArgumentError` on a client-side stream. This restores the `Sockets.getpeername(::HTTP.Stream)` capability from HTTP.jl 1.x (JuliaWeb#702) and addresses the long-standing client-IP need from JuliaWeb#699 and JuliaWeb#254 -- commonly used for rate limiting, audit logging, and per-client policy. The 1.x -> 2.x migration guide documents the `getpeername` -> `peeraddr` swap. Tests cover HTTP/1, HTTP/2, and TLS server streams plus the client-side and no-live-connection edge cases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1317 +/- ##
==========================================
+ Coverage 87.61% 87.68% +0.07%
==========================================
Files 30 30
Lines 11729 11739 +10
==========================================
+ Hits 10276 10293 +17
+ Misses 1453 1446 -7 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Why not overload |
|
Hi @fredrikekre If I understood correctly, HTTP.jl 1.x Sockets.getpeername(::HTTP.Stream) delegated to Julia’s Sockets.getpeername, returning the standard (ip::Sockets.IPAddr, port::UInt16) tuple. HTTP.jl v2 is built on Reseau, whose native remote endpoint type is Reseau.TCP.SocketAddr (SocketAddrV4/SocketAddrV6), with .ip as an NTuple of octets and .port::UInt16. So I think that even keeping the name, some refactoring would be necessary. Therefore, changing the name might draw more attention to the change and facilitate the migration. But of course, in the age of AI, perhaps this is just nitpicking on my part. |
|
I've tried to keep Reseau/HTTP completely Sockets-free during the migration/2.0 release, mostly as a sanitization thing and because AI models will reach for it all over the place while making fairly mundane changes/features. That said, I agree it's worth considering now that 2.0 has mostly settled; but then again, I think @PingoLee has some good points that, while similar, it's still different machinery under the hood. |
Summary
Adds
HTTP.peeraddr(stream), a public accessor returning the remote (client)SocketAddrof a server stream.Problem
HTTP 2.x has no public way to read the client IP from a server
Stream. In 1.x,Sockets.getpeername(::HTTP.Stream)covered this (PR #702), but there is no v2 equivalent — callers must reach through transport internals (stream.tracked.conn→.tcp/.tls→raddr), and the naive TCP path throws on TLS connections. The client IP is needed for rate limiting, audit logging, and per-client policy. This is a long-standing ask: #699 and #254.Change
peeraddr(stream)reads the remote endpoint via the publicremote_addraccessor that both the TCP and TLS transports expose (TLS.remote_addrdelegates to the underlying TCP conn), so there is no internal field access and no TLS footgun. It resolves the connection fromtracked(the normal path for both HTTP/1 and HTTP/2; an h2 stream is built withtrackedplus a sharedh2_conn), so it works across protocols and both plain-TCP and TLS. Returnsnothingwhen the peer endpoint is unavailable; throwsArgumentErroron a client-side stream.Naming
Named
peeraddrrather than restoringgetpeernamebecause the return type is now a ReseauSocketAddr(not aSockets(ip, port)tuple); reusing the old name with a new return type would be a silent migration trap. Happy to rename toremoteaddrfor transport-layer consistency if preferred.Compatibility
Purely additive.
peeraddris declaredpublic, documented in the server API reference, and the 1.x→2.x migration guide mapsgetpeername→peeraddr.Testing
New testsets cover HTTP/1, HTTP/2, and TLS server streams (client reported as IPv4 loopback with a non-zero source port), plus the client-side
ArgumentErrorand no-live-connection (nothing) edge cases.🤖 Generated with Claude Code