欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java Web 文件下载填坑指南

程序员文章站 2022-04-19 20:25:51
...

背景

今天,调整了一个旧项目的报表下载功能,原来文件是存储在服务器本地的,下载直接从本机获取就可以了,现在要改成从 FTP 服务器获取文件再返回给前台。

理论上,对代码稍微调整就可以了,实际上却踩了一个小坑,本文将整理 Java Web 应用文件下载的流程及注意点。

文件下载流程

文件下载是一个老生常谈的功能了,基本原理是直接向响应流写数据,并设置响应类型为二进制流格式:

  1. 设置响应编码 ;
  2. 设置响应文件类型 octet-stream ;
  3. 设置响应头域附件名称;
  4. 想 ServletResponse 的 OutputStream 流 write 数据。

第二、三、四步对应的代码为:
Java Web 文件下载填坑指南

下载操作源码

常见的文件下载代码为:

	@ResponseBody
	@RequestMapping(value = "/download")
	public void download(HttpServletRequest request, HttpServletResponse response,String reportId) {
		// TODO 根据 reportId 查询报表对应的文件名称
		String fileName = "xxx日报表文件.xlsx";

		//设置响应类型和附件头域
		response.setCharacterEncoding("utf-8");
		response.setContentType("application/octet-stream");
		response.setHeader("content-disposition", "attachment;filename="+ fileName);

		//读取报表内容写入响应流对象
		InputStream inputStream = null;
		try {
			OutputStream output = response.getOutputStream();

			//检查文件是否存在
			if(!isFileExist(fileName)){
				logger.warn("文件/目录 {} 不存在", pathName);
				response.getWriter().println("报表文件不存在!");
				return;
			}

			inputStream = new FileInputStream(new File(pathName));
			int len = -1;
			byte[] bytes = new byte[2048];
           // 向 Response 的响应流写入二进制数据
			while ((len = inputStream.read(bytes)) != -1) {
				output .write(bytes, 0, len);
			}
			output.flush();
		} catch (Exception e) {
			logger.error("下载文件异常",e);
			try {
				response.getWriter().println("下载文件异常!");
			} catch (IOException ex) {
				ex.printStackTrace();
			}
		}finally{
			if(output != null){
				try {
					output.close();
				} catch (IOException e) {
					logger.error("下载文件关闭输出流异常",e);
				}
			}
			if(inputStream != null){
				try {
					inputStream.close();
				} catch (IOException e) {
					logger.error("下载文件关闭输入流异常",e);
				}
			}
		}
	}

敲重点,运行下载操作后,xlsx 类型的报表文件现在下载栏中,查看网络请求的响应头为:
Java Web 文件下载填坑指南

响应头域设置位置

设置响应类型和头域信息必须在 write 写入之前,否则附件就是不可读的。调整代码顺序,先写后设置响应头:
Java Web 文件下载填坑指南
执行下载操作,发现 xlsx 类型的报表以 .zip 压缩包格式被下载,且内容不可读。

查看网络响应的头,可以看出,之后设置的头域没有生效:
Java Web 文件下载填坑指南

编程启示录

为什么设置顺序不同,下载附件显示就不一样呢?

反复验证了十几次,发现 response.setHeaderwrite 操作之后时,头域设置是无效的。推测这是由 http 通信协议包组装顺序决定的,因为 http 响应头域信息是在 body 之前组装的

最后,再总结下文件下载的要点:

  1. 下载路径必须在后台设置,不能直接接收前台的下载路径,否则就有 ../.. 路径的任意文件下载风险;如果要接收带路径的 fileName 参数,必须校验 fileName 不能包含../ 等特殊路径;
  2. 必须注意响应头域设置和流写入操作的顺序,写设头域后写,否则附件不可用;
  3. 从 FTP 下载也是一样,将 ServletResponseOutputStream 对象传给 FTPClientretrieveFile(filename, outputStream) 直接下载到输出流中。
相关标签: 项目开发