I'm trying to handle inbound connections closed by the client as silently as possible, i. e. not spam the app log with ERROR
messages and stacktraces but only log a brief WARN
message.
I'm using Jetty as embedded server in my Spring Boot app. By default, a closed connection leads to this error:
java.io.IOException: Close SendCallback@7cd49aa0[PROCESSING][i=HTTP/1.1{s=200,h=16,cl=-1},cb=org.eclipse.jetty.server.HttpChannel$SendCallback@e46a817] in state PROCESSING
at org.eclipse.jetty.util.IteratingCallback.close(IteratingCallback.java:428)
at org.eclipse.jetty.server.HttpConnection.onClose(HttpConnection.java:524)
at org.eclipse.jetty.io.ssl.SslConnection.onClose(SslConnection.java:378)
at org.eclipse.jetty.io.SelectorManager.connectionClosed(SelectorManager.java:345)
at org.eclipse.jetty.io.ManagedSelector$DestroyEndPoint.run(ManagedSelector.java:1104)
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(Unknown Source)
I can easily catch this with an @ExceptionHandler
. However the response of my ExceptionHandler causes a subsequent Exception since the channel is closed already:
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver: Failure in @ExceptionHandler com.acme.controller.CommonControllerErrorHandler#handleUncaught(Exception)
org.eclipse.jetty.io.EofException: Closed
at org.eclipse.jetty.server.HttpOutput.checkWritable(HttpOutput.java:771)
at org.eclipse.jetty.server.HttpOutput.write(HttpOutput.java:795)
at org.springframework.security.web.util.OnCommittedResponseWrapper$SaveContextServletOutputStream.write(OnCommittedResponseWrapper.java:638)
at org.springframework.util.StreamUtils$NonClosingOutputStream.write(StreamUtils.java:287)
at com.fasterxml.jackson.core.json.UTF8JsonGenerator._flushBuffer(UTF8JsonGenerator.java:2203)
at com.fasterxml.jackson.core.json.UTF8JsonGenerator.flush(UTF8JsonGenerator.java:1197)
at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:1063)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:456)
at org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:104)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:290)
at org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:219)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:78)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:135)
at org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver.doResolveHandlerMethodException(ExceptionHandlerExceptionResolver.java:428)
I tried not to send a response at all, but these approaches failed:
- Defining an
ExceptionHandler
with response typeResponseEntity
and returningnull
- Defining an
ExceptionHandler
withvoid
return type, a parameter of typeHttpServletResponse
and- doing nothing with the response
- calling
reset()
on the response
Instead, Spring MVC will return HTTP-200 OK
and a Cache-Control
header by default.
Is there any way to stop Spring MVC from doing this? I debugged through the internals of the framework but I'm reluctant to overwrite larger parts of the logic with custom beans. The internals of the exception handling ("behind the scenes") don't seem to be have been designed for being overwritten or extended.