编写下载服务器。 第二部分:标头:Last-Modified,ETag和If-None-Match

客户端缓存是万维网的基础之一。 服务器应告知客户端资源的有效性,客户端应尽可能快地对其进行缓存。 如我们所见,如果不缓存Web,将会非常慢。 只需在任何网站上Ctrl + F5并将其与普通F5进行比较-后者就会更快,因为它使用了已缓存的资源。 缓存对于下载也很重要。 如果我们已经获取了几兆字节的数据,并且它们没有改变,则通过网络推送它们是非常浪费的。

使用

HTTP ETag标头可用于避免重复下载客户端已有的资源。 服务器与第一响应服务器一起返回ETag标头,该标头通常是文件内容的哈希值。 客户端可以保留ETag并在以后请求相同资源时将其发送(在If-None-Match请求标头中)。 如果在此期间未更改,则服务器可以简单地返回304 Not Modified响应。 让我们从对ETag支持的集成测试开始:

def 'should send file if ETag not present'() {expect:mockMvc.perform(get('/download/' + FileExamples.TXT_FILE_UUID)).andExpect(status().isOk())}def 'should send file if ETag present but not matching'() {expect:mockMvc.perform(get('/download/' + FileExamples.TXT_FILE_UUID).header(IF_NONE_MATCH, '"WHATEVER"')).andExpect(status().isOk())
}def 'should not send file if ETag matches content'() {given:String etag = FileExamples.TXT_FILE.getEtag()expect:mockMvc.perform(get('/download/' + FileExamples.TXT_FILE_UUID).header(IF_NONE_MATCH, etag)).andExpect(status().isNotModified()).andExpect(header().string(ETAG, etag))
}

有趣的是,Spring框架中内置了ShallowEtagHeaderFilter 。 安装它会使所有测试通过,包括最后一个测试:

@WebAppConfiguration
@ContextConfiguration(classes = [MainApplication])
@ActiveProfiles("test")
class DownloadControllerSpec extends Specification {private MockMvc mockMvc@Autowiredpublic void setWebApplicationContext(WebApplicationContext wac) {mockMvc = MockMvcBuilders.webAppContextSetup(wac).addFilter(new Sha512ShallowEtagHeaderFilter(), "/download/*").build()}//tests...}

我实际上插入了使用SHA-512而不是默认MD5的自己的Sha512ShallowEtagHeaderFilter 。 同样由于某种原因,默认实现在哈希值前面加上0

public class ShallowEtagHeaderFilter {protected String generateETagHeaderValue(byte[] bytes) {StringBuilder builder = new StringBuilder("\"0");DigestUtils.appendMd5DigestAsHex(bytes, builder);builder.append('"');return builder.toString();}//...
}

与:

public class Sha512ShallowEtagHeaderFilter extends ShallowEtagHeaderFilter {@Overrideprotected String generateETagHeaderValue(byte[] bytes) {final HashCode hash = Hashing.sha512().hashBytes(bytes);return "\"" + hash + "\"";}
}

不幸的是,我们无法使用内置过滤器,因为它们必须首先完全读取响应主体才能计算ETag 。 这基本上关闭了上一篇文章中介绍的主体流传输–整个响应都存储在内存中。 我们必须自己实现ETag功能。 从技术上讲, If-None-Match可以包含多个ETag值。 但是,谷歌浏览器和ShallowEtagHeaderFilter支持它,因此我们也将跳过它。 为了控制响应头,我们现在返回ResponseEntity<Resource>

@RequestMapping(method = GET, value = "/{uuid}")
public ResponseEntity<Resource> download(@PathVariable UUID uuid,@RequestHeader(IF_NONE_MATCH) Optional<String> requestEtagOpt) {return storage.findFile(uuid).map(pointer -> prepareResponse(pointer, requestEtagOpt)).orElseGet(() -> new ResponseEntity<>(NOT_FOUND));
}private ResponseEntity<Resource> prepareResponse(FilePointer filePointer, Optional<String> requestEtagOpt) {return requestEtagOpt.filter(filePointer::matchesEtag).map(this::notModified).orElseGet(() -> serveDownload(filePointer));
}private ResponseEntity<Resource> notModified(String etag) {log.trace("Cached on client side {}, returning 304", etag);return ResponseEntity.status(NOT_MODIFIED).eTag(etag).body(null);
}private ResponseEntity<Resource> serveDownload(FilePointer filePointer) {log.debug("Serving '{}'", filePointer);final InputStream inputStream = filePointer.open();final InputStreamResource resource = new InputStreamResource(inputStream);return ResponseEntity.status(OK).eTag(filePointer.getEtag()).body(resource);
}

该过程由可选的requestEtagOpt控制。 如果存在并且与客户端发送的内容匹配,则返回304。否则照常发送200 OK。 本示例中介绍的FilePointer新方法如下所示:

import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.io.Files;public class FileSystemPointer implements FilePointer {private final File target;private final HashCode tag;public FileSystemPointer(File target) {try {this.target = target;this.tag = Files.hash(target, Hashing.sha512());} catch (IOException e) {throw new IllegalArgumentException(e);}}@Overridepublic InputStream open() {try {return new BufferedInputStream(new FileInputStream(target));} catch (FileNotFoundException e) {throw new IllegalArgumentException(e);}}@Overridepublic String getEtag() {return "\"" + tag + "\"";}@Overridepublic boolean matchesEtag(String requestEtag) {return getEtag().equals(requestEtag);}
}

在这里,您将看到FileSystemPointer实现,该实现直接从文件系统读取文件。 关键部分是缓存标记,而不是在每次请求时都重新计算标记。 上面的实现的行为符合预期,例如,Web浏览器不会再次下载资源。

3.使用

ETagIf-None-Match标头类似,还有Last-ModifiedIf-Modified-Since 。 我猜它们很容易解释:第一个服务器返回Last-Modified响应标头,指示给定资源的最后修改时间( duh! )。 客户端缓存此时间戳,并将其与后续请求一起传递给If-Modified-Since请求标头中的相同资源。 如果同时未更改资源,则服务器将响应304,从而节省带宽。 这是一个后备机制,同时实现ETagLast-Modified是一个很好的实践。 让我们从集成测试开始:

def 'should not return file if wasn\'t modified recently'() {given:Instant lastModified = FileExamples.TXT_FILE.getLastModified()String dateHeader = toDateHeader(lastModified)expect:mockMvc.perform(get('/download/' + FileExamples.TXT_FILE_UUID).header(IF_MODIFIED_SINCE, dateHeader)).andExpect(status().isNotModified())
}def 'should not return file if server has older version than the client'() {given:Instant lastModifiedLaterThanServer = FileExamples.TXT_FILE.getLastModified().plusSeconds(60)String dateHeader = toDateHeader(lastModifiedLaterThanServer)expect:mockMvc.perform(get('/download/' + FileExamples.TXT_FILE_UUID).header(IF_MODIFIED_SINCE, dateHeader)).andExpect(status().isNotModified())
}def 'should return file if was modified after last retrieval'() {given:Instant lastModifiedRecently = FileExamples.TXT_FILE.getLastModified().minusSeconds(60)String dateHeader = toDateHeader(lastModifiedRecently)expect:mockMvc.perform(get('/download/' + FileExamples.TXT_FILE_UUID).header(IF_MODIFIED_SINCE, dateHeader)).andExpect(status().isOk())
}private static String toDateHeader(Instant lastModified) {ZonedDateTime dateTime = ZonedDateTime.ofInstant(lastModified, ZoneOffset.UTC)DateTimeFormatter.RFC_1123_DATE_TIME.format(dateTime)
}

并执行:

@RequestMapping(method = GET, value = "/{uuid}")
public ResponseEntity<Resource> download(@PathVariable UUID uuid,@RequestHeader(IF_NONE_MATCH) Optional<String> requestEtagOpt,@RequestHeader(IF_MODIFIED_SINCE) Optional<Date> ifModifiedSinceOpt) {return storage.findFile(uuid).map(pointer -> prepareResponse(pointer,requestEtagOpt,ifModifiedSinceOpt.map(Date::toInstant))).orElseGet(() -> new ResponseEntity<>(NOT_FOUND));
}private ResponseEntity<Resource> prepareResponse(FilePointer filePointer, Optional<String> requestEtagOpt, Optional<Instant> ifModifiedSinceOpt) {if (requestEtagOpt.isPresent()) {final String requestEtag = requestEtagOpt.get();if (filePointer.matchesEtag(requestEtag)) {return notModified(filePointer);}}if (ifModifiedSinceOpt.isPresent()) {final Instant isModifiedSince = ifModifiedSinceOpt.get();if (filePointer.modifiedAfter(isModifiedSince)) {return notModified(filePointer);}}return serveDownload(filePointer);
}private ResponseEntity<Resource> serveDownload(FilePointer filePointer) {log.debug("Serving '{}'", filePointer);final InputStream inputStream = filePointer.open();final InputStreamResource resource = new InputStreamResource(inputStream);return response(filePointer, OK, resource);
}private ResponseEntity<Resource> notModified(FilePointer filePointer) {log.trace("Cached on client side {}, returning 304", filePointer);return response(filePointer, NOT_MODIFIED, null);
}private ResponseEntity<Resource> response(FilePointer filePointer, HttpStatus status, Resource body) {return ResponseEntity.status(status).eTag(filePointer.getEtag()).lastModified(filePointer.getLastModified().toEpochMilli()).body(body);
}

可悲的是,习惯上使用Optional不再看起来不错,所以我坚持使用isPresent() 。 我们同时检查If-Modified-SinceIf-None-Match 。 如果两者都不匹配,我们将照常提供文件。 只是为了让您了解这些标头的工作方式,让我们执行一些端到端测试。 第一个要求:

> GET /download/4a8883b6-ead6-4b9e-8979-85f9846cab4b HTTP/1.1
> ...
> 
< HTTP/1.1 200 OK
< ETag: "8b97c678a7f1d2e0af...921228d8e"
< Last-Modified: Sun, 17 May 2015 15:45:26 GMT
< ...

带有ETag后续请求(已缩短):

> GET /download/4a8883b6-ead6-4b9e-8979-85f9846cab4b HTTP/1.1
> If-None-Match: "8b97c678a7f1d2e0af...921228d8e"
> ...
> 
< HTTP/1.1 304 Not Modified
< ETag: "8b97c678a7f1d2e0af...921228d8e"
< Last-Modified: Sun, 17 May 2015 15:45:26 GMT
< ...

如果我们的客户仅支持Last-Modified

> GET /download/4a8883b6-ead6-4b9e-8979-85f9846cab4b HTTP/1.1
> If-Modified-Since: Tue, 19 May 2015 06:59:55 GMT
> ...
> 
< HTTP/1.1 304 Not Modified
< ETag: "8b97c678a7f1d2e0af9cda473b36c21f1b68e35b93fec2eb5c38d182c7e8f43a069885ec56e127c2588f9495011fd8ce032825b6d3136df7adbaa1f921228d8e"
< Last-Modified: Sun, 17 May 2015 15:45:26 GMT

有许多内置工具,例如过滤器,可以为您处理缓存。 但是,如果您需要确保在服务器端流传输文件而不是对其进行预先缓冲,则需要格外小心。

编写下载服务器

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

翻译自: https://www.javacodegeeks.com/2015/06/writing-a-download-server-part-ii-headers-last-modified-etag-and-if-none-match.html

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

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

相关文章

jwt重放攻击_4个点搞懂JWT、JWS、JWE

1.JWT是何物&#xff0c;有哪些常用的场景JWT(json web token)是设计一种简洁&#xff0c;安全&#xff0c;无状态的token的实现规范rfc7519&#xff0c;通常用于网络请求方和网络接收方之间的网络请求认证。jwt的常用场景1.1: restful api接口的无状态认证, 在传统的web应用中…

Verification Mind Games---how to think like a verifier像验证工程师一样思考

1. 有效的验证需要验证工程师使用不同于设计者的思维方式思考问题。具体来说&#xff0c;验证更加关心在严格遵循协议的基础上发现设计里面的bug&#xff0c;搜索corner cases&#xff0c;对设计的不一致要保持零容忍的态度。mindset&#xff1a;一套人们应该持有的确定的态度&…

discuz安装_手动搭建 Discuz! 论坛

一、搭建LAMP环境安装软件(Apache、MariaDB、PHP)yum install httpd php php-fpm php-mysql mariadb mariadb-server -y2.启动服务systemctl start httpdsystemctl start mariadbsystemctl start php-fpm3.安装后首次启动mariadb设置mysql_secure_installation4.登录 MariaDB&a…

关系数据库的几种设计范式介绍

关系数据库的几种设计范式介绍1、第一范式&#xff08;1NF&#xff09; 在任何一个关系数据库中&#xff0c;第一范式&#xff08;1NF&#xff09;是对关系模式的基本要求&#xff0c;不满足第一范式&#xff08;1NF&#xff09;的数据库就不是关系数据库。 所谓…

蓝桥杯 1223 第 2 场 小白入门赛

蓝桥小课堂-平方和 模拟 1 2 2 2 3 2 ⋯ n 2 n ⋅ ( n 1 ) ⋅ ( 2 n 1 ) 6 1^22^23^2\cdotsn^2\dfrac{n\;\cdot\;(n 1)\;\cdot\;(2n1)}{6} 122232⋯n26n⋅(n1)⋅(2n1)​。 write(n * (n 1) * (n * 2 1) / 6);房顶漏水啦 m a x ( 最大的行 − 最小的行 , 最大的列 −…

jar包是什么意思_面试难度五颗星:JVM有Full GC,为什么还会 OutOfMemoryError?

点击上方蓝色“后端面试那些事儿”&#xff0c;选择“设为星标”学最好的别人&#xff0c;做最好的我们来源&#xff1a;R 大zhihu.com/question/38511221问题&#xff1a;R大回复平时有逛知乎的习惯&#xff0c;一般对JVM相关话题比较感兴趣。偶然看到这个问题&#xff0c;结果…

mapreduce介绍_MapReduce:简单介绍

mapreduce介绍MapReduce是Google流行的一种并行编程技术。 它用于处理大量数据。 仅通过将工作并行分配给多台机器&#xff0c;就可以在合理的时间内完成这种处理。 每台机器都处理一小部分数据。 MapReduce是一种编程模型&#xff0c;使开发人员可以专注于编写处理数据的代码&…

系统执行sql很慢达梦工具执行很快的简单解决方式

现象描述&#xff1a;系统功能查询很慢&#xff0c;拷贝查询sql到达梦工具中执行速度很快 1.问题分析&#xff1a; 达梦SQL执行耗时异常问题排查_qq_39693441的博客-CSDN博客_sql耗时分析 2.解决方式1&#xff1a; 在程序sql中拼接随机数如&#xff1a; select /*动态随机数*…

PHP在程序处理过程中动态输出内容

在安装discuz或其他一些开源产品的时候&#xff0c;在安装数据库时页面上的安装信息都是动态输出出来的&#xff0c;主要通过php两个函数来实现的&#xff0c; flush();ob_flush(); 代码如下 <html xmlns"http://www.w3.org/1999/xhtml"><head> <meta…

roads 构筑极致用户体验_长安马自达「悦马星空」计划上线,为用户带来极致服务体验...

日前,第十八届广州车展顺利举行。期间各大汽车品牌齐聚亮相,这其中,也包括众人熟悉的长安马自达。据悉,在本次车展上,长安马自达除携品牌全系车型次世代MAZDA3 昂克赛拉、2020款MAZDA CX-5、MAZDA CX-30和MAZDA CX-8亮相外,还正式发布「悦马星空」用户共创计划。资料显示,「悦马…

从数百万个光纤(而不是数千个线程)中查询数据库

jOOQ是在Java中执行SQL的好方法&#xff0c; Quasar光纤带来了大大提高的并发性 我们很高兴在平行宇宙的 Fabio Tudone的jOOQ博客上宣布另一个非常有趣的来宾帖子。 Parallel Universe开发了一个开源堆栈&#xff0c;使开发人员可以轻松地在JVM上对极端的并发应用程序进行编码…

matlab向量的排序(自写函数)

function a_ed arraysort(a) %冒泡排序法 for i 1:length(a)-1%进行多少次比较for j1i:length(a)%每次求出最大的数&#xff0c;放在最后if(a(j)<a(i))tem a(i);a(i) a(j);a(j) tem;endenda_ed a; endclc; clear; a [2 4 4 6 14 0 2 8 4 1 9 4] b arraysort(a)转载于…

标准错误处理机制——error

在 Golang 中&#xff0c;错误处理机制一般是函数返回时使用的&#xff0c;是对外的接口&#xff0c;而异常处理机制 panic-recover 一般用在函数内部。 error 类型介绍 error 类型实际上是抽象了 Error() 方法的 error 接口&#xff0c;Golang 使用该接口进行标准的错误处理。…

ejb 2.0 3.0_EJB 3.1全局JNDI访问

ejb 2.0 3.0如本系列前面部分所述&#xff0c;EJB 3.0版规范的主要缺点是缺少可移植的全局JNDI名称。 这意味着没有可移植的方式将EJB引用链接到应用程序外部的Bean。 EJB v。3.1规范用自己的话填补了这一定义&#xff1a; “一个标准化的全局JNDI名称空间和一系列相关的名称空…

Oracle11.2.0.4 RAC安装文档

1 环境配置 参考官方文档《Grid Infrastructure Installation Guide for Linux》 1.1 软件环境 操作系统&#xff1a; [roothowe1 ~]# cat /etc/redhat-release Red Hat Enterprise Linux Server release 6.2 (Santiago) [roothowe1 ~]# uname -a Linux howe1 2.6.32-220.el6.i…

威海二职工业机器人专业_现在各大专开设的工业机器人专业前景如何?

我就是某专科学校工业机器人技术专业人的学生&#xff0c;专业是现在专业几个负责人15年向教育局申请申办的&#xff0c;16年正式招生&#xff0c;我也有幸作为第一届学生来到了该学校。先说教学吧&#xff0c;因为是第一届所以很多设备、设施都不完善&#xff0c;我觉得最重要…

协同遗漏的效果–使用简单的NIO客户端/服务器测量回送延迟

在这篇文章中&#xff0c;我演示了许多想法和技术&#xff1a; 如何编写一个简单的非阻塞NIO客户端/服务器 协调遗漏的影响 如何测量百分位数的延迟&#xff08;相对于简单平均&#xff09; 如何在计算机上计时延迟回送 我最近正在为客户端服务器应用程序开发低延迟基准测…

python 画蜘蛛_如何学习 R 绘图?

写在前面&#xff1a;为啥不用excel绘制这些图&#xff0c;用PoweBI&#xff0c;帆软BI等可视化软件来绘图&#xff0c;不是更方便吗&#xff1f;的确&#xff0c;这些工具都很方便&#xff0c;但同时&#xff0c;它们显得很呆&#xff0c;不够灵活&#xff0c;更为致命的是&am…

conflicting types for ‘方法名’ 的错误

将main()的实现写在drawShapes(),drawCircle(),drawRectangle()...之前. 结果编译的时候出现了 conflicting types for "方法名"的错误。故到网上查找答案&#xff0c;发现在这里需要严格按照函数出现的先后顺序才能成功编译&#xff0c;也就是main()要定义在最后&a…

Oracle用户system解锁

1.首先进入sql plus窗口&#xff08;参见上一篇文章&#xff09; 2.进入后&#xff1a;输入select username,account_status from dba_users where usernameSYSTEM; 3.查询system用户的状态和用户名&#xff0c;这里能查询出密码&#xff0c;但是查出来的密码是密文&#xff0c…