我的编程空间,编程开发者的网络收藏夹
学习永远不晚

HTTP中ETag语法及使用实战详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

HTTP中ETag语法及使用实战详解

一、ETag 简介

1.1 ETag 是什么

ETag(Entity Tag)是万维网协议 HTTP 的一部分。它是 HTTP 协议提供的若干机制中的一种 Web 缓存验证机制,并且允许客户端进行缓存协商。这使得缓存变得更加高效,而且节省带宽。如果资源的内容没有发生改变,Web 服务器就不需要发送一个完整的响应。

1.2 ETag 的作用

ETag 是一个不透明的标识符,由 Web 服务器根据 URL 上的资源的特定版本而指定。如果 URL 上的资源内容改变,一个新的不一样的 ETag 就会被生成。ETag 可以看成是资源的指纹,它们能够被快速地比较,以确定两个版本的资源是否相同。
需要注意的是 ETag 的比较只对同一个 URL 有意义 —— 不同 URL 上资源的 ETag 值可能相同也可能不同。

1.3 ETag 的语法

ETag: W/"<etag_value>"
ETag: "<etag_value>"
  • W/(可选):'W/'(大小写敏感) 表示使用弱验证器。弱验证器很容易生成,但不利于比较。强验证器是比较的理想选择,但很难有效地生成。相同资源的两个弱 Etag 值可能语义等同,但不是每个字节都相同。
  • "<etag_value>":实体标签唯一地表示所请求的资源。它们是位于双引号之间的 ASCII 字符串(如 “2c-1799c10ab70” )。没有明确指定生成 ETag 值的方法。通常是使用内容的散列、最后修改时间戳的哈希值或简单地使用版本号。比如,MDN 使用 wiki 内容的十六进制数字的哈希值。

1.4 ETag 的使用

在大多数场景下,当一个 URL 被请求,Web 服务器会返回资源和其相应的 ETag 值,它会被放置在 HTTP 响应头的 ETag 字段中:

HTTP/1.1 200 OK
Content-Length: 44
Cache-Control: max-age=10
Content-Type: application/javascript; charset=utf-8
ETag: W/"2c-1799c10ab70"

然后,客户端可以决定是否缓存这个资源和它的 ETag。以后,如果客户端想再次请求相同的 URL,将会发送一个包含已保存的 ETag 和 If-None-Match 字段的请求。

GET /index.js HTTP/1.1
Host: localhost:3000
Connection: keep-alive
If-None-Match: W/"2c-1799c10ab70"

客户端请求之后,服务器可能会比较客户端的 ETag 和当前版本资源的 ETag。如果 ETag 值匹配,这就意味着资源没有改变,服务器便会发送回一个极短的响应,包含 HTTP “304 未修改” 的状态。304 状态码告诉客户端,它的缓存版本是最新的,可以直接使用它。

HTTP/1.1 304 Not Modified
Cache-Control: max-age=10
ETag: W/"2c-1799c10ab70"
Connection: keep-alive

二、ETag 实战

2.1 创建 Koa 服务器

了解完 ETag 相关知识后,基于 koakoa-conditional-getkoa-etagkoa-static 这些库来介绍一下,在实际项目中如何利用 ETag 响应头和 If-None-Match 请求头实现资源的缓存控制。

// server.js
const Koa = require("koa");
const path = require("path");
const serve = require("koa-static");
const etag = require("koa-etag");
const conditional = require("koa-conditional-get");
const app = new Koa();
app.use(conditional()); // 使用条件请求中间件
app.use(etag()); // 使用etag中间件
app.use( // 使用静态资源中间件
  serve(path.join(__dirname, "/public"), {
    maxage: 10 * 1000, // 设置缓存存储的最大周期,单位为秒
  })
);
app.listen(3000, () => {
  console.log("app starting at port 3000");
});

在以上代码中,使用了 koa-static 中间件来处理静态资源,这些资源被保存在 public 目录下。在该目录下,创建了 index.html 和 index.js 两个资源文件,文件中的内容分别如下所示:

2.1.1 public/index.html

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ETag 使用示例</title>
    <script class="lazy" data-src="/index.js"></script>
</head>
<body>
    <h3>ETag 使用示例</h3>
</body>
</html>

2.1.2 public/index.js

console.log("大家好");

在启动完服务器之后,打开 Chrome 开发者工具并切换到 Network 标签栏,然后在浏览器地址栏输入 http://localhost:3000/ 地址,接着多次访问该地址(地址栏多次回车)。下图是多次访问的结果:

2021-05-26-10-45-35-710148.jpeg

2.2 ETag 和 If-None-Match

下面以 index.js 为例,来分析上图中与之对应的 HTTP 报文。对于 index.html 文件,感兴趣的小伙伴可以自行分析一下。接下来先来分析首次请求 index.js 文件的报文:

2.2.1 首次请求 — 请求报文

GET /index.js HTTP/1.1
Host: localhost:3000
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
...

2.2.2 首次请求 — 响应报文

HTTP/1.1 200 OK
Content-Length: 44
Cache-Control: max-age=10
ETag: W/"2c-1799c10ab70"
...

在使用了 koa-static 和 koa-etag 中间件之后,index.js 文件首次请求的响应报文中会包含 Cache-ControlETag 的字段信息。
Cache-Control 描述的是一个相对时间,在进行缓存命中的时候,都是利用客户端时间进行判断,所以相比较 ExpiresCache-Control 的缓存管理更有效,安全一些。

2.2.3 10s内 — 请求报文

GET /index.js HTTP/1.1
Host: localhost:3000
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
...

2.2.4 10s内 — 响应信息(General)

Request URL: http://localhost:3000/index.js
Request Method: GET
Status Code: 200 OK (from memory cache)
Remote Address: [::1]:3000
Referrer Policy: strict-origin-when-cross-origin

2.2.5 10s内 — 响应信息(Response Headers)

Cache-Control: max-age=10
Connection: keep-alive
Content-Length: 44
ETag: W/"2c-1799c10ab70"

由于设置了 index.js 资源文件的最大缓存时间为 10s,所以在 10s 内浏览器会直接从缓存中读取文件的内容。需要注意的是,此时的状态码为:Status Code: 200 OK (from memory cache)

2.2.6 10s后 — 请求报文

GET /index.js HTTP/1.1
Host: localhost:3000
Connection: keep-alive
If-None-Match: W/"2c-1799c10ab70"
Referer: http://localhost:3000/
...

因为 10s 之后,缓存已经过期了,而且在 index.js 文件首次请求的响应报文中也返回了 ETag 字段。所以此时浏览器会发起 If-None-Match 条件请求。这类请求可以用来验证缓存的有效性,省去不必要的控制手段。

2.2.7 10s后 — 响应报文

HTTP/1.1 304 Not Modified
Cache-Control: max-age=10
ETag: W/"2c-1799c10ab70"
Connection: keep-alive
...

因为文件的内容未发生改变,所以 10s 后的响应报文的状态码为 304 Not Modified。此外,响应报文中也返回了 ETag 字段。看到这里,有一些小伙伴可能会有疑惑 —— ETag 到底是如何生成的?接下来揭开 koa-etag 中间件背后的秘密。

三、如何生成 ETag

在前面的示例中,使用了 koa-etag 中间件来实现资源的缓存控制。其实该中间件的实现并不复杂,具体如下所示:

// https://github.com/koajs/etag/blob/master/index.js
const calculate = require('etag');
// 省略部分代码
module.exports = function etag (options) {
  return async function etag (ctx, next) {
    await next()
    const entity = await getResponseEntity(ctx)
    setEtag(ctx, entity, options)
  }
}

由以上代码可知,在 koa-etag 中间件内部会先通过 getResponseEntity 函数来获取响应实体对象,然后再调用 setETag 函数来生成 ETag。而 setETag 函数的实现很简单,在 setETag 函数内部,会通过 etag 这个第三方库来生成 ETag。

// https://github.com/koajs/etag/blob/master/index.js
function setEtag (ctx, entity, options) {
  if (!entity) return
  ctx.response.etag = calculate(entity, options)
}

etag 这个库对外提供了一个 etag 函数来创建 ETag,该函数的签名如下:

etag(entity, [options])
  • entity:用于生成 ETag 的实体,类型支持 StringsBuffersfs.Stats。除了 fs.Stats 对象之外,默认将生成 strong ETag
  • options:配置对象,支持通过 options.weak 属性来配置生成 weak ETag。

了解完 etag 函数的参数之后,来看一下该函数的具体实现:

function etag (entity, options) {
  if (entity == null) {
    throw new TypeError('argument entity is required')
  }
  // 支持fs.Stats对象
  // isstats 函数的判断规则:当前对象是否包含ctime、mtime、ino和size这些属性
  var isStats = isstats(entity)
  var weak = options && typeof options.weak === 'boolean'
    ? options.weak
    : isStats
  // 参数校验
  if (!isStats && typeof entity !== 'string' && !Buffer.isBuffer(entity)) {
    throw new TypeError('argument entity must be string, Buffer, or fs.Stats')
  }
  // 生成ETag标签
  var tag = isStats
    ? stattag(entity) // 处理fs.Stats对象
    : entitytag(entity)
  return weak
    ? 'W/' + tag
    : tag
}

etag 函数内部会根据 entity 的类型,执行不同的生成逻辑。如果 entityfs.Stats 对象,则会调用 stattag 函数来创建 ETag。

function stattag (stat) {
  // mtime:Modified Time,是在写入文件时随文件内容的更改而更改,是指文件内容最后一次被修改的时间。
  var mtime = stat.mtime.getTime().toString(16)
  var size = stat.size.toString(16)
  return '"' + size + '-' + mtime + '"'
}

而如果 entity 参数非 fs.Stats 对象,则会调用 entitytag 函数来生成 ETag。其中 entitytag 函数的具体实现如下:

function entitytag (entity) {
  if (entity.length === 0) {
    return '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"'
  }
  // 计算实体对象的哈希值
  var hash = crypto
    .createHash('sha1')
    .update(entity, 'utf8')
    .digest('base64')
    .substring(0, 27)
  // 计算实体对象的长度
  var len = typeof entity === 'string'
    ? Buffer.byteLength(entity, 'utf8')
    : entity.length
  return '"' + len.toString(16) + '-' + hash + '"'
}

对于非 fs.Stats 对象来说,在 entitytag 函数内部会使用 sha1 消息摘要算法来生成 hash 值并以 base64 格式输出,而实际的生成的 hash 值会取前 27 个字符。此外,由以上代码可知,最终的 ETag 将由实体的长度和哈希值两部分组成。
需要注意的是,生成 ETag 的算法并不是固定的, 通常是使用内容的散列、最后修改时间戳的哈希值或简单地使用版本号。

四、ETag vs Last-Modified

其实除了 ETag 字段之外,大多数情况下,响应头中还会包含 Last-Modified 字段。它们之间的区别如下:

  • 精确度上,Etag 要优于 Last-Modified。Last-Modified 的时间单位是秒,如果某个文件在 1 秒内被改变多次,那么它们的 Last-Modified 并没有体现出来修改,但是 Etag 每次都会改变,从而确保了精度;此外,如果是负载均衡的服务器,各个服务器生成的 Last-Modified 也有可能不一致。
  • 性能上,Etag 要逊于 Last-Modified,毕竟 Last-Modified 只需要记录时间,而 ETag 需要服务器通过消息摘要算法来计算出一个hash 值。
  • 优先级上,在资源新鲜度校验时,服务器会优先考虑 Etag。即如果条件请求的请求头同时携带 If-Modified-SinceIf-None-Match 字段,则会优先判断资源的 ETag 值是否发生变化。

以上就是HTTP中ETag语法及使用实战详解的详细内容,更多关于HTTP ETag语法的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

HTTP中ETag语法及使用实战详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

HTTP中ETag语法及使用实战详解

这篇文章主要为大家介绍了HTTP中ETag语法及使用实战详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-03-07

HTTP中ETag语法及使用方法是什么

这篇文章主要介绍“HTTP中ETag语法及使用方法是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“HTTP中ETag语法及使用方法是什么”文章能帮助大家解决问题。一、ETag 简介1.1 ETa
2023-07-05

Android中使用HTTP服务的用法详解

在Android中,除了使用Java.NET包下的API访问HTTP服务之外,我们还可以换一种途径去完成工作。Android SDK附带了Apache的HttpClient API。Apache HttpClient是一个完善的HTTP客户
2022-06-06

SqlServer事务语法及使用方法详解

事务是关于原子性的。原子性的概念是指可以把一些事情当做一个不可分割的单元来看待。从数据库的角度看,它是指应全部执行或全部不执行的一条或多条语句的最小组合。为了理解事务的概念,需要能够定义非常明确的边界。事务要有非常明确的开始和结束点。Sql
2022-12-16

C语言中*和&的区别及使用方法详解

在 c 语言中, 用于解引用指针,返回指向的值;&amp;amp;amp;amp;amp;amp; 用于取地址,返回指向该变量的指针。 通常用于访问或修改指针所指向的值;&amp;amp;amp;amp;amp;amp; 通
C语言中*和&的区别及使用方法详解
2024-04-03

Python中_new_方法详解及使用

_new_的作用在python中_new_方法与_init_方法类似,但是如果两都存在那么_new_闲执行。在基础类object中,_new_被定义成了一个静态方法,并且需要传递一个参数cls。Cls表示需实例化的类,此参数在实例化时由Py
2023-01-31

JavaScript中reduce()详解及使用方法

reduce()方法接收一个函数做为累加器,数组中的每一个值(从左到右)开始缩减,最终计算为一个值,下面这篇文章主要给大家介绍了关于JavaScript中reduce()详解及使用方法的相关资料,需要的朋友可以参考下
2023-05-18

C语言中static的使用方法实例详解

static一般用于修饰局部变量,全局变量,函数,下面这篇文章主要给大家介绍了关于C语言中static用法的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
2022-11-13

Go语言中rune方法使用详解

本文主要介绍了Go语言中rune方法使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2023-03-19

Android CardView详解及使用方法和实例

Android CardView详解 Android5.0中向我们介绍了一个全新的控件–CardView,从本质上看,可以将CardView看做是FrameLayout在自身之上添加了圆角和阴影效果。请注意:CardView被包装为一种布
2022-06-06

MySQL中Replace语句用法实例详解

目录前言php一、replace into函数二、replace into 、insert ignore 和 insert into的区别三、replace函数总结前言replace into平时在开发中很少用到,这次是因为在做一个生成分
2022-08-08

KM算法详解及如何使用java实现

今天就跟大家聊聊有关KM算法详解及如何使用java实现,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。匈牙利算法基本概念二分图:二分图又称为二部图.简单来说,如果图中点可以被分为两组,
2023-06-19

详解Android中使用OkHttp发送HTTP的post请求的方法

HTTP POST 和 PUT 请求可以包含要提交的内容。只需要在创建 Request 对象时,通过 post 和 put 方法来指定要提交的内容即可。 HTTP POST 请求的基本示例:public class PostString {
2022-06-06

Vue中$attrs和$listeners详解以及使用方法

最近在研究Vue的组件库,之前也用过$attrs和$listeners,官方文档描述的不太详细,也没有太好的例子,下面这篇文章主要给大家介绍了关于Vue中$attrs和$listeners详解以及使用的相关资料,需要的朋友可以参考下
2022-11-16

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录