Tomcat抛出ClientAbortException后导致IO流无法关闭问题

当在Java中使用URL类进行HTTP请求,特别是文件下载时,如果客户端提前断开连接,可能会引发ClientAbortException。此异常通常发生在HttpServletResponse的输出流尝试写入大量数据时。为避免资源泄露,应确保在finally块中正确关闭IO流,先关闭S3Object,然后关闭OutputStream。异常处理和流关闭顺序是解决此类问题的关键。
摘要由CSDN通过智能技术生成

在Java中,使用URL类发送http请求时,如果调用的是一个文件下载接口,而此接口返回的文件数据较大时,如果客户端突然关闭连接,则HttpServletResponse的输出流在写数据的时候会抛出一个ClientAbortException。

org.apache.catalina.connector.ClientAbortException: java.io.IOException: 你的主机中的软件中止了一个已建立的连接。
	at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:351)
	at org.apache.catalina.connector.OutputBuffer.flushByteBuffer(OutputBuffer.java:776)
	at org.apache.catalina.connector.OutputBuffer.append(OutputBuffer.java:681)
	at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:386)
	at org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:364)
	at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:96)
	at org.springframework.util.StreamUtils.copy(StreamUtils.java:167)
	at com.aws.demo.rest.TestController.download(TestController.java:26)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)

这个时候是可以通过异常堆栈信息看到到底是哪一行出的错的。 方法里面报了错之后,后面的代码就不会执行了。

在这里插入图片描述

当outputStream在输出时,如果抛出异常,则代码块中的后续close操作没有被执行,而正式因为没有被执行,导致一个输入流维护的http socket一直阻塞。

在这里插入图片描述
源码中的方法签名上确实明确了,如果此对象不能正确关闭,则会导致请求阻塞。所以这里必须使用try catch finally 结构,在finally中务必关闭IO流。

修改代码结构
 		S3Object s3Object = null;
        InputStream is = null;
        OutputStream os = null;
        try {
            Res res = ossService.doExecute(context, request, response);
            Object data = res.getData();
            if (data instanceof S3Object) {
                s3Object = (S3Object) data;
                //获取此对象的文件名
                String fileName = ossService.tryToGetFileName(key);
                if (S.isBlank(fileName)) {
                    String suffix = ossService.tryToGetSuffix(key);
                    //后缀是不带.的
                    if (S.isNotBlank(suffix)) {
                        fileName = "download." + suffix;
                    }
                }

                //如果依然为空,则设置一个默认名称
                if (S.isBlank(fileName)) {
                    fileName = "download";
                }

                is = s3Object.getObjectContent();
                response.setStatus(200);
                response.setCharacterEncoding("UTF-8");
                response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
                response.setContentType("application/octet-stream");

                //拷贝流
                os = response.getOutputStream();
                int length = StreamUtils.copy(is,os );
                response.setContentLength(length);
            } else {
                //有任何异常,下载的响应状态码为500
                response.setStatus(500);
                response.setContentType("application/json;charset=utf-8");
                response.getWriter().write(JSONObject.toJSONString(res));
                response.getWriter().close();
            }
        } catch (Exception e) {
            log.error(e.getMessage(),e);
        } finally {
            if (s3Object != null){
                s3Object.close();
                log.info("s3Object close 方法调用");
            }
            if (os != null){
                os.close();
                log.info("respose out close 方法调用");
            }

        }

将IO流对象提取到try代码块外面,然后在finally里面关闭流,这里特别注意

finally内的代码块如果有异常,也是不会执行后面的,如果此时先关闭os这个对象,会抛出异常,那么s3Object这个对象依然无法正常关闭,所以要先关闭s3Object这个对象,在关闭os这个对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值