HTTP Headers

上一篇我们对 HTTP 做了整体的介绍,主要介绍了 HTTP 不同版本的区别以及 HTTP Messages 相关的 Method,Status 和 Reason Phrase。这篇文章我们将重点放在 Headers 上,谈一谈 Headers 相关的 API 以及日常开发中涉及到的内容。

The Headers Interface

Headers 在浏览器环境下是有相关的 Interface 的,常和 Fetch API 结合使用。Headers 实例内部部署了 Iterator,所以可以使用for...of来遍历,相关的方法从字面意义上都很好分辨用途,如下所示:

  • append(name,value): 添加一个 name 对应的新值,如果 name 存在则在原来的 value 上追加新值,用逗号隔开
  • set(name, value): 添加一个 name 对应的新值,如果 name 存在则覆盖原来 name 对应的 value,所以更推荐使用set来添加新值
  • get(name): 返回 name 对应的 value
  • delete(name): 删除 name 及其对应的 value
  • has(name): 判断 name 是否存在
  • keys(),values(),entires(): 返回对应的迭代器用于遍历

Headers Interface 为我们提供了一整套维护 Headers 的机制,并且可以很方便的遍历其中的内容,所以推荐使用HeadersInterface 来维护需要的 Headers。

在使用Headers Interface 时需要注意,大写的 name 会自动转为小写,因为 HTTP 规范中明确 Headers 是大小写不敏感的,并且 HTTP/2 只支持小写的 Header name。

Forbidden Header Names

客户端如浏览器在发送请求到服务器时会自动添加一些 Headers,比如Cookie,Host等,这些 Headers 是不允许被手动修改的,类型包括Forbidden Request Header nameForbidden Response Header Name

当 Forbidde Header Name 被添加时,XMLHttpRequest.setRequestHeader()会抛出错误,fetch()则不会有提示,客户端会自动接管相关的 Headers。

Cache

HTTP Headers 的一大作用就是可以控制客户端的缓存,缓存可以分为强制缓存和协商缓存两个类型,强制缓存的优先级高于协商缓存,同一类型的缓存则 HTTP1.1 的优先级高于 HTTP/1.0。当客户端发起请求之后,服务器会返回相应的缓存响应头,强制缓存告诉客户端,在强制缓存过期之前,都可以使用本地的缓存来加快客户端响应速度。当强制缓存过期之后,客户端会在 Headers 中携带相关信息,再次发起请求给服务器,服务器根据相关携带的信息判断返回新内容(200 OK)或者告知客户端继续使用缓存(304 Not Modified),这个过程就是协商缓存。

上一篇在 HTTP 的各个版本区别中讲到 HTTP/0.9 是没有 Headers 相关的概念,在 HTTP/1.0 才增加了 Headers,可以对缓存进行初步的控制,HTTP/1.0 和缓存相关的 Headers 包括:

  • Expires: 响应头对应时间戳,属于强制缓存,表示在指定的时间内缓存有效,超出该时间之后向服务器重新请求资源。这种方法控制缓存的问题是服务器时间和客户端时间往往不能保持一致,导致缓存效果和预想的不一致。
  • Last-Modified: 响应头对应时间戳,属于协商缓存,在协商缓存过程中向服务器发起带If-Modified-Since请求头的请求,服务器会判断If-Modified-Since对应的时间戳和被请求的资源最后修改的时间戳是否一致,如果不一致则返回200 OK并将新内容返回,如果一致则返回304 Not Modified。这种方法控制缓存的问题是如果目标资源在协商缓存之前经历了多次变更,但是最终变更结果和最初的一致,这会导致最后修改的时间戳变了,缓存失效,但是请求的结果和原先缓存的内容还是一样的内容。另一个问题是Last-Modified是精确到秒的,当一个文件在一秒内的文件内容变更了,最后修改时间却还是之前的时间,这样协商缓存的结果会认为文件没有变更,继续使用之前缓存的内容,而我们想要的则是新内容。
  • Pragma: no-cache: 强制缓存,和Cache-Control: no-cache的效果一致,向服务器验证缓存是否失效。Pragma 的优先级在所有强制缓存中的优先级最高。

由此可以看出 HTTP/1.0 虽然加入了缓存相关的支持,但是会有各种问题存在,效果往往不尽如人意。HTTP/1.1 则在 HTTP/1.0 的基础上作出了改进,相关的新增 Headers 包括:

  • ETag: 属于协商缓存,服务器生成的唯一标识,唯一标识是如何生成的服务器可以自行决定,比较典型的 Nginx 默认生成 ETag 的算法为:
1
2
3
4
etag->value.len = ngx_sprintf(etag->value.data, "\"%xT-%xO\"",
r->headers_out.last_modified_time,
r->headers_out.content_length_n)
- etag->value.data;

也就是说 Nginx 中的 ETag 是根据响应头的Last-ModifiedContent-Length的十六进制组合而成,中间由-连接,形如:617fb60c-d4。(注意:这里的 ETag 生成算法是有问题的,具体可以结合 Last-Modified 的问题思考一下为什么)
ETag对应的是If-MatchIf-Not-Match,会向服务器发起带entity tagIf-MatchIf-Not-Match,服务器对比目标文件的 Etag 并根据对比结果返回 200 或者 304。

  • Cache-Control: HTTP/1.1 中新增的更加细粒度控制缓存的 Headers,除了本身的客户端缓存控制之外,还涉及到了中间层缓存如 CDN 之类的缓存控制。常见的缓存设置包括:
    • max-age=xxx: 缓存的相对于客户端的相对时长,单位为秒,超过则需要重新请求
    • no-cache: 可以缓存但是每次都需要向服务器验证是否过期
    • no-store: 客户端和中间层都禁止缓存
    • public: 客户端和中间层都可以缓存内容
    • private: 只有客户端可以缓存内容
      这些设置可以组合使用以达到预期效果

相比较于 HTTP/1.0 相关的缓存 Headers,HTTP/1.1 相关的缓存 Headers 提供了更优的解法和更细粒度的缓存控制。

关于缓存相关的 HTTP Headers 总结如下:

Header 引入版本 类型 相关 Header 缺点
Expires HTTP/1.0 强制缓存 / 客户端和服务器时间不能保持一致,导致缓存效果和预期不一致
Cache-Control HTTP/1.1 强制缓存 / 相关配置较多且复杂
Last-Modified HTTP/1.0 协商缓存 If-Modified-Since 对于两次请求的间隔内多次变更但是变更文件内容一致,或者目标文件一秒内变更多次的情况,都不能较好的处理
ETag HTTP/1.1 协商缓存 If-Not-Match 生成 ETag 的过程比较占用服务器资源,需要谨慎选择生成 ETag 的算法

CORS

CORS(Cross Origin Resource Sharing)跨域资源共享是前端跨域的常用解决方案,相比较 jsonp 只支持 GET 请求,CORS 支持 POST 等请求,具有更高的灵活性。CORS 分为简单请求和非简单请求两种类型。简单请求的定义为:

  • HTTP Request Method 为 GET,HEAD,POST 请求中的一种
  • HTTP Request Headers 范围为Forbidden Request Header name以及Accept,Accept-Language,Content-Language,Content-Type四种中的一种或多种,且Content-Type值只能是text/plain,application/x-www-form-urlencoded以及multipart/form-data三值中的一个(这三个值是 Form attributes enctype 中的可选值)

在进行非简单请求的时候需要preflight request,即发送 OPTIONS 给服务端,如果成功则返回204 Not Content并返回相关可用的 origin,methods,headers 等信息,并在发起certain request的时候带上这些信息 Headers。

为什么要在非简单请求正式发送请求之前做preflight request?
这里主要考虑到一些老的不支持 CORS 的服务器,在 CORS 出现之前浏览器对跨域的请求会直接拦截,而当跨域请求发起时,会携带 cookies 等用户认证信息给服务器。如果没有preflight request这一步,服务器对跨域没有限制,且请求中携带了用户信息,在涉及到一些危险操作如DELETE时则会操作成功直接删除。所以如果需要支持 CORS,服务器要对应升级接口支持preflight request对应的OPTIONS请求并控制可跨域的范围。

CORS 相关的 Headers 包括:

  • HTTP Request Headers:

    • Origin: 浏览器自动添加,用于验证 origin 是否在白名单内
    • Access-Control-Request-Headers: preflight 相关,用户相关自定义 Headers 会被传到后端验证是否允许
    • Access-Control-Request-Method: preflight 相关,请求方式会被传到后端验证是否允许
  • HTTP Response Headers:

    • Access-Control-Allow-Origin: 允许跨域的域名
    • Access-Control-Allow-Headers: 允许使用的 HTTP Request Headers
    • Access-Control-Allow-Methods: 允许使用的 HTTP Request Methods
    • Access-Control-Max-Age: preflight 缓存相关,表示多长时间内 preflight 缓存有效,单位为秒,默认值为 5 秒
    • Access-Control-Allow-Credentials: 只有一个值为true,表示允许跨域时传送相关的credentials(cookies 等),同时需要明确Access-Control-Allow-Origin,Access-Control-Allow-Headers,Access-Control-Allow-Methods相关的范围,不能使用*表示全部允许,否则浏览器会阻止certain request。同时 XMLHttpRequest 和 Fetch 相关的credentials需要配合设置为 true,后端才可以正确接收到credentials内容。
    • Access-Control-Expose-Headers: 允许 JavaScript 访问的 Headers

Summary

除了上述列举出来的关于缓存和 CORS 的 HTTP Headers 之外,还有很多通用的 Headers 以及用户自定义的 Headers,在日常的开发中遇到相关 Headers 需要多多总结。
下一篇我们对如何请求展开介绍。

References

Comments