502无法解析服务器标头

客户端缓存是万维网的基础之一。 服务器应通知客户端资源的有效性,客户端应尽可能快地对其进行缓存。 如我们所见,如果不缓存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

502无法解析服务器标头

502无法解析服务器标头_编写下载服务器。 第二部分:标头:Last-Modified,ETag和If-None-Match...相关推荐

  1. 502无法解析服务器标头_编写下载服务器。 第三部分:标头:内容长度和范围...

    502无法解析服务器标头 这次,我们将探索更多HTTP请求和响应标头,以改善下载服务器的实现: Content-length和Range . 前者表示下载量很大,后者允许部分下载文件,或者从我们开始的 ...

  2. mwc校准油门_编写下载服务器。 第五部分:油门下载速度

    mwc校准油门 在僵尸网络时代,您可以租用几百美元来运行自己的分布式拒绝服务攻击,拥有紧急开关来有选择地关闭昂贵的功能或严重降低性能是一个巨大的胜利. 在缓解问题的同时,您的应用程序仍可运行. 当然, ...

  3. 服务器禁止head 请求_编写下载服务器。 第四部分:有效地执行HEAD操作

    服务器禁止head 请求 HEAD是一个经常被遗忘的HTTP方法(动词),其行为类似于GET,但不返回正文. 您使用HEAD来检查资源的存在(如果不存在,它应该返回404),并确保您的缓存中没有陈旧的 ...

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

    服务器编写 就HTTP而言,客户端下载的只是一堆字节. 但是,客户真的很想知道如何解释这些字节. 它是图像吗? 还是ZIP文件? 本系列的最后一部分描述了如何向客户端提示她下载的内容. 设置 内容类型 ...

  5. grpc 流式传输_编写下载服务器。 第一部分:始终流式传输,永远不要完全保留在内存中...

    grpc 流式传输 下载各种文件(文本或二进制文件)是每个企业应用程序的生死攸关的事情. PDF文档,附件,媒体,可执行文件,CSV,超大文件等.几乎每个应用程序迟早都必须提供某种形式的下载. 下载是 ...

  6. 编写下载服务器。 第五部分:油门下载速度

    在僵尸网络时代,您可以租用几百美元来运行自己的分布式拒绝服务攻击,拥有紧急开关有选择地关闭昂贵的功能或极大地降低性能是一个巨大的胜利. 在缓解问题的同时,您的应用程序仍可运行. 当然,这种安全措施在高 ...

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

    客户端缓存是万维网的基础之一. 服务器应告知客户端资源的有效性,客户端应尽可能快地对其进行缓存. 如我们所见,如果不缓存Web,将会非常慢. 只需在任何网站上Ctrl + F5并将其与普通F5进行比较 ...

  8. 编写下载服务器。 第三部分:标头:内容长度和范围

    这次,我们将探索更多的HTTP请求和响应标头,以改善下载服务器的实现: Content-length和Range . 前者表示下载量很大,后者允许部分下载文件或在我们开始时失败后继续下载. Conte ...

  9. 编写下载服务器。 第四部分:有效地实现HEAD操作

    HEAD是一个经常被遗忘的HTTP方法(动词),其行为类似于GET,但不会返回正文. 您使用HEAD来检查资源的存在(如果不存在,它应该返回404),并确保您的缓存中没有陈旧的版本. 在这种情况下,您 ...

最新文章

  1. 广东外语外贸大学计算机考研,广东外语外贸考研难度,2021考研广东外语外贸大学MTI会挤破头很难吗?...
  2. linux内核 mpls,将MPLS编译进linux内核中
  3. 谈大数据也谈人工智能 郭为告诉你一个不一样的神州控股
  4. 存储过程与函数oracle
  5. mysql注解实体类_jpa实体类生成mysql表及字段注解
  6. ssh 连接_Docker实战——使用SSH连接docker容器
  7. netty大白话--helloword(一)
  8. eclipse android 第一个程序,Eclipse 开发 Android,第一个 HelloWord 程序(学习1)-Fun言
  9. PAT 乙级 1037. 在霍格沃茨找零钱(20)Java版
  10. 几种c++字符串split 函数实现的比较
  11. oracle之 监听器无法启动的几个原因总结
  12. 阶段5 3.微服务项目【学成在线】_day01 搭建环境 CMS服务端开发_22-页面查询服务端开发-Dao-基础方法测试...
  13. 局域网ip扫描工具_IP Scanner局域网IP扫描工具
  14. 开始学习爬虫:爬虫之爬取电影天堂网站资源到本地mysql数据库
  15. html样式类collapse,[ CSS-CSS3 ] 史上最全CSS样式一览表
  16. pycharm-03-工程结构
  17. IIS 发生意外错误 0x8ffe2740
  18. 【bzoj3926】[Zjoi20150]诸神眷顾的幻想乡 后缀自动机+trie
  19. 《科学之路》| 图灵奖得主杨立昆人工智能十问:AI会统治人类吗?
  20. ONE~~~~~~~~~

热门文章

  1. P2805-[NOI2009]植物大战僵尸【网络流,最大权闭合图】
  2. 欢乐纪中A组赛【2019.8.10】
  3. [XSY] 最长公共子串对(后缀自动机)
  4. SpringCloud Greenwich(三)注册中心之zookeeper、Zuul和 gateway网关配置
  5. Hadoop入门(二十一)Mapreduce的求和程序
  6. 常用公有云接入——腾讯
  7. java提高篇之详解内部类
  8. Mybatis-plus 思维导图,让 Mybatis-plus 不再难懂
  9. ssh(Spring+Spring mvc+hibernate)——Dept.hbm.xml
  10. 用startSmoothScroll实现RecyclerView滚动到指定位置并置顶,含有动画。