一、背景

基于SpringBoot 构建了一个http文件下载服务,检查tomcat access 发现偶尔出现500 状态码的请求,检查抛出的异常堆栈

  1. 2019-03-20 10:03:14,273 ERROR [http-bio-8080-exec-3] o.s.b.w.s.s.ErrorPageFilter - Forwarding to error page from request [/demo.xls] due to exception [org.springframewo
  2. rk.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: InputStream has already been read - do not use InputStreamResource if a stream needs to be
  3. read multiple times]
  4. javax.servlet.ServletException: org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: InputStream has already been read - do not
  5. use InputStreamResource if a stream needs to be read multiple times
  6. at com.vdian.vtrace.VtraceFilter.doFilter(VtraceFilter.java:65)
  7. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
  8. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
  9. at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
  10. at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
  11. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
  12. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
  13. at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
  14. at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
  15. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
  16. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
  17. at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
  18. at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
  19. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
  20. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
  21. at org.springframework.boot.web.servlet.support.ErrorPageFilter.doFilter(ErrorPageFilter.java:115)
  22. at org.springframework.boot.web.servlet.support.ErrorPageFilter.access$000(ErrorPageFilter.java:59)
  23. at org.springframework.boot.web.servlet.support.ErrorPageFilter$1.doFilterInternal(ErrorPageFilter.java:90)
  24. at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
  25. at org.springframework.boot.web.servlet.support.ErrorPageFilter.doFilter(ErrorPageFilter.java:108)
  26. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
  27. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
  28. at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
  29. at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
  30. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
  31. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
  32. at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
  33. at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
  34. at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
  35. at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
  36. at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
  37. at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:956)
  38. at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
  39. at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:436)
  40. at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1078)
  41. at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:625)
  42. at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)
  43. at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
  44. at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
  45. at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
  46. at java.lang.Thread.run(Thread.java:745)
  47. Caused by: org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: InputStream has already been read - do not use InputStreamResource if a stream needs to be read multiple times
  48. at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:986)
  49. at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:870)
  50. at javax.servlet.http.HttpServlet.service(HttpServlet.java:624)
  51. at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:855)
  52. at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
  53. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
  54. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
  55. at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
  56. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
  57. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
  58. at com.vdian.vtrace.VtraceFilter.doFilter(VtraceFilter.java:50)
  59. ... 40 common frames omitted
  60. Caused by: java.lang.IllegalStateException: InputStream has already been read - do not use InputStreamResource if a stream needs to be read multiple times
  61. at org.springframework.core.io.InputStreamResource.getInputStream(InputStreamResource.java:97)
  62. at org.springframework.http.converter.ResourceHttpMessageConverter.writeContent(ResourceHttpMessageConverter.java:130)
  63. at org.springframework.http.converter.ResourceHttpMessageConverter.writeInternal(ResourceHttpMessageConverter.java:124)
  64. at org.springframework.http.converter.ResourceHttpMessageConverter.writeInternal(ResourceHttpMessageConverter.java:45)
  65. at org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:230)
  66. at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:274)
  67. at org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:218)
  68. at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:82)
  69. at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:119)
  70. at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:870)
  71. at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:776)
  72. at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
  73. at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
  74. at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
  75. at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
  76. ... 50 common frames omitted

二、问题排查

从exception message 看,是

  1. java.lang.IllegalStateException: InputStream has already been read - do not use InputStreamResource if a stream needs to be read multiple times

StackOverflow 查一下发现有解决方案,就是把Resonse body 由InputStreamResource 类型改为ByteArrayResource即可,

但想八一八什么原因

(一)初步尝试

取异常请求的 URL,在chrome 浏览器上重试,并没有复现问题

(二)抓包查看

发现 500的请求中有Range Header

(三)问题复现

  1. curl -v -o test.amr -H "Range:bytes=0-" "http://aud.idcheihei.com/se1-sellerexpt-31e9000001698f06b9370a216239.amr"

 使用curl 命令带上Range header 请求后端,请求返回500,复现问题

(四)根据堆栈查看代码

org.springframework.core.io.InputStreamResource#getInputStream

inputstream 只能读取一次内容,所以这里有一个read 成员变量,控制resource 的读取

当resource 被读取一次后,第二次读取就会抛异常

那为什么会读取两次呢?

(五)本地debug

根据堆栈,打断点,org.springframework.core.io.InputStreamResource#getInputStream

逐步调试发现发起第一次getInputStream 调用的地方是:

org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)

由 outputValue = HttpRange.toResourceRegions(httpRanges, resource);抛异常,进入catch 逻辑

org.springframework.http.HttpRange#toResourceRegion 实现体

  1. public ResourceRegion toResourceRegion(Resource resource) {
  2. Assert.isTrue(resource.getClass() != InputStreamResource.class, "Cannot convert an InputStreamResource to a ResourceRegion");
  3.  
  4. try {
  5. long contentLength = resource.contentLength();
  6. Assert.isTrue(contentLength > 0L, "Resource content length should be > 0");
  7. long start = this.getRangeStart(contentLength);
  8. long end = this.getRangeEnd(contentLength);
  9. return new ResourceRegion(resource, start, end - start + 1L);
  10. } catch (IOException var8) {
  11. throw new IllegalArgumentException("Failed to convert Resource to ResourceRegion", var8);
  12. }
  13. }

org.springframework.core.io.AbstractResource#contentLength的实现体:

  1. public long contentLength() throws IOException {
  2. InputStream is = this.getInputStream();
  3.  
  4. try {
  5. long size = 0L;
  6.  
  7. int read;
  8. for(byte[] buf = new byte[255]; (read = is.read(buf)) != -1; size += (long)read) {
  9. }
  10.  
  11. long var6 = size;
  12. return var6;
  13. } finally {
  14. try {
  15. is.close();
  16. } catch (IOException var14) {
  17. }
  18. }
  19. }

 第二次getInputStream 调用的地方是输出内容的地方,/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.class:214

debug 到这里,已定位问题

(六)结论

1、文件下载 http 请求使用range header 时,response 需要设置一个Content-Range header,设置这个header 需要获取response body 的contentlength,对于InputStreamResource这种resource,需要读取整个inputstream的内容后才能得到body 的长度
2、resonse 填充文件内容的时候,由于 inputstream 读取完成后不能再次读取,所以抛出了InputStream has already been read - do not use InputStreamResource if a stream needs to be read multiple times 异常

本文作者:Blyde
原文链接:https://www.cnblogs.com/gliu/p/10570687.html 
版权归作者所有,转载请注明出处

【原创】InputStream has already been read - do not use InputStreamResource if a stream needs to be read multiple times的更多相关文章

  1. InputStream只能读取一次的解决办法 C# byte[] 和Stream转换

    x 情景--->>> 导入文件的时候,前台传过来一个文件, 后台接到: HttpPostedFileBase file = Request.Files[];由于对这个文件后台处理比较 ...

  2. [原创]PostgreSQL Plus Advince Server在 HA环境中一对多的Stream Replication配置(四)

    八.HA环境下配置多节点的sshVIP(s1):[root@s1 ~]# mkdir /opt/PostgresPlus/9.2AS/.ssh[root@s1 ~]# chown enterprise ...

  3. [原创]PostgreSQL Plus Advince Server在 HA环境中一对多的Stream Replication配置(三)

    五.准备HA环境1.准备yum源a.安装vsftp服务,将光盘镜像copy到本地ftp目录作为yum源.[root@s1 ~]# mount 可以看到cdrom已经挂载了,首先安装vsftp服务[ro ...

  4. [原创]PostgreSQL Plus Advince Server在 HA环境中一对多的Stream Replication配置(二)

    三.配置主机与备机的ssh无密码登录1.主机s1到备机s3的无密码登录a.创建ssh目录[root@s1 ~]# mkdir /opt/PostgresPlus/9.2AS/.sshb.修改ssh目录 ...

  5. [原创]PostgreSQL Plus Advince Server在 HA环境中一对多的Stream Replication配置(一)

    内容较多,开篇作为说明和目录. 实验环境规划:服务器:IBM x3500 m3三台其中两台用作HA,另外一台安装VMware ESXi安装两个虚机做Stream Replication.NAS存储IP ...

  6. Spring源码分析——资源访问利器Resource之实现类分析

    今天来分析Spring的资源接口Resource的各个实现类.关于它的接口和抽象类,参见上一篇博文——Spring源码分析——资源访问利器Resource之接口和抽象类分析 一.文件系统资源 File ...

  7. 通过`RestTemplate`上传文件(InputStreamResource详解)

    通过RestTemplate上传文件 1.上传文件File 碰到一个需求,在代码中通过HTTP方式做一个验证的请求,请求的参数包含了文件类型.想想其实很简单,直接使用定义好的MultiValueMap ...

  8. RestTemplate通过InputStreamResource上传文件

    需求:从ftp取文件并http调用某接口上传此文件 偷懒的话可以从ftp上取文件存到本地,再调用接口上传文件,如下 String ftpPath = "/ftp/path/file.bin& ...

  9. Java IO (1) - InputStream

    Java IO (1) - InputStream 前言 JavaIO一共包括两种,一种是stream,一种是reader/writer,每种又包括in/out,所以一共是四种包.Java 流在处理上 ...

随机推荐

  1. JAVA中的配置文件XML

    一:概念 1.XML  Extensible markup Language 可拓展标记语言 2.功能:存储数据(配置文件,在网络中传输数据) 3.html和xml的区别 3.1xml标记全是自定义的 ...

  2. 坑之mysql 字符串与数字操作

    select "123"+1 = 124; select "1a23"+1 = 2; select "aa23"+1 = 1; select ...

  3. Ubuntu 装nexus

    装nexus前提是装好JDK和maven 先下载 wget http://download.sonatype.com/nexus/oss/nexus-2.12.0-01-bundle.tar.gz 再 ...

  4. 商品批量删除(mybatis中集合的使用)

    <!-- 根据主键批量删除 --> <delete id="deleteByKeys"> DELETE FROM product WHERE id in & ...

  5. step_by_step_webapi执行时间

    做开发没多久,这次单位让我做对TB 的机票运价直连接口,其实主要是去sabre gds带上相应的参数去做查询,验仓,下单操作,这次用到asp.net boilerplate 项目模板搭建,用它的动态w ...

  6. echo不换行的实现

    1. echo的参数中, -e表示开启转义, /c表示不换行: echo -e "please input a value:/c" 2. -n不换行: echo -n " ...

  7. FortiGate防火墙HA下联堆叠交换机

    1.拓扑图 2.防火墙配置 3.交换机配置 interface GigabitEthernet1/0/47 switchport access vlan 30 switchport mode acce ...

  8. Centos7+hadoop2.7.3+jdk1.8

     修改主机名 1.       修改主机名 vi /etc/sysconfig/network ,改为 master , slave1 , slave2 2.       source /etc/sy ...

  9. MQ消息队列在软件开发中的作中

    MQ的作用是非常之大的. 1.解耦. 当一个大型的系统.比如,商城系统.包括以下的功能: 1.发邮件 2.发短信 3.抽奖 4.搜索等 如果你都用一台服务器,做到一个程序里,代码会非常庞大,不利于维护 ...

  10. Codeforces Round #436 (Div. 2)C. Bus 模拟

    C. Bus time limit per test: 2 seconds memory limit per test: 256 megabytes input: standard input out ...