项目中有一个下载docx模板文件的功能。开发同学反馈:本地测试可以正常下载;部署到测试服务器后,下载的文件为空。
定位问题开发环境和测试环境有哪些差异呢?
- 环境差异
- 开发环境为windows
- 测试环境为linux
- 因java跨平台,这个差异基本可排除
- 运行方式差异
- 开发环境直接从IDE中run
- 测试环境是打包为jar后在run
- 本地打包jar后run,可复现
下载文件为空的代码
@GetMapping("/downloadEmpty") public void downloadEmpty(HttpServletResponse res) { String path = "templates/demo.docx"; ClassLoader classLoader = Demo3Application.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(path); OutputStream outputStream = res.getOutputStream()) { res.addHeader("Content-Disposition", "attachment;filename=empty.docx"); res.addHeader("Content-Length", String.valueOf(inputStream.available())); res.setContentType("application/octet-stream"); byte[] bys = new byte[1024]; int len; while ((len = inputStream.read(bys)) != -1) outputStream.write(bys, 0, len); outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } }
以下代码输出docx文件的一些信息,经分析可知:读取到的inputStream正常,只是inputStream.available() == 0
@GetMapping("/docx") public String doc() { StringBuilder sb = new StringBuilder(); String path = "templates/demo.docx"; ClassLoader classLoader = Demo3Application.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(path)) { sb.append("path: ") .append(path) .append("解决方案
ClassLoader: ") .append(classLoader.getClass()) .append("
InputStream: ") .append(inputStream.getClass()) .append("
inputStream.available: ") .append(inputStream.available()); byte[] bys = new byte[1024]; int len = 0, total = 0; while ((len = inputStream.read(bys)) != -1) total += len; sb.append("
length: " + total); } catch (IOException e) { e.printStackTrace(); } return sb.toString(); }
经测试及分析,注释掉res.addHeader(“Content-Length”, String.valueOf(inputStream.available()));即可;经验证,下载文件正常,代码如下:
@GetMapping("/download") public void download(HttpServletResponse res) { String path = "templates/demo.docx"; ClassLoader classLoader = Demo3Application.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(path); OutputStream outputStream = res.getOutputStream()) { res.addHeader("Content-Disposition", "attachment;filename=demo.docx"); // res.addHeader("Content-Length", String.valueOf(inputStream.available())); res.setContentType("application/octet-stream"); byte[] bys = new byte[1024]; int len; while ((len = inputStream.read(bys)) != -1) outputStream.write(bys, 0, len); outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } }原理分析
经测试及分析,看似下载文件为空的问题,实质是获取到输入流inputStream.available() == 0的问题。为什么会返回0呢?
我们观察到读取docx文件时候,返回的inputStream是org.springframework.boot.loader.data.RandomAccessDataFile$DataInputStream;翻开源码看一下,DataInputStream继承了InputStream,而InputStream的available()方法直接返回了0。
在实际验证问题的过程中并没有这么一帆风顺。起初我使用了一个txt文件来验证此问题,但是无法重现,也就是说,打包jar后也可以通过available()方法获取到文件的大小。测试代码如下:
@GetMapping("/txt") public String txt() { StringBuilder sb = new StringBuilder(); String path = "templates/demo.txt"; ClassLoader classLoader = Demo3Application.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(path)) { sb.append("path: ") .append(path) .append("
ClassLoader: ") .append(classLoader.getClass()) .append("
InputStream: ") .append(inputStream.getClass()) .append("
inputStream.available: ") .append(inputStream.available()); } catch (IOException e) { e.printStackTrace(); } return sb.toString(); }
查看源码时候,也得到了验证。txt文件属于压缩文件(DEFLATED),返回inputStream是org.springframework.boot.loader.jar.ZipInflaterInputStream,
而返回inputStream是org.springframework.boot.loader.data.RandomAccessDataFile$DataInputStream的文件属于非压缩文件(STORED)
- Spring Boot打包为jar后运行时,通过class org.springframework.boot.loader.LaunchedURLClassLoader读取resources目录下文件。
- 分为2种类型文件
- STORED类型
- 读取到inputStream是org.springframework.boot.loader.data.RandomAccessDataFile$DataInputStream
- input.available() == 0
- DEFLATED
- 读取到inputStream是org.springframework.boot.loader.jar.ZipInflaterInputStream
- input.available() == size(文件大小)
- STORED类型
以上给出了解决方案及原理分析,但并不建议将下载的文件放到resources目录下;可以放到分布式存储或其他文件系统中。
附完整的测试代码
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @RestController @RequestMapping(value = "/") public class DemoController { @GetMapping("/docx") public String doc() { StringBuilder sb = new StringBuilder(); String path = "templates/demo.docx"; ClassLoader classLoader = Demo3Application.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(path)) { sb.append("path: ") .append(path) .append("Demo地址
ClassLoader: ") .append(classLoader.getClass()) .append("
InputStream: ") .append(inputStream.getClass()) .append("
inputStream.available: ") .append(inputStream.available()); byte[] bys = new byte[1024]; int len = 0, total = 0; while ((len = inputStream.read(bys)) != -1) total += len; sb.append("
length: " + total); } catch (IOException e) { e.printStackTrace(); } return sb.toString(); } @GetMapping("/txt") public String txt() { StringBuilder sb = new StringBuilder(); String path = "templates/demo.txt"; ClassLoader classLoader = Demo3Application.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(path)) { sb.append("path: ") .append(path) .append("
ClassLoader: ") .append(classLoader.getClass()) .append("
InputStream: ") .append(inputStream.getClass()) .append("
inputStream.available: ") .append(inputStream.available()); } catch (IOException e) { e.printStackTrace(); } return sb.toString(); } @GetMapping("/downloadEmpty") public void downloadEmpty(HttpServletResponse res) { String path = "templates/demo.docx"; ClassLoader classLoader = Demo3Application.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(path); OutputStream outputStream = res.getOutputStream()) { res.addHeader("Content-Disposition", "attachment;filename=empty.docx"); res.addHeader("Content-Length", String.valueOf(inputStream.available())); res.setContentType("application/octet-stream"); byte[] bys = new byte[1024]; int len; while ((len = inputStream.read(bys)) != -1) outputStream.write(bys, 0, len); outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } } @GetMapping("/download") public void download(HttpServletResponse res) { String path = "templates/demo.docx"; ClassLoader classLoader = Demo3Application.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(path); OutputStream outputStream = res.getOutputStream()) { res.addHeader("Content-Disposition", "attachment;filename=demo.docx"); // res.addHeader("Content-Length", String.valueOf(inputStream.available())); res.setContentType("application/octet-stream"); byte[] bys = new byte[1024]; int len; while ((len = inputStream.read(bys)) != -1) outputStream.write(bys, 0, len); outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)