Skip to content

Add HTTP.peeraddr(::Stream) for the client socket address#1317

Merged
quinnj merged 1 commit into
JuliaWeb:masterfrom
PingoLee:fix/peeraddr
Jun 23, 2026
Merged

Add HTTP.peeraddr(::Stream) for the client socket address#1317
quinnj merged 1 commit into
JuliaWeb:masterfrom
PingoLee:fix/peeraddr

Conversation

@PingoLee

Copy link
Copy Markdown
Contributor

Summary

Adds HTTP.peeraddr(stream), a public accessor returning the remote (client) SocketAddr of 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/.tlsraddr), 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 public remote_addr accessor that both the TCP and TLS transports expose (TLS.remote_addr delegates to the underlying TCP conn), so there is no internal field access and no TLS footgun. It resolves the connection from tracked (the normal path for both HTTP/1 and HTTP/2; an h2 stream is built with tracked plus a shared h2_conn), so it works across protocols and both plain-TCP and TLS. Returns nothing when the peer endpoint is unavailable; throws ArgumentError on a client-side stream.

Naming

Named peeraddr rather than restoring getpeername because the return type is now a Reseau SocketAddr (not a Sockets (ip, port) tuple); reusing the old name with a new return type would be a silent migration trap. Happy to rename to remoteaddr for transport-layer consistency if preferred.

Compatibility

Purely additive. peeraddr is declared public, documented in the server API reference, and the 1.x→2.x migration guide maps getpeernamepeeraddr.

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 ArgumentError and no-live-connection (nothing) edge cases.

🤖 Generated with Claude Code

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

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.68%. Comparing base (b5219f5) to head (9e2380e).

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@quinnj quinnj left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM; thanks!

@quinnj quinnj merged commit 11eb5f8 into JuliaWeb:master Jun 23, 2026
8 checks passed
@fredrikekre

Copy link
Copy Markdown
Member

Why not overload Sockets.getpeername?

@PingoLee

Copy link
Copy Markdown
Contributor Author

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.

@PingoLee PingoLee deleted the fix/peeraddr branch June 23, 2026 10:55
@quinnj

quinnj commented Jun 23, 2026

Copy link
Copy Markdown
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants