jetty.project icon indicating copy to clipboard operation
jetty.project copied to clipboard

Client: requests with MultiPartRequestContent take excessively long with some servers

Open mperktold opened this issue 1 year ago • 1 comments

Jetty version(s) 12.0.10

Jetty Environment core

Java version/vendor (use: java -version) openjdk version "21.0.3" 2024-04-16 LTS OpenJDK Runtime Environment Temurin-21.0.3+9 (build 21.0.3+9-LTS) OpenJDK 64-Bit Server VM Temurin-21.0.3+9 (build 21.0.3+9-LTS, mixed mode, sharing)

OS type/version Windows 11

Description Some of our requests take an excessively long, but only with some servers and not with others. It appears to be an issue with MultiPartRequestContent; when the same request is made with a StringRequestContent, everything works as expected.

How to reproduce?

I cannot reproduce this with a local Jetty server, but with some public servers that we are working with:

long t0 = System.nanoTime();
var multiPart = new MultiPartRequestContent();
try (multiPart) {
    multiPart.addPart(new MultiPart.ContentSourcePart(
        "action",
        null,
        HttpFields.EMPTY,
        new StringRequestContent("text/plain", "OTA_Ping:Handshaking")
    ));
}
var httpClient = new HttpClient();
httpClient.start();
ContentResponse response = httpClient.newRequest("https://api.iperbooking.net/alpinebits/2017-10/")
    .method(HttpMethod.POST)
    .accept("text/xml, application/xml")
    .body(multiPart)
    .timeout(5, TimeUnit.MINUTES)
    .idleTimeout(5, TimeUnit.MINUTES)
    .onRequestBegin(r -> System.out.println("request begin"))
    .onRequestHeaders(r -> System.out.println("request headers"))
    .onRequestContent((r, c) -> System.out.println("request content"))
    .onRequestSuccess(r -> System.out.println("request success"))
    .onRequestFailure((r, x) -> System.out.println("request failure"))
    .onResponseBegin(r -> System.out.println("response begin"))
    .onResponseHeaders(r -> System.out.println("response headers"))
    .onResponseContent((r, c) -> System.out.println("response content"))
    .onResponseSuccess(r -> System.out.println("response success"))
    .onResponseFailure((r, x) -> System.out.println("response failure"))
    .send();
long t1 = System.nanoTime();
System.out.println("got response after " + TimeUnit.NANOSECONDS.toSeconds(t1 - t0) + " seconds:");
System.out.println(response);
httpClient.stop();

The code above sends a request with multipart content to a specific server and waits for the response, which takes about two minutes to complete. Most of the request and response events are logged, so you can see that the request immediately goes to success, but processing of the response only begins after about two minutes.

However, when the body is first converted to a string, e.g. like this:

...
.body(new StringRequestContent(multiPart.getContentType(), Content.Source.asString(multiPart)))
...

The request completes in about one second.

mperktold avatar Jun 22 '24 05:06 mperktold

I should add that with MultiPartRequestContent, the server responds with 500 Internal Server Error, whereas with StringRequestContent, the server responds with 200 OK.

To me, it seems that in the former case, the request only completes because the server reaches some timeout while waiting for some request content, or something similar.

mperktold avatar Jun 24 '24 12:06 mperktold

@mperktold I double checked that we generate the correct multipart content, and we do.

The multipart content is sent with Transfer-Encoding: chunked.

I am guessing that the server (Microsoft-IIS/8.5) is not able to read this combination.

Googling around a bit, it seems that Microsoft-IIS/8.5 is not able to read chunked uploads, or needs to be configured to do that.

The fact that when you use a StringRequestContent works is probably due to the fact that the client does not use Transfer-Encoding: chunked because it knows the whole length of the string, and the server is able to parse the upload.

Closing, as it does not seem to be a Jetty issue.

sbordet avatar Aug 14 '24 08:08 sbordet