Affects: 5.2.9.RELEASE
According to the specification RFC https://tools.ietf.org/html/rfc7231#section-4.3.2
The server SHOULD send the same
header fields in response to a HEAD request as it would have sent if
the request had been a GET, except that the payload header fields
(Section 3.3) MAY be omitted.
This is the endpoint
private final DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
@GetMapping("/headers")
public ResponseEntity<Flux<DataBuffer>> testHeadersGET() {
return ResponseEntity
.ok()
.body(Flux.defer(() -> {
byte[] bytes = "variableLengthString".getBytes();
return Flux.just(dataBufferFactory.wrap(bytes));
}));
}
With GET request these are the request/response headers
GET /headers HTTP/1.1
Host: localhost:8080
Accept: */*
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/event-stream
With HEAD request these are the request/response headers
GET /headers HTTP/1.1
Host: localhost:8080
Accept: */*
HTTP/1.1 200 OK
Content-Type: text/event-stream
Content-Length: 20
In the first case with GET, transfer-encoding
is used, while in the case with HEAD it is Content-Length: 20
. Why is that?
Also additional question why Spring Framework decides to return Content-Type: text/event-stream
as it is not specified that this is SSE.
This is related to https://github.com/reactor/reactor-netty/issues/1333
Comment From: rstoyanchev
The first issue is that both DataBufferEncoder
and ServerSentHttpMessageWriter
return true
from canWrite
for Flux<DataBuffer>
but the choice to write SSE should be made only if there is something more explicit such as a media type hint (via Accept
header or a produces
condition) or ServerSentEvent
as the return type. By comparison Spring MVC only writes SSE in such conditions. Based on this, "text/event-stream" is the chosen content type but DataBufferEncoder
is still first in the order and used to write.
When this is corrected, "application/octet-stream" is the chosen content type as a fallback. However there is a second issue. For HTTP HEAD in HttpHeadResponseDecorator
we aggregate a Flux
and set the content-length. Generally for HTTP GET we set the content-length only for a Mono
in EncoderHttpMessageWriter
and most other HttpMessageWriter
don't set it either. So we are a little too aggressive in setting a content-length for a Flux
return value which means HEAD and GET won't have the exact same headers but having this on HEAD is also helpful in a way.
A third issue is that DataBufferEncoder
does not implement HttpMessageEncoder
and therefore does not have any knowledge of streaming media types. That means even if the media type was "text/event-stream", EncoderHttpMessageWriter
would still write as a regular response via writeWith
instead of via writeAndFlushWith
. This could come up in a scenario with a proxy that is simply passing raw data from an already fully formatted stream.
Comment From: rstoyanchev
I've updated HttpHeadResponseDecorator
in 5.2.x to avoid adding Content-Length
if there is an existing Content-Length
and Transfer-Encoding
header.
I've extended this further in 5.3 so that Content-Length
is only calculated for a Mono
since normally for a GET HttpMessageWriter
implementations typically do not set a Content-Length
for a Flux
.