Skip to content
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

Missing First Byte in InputStream with PHP Client on Jetty Server 12 and Jersey #5763

Open
Ostico opened this issue Oct 10, 2024 · 2 comments · May be fixed by #5806
Open

Missing First Byte in InputStream with PHP Client on Jetty Server 12 and Jersey #5763

Ostico opened this issue Oct 10, 2024 · 2 comments · May be fixed by #5806
Assignees

Comments

@Ostico
Copy link

Ostico commented Oct 10, 2024

I noticed a problem with the first byte being missing in the inputStream when I call Jetty server 12 with Jersey as the servlet container. This only happens from PHP with a sufficiently large payload (my test is about 1.5MB). It doesn't happen when the same call is made in Java (but I can't replicate the identical behavior with Java).

It seems that the only Jersey versions exhibiting the issue are 3.1.8 and 3.1.9, not 4.0.0-M1 or < 3.1.7.

java --version
openjdk 17.0.12 2024-07-16
OpenJDK Runtime Environment (build 17.0.12+7-Ubuntu-1ubuntu222.04)
OpenJDK 64-Bit Server VM (build 17.0.12+7-Ubuntu-1ubuntu222.04, mixed mode, sharing)

<jetty.version>12.0.14</jetty.version>

PHP version 7.4.33
PHP version 8.3.12

Affected Jersey Versions:

 <jersey.version>3.1.8</jersey.version>
 <jersey.version>3.1.9</jersey.version>

In this repository:

https://github.com/Ostico/TestBug-Jersey-Container

there are two Main classes that are both Jetty servers. The first Main.java has a Jersey ServletContainer, while the second Main2.java is a pure implementation in Jetty12.

The first one exhibits the bug/problem, while the second does not.

What happens is that when running the PHP script PhpCurlCall.php (essentially a while loop with a POST call), the server at some point starts to return a 500 internal server error (my code raise an exception), indicating that the first byte is different from what was expected.

This happens randomly and sporadically.

/usr/bin/php /home/hashashiyyin/IdeaProjects/TestJetty12/PhpCurlCall.php

Response: {"status":"OK"}
Response: {"status":"OK"}

Response: <html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/>
<title>Error 500 Internal Server Error</title>
</head>
<body>
<h2>HTTP ERROR 500 Internal Server Error</h2>
<table>
<tr><th>URI:</th><td>http://localhost:8080/api/v1/analyze</td></tr>
<tr><th>STATUS:</th><td>500</td></tr>
<tr><th>MESSAGE:</th><td>Internal Server Error</td></tr>
</table>
<hr/><a href="https://jetty.org/">Powered by Jetty:// 12.0.14</a><hr/>

</body>
</html>

Response: {"status":"OK"}
Response: {"status":"OK"}
Response: {"status":"OK"}
Response: {"status":"OK"}
Response: {"status":"OK"}
Response: {"status":"OK"}
Response: {"status":"OK"}
Response: {"status":"OK"}
Response: {"status":"OK"}

Response: <html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/>
<title>Error 500 Internal Server Error</title>
</head>
<body>
<h2>HTTP ERROR 500 Internal Server Error</h2>
<table>
<tr><th>URI:</th><td>http://localhost:8080/api/v1/analyze</td></tr>
<tr><th>STATUS:</th><td>500</td></tr>
<tr><th>MESSAGE:</th><td>Internal Server Error</td></tr>
</table>
<hr/><a href="https://jetty.org/">Powered by Jetty:// 12.0.14</a><hr/>

</body>
</html>

Response: {"status":"OK"}

Response: <html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/>
<title>Error 500 Internal Server Error</title>
</head>
<body>
<h2>HTTP ERROR 500 Internal Server Error</h2>
<table>
<tr><th>URI:</th><td>http://localhost:8080/api/v1/analyze</td></tr>
<tr><th>STATUS:</th><td>500</td></tr>
<tr><th>MESSAGE:</th><td>Internal Server Error</td></tr>
</table>
<hr/><a href="https://jetty.org/">Powered by Jetty:// 12.0.14</a><hr/>

</body>
</html>

This is a TcpDump that allowed me to see that the issue could be related to the Expect: 100-continue header, which is not sent from the Java client.

$# sudo tcpdump -i lo -A -s 0 'tcp port 8080 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'

listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes
18:13:23.928242 IP localhost.46056 > localhost.http-alt: Flags [P.], seq 3037887848:3037888012, ack 2284102578, win 512, options [nop,nop,TS val 3435051440 ecr 3435051440], length 164: HTTP: POST /api/v1/analyze HTTP/1.1
E.....@[email protected]..............}h.$.............
........POST /api/v1/analyze HTTP/1.1
Host: localhost:8080
Accept: */*
Content-Length: 1591670
Content-Type: application/x-www-form-urlencoded
Expect: 100-continue


18:13:23.929355 IP localhost.http-alt > localhost.46056: Flags [P.], seq 1:26, ack 164, win 512, options [nop,nop,TS val 3435051442 ecr 3435051440], length 25: HTTP: HTTP/1.1 100 Continue
E..M..@.@................$....~......A.....
........HTTP/1.1 100 Continue


18:13:23.929432 IP localhost.46056 > localhost.http-alt: Flags [.], seq 164:32932, ack 26, win 512, options [nop,nop,TS val 3435051442 ecr 3435051442], length 32768: HTTP
E..4..@.@.................~..$......~).....
........[{"_id":"6707ffeb262211f0a87ba1ac","index":0,"guid" ... ect... etc ...
@senivam
Copy link
Contributor

senivam commented Nov 14, 2024

I suspect this happens due to this change.

When it is just requestContext.setEntityStream(servletRequest.getInputStream()); the given reproducer works just fine.

BTW, the reproducer is perfect, having it I'm able to investigate the issue quite deeply. Thanks a lot.

I will investigate further on how to possibly fix the issue and keep the introduced changes inline.

@senivam
Copy link
Contributor

senivam commented Nov 22, 2024

the current state of my investigation is, that we cannot eliminate the fact that Jersey sometimes wraps the original request's stream into the PushbackInputStream to determine if the stream is empty. If the available() method of the request's InputStream returns 0, the stream gets wrapped. I've attempted to fix the issue for your case because Jetty does return available() greater than 0, but overall it's not possible to avoid this one-byte reading and wrapping.

Generally speaking, instead of referring to

        @Context
        private HttpServletRequest request;

you can refer to

        @Context
        private Request request;

which is jakarta.ws.rs.core.Request; and then we came to final InputStream entityInputStream = ((org.glassfish.jersey.server.ContainerRequest) request).getEntityStream(); which is InputStream from Jersey (either wrapped or not), so the error with missing the first byte never occurs there.

However, the other question remains - why is the proper stream not being injected into the HttpServletRequest? I continue the investigation to find out why it's not being injected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants