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.