Affects: Spring Webflux 5.3.20


Running our app in our kubernetes cluster (or simulating it via spring.main.cloud-platform=kubernetes) or using server.forward-headers-strategy=native produces the wrong scheme when calling ServerWebExchange.getRequest().getURI() when forward headers are present.

This seems to be because ReactorServerHttpRequest uses request.scheme() (which netty fills from X-Forwarded-Proto if present), and combines it with the Host header, where there's no clear correlation between those two headers.

Effectively, the following cURL request:

curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: my-gateway" -H "X-Forwarded-Port: 8181" http://localhost:8080/print-uri

... will return ServerWebExchange.getRequest().getURI() = https://localhost:8080/print-uri, even though the scheme used for the request was HTTP.

Minimal Controller Example: https://github.com/mabako/spring-native-forwarding-headers-webflux/blob/master/src/main/java/com/example/demo/PrintUriController.java

Comment From: rstoyanchev

Thanks for the sample.

Indeed, we take the scheme from the Reactor Netty HttpServerRequest, which reflects forwarded headers, but the "Host" header, which we use for the host and port, does not:

https://github.com/spring-projects/spring-framework/blob/d7824c7831eebbce69efeb6ad7331a7512b56070/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java#L80-L114

It looks like up until 5cac619e237f8f71208142ad78c06a7d15b34c72 before we were using the remoteAddress incorrectly (itt's the client address). At that point we switched to using to the "Host" header, falling back on the localAddress which does reflect forwarded headers, but only if the "Host" header is not present.

@violetagg what is your recommendation for how we should be getting the host and address? Is there a reason to look at the "Host" header, or should we always use request.hostAddress()?

/cc @bclozel

Comment From: violetagg

@rstoyanchev I think that X-Forwarded-Proto is the scheme between the client and the proxy/load balancer, isn't it?

The X-Forwarded-Proto (XFP) header is a de-facto standard header for identifying the protocol (HTTP or HTTPS) that a client used to connect to your proxy or load balancer. Your server access logs contain the protocol used between the server and the load balancer, but not the protocol used between the client and the load balancer. To determine the protocol used between the client and the load balancer, the X-Forwarded-Proto request header can be used.

X-Forwarded-Host should be the Host header

The X-Forwarded-Host (XFH) header is a de-facto standard header for identifying the original host requested by the client in the [Host](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host) HTTP request header.

Recently we exposed all information:

See https://projectreactor.io/docs/netty/release/api/reactor/netty/http/server/ConnectionInformation.html It is available from HttpServerRequest and HttpServerResponse

Comment From: rstoyanchev

@violetagg, for the scheme, we use request#scheme() and it reflects X-Forwarded-Proto. In the above example, it is "https", which is the original between client and proxy. This part works as expected, no problem that I see there.

The Host header is "localhost:8080", and not the "mygateway:8181" from the forwarded headers. Is this expected? If not you can run the sample and have a closer look. If it is, then I'm wondering if we should just stop parsing the "Host" header ourselves, and use the API you suggested, ConnectionInformation instead.