Java Web 文件下載填坑指南

背景

今天,調整了一箇舊項目的報表下載功能,原來文件是存儲在服務器本地的,下載直接從本機獲取就可以了,現在要改成從 FTP 服務器獲取文件再返回給前臺。

理論上,對代碼稍微調整就可以了,實際上卻踩了一個小坑,本文將整理 Java Web 應用文件下載的流程及注意點。

文件下載流程

文件下載是一個老生常談的功能了,基本原理是直接向響應流寫數據,並設置響應類型爲二進制流格式:

  1. 設置響應編碼 ;
  2. 設置響應文件類型 octet-stream ;
  3. 設置響應頭域附件名稱;
  4. 想 ServletResponse 的 OutputStream 流 write 數據。

第二、三、四步對應的代碼爲:
在這裏插入圖片描述

下載操作源碼

常見的文件下載代碼爲:

	@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 類型的報表文件現在下載欄中,查看網絡請求的響應頭爲:
在這裏插入圖片描述

響應頭域設置位置

設置響應類型和頭域信息必須在 write 寫入之前,否則附件就是不可讀的。調整代碼順序,先寫後設置響應頭:
在這裏插入圖片描述
執行下載操作,發現 xlsx 類型的報表以 .zip 壓縮包格式被下載,且內容不可讀。

查看網絡響應的頭,可以看出,之後設置的頭域沒有生效:
在這裏插入圖片描述

編程啓示錄

爲什麼設置順序不同,下載附件顯示就不一樣呢?

反覆驗證了十幾次,發現 response.setHeaderwrite 操作之後時,頭域設置是無效的。推測這是由 http 通信協議包組裝順序決定的,因爲 http 響應頭域信息是在 body 之前組裝的

最後,再總結下文件下載的要點:

  1. 下載路徑必須在後臺設置,不能直接接收前臺的下載路徑,否則就有 ../.. 路徑的任意文件下載風險;如果要接收帶路徑的 fileName 參數,必須校驗 fileName 不能包含../ 等特殊路徑;
  2. 必須注意響應頭域設置和流寫入操作的順序,寫設頭域後寫,否則附件不可用;
  3. 從 FTP 下載也是一樣,將 ServletResponseOutputStream 對象傳給 FTPClientretrieveFile(filename, outputStream) 直接下載到輸出流中。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章