Skip to content

Conversation

@alukach
Copy link
Contributor

@alukach alukach commented Dec 15, 2025

What I'm changing

Updates the ProxyHeaderMiddleware._get_forwarded_url_parts() to better respect proxy forwarding headers. This ensures that applications behind reverse
proxies/load balancers can correctly determine the original client request URL.

How I did it

Rework the middleware to determine the port using the following priority order:

Port Resolution Rules

The middleware determines the port using the following priority order:

  1. Initial Port Setup

    Start with protocol default: 443 for https, 80 for http

    If Host header exists:

    • With explicit port → use that port
    • Without port → keep protocol default

    If no Host header:

    • Use scope["server"] port (server's listening port)
  2. Forwarding Header Override

    The middleware checks for forwarding headers in priority order:

    Standard Forwarded header (highest priority):

    • If host=domain:port → use explicit port
    • If host=domain (no port) → apply protocol default based on proto value
    • If host=domain:invalid (malformed port) → apply protocol default

    Non-standard X-Forwarded-* headers (fallback):

    • If X-Forwarded-Port is present and valid → use that port
    • If X-Forwarded-Host or X-Forwarded-Proto is present without valid port → apply protocol
      default

Examples

Protocol changes trigger port defaults:

# Original request
scheme: "http"
server: ["localhost", 8080]
headers: [(b"`x-forwarded-proto`", b"https")]
# Result
("https", "localhost", 443)  # port changed from 8080 to 443

Explicit ports are always respected:

# Original request
scheme: "http"
server: ["localhost", 8080]
headers: [(b"forwarded", b"proto=https;host=api.example.com:8443")]
# Result
("https", "api.example.com", 8443)

Invalid ports use protocol default (not original port):

# Original request
scheme: "http"
server: ["testserver", 80]
headers: [(b"forwarded", b"proto=https;host=test:not-an-integer")]
# Result
("https", "test", 443)  # uses protocol default, not 80

Server port only used when no forwarding occurs:

# Original request (no Host header, no forwarding headers)
scheme: "https"
server: ["localhost", 8000]
headers: []
# Result
("https", "localhost", 8000)  # server port preserved

Domain changes without port trigger protocol default:

# Original request
scheme: "http"
server: ["testserver", 8080]
headers: [
    (b"host", b"example.com:8080"),
    (b"`x-forwarded-proto`", b"https"),
    (b"`x-forwarded-host`", b"myproxy.com")
]
# Result
("https", "myproxy.com", 443)  # not 8080

Error Handling

  • Invalid port strings in Host header → silently ignored, keeps protocol default
  • Invalid port strings in forwarding headers → treated as missing, uses protocol default
  • Multiple chained proxies → uses the last entry in comma-separated Forwarded header
  • All ValueError exceptions during port conversion are suppressed

Related Issue(s):

PR Checklist:

  • pre-commit hooks pass locally
  • Tests pass (run make test)
  • Documentation has been updated to reflect changes, if applicable, and docs build successfully (run make docs)
  • Changes are added to the CHANGELOG.

@vincentsarago
Copy link
Member

@alukach looks good to me but I didn't really follow all the logic. I'll trust your expertise on this.

Would you be able to update the changelog and do a patch release with this?

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.

2 participants