Skip to content

Latest commit

 

History

History
68 lines (57 loc) · 4.42 KB

File metadata and controls

68 lines (57 loc) · 4.42 KB
title (go1.15) Fixing Bad Transfer-Encoding
expires_at never
tags
routing-release
1.15

(go1.15) Fixing Bad Transfer-Encoding

Context

To read more about the problem that this doc is fixing, see the release notes for routing-release 0.209.0.

Fixing your apps

In general, to resolve this issue you need to review applications experiencing these errors and make code changes to ensure that multiple or duplicate transfer-encoding headers are not being returned to Gorouter. This is not a change or fix that can be made on the platform itself.

  1. For streaming results from the server to a client, use Spring's built-in support for this. Using a ResponseBodyEmitter or a SseEmitter, you can easily stream content back to your clients and Spring & Tomcat will ensure that the transfer-encoding header is set correctly.

  2. If you created a route service based on the following format, you will be impacted as the example code was copying all response headers, including the transfer-encoding headers from the proxied response to the client response (see the 👈 line below).

        return this.webClient
            .method(request.getMethod())
            .uri(forwardedUrl)
            .headers(headers -> headers.putAll(forwardedHttpHeaders))
            .body((outputMessage, context) -> outputMessage.writeWith(request.getBody()))
            .exchange()
            .map(response -> {
                this.logger.info("Outgoing Response: {}", formatResponse(response.statusCode(), response.headers().asHttpHeaders()));

                return ResponseEntity
                    .status(response.statusCode())
                    .headers(response.headers().asHttpHeaders()) 👈👈👈
                    .body(response.bodyToFlux(DataBuffer.class));
            });

To implement correctly, you will need to strip out the transfer-encoding header, like in this example.

return this.webClient
            .method(request.getMethod())
            .uri(forwardedUrl)
            .headers(headers -> headers.putAll(forwardedHttpHeaders))
            .body((outputMessage, context) -> outputMessage.writeWith(request.getBody()))
            .exchange()
            .map(response -> {
                HttpHeaders headers = getResponseHeaders(response.headers().asHttpHeaders()); 👈👈👈

                this.logger.info("Outgoing Response: {}", formatResponse(response.statusCode(), headers));

                return ResponseEntity
                    .status(response.statusCode())
                    .headers(headers)
                    .body(response.bodyToFlux(DataBuffer.class));
            });
    }

Where the getResponseHeaders function looks like this:

private HttpHeaders getResponseHeaders(HttpHeaders headers) {
    return headers.entrySet().stream()
        .filter(entry -> !entry.getKey().equalsIgnoreCase(TRANSFER_ENCODING))
        .collect(HttpHeaders::new, (httpHeaders, entry) -> httpHeaders.addAll(entry.getKey(), entry.getValue()), HttpHeaders::putAll);
}

As an additional note, copying all headers when proxying traffic is poor practice. The example above strips out a few request headers and the transfer-encoding header on the response, but production applications should be more restrictive to prevent clients from sending headers that manipulate backends in unintended ways (such as the HTTPoxy vulnerability) and to prevent information like CORS headers from leaking out of backend services.

  1. This situation can be mitigated by not directly returning ResponseEntity objects from RestTemplate.exchange. You need to first remove the transfer-encoding header if it is present. You could directly modify that object in your Controller before returning it, but that could get repetitive across many methods and controllers. A less invasive approach is to use a RestTemplate interceptor.

  2. Another option is to utilize Spring WebClient. You may use WebClient in both Spring MVC (Servlet) and Spring Webflux apps. With WebClient, you can return a Flux and that will trigger a streamed response (i.e. transfer-encoding chunked). In this situation, the transfer-encoding header will only be set once.

  3. If you are manually adding any Transfer-Encoding headers, remove them. How you add headers will vary from one language/framework to another. An example in Java would be using HttpServletResponse.addHeader to add a transfer-encoding header.