文章目录
环境:Win 11、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、JDK 1.8 实现(传统方法)
@GetMapping("/download-1/{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 (InputStream in = new FileInputStream(file);
ServletOutputStream out = resp.getOutputStream()) {
int len;
// 缓冲区大小 4 KB
byte[] buffer = new byte[4096];
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
}
Servlet、JDK 11 实现(利用 readAllBytes 方法)
@GetMapping("/download-2/{filename}")
public void download0(@PathVariable("filename") String filename, HttpServletResponse resp) throws IOException {
Path path = Paths.get("upload", filename);
File file = path.toFile();
// 省略健壮性代码
// 设置下载响应头
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、NIO 实现(利用 Files.copy 方法)
@GetMapping("/download-3/{filename}")
public void download1(@PathVariable("filename") String filename, HttpServletResponse resp) throws IOException {
Path path = Paths.get("upload", filename);
File file = path.toFile();
// 省略健壮性代码
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 实现(利用 ResponseEntity)
@GetMapping("/download-4/{filename}")
public ResponseEntity<Resource> download2(@PathVariable String filename) throws IOException {
// 构建文件路径
Path filePath = Paths.get("upload", filename);
Resource resource = new UrlResource(filePath.toUri());
// 省略健壮性代码
// 设置下载响应头
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
@GetMapping("/download-5/{fileName}")
public ResponseEntity<Resource> download3(@PathVariable String fileName) throws IOException {
// 构建文件路径
Path filePath = Paths.get("upload").resolve(fileName);
Resource resource = new UrlResource(filePath.toUri());
// 省略健壮性代码
// 设置下载响应头,可省略
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 文件将不再下载,改为预览形式(浏览器不支持预览的类型依然下载)