Skip to content

Reactive stack on Tomcat does not handle chunked transfer encoding correctly #32427

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
jshs opened this issue Mar 12, 2024 · 9 comments
Closed
Assignees
Labels
for: external-project Needs a fix in external project in: web Issues in web modules (web, webmvc, webflux, websocket) status: feedback-provided Feedback has been provided

Comments

@jshs
Copy link

jshs commented Mar 12, 2024

Summary

HTTP 1.1 request bodies using Transfer-Encoding: chunked are sometimes rejected when running a reactive servlet on top of Tomcat.
This affects at least Spring Boot 3.2.3 when combining the webflux and tomcat starters.

Example

A complete code base to reproduce the problem is available here.

It consists of a simple server application with a POST endpoint:

  @PostMapping(path = "/length", consumes = "*/*", produces = "application/json")
  public Mono<Integer> length(ServerHttpRequest request) {
    return request.getBody().map(DataBuffer::readableByteCount).reduce(0, Integer::sum);
  }

and a client that streams a request to this endpoint:

  private static final int LENGTH = 1_000_000;

  void test() {
    Integer result = WebClient.builder()
        .baseUrl("http://localhost:" + port)
        .build()
        .post()
        .uri("/length")
        .body(BodyInserters.fromOutputStream(this::writeBody, executor))
        .retrieve()
        .bodyToMono(Integer.class)
        .block();
    assertEquals(LENGTH, result);
  }

  private void writeBody(OutputStream output) {
    try {
      for (int i = 0; i < LENGTH; ++i) {
        output.write(65);
        output.flush();
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

The expected outcome is that the server responds as asserted. Instead, we observe a prematurely closed connection on the client and the following exception on the server:

org.apache.coyote.BadRequestException: Invalid chunk header

(others might be possible)

This example was extracted from a more complex application where we first noticed the problem, hence the artificial client setup.

Remarks

The above example is potentially a flaky test, although I can reliably reproduce the issue on my machine. The problem does not seem to occur with Undertow, nor if MVC is used on the server.

My hypothesis is that the Coyote HTTP/1.1 connector does not support the combination of a non-blocking socket and chunked transfer encoding. When parsing chunk headers or the CRLF sequence after each chunk, a read size of 0 results in the request being aborted:

These parsers would need to store their state and yield control (like doRead) if no input is available at the socket.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Mar 12, 2024
@bclozel bclozel transferred this issue from spring-projects/spring-boot Mar 12, 2024
@bclozel bclozel added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Mar 12, 2024
@poutsma
Copy link
Contributor

poutsma commented Apr 3, 2024

So far, I am not capable of reproducing the issue with the linked sample. Could you please specify which OS and JVM versions you are using?

@poutsma poutsma added the status: waiting-for-feedback We need additional information before we can continue label Apr 3, 2024
@jshs
Copy link
Author

jshs commented Apr 4, 2024

Thanks for looking into this issue. I observe the test failure using Microsoft Windows 11 Pro (10.0.22631 Build 22631) and OpenJDK 64-Bit Server VM 17.0.8.1 (Temurin). It doesn't appear to be reproducible on Linux, however.

Note: The --rerun flag should be used with gradle test to ensure that the test is actually run. I've updated the readme.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Apr 4, 2024
@poutsma poutsma self-assigned this Apr 4, 2024
@poutsma
Copy link
Contributor

poutsma commented Apr 4, 2024

I can verify that the tests fails under Windows 10 as well 11 (but not MacOS nor Linux). I can also add that the test succeeds with Jetty as well as Undertow; just not Tomcat.

@poutsma
Copy link
Contributor

poutsma commented Apr 4, 2024

@markt-asf, does @jshs 's theory about the ChunkedInputFilter, listed in Remarks above, hold water? Or is Spring Framework doing something wrong here?

@poutsma
Copy link
Contributor

poutsma commented Apr 4, 2024

Additionally, the test succeeds when using a JdkClientHttpConnector for the WebClient, instead of the default ReactorClientHttpConnector. So the issue only seems to occur when using Reactor Netty on the client in combination with Tomcat on the server.

@markt-asf
Copy link

@poutsma The theory that Tomcat doesn't handle a non-blocking read of a chunk body if a partial chunk header is received looks plausible. I'll create a Tomcat unit test to simulate that scenario and report back.

@markt-asf
Copy link

@poutsma Confirmed. It was a Tomcat bug. The bug has been fixed in all currently supported branches and will be included from the May releases.

@poutsma poutsma added for: external-project Needs a fix in external project and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Apr 29, 2024
@poutsma
Copy link
Contributor

poutsma commented Apr 29, 2024

Thank you, @markt-asf. Is there an issue we can to link to here?

@poutsma poutsma closed this as not planned Won't fix, can't repro, duplicate, stale Apr 29, 2024
@markt-asf
Copy link

No issue, but this is the key commit for main: apache/tomcat@cbed8e1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
for: external-project Needs a fix in external project in: web Issues in web modules (web, webmvc, webflux, websocket) status: feedback-provided Feedback has been provided
Projects
None yet
Development

No branches or pull requests

5 participants