https://www.jianshu.com/p/5eb3fd7c499e
原因是由于处理http连接时,正在输出内容时,用户关闭了浏览器,会出现一个"ClientAbortException",属于I/O处理中出现的一个异常,应用服务器应该会捕捉
1、SpringMvc增加全局异常处理捕获
问题描述
Java web系统,每次都会莫名其妙的显示一些org.apache.catalina.connector.ClientAbortException的异常。
网上关于这个问题原因的解释:
- 浏览器关闭了连接,导致数据写入的时候无法写入,然后报客户端打断异常。
解决方案分为几种:
- 浏览器端去解决,提高和后端连接的时间;
- tomcat去做配置;
- nignx去修改。
但是在有基本规模的公司,前后端分离并且已经有一套成熟的底层服务,后端开发人员很少能去修改Tomcat、nignx等服务,只能控制后端代码。所以准备从Java角度解决问题。
解决方案
核心方案
增加全局异常拦截,并且判断异常名称,如果每次为 org.apache.catalina.connector.ClientAbortException,则返回null。
if ("org.apache.catalina.connector.ClientAbortException".equals(ex.getClass().getName())) {
log.error("发生clientAbortException");
return null;
}
使用ResponseEntityExceptionHandler作为全局拦截方案
@ControllerAdvice
@Slf4j
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
protected ResponseEntity<String> handleExceptions(Exception ex, WebRequest request) {
if ("org.apache.catalina.connector.ClientAbortException".equals(ex.getClass().getName())) {
log.error("发生clientAbortException");
return null;
}
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("Content-Type", "application/json;charset=UTF-8");
return new ResponseEntity<String>(
"{\"code\":500,\"data\":null,\"msg\":\"服务器闹脾气,请稍后再试\"}", httpHeaders, HttpStatus.OK);
}
}
@Slf4j是lombok提供的注解,如果你没有使用过,可以自己创建日志。
错误详情
org.apache.catalina.connector.ClientAbortException: java.io.IOException: Broken pipe
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:393)
at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:426)
at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:342)
at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:317)
at org.apache.catalina.connector.CoyoteOutputStream.flush(CoyoteOutputStream.java:110)
at com.fasterxml.jackson.core.json.UTF8JsonGenerator.flush(UTF8JsonGenerator.java:1100)
at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:915)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:286)
at org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:106)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:231)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:174)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:113)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:347)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:263)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:521)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1096)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:674)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.io.IOException: Broken pipe
at sun.nio.ch.FileDispatcherImpl.write0(Native Method)
at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:47)
at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93)
at sun.nio.ch.IOUtil.write(IOUtil.java:65)
at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:471)
at org.apache.tomcat.util.net.NioChannel.write(NioChannel.java:124)
at org.apache.tomcat.util.net.NioBlockingSelector.write(NioBlockingSelector.java:101)
at org.apache.tomcat.util.net.NioSelectorPool.write(NioSelectorPool.java:172)
at org.apache.coyote.http11.InternalNioOutputBuffer.writeToSocket(InternalNioOutputBuffer.java:139)
at org.apache.coyote.http11.InternalNioOutputBuffer.addToBB(InternalNioOutputBuffer.java:197)
at org.apache.coyote.http11.InternalNioOutputBuffer.access$000(InternalNioOutputBuffer.java:41)
at org.apache.coyote.http11.InternalNioOutputBuffer$SocketOutputBuffer.doWrite(InternalNioOutputBuffer.java:320)
at org.apache.coyote.http11.filters.IdentityOutputFilter.doWrite(IdentityOutputFilter.java:93)
at org.apache.coyote.http11.AbstractOutputBuffer.doWrite(AbstractOutputBuffer.java:256)
at org.apache.coyote.Response.doWrite(Response.java:501)
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:388)
2、Nginx解决方案
最近发现服务器上出现很多499的错误,出现499错误的原因是客户端关闭了连接,在我这篇文章:服务端在执行时中途关闭浏览器退出之后php还会继续执行吗?个人实践实验得到结果(http://www.04007.cn/article/356.html )里,测试中断时,服务器nginx的日志就是499记录。nginx报49*错误
400-499 用于指出客户端的错误。 (自己电脑这边的问题) 自己电脑这边的问题)
495 :https certificate error
496 :https no certificate
497 :http to https
498 :canceled
499 :client has closed connection
即499错误是客户端主动断开了连接。 如何关闭报499这个错误码呢?可以通过配置:proxy_ignore_client_abort来处理。
proxy_ignore_client_abort:是否开启proxy忽略客户端中断。即如果此项设置为on开启,则服务器会忽略客户端中断,一直等着代理服务执行返回。并且如果执行没有发生错误,记录的日志是200日志。如果超时则会记录504。如果设置为off,则客户端中断后服务器端nginx立即记录499日志,但要注意,此时代理端的PHP程序会依然继续执行。可查看上面写的那篇文章。
nginx的proxy_ignore_client_abort默认是关闭的,即请求过程中如果客户端端主动关闭请求或者客户端网络断掉,那么Nginx会记录499。所以如果不想看到499报错,可以修改配置:
proxy_ignore_client_abort on ;
这样来说,499错误并不是一个问题,如果出现了大量的499的话,需要考虑为什么发生了这么多的客户端中断的问题。
另外需要注意的一项是:proxy_ignore_client_abort配置只会对代理的配置,如果请求的是当前nginx服务器,直接执行PHP程序返回。则设置proxy_ignore_client_abort为on也不会起作用,仍会是记录499日志。proxy_ignore_client_abort的配置是配置在代理处理时用。如下:
location =/b.php {
proxy_ignore_client_abort on;
proxy_pass http://service_backends;}
3、增加单独Filter 在外捕获异常 或重写DispatcherServlet 不抛出该异常
异常抛出点 .DispatcherServlet.doDispatch(DispatcherServlet.java:967)
根本原因 :类 ServletInvocableHandlerMethod,处理returnvalue 返回值时,客户端提前关闭 流,因为输出流已经关闭,所以会有走这里,抛出ClientAbortException异常
过滤器很简单,放在执行的servlet之前,try catch即可
public class ClientAbortFixFilter implements Filter{
private static final Logger logger = LoggerFactory.getLogger(ClientAbortFixFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request,response);
} catch (IOException e) {
logger.warn("IoException!", e);
}
}
@Override
public void destroy() {
}
}