在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这个对象。