编写下载服务器。 第六部分:描述您发送的内容(内容类型等)

pdf_binary 就HTTP而言,客户端下载的只是一堆字节。 但是,客户真的很想知道如何解释这些字节。 它是图像吗? 或者也许是ZIP文件? 本系列的最后一部分描述了如何向客户端提示她下载的内容。

设置

内容类型描述了要返回的资源的MIME类型 。 此标头指示Web浏览器如何处理从下载服务器流出的字节流。 如果没有此标头,浏览器将无法得知其实际接收到的内容,只会像显示文本文件一样显示内容。 不用说二进制PDF(请参见上面的屏幕截图),像文本文件一样显示的图像或视频看起来并不好。 最难的部分是以某种方式实际获得媒体类型。 幸运的是,Java本身有一个用于根据资源的扩展名和/或内容来猜测媒体类型的工具:

import com.google.common.net.MediaType;
import java.io.*;
import java.time.Instant;public class FileSystemPointer implements FilePointer {private final MediaType mediaTypeOrNull;public FileSystemPointer(File target) {final String contentType = java.nio.file.Files.probeContentType(target.toPath());this.mediaTypeOrNull = contentType != null ?MediaType.parse(contentType) :null;}

请注意,使用Optional<T>作为类字段不是惯用的,因为它不是可Serializable并且我们避免了潜在的问题。 知道媒体类型后,我们必须在响应中返回它。 注意,这一小段代码使用了JDK 8和Guava中的Optional ,以及Spring框架和Guava中的MediaType类。 多么糟糕的类型系统!

private ResponseEntity<Resource> response(FilePointer filePointer, HttpStatus status, Resource body) {final ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.status(status).eTag(filePointer.getEtag()).contentLength(filePointer.getSize()).lastModified(filePointer.getLastModified().toEpochMilli());filePointer.getMediaType().map(this::toMediaType).ifPresent(responseBuilder::contentType);return responseBuilder.body(body);
}private MediaType toMediaType(com.google.common.net.MediaType input) {return input.charset().transform(c -> new MediaType(input.type(), input.subtype(), c)).or(new MediaType(input.type(), input.subtype()));
}@Override
public Optional<MediaType> getMediaType() {return Optional.ofNullable(mediaTypeOrNull);
}

保留原始文件名和扩展名

当您直接在Web浏览器中打开文档时,虽然Content-type效果很好,但是可以想象您的用户将该文档存储在磁盘上。 浏览器是决定显示还是存储下载的文件不在本文的讨论范围之内,但是我们应该为两者做好准备。 如果浏览器只是将文件存储在磁盘上,则必须使用某种名称进行保存。 默认情况下,Firefox将使用URL的最后一部分,在本例中,该部分恰好是资源的UUID。 不太用户友好。 铬是好一点-知道根据MIME类型Content-type报头,将试探性地加入适当的扩展名,例如.zip中的情况下, application/zip 。 但是文件名仍然是随机的UUID,而用户上传的文件可能是cats.zip 。 因此,如果您的目标是浏览器而不是自动化客户端,则最好使用真实名称作为URL的最后一部分。 我们仍然希望使用UUID在内部区分资源,避免冲突并且不公开我们的内部存储结构。 但是在外部,我们可以重定向到用户友好的URL,但为了安全起见保留UUID。 首先,我们需要一个额外的端点:

@RequestMapping(method = {GET, HEAD}, value = "/{uuid}")
public ResponseEntity<Resource> redirect(HttpMethod method,@PathVariable UUID uuid,@RequestHeader(IF_NONE_MATCH) Optional<String> requestEtagOpt,@RequestHeader(IF_MODIFIED_SINCE) Optional<Date> ifModifiedSinceOpt) {return findExistingFile(method, uuid).map(file -> file.redirect(requestEtagOpt, ifModifiedSinceOpt)).orElseGet(() -> new ResponseEntity<>(NOT_FOUND));
}@RequestMapping(method = {GET, HEAD}, value = "/{uuid}/{filename}")
public ResponseEntity<Resource> download(HttpMethod method,@PathVariable UUID uuid,@RequestHeader(IF_NONE_MATCH) Optional<String> requestEtagOpt,@RequestHeader(IF_MODIFIED_SINCE) Optional<Date> ifModifiedSinceOpt) {return findExistingFile(method, uuid).map(file -> file.handle(requestEtagOpt, ifModifiedSinceOpt)).orElseGet(() -> new ResponseEntity<>(NOT_FOUND));
}private Optional<ExistingFile> findExistingFile(HttpMethod method, @PathVariable UUID uuid) {return storage.findFile(uuid).map(pointer -> new ExistingFile(method, pointer, uuid));
}

如果仔细观察,甚至没有使用{filename} ,它只是浏览器的提示。 如果需要额外的安全性,可以将提供的文件名与映射到给定UUID文件名进行比较。 这里真正重要的是,仅要求提供UUID重定向我们:

$ curl -v localhost:8080/download/4a8883b6-ead6-4b9e-8979-85f9846cab4b
> GET /download/4a8883b6-ead6-4b9e-8979-85f9846cab4b HTTP/1.1
...
< HTTP/1.1 301 Moved Permanently
< Location: /download/4a8883b6-ead6-4b9e-8979-85f9846cab4b/cats.zip

而且您需要进行一次额外的网络行程来获取实际文件:

> GET /download/4a8883b6-ead6-4b9e-8979-85f9846cab4b/cats.zip HTTP/1.1
...
> 
HTTP/1.1 200 OK
< ETag: "be20c3b1...fb1a4"
< Last-Modified: Thu, 21 Aug 2014 22:44:37 GMT
< Content-Type: application/zip;charset=UTF-8
< Content-Length: 489455

该实现很简单,但是为了避免重复,对其进行了一些重构:

public ResponseEntity<Resource> redirect(Optional<String> requestEtagOpt, Optional<Date> ifModifiedSinceOpt) {if (cached(requestEtagOpt, ifModifiedSinceOpt))return notModified(filePointer);return redirectDownload(filePointer);
}public ResponseEntity<Resource> handle(Optional<String> requestEtagOpt, Optional<Date> ifModifiedSinceOpt) {if (cached(requestEtagOpt, ifModifiedSinceOpt))return notModified(filePointer);return serveDownload(filePointer);
}private boolean cached(Optional<String> requestEtagOpt, Optional<Date> ifModifiedSinceOpt) {final boolean matchingEtag = requestEtagOpt.map(filePointer::matchesEtag).orElse(false);final boolean notModifiedSince = ifModifiedSinceOpt.map(Date::toInstant).map(filePointer::modifiedAfter).orElse(false);return matchingEtag || notModifiedSince;
}private ResponseEntity<Resource> redirectDownload(FilePointer filePointer) {try {log.trace("Redirecting {} '{}'", method, filePointer);return ResponseEntity.status(MOVED_PERMANENTLY).location(new URI("/download/" + uuid + "/" + filePointer.getOriginalName())).body(null);} catch (URISyntaxException e) {throw new IllegalArgumentException(e);}
}private ResponseEntity<Resource> serveDownload(FilePointer filePointer) {log.debug("Serving {} '{}'", method, filePointer);final InputStreamResource resource = resourceToReturn(filePointer);return response(filePointer, OK, resource);
}

您甚至可以进一步使用高阶函数来避免重复:

public ResponseEntity<Resource> redirect(Optional<String> requestEtagOpt, Optional<Date> ifModifiedSinceOpt) {return serveWithCaching(requestEtagOpt, ifModifiedSinceOpt, this::redirectDownload);
}public ResponseEntity<Resource> handle(Optional<String> requestEtagOpt, Optional<Date> ifModifiedSinceOpt) {return serveWithCaching(requestEtagOpt, ifModifiedSinceOpt, this::serveDownload);
}private ResponseEntity<Resource> serveWithCaching(Optional<String> requestEtagOpt, Optional<Date> ifModifiedSinceOpt, Function<FilePointer, ResponseEntity<Resource>> notCachedResponse) {if (cached(requestEtagOpt, ifModifiedSinceOpt))return notModified(filePointer);return notCachedResponse.apply(filePointer);
}

显然,额外的重定向是每次下载都必须支付的额外费用,因此这是一个折衷方案。 您可以考虑基于User-agent启发式(如果是浏览器,则为重定向;如果是自动客户端,则为服务器),以避免非人工客户端的重定向。 这样就结束了我们有关文件下载的系列文章。 HTTP / 2的出现必将带来更多的改进和技术,例如确定优先级。

编写下载服务器

  • 第一部分:始终流式传输,永远不要完全保留在内存中
  • 第二部分:标头:Last-Modified,ETag和If-None-Match
  • 第三部分:标头:内容长度和范围
  • 第四部分:有效地实现HEAD操作
  • 第五部分:油门下载速度
  • 第六部分:描述您发送的内容(内容类型等)
  • 这些文章中开发的示例应用程序可在GitHub上找到。

翻译自: https://www.javacodegeeks.com/2015/07/writing-a-download-server-part-vi-describe-what-you-send-content-type-et-al.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/359709.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ruby中的self

self&#xff0c;自己&#xff0c;在ruby中表示当前对象或默认对象。程序执行的任一时刻&#xff0c;有且仅有一个self。 1.谁成为self&#xff0c;在什么位置成为self&#xff1f; 要知道哪个对象是self&#xff0c;就必须知道当前的上下文。上下文主要有顶层上下文&#xff0…

二手宏碁上网本装linux,Acer国内20日首发“上网本” 放弃Linux使用XP

据相关媒体报道 8月20日将在国内正式推出XP系统版的8.9英寸超便携笔记本电脑Aspire One&#xff0c;放弃使用Linux系统&#xff0c;硬件配置方面&#xff0c;除了依然采用英特尔Atom处理器之外&#xff0c;推出120GB传统硬盘和SSD两个版本。其中&#xff0c;XP系统 120G传统硬盘…

免费WiFi,仅仅为好久没联系的你们

昨日&#xff0c;认识五年的朋友搬来与我一起住了&#xff0c;说不上来&#xff0c;没有激动&#xff0c;仅仅是突然感觉生活又多了一点生机。兴致上来&#xff0c;晚上立马联系了已经近四个月没有联系的好友&#xff0c;才知道他们的生活也因这几个月发生了翻天覆地的变化。究…

五猴分桃c语言课程设计,c语言程序设计五猴分桃问题实验报告.doc

c语言程序设计五猴分桃问题实验报告.doc 课程设计报告学院、系&#xff1a;吉林大学珠海学院计算机科学与技术系专业名称&#xff1a;软件工程课程设计科目C语言程序课程设计所在班级&#xff1a;10班学生学号&#xff1a;04121010学生姓名&#xff1a;赵学文指导教师&#xff…

c语言100以内奇数的和为多少,编写C#程序,计算100以内所有奇数的和。谢谢了,大神帮忙啊...

编写C#程序&#xff0c;计算100以内所有奇数的和。谢谢了&#xff0c;大神帮忙啊以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;编写C#程序&#xff0c;计算100以内所有奇数的和。谢谢了&am…

监控系统的多协议直播(RTSP RTMP HTTP Live Streaming)

监控系统的多协议直播&#xff08;RTSP RTMP HTTP Live Streaming) 转载于:https://www.cnblogs.com/cl1024cl/p/6204791.html

阿里媒体转码公共参数_Xuggler教程:转码和媒体修改

阿里媒体转码公共参数注意&#xff1a;这是我们的“ Xuggler开发教程 ”系列的一部分。 在上一教程中&#xff0c;我对视频处理Xuggler进行了简短介绍 。 在这一部分中&#xff0c;我们将看到Xuggler和FFmpeg提供的一些更令人兴奋的功能&#xff0c;例如视频转码和媒体修改。 别…

52单片机iic读写c语言,如何52单片机的I2C读写24C08程序问题排查修改

------波形在一楼isoimg2130老师提供在单片机正常运行的程序&#xff1a;#include "reg52.h"#include "intrins.h"typedef unsigned char u8;sbit SCLP2^1; //I2C 时钟sbit SDAP2^2; …

qfp封装能够linux,QFP、PQFP、LQFP、TQFP封装形式及PCB详解

问题&#xff1a;画PCB时&#xff0c;会发现很多的集成电路都是QFP封装&#xff0c;比如很多的单片机都有这种封装。各个器件商会在自己的数据手册中说明他的器件是QFP&#xff0c;LQFP或TQFP&#xff0c;然后&#xff0c;有的给出封装尺寸图&#xff0c;有的则不给。那么&…

dede文章列表加上序号效果

dede文章列表加上序号效果 css代码部分 <style type"text/css"> <!-- .downtop { FLOAT: left; OVERFLOW: hidden; WIDTH: 218px; HEIGHT: 278px } .downtop UL.text { MARGIN: 0px 10px; WIDTH: 198px; PADDING-TOP: 5px } .downtop UL.text LI { WIDTH: 1…

HDU 2845 Beans

本来是很简单的一道题&#xff0c;却想了好长时间 由于数据量比较大&#xff0c;所以逐行读入&#xff0c;逐行处理 先处理每一行的不相邻元素和的最大值&#xff0c;记录在数组b中 最后计算不相邻行的和的最大值 二者的状态转移方程都类似&#xff1a;dp[j] max(dp[j - 1], d…

ASP.NET MVC IOC 之AutoFac攻略

一、为什么使用AutoFac&#xff1f; 之前介绍了Unity和Ninject两个IOC容器&#xff0c;但是发现园子里用AutoFac的貌似更为普遍&#xff0c;于是捯饬了两天&#xff0c;发现这个东东确实是个高大上的IOC容器~ Autofac是.NET领域最为流行的IOC框架之一&#xff0c;传说是速度最快…

apache cxf_Apache CXF负载平衡和故障转移

apache cxf不久前&#xff0c;我们已经面临了基于Apache CXF的负载平衡Web服务客户端的需求。 此外&#xff0c;当某些服务器关闭时&#xff0c;客户端应自动进行故障转移。 更糟糕的是&#xff0c;服务器目标地址列表要从外部服务获取并在运行时更新。 最终&#xff0c;我们最…

HDU 1874 最直接的最短路径问题

题目链接&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid1874 Problem Description某省自从实行了很多年的畅通工程计划后&#xff0c;终于修建了很多路。不过路多了也不好&#xff0c;每次要从一个城镇到另一个城镇时&#xff0c;都有许多种道路方案可以选择&#xf…

领域驱动设计模式设计与实践_在域驱动设计中使用状态模式

领域驱动设计模式设计与实践域驱动设计&#xff08;DDD&#xff09;是一种开发软件的方法&#xff0c;其中&#xff0c;通过将实现与核心业务概念的不断发展的模型相联系&#xff0c;解决了问题的复杂性。 该术语是由Eric Evans创造的&#xff0c;并且有一个DDD专用站点可以促进…

html 英文文字纵向排列,CSS几种简单方法实现文字竖向排版

1.一个句子的竖向排列如图&#xff1a;1.2. test.one {width: 20px;margin: 0 auto;line-height: 24px;font-size: 20px;}.two {width: 15px;margin: 0 auto;line-height: 24px;font-size: 20px;word-wrap: break-word;/*英文的时候需要加上这句&#xff0c;自动换行*/}我是竖列…

jstree 节点拖拽保存数据库

需要jstree具有拖拽功能需要在加载jstree时添加dnd插件&#xff0c;具体看代码&#xff1a; $(**).jstree({//plugins-各种jstree的插件引入&#xff0c;展示树的多样性 plugins : [ "dnd", "types", "wholerow" ], core : {"check_callbac…

自动添加html结束标志,HTML:包含或排除可选的结束标记?

MYYA我在这里添加一些链接来帮助您了解HTML的历史&#xff0c;以便您了解各种矛盾。这不是你的问题的答案&#xff0c;但在阅读这些各种摘要后你会知道更多。我们是怎么来到这里的&#xff1f; - 潜入HTML5网络历史HTML简史HTML的历史 - HTML WG WikiDive Into HTML5的一些摘录…

JAR清单类路径不仅适用于Java Application Launcher

自从我开始学习Java以来​​&#xff0c;我几乎已经知道&#xff0c; 清单文件中的Class-Path标头字段为可执行JAR &#xff08;具有由另一个称为Main-Class清单指定应用程序起点的 JAR&#xff09;指定相对运行时类路径。 一个同事最近碰到一个让我感到惊讶&#xff0c;因为它…

通过url,获取html内容,并解析,如何使用 JavaScript 解析 URL

在 Web 开发中&#xff0c;有许多情况需要解析 URL&#xff0c;这篇主要学习如何使用 URL 对象实现这一点。开始创建一个以下内容的 HTML 文件&#xff0c;并在浏览器中打开。JavaScript URL parsing// 激动人心的代码即将写在这里如果你想尝试本文中的任何内容&#xff0c;可以…