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
ExceptionHandlerwith response typeResponseEntityand returningnull - Defining an
ExceptionHandlerwithvoidreturn type, a parameter of typeHttpServletResponseand- 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.