[
https://issues.apache.org/jira/browse/CXF-9057?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17921676#comment-17921676
]
Johannes Herr commented on CXF-9057:
------------------------------------
Thank you for the explanation. I understand what you mean now.
How Jetty behaves when an exception is thrown can be seen in the test
chunked.ServerMockTest#stream_with_error_and_raw_socket_from_servlet in my
reproducer.
Basically when it receives and exception Jetty will:
1) Send an error status and the default error page if it still has the chance.
That is, if it has not started sending the response. One can try this if one
removes the {{flush()}} call in
{{{}chunked.ServerMock.StreamingServlet#doGet{}}}. The test data is small
enough to be buffered, so when Jetty receveives the exception it instead sends
the stanard error page.
{noformat}
HTTP/1.1 500 Server Error
Cache-Control: must-revalidate,no-cache,no-store
Content-Type: text/html;charset=iso-8859-1
Content-Length: 2646
Connection: close
Server: Jetty(9.4.56.v20240826)
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/>
<title>Error 500 java.lang.IllegalArgumentException: simulated error</title>
</head>
<body><h2>HTTP ERROR 500 java.lang.IllegalArgumentException: simulated
error</h2>
<table>
<tr><th>URI:</th><td>/servlet/endpoint</td></tr>
<tr><th>STATUS:</th><td>500</td></tr>
<tr><th>MESSAGE:</th><td>java.lang.IllegalArgumentException: simulated
error</td></tr>
<tr><th>SERVLET:</th><td>chunked.ServerMock$StreamingServlet-4f8969b0</td></tr>
<tr><th>CAUSED BY:</th><td>java.lang.IllegalArgumentException: simulated
error</td></tr>
</table>
<h3>Caused by:</h3><pre>java.lang.IllegalArgumentException: simulated error
at chunked.ServerMock$StreamingServlet.doGet(ServerMock.java:140)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at
org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:799)
at
org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:554)
at
org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233)
at
org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1440)
at
org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188)
at
org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:505)
at
org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186)
at
org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1355)
at
org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at
org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
at org.eclipse.jetty.server.Server.handle(Server.java:516)
at
org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:487)
at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:732)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:479)
at
org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:277)
at
org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104)
at
org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:883)
at
org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034)
at java.base/java.lang.Thread.run(Thread.java:1583)
</pre>
<hr/><a href="https://jetty.org/">Powered by Jetty:// 9.4.56.v20240826</a><hr/>
</body>
</html>
{noformat}
2. If flush is used and data has been sent already it would not make sense to
include a html page in the middle of some other (maybe binary) data and Jetty
does not do that but skips the final 0 instead.
{noformat}
HTTP/1.1 200 OK
Date: Tue, 28 Jan 2025 09:57:23 GMT
Content-Type: text/plain;charset=utf-8
Transfer-Encoding: chunked
Server: Jetty(9.4.56.v20240826)
7
Data 1
7
Data 2
7
Data 3
7
Data 4
7
Data 5
{noformat}
Both would result in exceptions in the client instead of silently passing
invalid data. I guess the question is if other behaviour is broken? For example
can you still send structured error objects as SOAPFaults when using MTOM in
cases when you recognise the error before sending data (say the request is
invalid or you cannot even connect to the database)? If no existing behaviour
is broken this seems a clear improvement.
I understand that in case 1 where it is still technically possible to send a
better error message it would be nice to do that. I do not know if the servlet
api provides a way to do that though. At the moment the user seems get an empty
file with a success status in this case (error right at the start, while jetty
is still buffering):
{noformat}
HTTP/1.1 200 OK
Date: Tue, 28 Jan 2025 10:13:58 GMT
Content-Type: multipart/related; type="application/xop+xml";
boundary="uuid:e362c4ac-b774-47ed-9c09-0057c03dc0af";
start="<[email protected]>"; start-info="text/xml"
Content-Length: 770
Server: Jetty(10.0.21)
--uuid:e362c4ac-b774-47ed-9c09-0057c03dc0af
Content-Type: application/xop+xml; charset=UTF-8; type="text/xml"
Content-Transfer-Encoding: binary
Content-ID: <[email protected]>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:downloadNextResponse
xmlns:ns2="http://www.example.com/"><ns2:downloadNextResponse><dataContent><xop:Include
xmlns:xop="http://www.w3.org/2004/08/xop/include"
href="cid:[email protected]"/></dataContent></ns2:downloadNextResponse></ns2:downloadNextResponse></soap:Body></soap:Envelope>
--uuid:e362c4ac-b774-47ed-9c09-0057c03dc0af
Content-Type:
Content-Transfer-Encoding: binary
Content-ID: <[email protected]>
{noformat}
(The closing multipart boundary seems missing, but still no error in spite of
that.)
So that is also a silent data corruption. Any kind of exception in the client
seems better (if it can be implemented without breaking existing behaviour.)
> Chunked Stream is closed regularly when Exception is thrown
> -----------------------------------------------------------
>
> Key: CXF-9057
> URL: https://issues.apache.org/jira/browse/CXF-9057
> Project: CXF
> Issue Type: Bug
> Affects Versions: 3.5.8
> Reporter: Johannes Herr
> Assignee: Andriy Redko
> Priority: Major
> Fix For: 3.5.11, 3.6.6, 4.0.7, 4.1.1
>
> Attachments: activate-chunked-in-unit-text.patch,
> chunked-reproducer.tar.gz
>
>
> In response to SOAP requests served by Apache CXF we send large datasets
> streamed directly from a database. Because of the size of the data, chunked
> encoding is used. Unfortunately when a database error occurs and an exception
> is thrown while data is sent, the client will receive a regular ending to the
> chunked data stream. That means he has no way to recognise that an error
> occurred and that he has only received a partial file.
> To be more specific the response looks somewhat like this:
> {code:java}
> HTTP/1.1 200 OK
> Date: Tue, 10 Sep 2024 16:17:45 GMT
> Content-Type: multipart/related; type="application/xop+xml";
> boundary="uuid:49aa53f9-ec29-4f1a-bc07-a21256c2f940";
> start="<[email protected]>"; start-info="text/xml"
> Transfer-Encoding: chunked
> Server: Jetty(10.0.21)
> 8000
> --uuid:49aa53f9-ec29-4f1a-bc07-a21256c2f940
> Content-Type: application/xop+xml; charset=UTF-8; type="text/xml"
> Content-Transfer-Encoding: binary
> Content-ID: <[email protected]>
> <soap:Envelope
> xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">[...]</soap:Envelope>
> --uuid:49aa53f9-ec29-4f1a-bc07-a21256c2f940
> Content-Type:
> Content-Transfer-Encoding: binary
> Content-ID: <[email protected]>
> xxxxxxxxxxxx[...]xxxxxxx
> 356
> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
> 0
> {code}
> That means the response ends with the "0" entry, indicating that the transfer
> is complete.
> What should happen instead is that the response should be closed without
> sending the final 0 entry. ([https://stackoverflow.com/a/17203961/136247])
> For example when we use a Servlet to stream data to a client a throw an
> exception the result will look something like this:
> {code:java}
> HTTP/1.1 200 OK
> Date: Fri, 13 Sep 2024 09:31:48 GMT
> Content-Type: text/plain;charset=utf-8
> Transfer-Encoding: chunked
> Server: Jetty(10.0.21)
> 8
> Chunk 1
> 8
> Chunk 2
> 8
> Chunk 3
> 8
> Chunk 4
> 8
> Chunk 5
> {code}
> Here there is no closing 0 marker. As a result clients can recognise the
> error. (Curl will report "curl: (18) transfer closed with outstanding read
> data remaining", Chrome "Failed to load resource:
> net::ERR_INCOMPLETE_CHUNKED_ENCODING")
> CXF should do the same and not end the chunked stream regularly, when an
> exception is thrown.
> I have tested with Tomcat and Jetty. Both show the same behaviour.
> To provide some detail, Exceptions are caught by CXF here:
> org/apache/cxf/phase/PhaseInterceptorChain.java:328
> {code:java}
> } catch (RuntimeException ex) {
> if (!faultOccurred) {
> faultOccurred = true;
> wrapExceptionAsFault(message, ex);
> }
> state = State.ABORTED;
> }
> {code}
> The exception is logged, but not rethrown.
> If the exception would be thrown, for instance Tomcats ErrorReportValve would
> shut down the output and thereby prevent the end of the input and closing 0
> to be written.
> org/apache/catalina/valves/ErrorReportValve.java:112
> {code:java}
> // Now close immediately to signal to the client that
> // something went wrong
> response.getCoyoteResponse().action(ActionCode.CLOSE_NOW,
>
> request.getAttribute(RequestDispatcher.ERROR_EXCEPTION));
> {code}
> Without an exception to prevent this, a regular end of the stream will be
> written by
> org/apache/catalina/connector/CoyoteAdapter.java:371
> response.finishResponse();
> In Summary: CXF ends a chunked encoded stream in a regular way in spite of
> exceptions occurring, which makes it impossible for clients to recognise that
> they have downloaded partial and therefore corrupt data.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)