Closed konrad-garus closed 7 years ago
I'll move this bug to spring-framework.
@konrad-garus Actually I wouldn't be surprised if something in Spring Boot is complicating things. The JSON looks like it's from DefaultErrorAttributes
and is probably returned from our BasicErrorController
.
Investigating...
Several things going on..
The Servlet API has a timeout notification for async requests. In Spring MVC you can register timeout logic in SseEmitter#onTimeout
(per request) or also globally via WebAsyncConfigurer#configureAsyncSupport
. By default on timeout Spring MVC sets the response to 503 if response is not committed. However with HTTP streaming in Spring Framework 4.2, the response is likely committed so no timeout handling from the Spring MVC side.
In the absence of any concrete timeout logic, Tomcat turns the timeout into an ERROR dispatch which is then picked up by Boot's "/error" mapping and the default ErrorController is responsible for the JSON at the end. Tomcat actually does this with an "include" since the response is committed, and that allows Boot to append to the response.
So my suggestion for Spring Boot is to check if the response is already committed and avoid writing anything in BasicErrorController
. I'd argue in such situations you can't make any assumptions about what the response is and what status, headers, and content have been written so far, so it shouldn't be altered further.
It gets stranger from there as shown in https://jira.spring.io/browse/SPR-13498. If the client connects again the "old" response suddenly starts writing to the "new" request. That I believe is a Tomcat bug and I confirmed that Jetty doesn't behave like that. I've created a bug in Tomcat https://bz.apache.org/bugzilla/show_bug.cgi?id=58457. This is not related to Spring Boot but I mention it for completeness.
Is this fixed? We are planning to SSE using spring boot for one of our important use cases.
It is quite possible that due the fix for https://jira.spring.io/browse/SPR-14669 in 4.3.3 this may no longer be an issue. We no longer set the response to 503 in the timeout handler but instead do an ASYNC dispatch back into the container with an AsyncTimeoutException. So even though we still can't set the response to 503 when the ASYNC dispatch completes, so does the async request.
Confirmed, this works fine now.
Whenever Tomcat async request times out, an error appears in the log and unexpected JSON message is emitted in response. For example, it happens with embedded Tomcat that arrives with Boot, where asyncTimeout appears to be 30 seconds.
To reproduce, create a Spring Boot app with embedded Tomcat. Just spring-boot-starter-web, version 1.3.0.M5. Let the main class look like:
Run the app.
Get the event stream, and observe:
Note the JSON at the very bottom.
In addition, when it happens the server log prints: