V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
codehole
V2EX  ›  程序员

鲜为人知的 HTTP 协议头字段详解大全「原创」

  •  
  •   codehole ·
    pyloque · 2018-03-24 22:49:04 +08:00 · 7227 次点击
    这是一个创建于 2222 天前的主题,其中的信息可能已经有所发展或是发生改变。

    继上篇讲了HTTP 协议的基础之后,本篇重点介绍一下 HTTP 常用的 Header。

    HTTP Header 非常之多,很少有人能完全分清这些 Header 到底是干什么的。鉴于 RFC 文件规范艰深晦涩难懂,本文对协议规范中列出的 HTTP Header 进行了梳理,用通俗的语言进行表达,便于读者吃透 HTTP 协议。

    作者在阅读 RFC 文档的时候发现了很多以前没注意到的知识,估计做 web 开发的小伙伴们也大多忽视了这些知识,阅读文本会给你们带来很多意外的惊喜。

    免责声明:如果下面有那句话有不对的地方,还请喷少点口水。

    Accept

    表示客户端期望服务器返回的媒体格式。客户端期望的资源类型服务器可能没有,所以客户端会期望多种类型,并且设置优先级,服务器根据优先级寻找相应的资源返回给客户端。

    # 注意:先逗号分割类型,再分号分割属性
    Accept: audio/*; q=0.2, audio/basic
    

    表示 audio/basic 类型的资源优先,如果没有,就随便其它什么格式的 audio 资源都可以。q 的取值范围是(0-1],其具体值并没有意义,它仅用来排序优先级,如果没有 q,默认 q=1,也就是最高优先级。

    Accept-Charset

    表示客户端期望服务器返回的内容的编码格式。它同 Accept 头一样,也可以指定多个编码,以 q 值代表优先级。

    # 注意:先逗号分割类型,再分号分割属性
    Accept-Charset: utf8, gbk; q=0.6
    

    表示 utf8 编码优先,如果不行,就拿 gbk 编码返回.

    Content-Type

    Content-Type 是服务器向客户端发送的头,代表内容的媒体类型和编码格式,是对 Accept 头和 Accept-Charset 头的统一应答。

    Content-Type: text/html; charset=utf8
    

    表示返回的 Body 是个 html 文本,编码为 utf8

    Accept-Language

    表示客户端期望服务器返回的内容的语言。很多大型互联网公司是全球化的,它的技术文档一般有有多种语言,通过这个字段可以实现文档的本地化,对国内用户呈现简体中文文档,对英语系用户呈现英文文档。

    Accept-Language:zh-CN,en-US;q=0.8,zh-TW;q=0.6
    

    表示大陆简体中文优先,其次英语,再其次台湾繁体中文

    Content-Language

    这个头字段内容是对 Accept-Language 的应答。服务器通过此字段告知客户端返回的 Body 信息的语言是什么。

    Content-Length

    表示传输的请求/响应的 Body 的长度。GET 请求因为没有 Body,所以不需要这个头。携带 Body 的并且可以提前知道 Body 长度的请求/响应必须带上这个字段,以便对方可以方便的分辨出报文的边界,也就是 Body 数据何时结束。如果 Body 太大,需要边计算边传输,不到最后计算结束是无法知道整个 Body 大小的,这个时候可以使用 http 分块传输,这个时候也是不需要 Content-Length 字段的。

    Content-Location

    当客户端请求的资源在服务器有多个地址时,服务器可以通过 Content-Location 字段告知客户端其它的可选地址。这个字段比较少见。

    Content-MD5

    在 Header 中提供这个信息是用来做 Body 内容校验。它表示 Body 信息被 md5 算法处理后的 base64 字符串。这个字段也比较少见。因为校验机制在 TCP 层已经有实现了,再来一层校验并没有多大意义。另外资源的 md5 值往往用来放在后面的 ETag 头信息中作为资源的唯一标识来使用。

    Date

    如果服务器没有缓存,那么 Date 就是响应的即时生成时间。如果服务器设有缓存,那么 Date 就是响应内容被缓存的时间。它必须符合规范里定义的特定格式,这种格式叫着 HTTP-Date,不支持随意定义自己的时间格式。

    Date: Tue, 15 Nov 1994 08:12:31 GMT
    

    Age

    表示资源缓存的年龄,也就是资源自缓存以来到现在已经过去了多少时间,单位是秒。

    Age: 86400
    

    Expires

    服务器使用 Expect 头来告知对方资源何时失效。如果它的值等于 Date 头的值,就表示资源已经实效。

    Expires: Thu, 01 Dec 1994 16:00:00 GMT
    

    ETag

    资源标签,每个资源可以提供多个标签信息。它一般用来和下面的 If-Match 和 If-None-Match 配合使用,用来判断缓存资源的有效性。比较常见的标签是资源的版本号,比如可以拿资源数据的 md5 校验码作为版本号。

    If-Match

    If-Match 的值一般是上面提到的 ETag 的值,它常用于 HTTP 的乐观锁。所谓 HTTP 乐观锁,是指客户端先 GET 这个资源得到 ETag 中的版本号,然后发起一个资源修改请求 PUT|PATCH 时通过 If-Match 头来指定资源的版本号,如果服务器资源满足 If-Match 中指定的版本号,请求就会被执行。如果不满足,说明资源被并发修改了,就需要返回状态码为 412 Precondition failed 的错误。客户端可以选择放弃或者重试整个过程。

    If-None-Match

    类似于 If-Match,只是条件相反。

    Allow

    表示资源支持访问的 HTTP Method 类型。它是服务器对客户端的建议,告知对方请使用 Allow 中提到的 Method 来访问资源。

    Allow: GET, HEAD, PUT
    

    Connection

    当客户端和服务器需要协商连接的属性时,可以使用 Connection 头部。比较常用的一个值是 close,用来通知对方在当前请求结束后关闭连接。

    Connection: close
    

    Expect

    用于请求发送之前向服务器询问许可。譬如要向服务器发送一个很大的文件而不确定是否超出限制,就可以在请求头里携带一个 Expect 头部

    Expect: 100-continue
    

    如果服务器说不行,就会返回 417 Expectation Failed 错误告知客户端放弃。如果可以那就返回 100 continue 状态码告知客户端放马过来吧,于是客户端就会继续上传 Body 内容。如果服务器提前收到 Body 内容就会放弃返回 100 continue 响应。

    From

    该字段一般用来标记请求发起者的邮件地址,相当于给请求赋予一个责任人。如果服务器发现请求存在问题,就会通过此字段联系到发起人进行处理。因为邮件地址涉及到隐私信息,所以请求携带 From 头需要征得用户的同意。RFC 协议建议所有的机器人代理发起的请求应该携带此头部,以免遇到问题时可以找到责任人。不过如果是恶意的机器人,估计这样的建议也只是耳边风而已。

    Host

    RFC 协议规定所有的 HTTP 请求必须携带 Host 头,即使 Host 没有值,也必须带上这个 Host 头附加一个空串,如果不满足,应用服务器应该抛出 400 Bad Request。协议虽然这样规定,不过大部分网关或者服务器都比较仁慈,既然没有指定 Host 字段,那就给你默认加上一个。 网关代理可以根据不同的 Host 值转发到不同的 upstream 服务节点,它常用于虚拟主机服务业务。

    Last-Modified

    标记资源的最近修改时间,它和 Date 比较类似,区别是 Last-Modified 代表修改时间,而 Date 是创建时间。

    If-Modified-Since

    浏览器向服务器请求静态资源时,如果浏览器本地已经有了缓存,就会携带 If-Modified-Since 头,值为资源的 Last-Modified 时间,询问服务器该资源自从这个 Last-Modified 时间之后有没有被修改。如果没有修改过,就会向浏览器返回 304 Not Modified 通知浏览器可以放心使用缓存内的资源。如果资源修改过,那就像正常的 GET 请求一样,携带资源的内容返回 200 OK。

    If-Unmodified-Since

    类似于 If-Modified-Since,意义相反。区别是当服务器资源条件不满足时,不是返回 304 Not Modified,而是返回 412 Precondition Failed。

    Range

    支持断点续传的服务器必须处理 Range 头,它表示客户端请求资源的一部分时指定的请求字节范围。它是客户端向服务器发送的请求头。

    Range: bytes=500-999
    

    Content-Range

    针对上面的 Range 头,服务器响应客户端时也需提供相应的 Content-Range 头,表示传输的 Body 数据在整体资源块中的字节范围。比如下面的例子表示该资源总共有 47022 字节,当前响应的内容是 21010-47021 字节之间的内容。

    Content-Range: bytes 21010-47021/47022
    

    之所以是 47021 而不是 47022 是因为 offset 是以 0 开始的,47021 就是最后一个字节。

    If-Range

    在断点续传时,为确保连续 2 个请求之间服务器资源本身没有发生变化,需要 If-Range 头带上 ETag 的资源版本号。服务器资源根据这个版本号来判定资源是否改变了。如果没变,就返回 206 Partial Content 将部分资源返回。如果资源变了,那就相当于一个普通的 GET 请求,返回 200 OK 和整个资源内容。

    Location

    服务器向客户端发送 302 跳转的时候,总会携带 Location 头信息,它的值为目标 URL。

    HTTP/1.1 302 Temporary Redirect
    Location: https://www-temp.example.org/
    

    Max-Forwards

    用来限定网关或者代理的层数,也就是最大转发次数。HTTP 每经过一个网关或者代理层,Max-Forwards 值就要减 1。如果 nginx 接收到前端请求的时候 Max-Forwards 已经等于 0,那么它就不应该再将请求转发到 upstream 指定的服务节点上。

    Pragma

    这个头是比较常见的,在前端开发模式下经常会加上这个头部。

    Pragma: no-cache
    

    当网关收到一个带有这样请求的头部时,即使内部存在该请求资源的缓存并且有效也不可以直接发送给客户端,而必须转发给后面的 upstream 进行处理。 不过如果真的所有的网关都遵循这个协议的话,攻击是很容易构造的,所以它一般仅用于开发模式,防止静态资源修改后前端得不到即时更新。其它值的 pragma 值没有遇到过。

    Referer

    Referer 是非常常用的头,它表示请求的发起来源 URI,也就是当前页面资源的父页面。如果你从 A 页面跳转到 B 页面,那么请求 B 页面的请求头里面就会有 Referer 信息,它的值就是 A 页面的访问地址。通过追踪 Referer,可得出资源页面之间复杂的跳转链,它非常适合用于网页的数据分析和路径优化。

    Retry-After

    服务器升级时,来自客户端的请求会直接给予 503(Service Unavailable)错误,通过在返回头里面加入 Retry-After 字段告知客户端何时服务可以恢复正常访问。Retry-After 的头可以是 HTTP-Date,也可以是整数,表示多少秒后服务可以恢复正常访问。浏览器在拿到这个值之后可以考虑增加一个定时器在未来的某个时间进行重试。

    Server

    用于返回服务器相关的软件信息,来告知客户端当前的 HTTP 服务是由某某软件提供的,可以看成是一种软件广告。 RFC 协议里对这个头信息做了警告:暴露出服务器信息可能会导致黑客更易于攻击你的服务,建议谨慎使用。

    User-Agent

    携带当前的用户代理信息,一般包含浏览器、浏览器内核和操作系统的版本型号信息。它和 Server 头是对应的,一个是表达服务器信息,一个是表达客户端信息。服务器可以根据用户代理信息统计出网页服务的浏览器、操作系统的使用占比情况,服务器也可以根据 UA 的信息来定制不一样的内容。

    Transfer-Encoding

    传送 Body 信息时需要对 Body 数据采取何种变换。当 HTTP 对 Body 进行分块传送时,需要增加下面的头部信息才可以进行分块传送。其它类型目前没有遇到过。

    Transfer-Encoding: chunked
    

    Upgrade

    服务器建议客户端升级传输协议。比如当客户端使用 HTTP/1.0 发送请求时,服务器就可以建议客户端升级到 HTTP/1.1。 这个时候就可以使用 Upgrade 头。客户端收到这个 Upgrade 后就会将后续请求转成 HTTP/1.1 格式继续进行交流。可以支持多个参数,使用逗号分割即可。

    Upgrade: HTTP/1.1
    

    当客户端要和服务器进行 Websocket 进行通讯时,在握手阶段服务器也会向客户端发送 Upgrade 头部信息,提示客户端将协议切换到 Websocket。

    Upgrade: WebSocket
    

    Vary

    该头部用于缓存控制。对于一些缓存服务器,我们在请求里加入 Vary 参数可以告知缓存服务器对不同的 Vary 参数的响应使用不同的缓存单元。比如 Vary 参数里放入编码参数,那么不同编码的网页就会有不同的缓存。Vary 的值可以有多个,只要任意一个值不一样就会有不同的缓存。 比如下面的这个例子告知缓存服务器对不同语言和不同编码的网页响应使用不同的缓存单元。

    Vary: Accept-Encoding,Accept-Language
    

    Via

    该字段用来标识一个请求经过的网关路由节点。如果这个请求经过了多个代理层,Via 头部就会有多个网关信息。

    Warning

    用于在响应中添加一些附加的警告信息,警告信息包含一个错误码和错误说明。通用的一些错误码在 RFC 协议中有具体规定。比如 111 号错误码表示缓存服务器的缓存项目已经过期,并且尝试 reload 资源,但是 reload 失败了,所以只好返回了旧的已经过期的内容,这个时候就需要通过 warning 头反馈客户端。

    Warning: 111 Revalidation failed
    

    WWW-Authenticate

    WWW-Authenticate 是 401 Unauthorized 错误码返回时必须携带的头,该头会携带一个问题 Challenge 给客户端,告知客户端需要携带这个问题的答案来请求服务器才可以继续访问目标资源。这种问题 Challenge 可以自定义,比较常见的是 Basic 认证。

    WWW-Authenticate: Basic realm=xxx
    

    Basic 指代 base64 加密算法(不安全),realm 指代认证范围 /场合 /情景名称。

    Authorization

    对于某些需要特殊权限才能访问的资源需要客户端在请求里提供用户名密码的认证信息。它是对 WWW-Authenticate 的应答。

    # value = base64(user_name:password)
    Authorization: Basic YWRtaW46YWRtaW4xMjM=
    

    Proxy-Authenticate

    同 WWW-Authorization 头部,用于代理服务器认证。

    Proxy-Authorization

    同 Authorization 头部,用于代理服务器认证。

    ETag vs Last-Modified vs Expires

    ETag 一般携带的是资源的版本号,协议没有具体规定版本号是什么。它可以是资源的 md5 校验码,也可以是 uuid,甚至可以是自增的数字,也可以是资源的修改时间。它的匹配方式是相等 /不相等。因为服务器需要维护版本号,取决的版本号是什么,这可能是一个存储和计算的负担。

    Last-Modified 携带的资源的修改时间。它的匹配方式是大于 /小于。如果是静态资源文件,一般就是操作系统记录的文件修改时间。

    Expires 是服务器告知客户端资源的过期时间。客户端缓存的资源在这个时间之后自动过期,而不需要非得向服务器确认一下是不是 304 Not Modified 才认为没过期。

    Cache-Control

    这可能是 HTTP 头里面最复杂的一个头了。这个头既可以用于请求,也可以用于响应。在请求和响应的取值不一样,分别代表了不同的意思。

    1. no-cache 如果 no-cache 没有指定值,那就表示不允许缓存。对于请求来说,服务器不得使用缓存内容直接返回。对于响应来说,客户端不得缓存响应的资源内容。如果 no-cache 指定了值,那就表示值对应的头信息不得使用缓存,其它的信息还是可以缓存的。告知对方我只要新鲜刚出浴的数据。
    2. no-store 告知对方不要持久化请求 /响应数据到其它地方,这种信息是敏感的,要保持它的易失性。告知对方记在心里(memory)就行,别写在纸上(disk)。
    3. no-transform 告知对方不要转换数据。比如客户端上传了 raw 图像数据,服务器一般都会选择性压缩图像数据进行存储。no-transform 告知对方保留原始数据信息,不要进行任何转换。告知对方不要乱动我发过来的东西。
    4. only-if-cached 用于请求头,告知服务器只要那些已经缓存的内容,不要去 reload。如果没有缓存内容就返回 504 Gateway Timeout 错误。表示客户端不想太麻烦服务器,有就给,没就算了。
    5. max-age 用于请求头。限制缓存内容的年龄,如果超过 max-age 年龄的,需要服务器去 reload 内容资源。这叫客户端的年龄歧视。
    6. max-stale 用于请求头。客户端允许服务器返回缓存已过期的资源内容,但是限定了最大过期时间。表示客户端虽然很宽容,那是也是有限度的。
    7. min-fresh 用于请求头。客户端限制服务器不要那些即将过期的资源内容。就好比我们去超市买牛奶,如果牛奶快过期了虽然还在保质期内咱们也就不会考虑。
    8. public 用于响应头。表示允许客户端缓存响应信息,并可以给别人使用。比如代理服务器缓存静态资源供所有代理用户使用。
    9. private 用于响应头。表示仅允许客户端缓存响应信息给自己使用,不得分享给别人。这样是为了禁止代理服务器进行缓存,而允许客户端自己缓存资源内容。意思是你个人留着用就行,别借给别人用。

    因为 HTTP 协议细节非常繁多,以上文字并不能完全将 HTTP 的所有的头部细节都讲清楚。后续 [码洞] 会继续更新更加精细的文章,建议读者关注公众号 [码洞] 第一时间看到相关文章。

    第 1 条附言  ·  2018-03-25 05:16:35 +08:00

    成为Linux高级玩家必会的技巧

    Shell 文本处理编写单行指令的诀窍

    第 2 条附言  ·  2018-03-25 05:18:41 +08:00
    19 条回复    2018-03-26 14:57:52 +08:00
    Kilerd
        1
    Kilerd  
       2018-03-24 23:29:38 +08:00   ❤️ 1
    道理我都懂,你在最后加个妹纸的图片是什么意思
    codehole
        2
    codehole  
    OP
       2018-03-24 23:50:34 +08:00
    @Kilerd 哈哈,酥抚酥抚一下你看累的眼睛
    DevNet
        3
    DevNet  
       2018-03-25 00:50:17 +08:00 via Android
    先马为敬
    scriptB0y
        4
    scriptB0y  
       2018-03-25 01:32:14 +08:00
    小知识:HTTP referer 是个 typo 正确应为 referrer,为了向下兼容不改了……
    dobelee
        5
    dobelee  
       2018-03-25 01:33:48 +08:00 via Android
    mark 一下,妹子好评。
    WordTian
        6
    WordTian  
       2018-03-25 01:40:04 +08:00 via Android
    基本都了解过,但长时间不接触忘的差不多了,顶一下
    yuedingwangji
        7
    yuedingwangji  
       2018-03-25 02:55:17 +08:00
    道理我都懂,你在最后加个妹纸的图片是什么意思
    mushan099
        8
    mushan099  
       2018-03-25 03:20:56 +08:00 via iPhone
    先马为敬
    Keyes
        9
    Keyes  
       2018-03-25 10:20:32 +08:00 via Android
    看了标题,拉到最后,果然!
    我只能说,珍惜账号
    HXM
        10
    HXM  
       2018-03-25 12:47:41 +08:00 via Android
    学习了!谢谢
    Cinux
        11
    Cinux  
       2018-03-25 21:58:34 +08:00
    dalieba
        12
    dalieba  
       2018-03-26 00:28:06 +08:00 via Android
    学习了!斯巴西巴
    forestyuan
        13
    forestyuan  
       2018-03-26 08:54:57 +08:00
    特别想喷一下标题里的“鲜为人知”
    lanced
        14
    lanced  
       2018-03-26 09:07:21 +08:00
    非常感谢
    sosloop
        15
    sosloop  
       2018-03-26 09:19:59 +08:00
    学习了!谢谢!敢问这个小姐姐姓甚名谁?
    codehole
        16
    codehole  
    OP
       2018-03-26 09:20:06 +08:00 via Android
    @forestyuan 我本来想写无人知晓的
    codehole
        17
    codehole  
    OP
       2018-03-26 09:21:38 +08:00 via Android
    @chenpipguge 总算有读者上钩了,小姐姐的信息是要付费的
    flyingghost
        18
    flyingghost  
       2018-03-26 14:41:39 +08:00
    @forestyuan 这要考虑到断句问题。
    鲜为人知的...字段,这个不成立,HTTP 协议头是基础,大家都知道。
    鲜为人知的...大全,可以这么讲,这份大全确实知道的人不多。美中不足是依然会引起读者的误解。
    鲜为人知的...「原创」,这就对了。蛤?这也要原创?这也能原创?/doge
    codehole
        19
    codehole  
    OP
       2018-03-26 14:57:52 +08:00
    @flyingghost 鲜为人知的逻辑
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1029 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 19:19 · PVG 03:19 · LAX 12:19 · JFK 15:19
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.