文章目录
- HTTP Header 之 Content-Disposition
- 文件下载代码
- Servlet 实现方式 1
- Servlet 实现方式 2
- Spring 实现方式 1
- Content-Disposition 指定 inline
Win、JDK 17、 Spring Boot 3.1.2
HTTP Header 之 Content-Disposition
以下内容来自 mdn web docs
在常规的 HTTP 应答中,Content-Disposition 响应标头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地。
在 multipart/form-data 类型的应答消息体中,Content-Disposition 通用标头可以被用在 multipart 消息体的子部分中,用来给出其对应字段的相关信息。各个子部分由在 Content-Type 中定义的边界(boundary)分隔。用在消息体自身则无实际意义。
Content-Disposition 标头最初是在 MIME 标准中定义的,HTTP 表单及 POST 请求只用到了其所有参数的一个子集。只有 form-data 以及可选的 name 和 filename 三个参数可以应用在 HTTP 上下文中。
语法
作为消息主体的标头在 HTTP 场景中,第一个参数或者是 inline(默认值,表示回复中的消息体会以页面的一部分或者整个页面的形式展示),或者是 attachment(意味着消息体应该被下载到本地;大多数浏览器会呈现一个“保存为”的对话框,将 filename 的值预填为下载后的文件名,假如它存在的话)。
Content-Disposition: inline
Content-Disposition: attachment
Content-Disposition: attachment; filename=“filename.jpg”
文件下载代码
Servlet 实现方式 1
@GetMapping("/download-0/{filename}")
public void download0(@PathVariable("filename") String filename, HttpServletResponse resp) throws IOException {Path path = Paths.get("upload", filename);File file = path.toFile();// 检查文件是否存在并可读if (!file.exists() || !file.canRead()) {resp.sendError(HttpServletResponse.SC_NOT_FOUND);return;}// 设置下载响应头resp.setContentType("application/octet-stream");resp.setHeader("Content-Disposition", "attachment; filename=" + filename);try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));ServletOutputStream sos = resp.getOutputStream()) {sos.write(bis.readAllBytes());}
}
设置 Content-Disposition 硬编码较多,不够优雅,使用 Spring 提供的 ContentDisposition 类优化:
ContentDisposition contentDisposition = ContentDisposition
.attachment()
.filename(filename,StandardCharsets.UTF_8)
.build();resp.setHeader(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString());
Servlet 实现方式 2
@GetMapping("/download-1/{filename}")
public void download1(@PathVariable("filename") String filename, HttpServletResponse resp) throws IOException {Path path = Paths.get("upload", filename);File file = path.toFile();// 检查文件是否存在并可读if (!file.exists() || !file.canRead()) {resp.sendError(HttpServletResponse.SC_NOT_FOUND);return;}resp.setContentType("application/octet-stream");ContentDisposition contentDisposition = ContentDisposition.attachment().filename(filename, StandardCharsets.UTF_8).build();resp.setHeader(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString());Files.copy(path, resp.getOutputStream());
}
Spring 实现方式 1
@GetMapping("/download-2/{filename}")
public ResponseEntity<Resource> download2(@PathVariable String filename) throws IOException {// 构建文件路径Path filePath = Paths.get("upload", filename);Resource resource = new UrlResource(filePath.toUri());// 检查文件是否存在并可读if (!resource.exists() || !resource.isReadable()) {return ResponseEntity.status(HttpStatus.NOT_FOUND).build();}// 设置下载响应头ContentDisposition contentDisposition = ContentDisposition.attachment().filename(filename, StandardCharsets.UTF_8).build();HttpHeaders headers = new HttpHeaders();headers.setContentDisposition(contentDisposition);return ResponseEntity.ok().headers(headers).body(resource);
}
Content-Disposition 指定 inline
不指定默认也为 inline
@GetMapping("/download-3/{fileName}")
public ResponseEntity<Resource> download3(@PathVariable String fileName) throws IOException {// 构建文件路径Path filePath = Paths.get("upload").resolve(fileName);Resource resource = new UrlResource(filePath.toUri());// 检查文件是否存在并可读if (!resource.exists() || !resource.isReadable()) {return ResponseEntity.status(HttpStatus.NOT_FOUND).build();}// 设置下载响应头,可省略ContentDisposition contentDisposition = ContentDisposition.inline().build();HttpHeaders headers = new HttpHeaders();String mimeType = Files.probeContentType(filePath);// 不可省略headers.setContentType(MediaType.valueOf(mimeType + "; charset=UTF-8"));// 可省略headers.setContentDisposition(contentDisposition);return ResponseEntity.ok().headers(headers).body(resource);
}
请求该 url 文件将不再下载,改为预览形式(浏览器不支持预览的类型依然下载)