HTTP 协议详解

理解 HTTP 协议对构建网络应用是一个非常基础的要求,比如爬虫类程序,必须深入理解 Request 和 Resonse 各首部信息(当然,这个前提是建立在对方站点完全遵循协议)。若是网站类程序,更需要理解这些含义,因为客户端往往就是浏览器,一般来讲都会严格遵循协议。参考:httpwg(全面权威)、MDNwiki协议

一、基础(杂项)首部

Request 首部

请求主机:端口
Host: domain.com:8080

请求来源的 主机:端口 和 页面URL
Origin: http://domain.org:8080
Referer: http://domain.org:8080/page

连接信息:是否允许复用 TCP 连接:HTTP 是建立在 TCP 之上的,告知本次请求结束后,是否关闭 TCP,还是在请求下一个页面时直接复用 TCP 通道,若复用,还可以设置保持连接时长、最大复用次数。
该首部在使用 HTTP /2 协议时不会(得)发送,总是 keep-alive。
Connection: close
Connection: keep-alive
Keep-Alive: timeout=5, max=1000

请求报文的创建时间,一般即为当前时间,几乎没有客户端会发送该首部
Date: Wed, 21 Oct 2015 07:28:00 GMT

客户端信息,可能是浏览器/App/蜘蛛等任何可用信息
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0)

比如爬虫程序,若请求较为频繁,可携带该首部,便于对方网站联系你
From: webmaster@example.org

通知服务端,客户端即将传输大文件,希望服务端返回 100 响应,以便客户端继续;
没有浏览器会使用这个,但如 cURL 等会使用,服务端可以响应:同意(100)、拒绝(4XX)
Expect: 100-continue

是否禁止追踪 (君子协定,需要看服务端什么态度)
DNT: 0 (允许) 或 DNT: 1 (禁止)

Response 首部

连接信息,参见 Request 首部说明
Connection: close | keep-alive
Keep-Alive: timeout=5, max=1000

报文的创建时间,根据 RFC 7231,除非是服务器时钟不准确,都必须发送该首部
Date: Wed, 21 Oct 2015 07:28:00 GMT

在无法处理 Request method 时发送 405 响应,通过该首部告知客户端可处理的 method (可以为空)
Allow: GET, POST, HEAD

网站维护,当 Response 为 503 或 301 响应时,可告知再次可访问的时间;对于浏览器客户端用处不大,但对于蜘蛛等自动程序,该首部有一定意义。
Retry-After: Wed, 21 Oct 2015 07:28:00 GMT
Retry-After: 120

处理请求的软件或者产品(或组件产品)的名称
Server: Apache/2.4.1 (Unix)

服务端处理请求的各种调试信息,生产版切勿发送,有安全隐患
Server-Timing: cpu;dur=2.4, total;dur=123.4

服务端应用程序信息,生产版切勿发送,有安全隐患
X-Powered-By: PHP/7.2

语义上与 Link 相同,目前仍处于 草案 阶段,感觉这个不实用,前后端分离搞了这么多年,这个岂不是开倒车了。但可能对于某些全局通用的 Link 有那么一丢丢作用,可作为通用的缺省 Link。另外对于追求极致体验的,可以帮助浏览器更快(先于页面或同时加载)请求 CSS 、JS 资源,快速完成页面渲染,相当于在页面加载完成前,预加载所需资源。
Link: <favicon.ico>; rel="icon", <main.css>; rel="stylesheet"

来源信息控制,可禁止跟踪(在当前 Response 页面打开外链不发送 referrer);一般不使用 header 发送,而是通过页面的 meta 等设定(详情)。https -> http 跳转默认不发送,http 若是广告主,可能就需要设置该首部让浏览器发送以便对方统计。
Referrer-Policy: no-referrer | unsafe-url | .....

通常为一些 Css / Js 资源,指明 SourceMap 地址用于帮助在浏览器调试,但由于资源本事直接支持设置 SourceMap,所以该首部很不常用。非标准协议,但 支持度 还不错。
SourceMap: /path/to/file.js.map

针对浏览器的,设置允许 调试 加载信息的域名。比如当前 response 为一张图片,嵌入到其他网站,那么就可以使用该首部来 允许/禁止 该站点调试。
Timing-Allow-Origin: *
Timing-Allow-Origin: https://domain.com

针对浏览器的,加载页面实体时就开始预读取图片、静态资源、甚至是链接的 DNS 以加快渲染;该指令也可通过 meta 设定:参见
X-DNS-Prefetch-Control: on / X-DNS-Prefetch-Control: off

针对 IE 浏览器,等同于 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
X-UA-Compatible: IE=Edge,chrome=1

告知跟踪情况,详情
Tk: N

二、HTTP 认证

Response: 未认证请求,服务端响应 401,并告知认证方式
WWW-Authenticate: <type> realm=<realm>

Request: 客户端携带认证首部进行访问
Authorization: <type> <credentials>

Response: 服务端为代理服务器,且也需要认证;响应 407,告知客户端认证方式
Proxy-Authenticate: <type> realm=<realm>

Request: 同样的,客户端携带代理服务器的认证信息
Proxy-Authorization: <type> <credentials>

逻辑较为简单,更多信息可 参考

三、内容协商

内容格式: Accept & Accept-Charset / Content-Type & Accept-Patch

Request: 表明客户端可处理的格式,可能的单个、多个 或 任意
Accept: text/html
Accept: text/html, application/xhtml+xml, image/*, application/xml;q=0.9, */*;q=0.8
Accept: */*

Request: 表明客户端可处理的字符集
Accept-Charset: utf-8, iso-8859-1;q=0.5

Response: 指明响应资源的 格式;字符集 (若 Response 可满足客户端需求)
Content-Type: text/html
Content-Type: text/html; charset=utf-8

Response: 若无法满足客户端需求,返回 406 或 415
HTTP 406 (告知无法返回所支持的格式)
HTTP 415 (无法满足需求,同时告知服务端可响应的格式、字符集)
Accept-Patch: application/example, text/example
Accept-Patch: text/example;charset=utf-8

一般情况下,服务端对格式协商不太准守协议,无论客户端是否可处理,总是任性的返回资源格式/字符集。客户端若无法处理响应格式,往往表现为 “保存为文件”。
若严格准守协议,一般也是返回 406;即使返回 415 并告知支持格式,客户端仍然无法进行修正。因为客户端通常已经在 Accept 中已告知了所有可处理的格式,即使通过 Accept-Patch 告知也无济于事。
但该首部仍然有一定意义,比如对于 API 接口,可根据请求返回 json 或 xml 以加强接口健壮性。

内容语言:Accept-Language / Content-Language & Content-Location

Request: 客户端可处理的语言
Accept-Language: zh-CN,zh;q=0.9,ca;q=0.8,en;q=0.7,zh-TW;q=0.6,ja;q=0.5,cs;q=0.4,ko;q=0.3

Response: 当前资源包含的语言
Content-Language: de, en

对于支持多语言的网站可通过这两个首部判断返回。
不建议依赖该 header , 最好设计其他逻辑由用户自主设置语言,比如通过 url path 或 cookie 等作为依赖。

对于同一个 url 多次访问可能返回不同内容(比如多语言页面)。可对展示不同语言的页面设置一个实际 Content-Location。 当两次访问同一个 URL,但返回的 Content-Location 不同,客户端就不会使用上次的缓存内容。比如访问 index.html,则可以根据当前语言设置首部
Content-Location: /index_zh.html
Content-Location: /index_en.html
所以:再次不建议同一个 url 展示多语言,有很多不易处理的逻辑。

内容压缩:Accept-Encoding / Content-Encoding

Request: 客户端可处理的压缩算法
Accept-Encoding: gzip, deflate, br

Response: 响应使用的压缩算法
Content-Encoding: gzip

内容传输:Accept-Ranges & Content-Disposition / If-Range & Range / Content-Length & Content-Range

Response: 对于首次请求,服务端应返回是否支持的断点传输,none 就是不支持
Accept-Ranges: none(缺省)
Accept-Ranges: bytes

Response: 希望客户端如何处理: 使用默认方式打开内容 (inline) 或 保存为文件 (attachment)
Content-Disposition: inline (缺省)
Content-Disposition: attachment
Content-Disposition: attachment; filename="filename.jpg"

Request: 若允许断点下载,通过 If-Range (或 If-Match, 不推荐) 设置获取到的 Etag (推荐) 或 Last-Modified,以便服务端判断该文件在上次传输之后是否发生变动,能否续传。
If-Range: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-Range: Wed, 21 Oct 2015 07:28:00 GMT

Request: 告知本次请求所需内容范围,可能为多段。start-end (end 为空代表请求到结束为止)
Range: bytes=200-1000, 2000-6576, 19000-

Response: 返回内容总长度,无论是否为续传,该值总应该返回
Content-Length: <length>

Response: 对于断点续传,根据 Request Range 返回本次传输数据的起始范围/总长度
Accept-Ranges: bytes (仍需返回该首部)
Content-Range: bytes 200-1000/67589
Content-Range: bytes 200-1000/*

内容分块: TE / Transfer-Encoding & Trailer

HTTP 协议支持 TCP 通道复用,这意味着一个 TCP 通道可处理多次 HTTP 的数据传输,所以需要知道每次 HTTP 实体资源的长度以便区分、避免混淆,也就是为什么 Response 必须要指明 Content-Length。但有时候 Response 实体是动态生成的,需要完整生成后才能知道长度,较为耗时,更好的方案是边生成、边传输的分块传输。

Request: 告知客户端希望分块传输使用的压缩算法
TE: trailers, gzip;q=0.8, deflate;q=0.5
常用 compress / deflate / gzip,还有一个特殊的 trailers; 这里是“希望”,而不是“必须”、“仅支持”。客户端支持的压缩算法由 Accept-Encoding 提供,实际上,很少有浏览器会发送该报文。

Response: 返回 分块传输 所用的压缩算法
Transfer-Encoding: chunked (告知要分块传输,并未压缩)
Transfer-Encoding: gzip, chunked (告知要分块传输,且使用了 gzip 压缩)

Response: 告知分块数据中携带额外的 元信息
Trailer: header-names
服务端若想传输该报文,前提是客户端发送报文中包含 TE: trailers(表示客户端可以处理元信息),但由于浏览器基本都不会发送 TE报文,所以 Trailer 报文很少用到。

相对于 Content-EncodingTransfer-Encoding 的特点 :

  • 必须包含 chunked,当使用压缩算法时,“务必”将 chunked 放到最后
  • 采用分块传输时,Response 不应该发送 Content-Length 报文
  • Transfer-Encoding 针对的是分块数据使用的压缩算法,分块数据在经过代理服务器时,可以被解码并重新使用其他算法再压缩,并同时修改 Transfer-Encoding 报文分发给下级;而 Content-Encoding 是针对完整实体的压缩算法,也就是说,客户端在获取完整实体后,根据该报文整体解码,代理服务器中途不得修改。
    ● 但考虑到分块传输一般是在不方便获取完整实体时才使用的手段,并且二次压缩并不能获得较大收益,还会增加服务器负担,所以不应该有两个报文同时存在
    ● 该结论仅根据文档得出,需实际验证,事实上不同客户端对此的理解可能有偏差。
  • 与断点传输不同之处在于:分块传输是在一次 HTTP 通信中将消息实体分块传输;断点传输的每一次传输都是一个完整的 HTTP 通信,比如客户端暂停下载后重新开始,或多进程发送请求分段下载,最后合并。所以断点传输按照正常请求响应,使用 Accept-Encoding / Content-Encoding 协商机制即可。

内容上传:Content-*

Request: 比如 POST 或 PUT 操作,会发送实体内容给服务端,以下首部也可用在请求的报文中
Content-Type: multipart/form-data; boundary=**
Content-Length: <length>
Content-Encoding: gzip
Content-Language: zh

通常,在浏览器中,POST 请求会自动设置实体报文,一般只有 Content-TypeContent-Length,这对于常见的表单提交已够用。但如果需要上传大文件,是无法简单通过 HTTP 协议进行优化的,需要前后端程序实现相关逻辑,一般有以下优化方向:

  1. 若需要断点上传,可考虑将 Request 实体分片传输(分片后可并发同时传输提升效率),这需要服务端程序配合,不再是协议范围内的知识了。可 参阅
  2. 浏览器默认是不会对 Request 实体进行压缩的,可考虑使用如 pako 这样的 JS 扩展来压缩传输实体以减少传输流量、提升速度。并使用 Content-Encoding 报头标明压缩算法。

Request: 在 MDN 将分块传输首部归到了 Response header,但根据 RFC 7230 的描述来看,Request 消息实体也是可以分块传输的,当然,浏览器无法直接支持该操作,需自行通过程序处理。
Transfer-Encoding:chunked

内容摘要:Content-MD5 / Want-Digest & Digest

可使用在 Request 和 Response,用于对方校验接收到的实体内容是否完整。
Content-MD5: <hex_digest>

该首部曾经属于标准协议,但 rfc7231 [page 92] 移除了该首部,目前浏览器客户端已不会针对 Response 的 Content-MD5 进行验证,但这并不妨碍代理服务器进行识别验证,也能用于设计分片断点上传、分块传输的数据一致性校验。

新的 草案 设计了一组新的报文用于校验消息实体
Request: 客户端希望服务端使用摘要算法
Want-Digest: SHA-256;q=0.3, sha;q=1
Response: 服务端返回的实体消息摘要
Digest: sha-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=

  • Want-Digest 只能用于 Request,Digest 按协议可用于 Request 或 Response
  • Digest 支持的算法可参见 草案,摘要值使用 Base64 格式。
  • Digest 仅针对消息实体,计算摘要时不能包括报文。且是针对完整消息,即使对于断点传输,摘要也应该是完整实体消息的摘要。该报文也可用于无实体消息响应,比如 Method Header 的请求,依然可以提前返回摘要信息。
  • 吐槽:为啥不用 Accept-DigestContent-Digest 与实体首部保持一致性呢?

内容跳转: Location

Response: 将页面重新定向至的地址。
Location: <url>

30X 跳转响应:

  • 301 Moved Permanently:永久迁移,旧地址不在使用,搜索引擎应将索引改为新地址,不再爬取旧地址
  • 302 Found:资源移动,旧地址仍可使用。搜索引擎应仍索引旧地址,但内容从新地址抓取。
  • 303 See Other :PUT 或 POST 操作后,没有转向新资源,而是转向一个过渡页(如消息确认、上传进度页)
  • 307 Temporary Redirect:临时移动,日后应仍访问旧地址以确认是否恢复
  • 308 Permanent Redirect:永久迁移,且保持 method (如 301 在跳转时会将 POST 改为 GET;308不能这样)
  • 305 / 306 已不再(不推荐)使用;304 为内容缓存可用时的响应(见下面),不用于跳转。

201 Created:该响应也能携带 Location 首部,跳转到新建成功的资源地址。

Response: 刷新 / 跳转
Refresh: <seconds>[; url=<url>]

这不是一个非标准首部,RFC 标准未规定过该首部,但浏览器实际上基本都支持。作用就是在 seconds 秒之后刷新,若指定 url,则是跳转到指定地址。其效果等同于 <meta http-equiv="Refresh" value="0; url=//domain.com/" />,推荐使用 meta 方式实现目的,该 meta 的 支持度 非常好

四、内容缓存

缓存时长: Cache-Control & Expires

Response: 告知客户端资源缓存的最大时长,过期后应该重新请求。(其中 max-age 优先级更高)
Cache-Control: max-age=36000, s-maxage=700 (缓存时长)
Expires: Wed, 21 Oct 2015 07:28:00 GMT (直接告知过期时间;HTTP/1.0 定义,已不推荐使用)

Response: 若使用 Expires 设置缓存时长,另外牵涉下面两个首部
Date: Wed, 21 Oct 2015 07:28:00 GMT
Age: 300

说明:

  • 代理服务器缓存时长优先级 s-maxage > max-age,客户端会忽略 s-maxage; 若没有 Cache-Control,客户端、代理服务器都应根据 Expires 进行缓存。
  • 若使用 Expires,强烈建议设置 Date (报文创建时间,一般即为服务器的当前时间,对于代理服务器,应该缓存资源时的时间), 客户端可使用 Expires - Date = maxAge 计算出缓存实际生命长度,若没有 Date,将使用客户端时钟,但客户端时钟可能不准确,便无法准确计算。
  • 对于代理服务器,建议设置 Age (告知资源已在代理服务器上已存活的时长,不设置则认为是 0),若未设置 Age,客户端可使用 client_now - Date = Age 计算缓存实际已消耗的生命长度,客户端的最终对资源可缓存时长为maxAge - Age,这样便可在过期后进行了校验了。但鉴于对客户端时钟的不信任,建议直接返回 Age 首部。
  • 推荐使用 Cache-Control: max-age 直接设置,但建议同时返回 ExpiresDateAge 以兼容不支持 Cache-Control 的客户端。更详细说明可参见 RFC 2616

在未过期时间内,客户端都不应重新请求服务端,而是直接使用缓存。如果使用浏览器验证的话:

  • 访问资源,直接刷新,每次都会请求(在 Chrome 中已可看到 304 响应,注意不要勾选开发者工具的 "Disable cache");测试方法:打开新的 Tab 直接访问,就可以看到,使用的是缓存。
  • 也可以使用内嵌元素,如 img,设置其 max-age,将其嵌套在另外一个页面进行测试,除非是 ctrl + F5 强制刷新,普通刷新会直接使用缓存。
  • 设置缓存时长特别适用于静态资源,现在很少有直接使用原 URL 替换静态资源的,一般都是新建静态资源替换,那么就可以设置尽可能长的 max-age 来利用缓存策略。
  • 对于非静态资源,这种方式无法让用户获得及时的更新,可设置 max-age=0,即让客户端缓存,但每次都要请求服务端进行校验,校验通过,仅响应 304 即可,无需发送实体,减少数据传输。

缓存策略:Cache-Control & Pragma

用于 Response 首部

Cache-Control:no-store 客户端、代理服务器都不能缓存,设置该首部后,即使 Response 返回了指纹(如 Etag 或 Last-Modified),客户端也不缓存,下次请求也不会携带指纹。可用于随时会变动的页面,如首页、时间线页面等。

Cache-Control:no-cache 该名称非常具有迷惑性,其实际作用并不是不能缓存,而是客户端、代理服务器都可缓存,但每次都需要进行校验(前提是 Response 报文包含验证指纹),相当于 Cache-Control:max-age=0

Cache-Control:private 最终客户端可缓存,但代理服务器不能缓存,另外,对于多用户浏览器、或多用户系统,不同用户之间也不能共用缓存。比如用在登录后才能看到的页面。

Cache-Control:public 客户端、代理服务器都可缓存,广泛用于静态资源(如图片等)

Cache-control: must-revalidate 基本相当于 public (都可缓存),但缓存过期后必须校验,且确保资源新鲜后才能返回内容;若校验失败,应返回 4xx 或 5xx。而 public 则不然,比如碰到源服务器宕机,未能校验成功,有可能使用过期的缓存,代理服务器还应发送 warning 报头提醒。

Cache-Control:proxy-revalidate 相当于代理服务器使用 must-revalidate 策略,客户端使用 public 策略

Cache-Control:no-transform 针对代理服务器,不能对缓存内容进行转换,比如为节省流量,有些代理服务器可能会对图像格式进行转换。

Cache-Control:immutable 针对客户端,比如上面举例中 刷新 或 ctrl+F5 强刷,设置该值是告诉浏览器即使在强刷情况下,也无需校验,仍使用未过期缓存。该值有兼容性问题,并不是所有浏览器都已实现,但对于确定永远不会发生变化的静态资源可返回该首部,对于不支持的浏览器也不会有什么副作用。

还有两个试验性的,并发所有浏览器都支持。
Cache-Control:stale-while-revalidate=<seconds> 当验证时 - 过期缓存有效。如果响应是一个较大的资源,可以使用该值。客户端在缓存过期时可直接使用过期缓存,异步获取新鲜资源。但如果过期时长超过了指定秒数,客户端不可直接使用过期资源,必须重新校验。
Cache-Control:stale-if-error=<seconds> 当发生错误时 - 过期缓存有效。与上面的类似

  • 若未设置,大部分浏览器会按照 private 来处理。
  • 以上值并不是互斥的,可设置多个,比如 Cache-Control: proxy-revalidate, no-transform
  • 另外,浏览器总是会倾向于更严格的缓存策略,比如 public, private 则使用 privatepublic, no-store 则使用 no-storeno-cache, max-age=100 则会自动忽略 max-age
  • 对于不希望客户端缓存的,可设置为 Cache-Control: no-store, no-cache, must-revalidate,这样可避免某一个值不被客户端支持,只要客户端支持任意一个值,浏览器就可以收到二次请求。

Cache-Control 也可用于 Request 报头,这些报头一般是针对代理服务器而言的,源服务器大部分情况下无需针对该报头做特殊处理,可用值如下:
Cache-Control:no-store 必须返回新鲜资源,即代理服务器、源服务器都不应该返回 304,而应该返回 200 并传输实体内容。
Cache-Control:no-cache 可以返回304,但代理服务器必须校验是否最新(源服务器无论有没有该报头总应该这么做)
Cache-Control:no-transform 告知代理服务器,不要转换格式
Cache-Control:only-if-cached 针对代理服务器,若已缓存,不要进行校验,请直接返回。看语义,若未缓存,应该返回 4xx 响应。应该很少有客户端有这么变态的要求,可能适合用在一些数据丢失,尝试从代理服务器恢复的场景。

Cache-Control:max-age=<seconds> 代理服务器缓存时长若超过 max-age 的话,请校验新鲜度。所以 max-age=0 等价于 no-cache,必须进行新鲜度校验。
Cache-Control:min-fresh=<seconds> 代理服务器的缓存剩余生命长度不得低于该秒数
Cache-Control:max-stale[=<seconds>] 客户端可接受过期缓存【若设置秒数,表示过期时长不能超过该秒数】

Pragma 首部,可同时用于 Request 和 Response
这是一个 HTTP/1.0 版本时规定的首部,仅有一个 no-cache 值 (RFC 7234),当与 Cache-Control 同时出现时,后者优先

Request: 请代理服务器返回新鲜资源,不要缓存
Pragma: no-cache

Response: 请客户端不要缓存资源
Pragma: no-cache

指纹验证:ETag / If-Match & If-None-Match

Response: 返回资源的标识符
Etag: "c0bea9ae76c87756d20d0dd9012f8a52" (包括引号,强验证)
Etag: W/"0815" (弱验证,W必须大写)

强弱与否,对于客户端可能没有意义,只是方便服务端知道。以便在下次验证时做区分:对于强验证,哪怕一个字节发生变化,都应返回新资源;对于弱验证,只有主体内容变化,才需返回新资源(比如一个页面内容无变化,仅某一个广告发生变化,可能就无需重新返回)

Request: 若客户端缓存了上次 Response 资源,下次请求会携带 etag,有两种携带方式

  1. 缓存验证(较为常用)
    If-None-Match: "bfc13a64729c4290ef5b2c2730249c88ca92d82d"
    If-None-Match: W/"67ab43", "54ed21", "7892dd"
    If-None-Match: *

客户端携带之前缓存的标识符发送请求:

  • 若服务端无法匹配标识符(None-Match = true),返回正常的 200 响应,并发送最新 Etag;
  • 若够匹配到(None-Match = false),返回 304 (并携带原本可能出现在 200 响应中的首部:Cache-Control、Content-Location、Date、ETag、Expires 和 Vary ),无需发送实体,大大减少了数据传输
  1. 避免“空中碰撞”
    If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
    If-Match: W/"67ab43", "54ed21", "7892dd"
    If-Match: *

比如编辑页面,访问页面后获取到 etag,在更新时携带获取到的 etag:

  • 若匹配成功(Match=true),说明该资源没有被其他操作二次改动过,更新成功,返回正常的 200、新的 etag;
  • 若匹配失败(Match=false),说明在提交前已被其他用户修改,返回 412 响应

时间验证:Last-Modified / If-Modified-Since & If-Unmodified-Since

Response: 返回资源的最后修改时间
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT

Request: 若客户端缓存了上次 Response 资源,下次请求可使用最后修改时间验证

缓存验证
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
若服务端文件修改过(Modified=true),返回新的资源内容,正常 200 响应;若未修改过(Modified=false),则返回 304 响应。

避免“空中碰撞”
If-Unmodified-Since: Wed, 21 Oct 2015 07:28:00 GMT
更新内容时,需保证资源未修改(Unmodified=true),若验证通过,才会返回正常 200 响应,否则返回 412 响应。

对比一下就会发现,Modified 与 etag 二者逻辑是完全相同的;实际上 Modified 首部出现较早 (HTTP/1.0),etag 首部出现较晚 (HTTP/1.1),是用来替代 Modified 的;显而易见,etag 验证更加稳定准确,毕竟修改时间的改变并不代表内容一定发生变化,所以 推荐使用 etag;若二者同时出现,etag 优先,当前所有浏览器都已支持 etag。
但如果需要兼容旧版本浏览器,则建议二者同时发送;如果想减少报文大小或服务端不方便计算 etag,也可以仅发送 Modified,实际上,即使在今天,仍有不少大公司仅发送 Modified 首部。

缓存条件:Vary

Response: 缓存条件:若服务端在 Request 报文不同时,返回的内容不同。必须在 Response 告知客户端下次请求,只有在全部或指定的 Request header 完全相同时,才能直接使用缓存,否则必须发送请求验证资源是否更新或直接重新请求资源。
Vary: *
Vary: User-Agent, Content-Type, ...

另外关于缓存的几点总结

  • 服务端仅设置了 “验证首部(指纹或时间)”报文,客户端每次都会重新发送请求,服务端可返回 304 或 响应 200 重新发送资源实体。
  • 服务端仅设置了 “缓存时长”,客户端在缓存到期后会重新发送请求,由于没有 “验证首部”,即使服务端资源没有任何变化,也无法验证、无法返回 304,只能响应 200,重新发送资源实体。
  • 二者都设置了,客户端在缓存到期后会重新发送请求,服务端可返回 304 或 200。

私人信息:Cookie

Response: 设置cookie,可多次发送
Set-Cookie: sessionid=38afes7a8; HttpOnly; Path=/
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly

Request: 携带已设置 cookie
Cookie: sessionid=38afes7a8; id=a3fWa

缓存清除:Clear-Site-Data

Response: 告知要清除的缓存,可参见:Clear-Site-Data
Clear-Site-Data: "cache", "cookies", "storage", "executionContexts"
Clear-Site-Data: "*"

五、内容安全

以下报文相对而言都比较新,有些甚至还尚在实验中,并不是所有浏览器都支持,并且有些不仅报文可以实现,也可以通过其他方式。关于安全性的问题都比较复杂,下面每一个首部可能都值得、或者说需要一篇单独的文章才能讲清楚,这里仅列一个大概

Resonse: Content-Security-PolicyContent-Security-Policy-Report-Only
用于降低 XSS 风险,也可使用 meta 方式,可 参见,特别适合 UGC 类型,比如 facebook、instagram 就设置了该报文

Response: X-XSS-Protection
另外一个降低 XSS 风险的报文:参见

Response: Cross-Origin-Opener-PolicyCross-Origin-Embedder-PolicyCross-Origin-Resource-Policy
用于降低跨域资源或钓鱼造成的攻击,可参考 XS-Leaks跨域隔离跨域策略

Response: Origin-Isolation
来源隔离机制: 参见

Response: Feature-Policy
在直接访问或 Iframe 访问时,启用/禁用 浏览器的一些特性:参见

Response: X-Content-Type-Options
nosniff: 禁止浏览器对 css/js 进行自动嗅探,降低 XSS 风险:参见

Response: X-Frame-Options
确保网站没有被嵌入到别人的站点里面,可参考 Clickjacking攻防

Response: X-Download-Options: noope
针对 IE 浏览器的报文,下载文件的弹出框不显示“打开”选项 (仅保留“保存”选项),避免钓鱼攻击,也可通过 meta 设置,参见

Response: X-Permitted-Cross-Domain-Policies
针对 crossdomain.xml (允许跨域策略文件)的报文,主要是如 flash/Silverlight/Flex 等嵌入式旧技术,现在很少用的到,如有需要请自行找资料

六、客户端信息

以下为一些针对现代浏览器,尤其是为不同尺寸终端设计的报文,并未获得广泛良好的 支持,目前仍属于 草案,需谨慎依赖。(吐槽一下,google 对这种收集客户端信息的特别上心,参与提交草案,且 chrome 基本实现,苹果就比较反感这种有隐私顾虑的,估计 Safari 肯定不上心。另外 一例

Response: 期望客户端请求携带的信息、以及该配置的有效时长
Accept-CH: <list of client hints>,如 Accept-CH: DPR, Viewport-Width
Accept-CH-Lifetime: <age>, 如 Accept-CH-Lifetime: 86400

Request: 根据服务端响应,携带其所需报文,这里列举一些,可能有会有更多
DPR: 1.0 客户端设备的像素比
Device-Memory: 1 客户端设备内存的近似大小
Viewport-Width: 667 布局视口宽度
Width: 240 物理像素宽度

服务端可根据这些报文,针对不同的设备参数返回不同的响应,但大部分情况,在前端使用 JS 处理可能会更好一点。之所以设计出这种报文,可能是为了充分利用客户端缓存功能,以便尽可能减少流量传输。所以这里就不得不说一下,若出于此目的,切记 Response 使用 Via 指明缓存有效的报文字段,如:
Via: DPR, Viewport-Width

Request:客户端在网络状况不好或设备性能不足时,可使用该报文希望服务端发送简洁版本的响应,以减少加载时长、降低对设备的性能需求。
Save-Data: on (需要) 或 Save-Data: off (不需要)

七、代理服务器相关

如果不是编写代理软件,以下报文只有 “Forwarded” 需要在应用程序中关心。其他报文通常是代理软件处理,如 Nginx / Apache 等网关应用、云厂商提供的负载均衡产品等。但了解这些报文,也能更好的使应用程序与代理软件交互。

Request & Response: Cache-Control
有些值是专门针对代理服务器的,参见上面缓存策略章节说明

Response: 报文的创建时间,已缓存时长,参见上面缓存时长章节说明
Date: Wed, 21 Oct 2015 07:28:00 GMT
Age: 300

Request & Response: 代理服务器信息,请求/响应中均可使用,可用于分析请求链、防止循环请求等
Via: [ <protocol-name> "/" ] <protocol-version> <host> [ ":" <port> ] 如:Via: 1.1 cdn.com / Via: HTTP/1.1 cdn.com:8080
或 (pseudonym 为内部代号)
Via: [ <protocol-name> "/" ] <protocol-version> <pseudonym> 如:Via: 1.1 Name / Via: HTTP/1.1 Node

Response: 警告报文,返回给客户端或下一级代理服务器不新鲜的缓存资源,同时发出警告。
Warning: <warn-code> <warn-agent> <warn-text> [<warn-date>]
该首部可多次发送;也可用于 Request,但较为少见


Request: 代理服务器在收到客户端请求,向源服务器转发请求时,可能会丢失一些信息,所以一些应用程序或 CDN厂商 自创了一些 header 首部来拟补这些损失,目前有部分首部已纳入到协议标准。
Forwarded: by=<identifier>; for=<identifier>; host=<host>; proto=<http|https>

但一些旧程序仍然在使用原来的首部,所以源服务端也应该认真对待

Request: 在进入该级代理服务器前,请求 ip 链,第一个即为实际客户端的 IP
X-Forwarded-For: 2001:db8:85a3:8d3:1319:8a2e:370:7348, 70.41.3.18, 150.172.238.178 (常见)
X-ProxyUser-Ip: ip, ip

Request: 客户端实际请求的 host,比如是 CDN 节点 HOST,源服务器自身的 HOST 一般与此不同。
X-Forwarded-Host: id42.cdn.com

Request: 客户端请求实际的 scheme,源服务器可能是 http 服务器,https 在代理服务器层完成.
X-Forwarded-Proto: https (常见)
X-Forwarded-Protocol: https
X-Url-Scheme: https
X-Forwarded-Ssl: on
Front-End-Https: on

标准首部 Forwarded 其实就是合并版,其中 for 格式如下,ipv6 会使用方括号括起来
Forwarded: for=192.0.2.43, for="[2001:db8:cafe::17]"; host=id42.cdn.com; proto=https

Forwarded by 介绍 : The "by" parameter is used to disclose the interface where the request came in to the proxy server,看样子好像是当前代理服务器的上一级请求的 IP (可能是客户端或代理服务器),这样子其实是与 for 重复了,该值的介绍有点模糊,根据实际情况而定。

注意:由于该值十分容易伪造,所以对于源服务器而言,应该仅信任确定是代理服务器发送的值。


Request: 可以看到,代理服务器可能有多个,这对于客户端,就需要一直等候,所以这里有一个新 草案 报文,按照协议,每经一级代理服务器,该值就减小 1,当减小至 0 时,不应继续向上级请求,而是直接返回。另外有一个关于该报文的 问题
Max-Forwards:3

八、跨域请求

若是符合以下条件的简单请求:

  1. Method 为 : GET、POST、HEAD
  2. Request Header 为安全首部:如 Accept,Accept-Language 等

无论是否跨域,浏览器都将直接发送请求,根据返回的报文决定是拦截,还是将内容返回给请求方

Response: 允许跨域查询的域名,可使用 “*” 允许所有域名跨域
Access-Control-Allow-Origin: <origin> / Access-Control-Allow-Origin: *
(指定域名需包括 scheme,如: Access-Control-Allow-Origin: https://domain.com

Response: 是否允许 Request 包含认证信息,比如 cookie / authorization headers / TLS client certificates
Access-Control-Allow-Credentials: true
(如果 Response 包含该报文,Access-Control-Allow-Origin 不能使用 “*” 通配符,必须指定域名)
(如果 Response 不含该报文,而 Request 又发送了认证信息,浏览器会拦截响应,即客户端无法获得资源)

Response: 允许暴漏给外部的响应首部,默认仅暴漏 简单首部
Access-Control-Expose-Headers: <header-name>, <header-name>, ...


非简单请求,客户端会首先使用 OPTIONS Method 预检请求

Request: 告知即将使用何种 Method 发送请求
Access-Control-Request-Method: PUT

Request: 告知会携带的非安全 header 首部
Access-Control-Request-Headers: <header-name>, <header-name>, ...

Response: 返回允许的 Method
Access-Control-Allow-Methods: <method>, <method>, ...

Response: 返回允许 Request 使用的非安全 header 首部
Access-Control-Allow-Headers: *Access-Control-Allow-Headers: <header-name>[, <header-name>]*

Response: 其他所需信息(参见上面简单请求的说明)
Access-Control-Allow-Origin
Access-Control-Allow-Credentials
Access-Control-Expose-Headers

Response: 预检配置的有效时长,即在指定秒数内,无需再次预检,直接使用本次返回的结果。
Access-Control-Max-Age: 86400


Request: 在使用 XMLHttpRequest 执行 Ajax 请求时,浏览器可能会携带该首部,但并不是一定,该首部并不属于协议,所以服务端不能武断的使用该首部判断是否为 Ajax 请求。
X-Requested-With: XMLHttpRequest


Request: “Sec-Fetch” 请求元数据系列,该首部由浏览器添加(仅在 HTTPS 下发送),代理服务器等不得更改,不属于协议,仍处于 实验阶段
Sec-Fetch-Dest / Sec-Fetch-Mode / Sec-Fetch-Site / Sec-Fetch-User
服务端可利用这些报文判断访问合法性,可参见:MDN详解,但由于 HTTP 头可以伪造,跨域安全性问题不应依赖该报文。

九、HTTPS / WebSocket 等

HTTP 协议版本:从 HTTP的发展 来看,最主要的为 1.0 / 1.1 / 2。简单介绍下版本的选择机制:

  • 客户端直接使用 HTTPS 请求,Nginx 等服务器软件可使用的 ALPN 协商让客户端直接使用 HTTP/2 协议,应用层无感。
  • 若服务端 ALPN 协商告知不支持 HTTP/2 或 客户端使用 HTTP 请求:当下,浏览器都默认使用 HTTP / 1.1;但不排除有其他客户端会使用 HTTP/1.0
  • 若客户端使用 HTTP/1.0 请求,则服务端应最好是仅发送 1.0 支持的首部(不支持的报文会被客户端忽略),且不应依赖在 1.1 才有的请求首部。主要区别:
    • Host:Request 不发送 Host 首部,这个最蛋疼的一个问题。同一个 IP 可能会绑定多个域名,若 Request 不发送 Host,就没办法确定请求的是哪一个网站。所以如果需要支持这种请求,就需要给域名单独分配一个 IP,比如百度;好在这种请求现在越来越少了,对于不发送 Host 的请求,一般返回 400,比如知乎。
    • Connection / Keep-Alive:1.0 不支持 TCP 复用,一次请求结束后立即关闭 TCP 通道,Request 不会发送该首部,Response 也不应返回该首部,所以 Response 也可以不发送 Content-Length 首部。
    • Accept-*:1.0 还未引入协商机制,请求报文不会包括相关首部。但服务端应返回 Content-* 首部告知格式、语言等,且需注意,1.0 虽支持消息压缩传输,但压缩算法 支持 与 1.1 并不同。
    • Transfer-Encoding:1.0 不支持分块传输
    • Cache-Control / ETag:1.0 不支持这种缓存策略的报文,仅支持 Pragma / Last-Modified
    • 最后:1.0 与 1.1 并不是完全隔绝的两种东西,事实上,在 1.1 标准确定之前,好多 1.0 客户端已会发送 Host首部、支持复用 TCP(发送 Connection 首部),所以服务端对于 1.0/1.1 请求以收到的首部为准。
  • 服务端的 Response 协议版本不应大于 Request 所使用的版本,否则客户端可能无法处理。
  • ALPN 协商可以让客户端直接使用 HTTP/2,但其实对于 HTTP/1.1 请求,还可通过 Upgrade 首部协商升级(浏览器通常不会做,服务端对于这种请求一般是 301 到 支持 HTTP/2 的 https 地址以完成升级)。

Request: 请求可使用 HTTP/1.1 连接,之后发送首部协商升级。该用法仅支持 1.1,1.0 还未设计该机制,2 不支持协商(用意应该是不能降级,后续版本的协商方式也不是通过首部)
Connection: Upgrade
Upgrade: h2c

HTTP Method
HTTP/1.0 协议只定义了 GET、HEAD、POST;其他的一些 Method 是在 1.1 甚至是补充协议中定义的。对于一些严格验证 Method 的服务端,比如一些 REST API 服务端,那么在无法发送 PUT 等 Method 的客户端通常会使用 POST 发送,但通过首部来指明 Method 语义,常用的有以下两个
X-METHOD-OVERRIDE: PUT
X-HTTP-METHOD-OVERRIDE: PUT

HTTP/2 相关
该版本进行了大量优化,但大部分细节都是在 TCP 通信部分,主要由诸如 Nginx 等服务器软件完成。对于网络应用程序而言,并无太大差异,但对于以前奉为圭臬的一些网络优化手段,则发生了较大变化,这里简单提一下:

  • 域名散列:之前为了提高并发吞吐,会采用多域名策略,在 http/2 这样做,反而会降低性能。这主要是 http/2 使用了多路复用技术,域名少反而更有优势
  • 资源合并:原因与上面相同,http/2 并不介意同一个页面请求更多资源,因为并不会创建更多的 TCP 通道,资源合并反而因为缓存更容易失效成为劣势,想像一下,任何一个小文件的改动都会导致整个合并资源需要更新。
  • 资源内联:之前为了让页面加载即渲染,可能将 css 直接内置到页面中,但这样却无法让客户端缓存样式资源、也会让不同页面无法使用同一个资源缓存。

推荐方式

  • 尽可能给响应添加 etag 报文,利用缓存
  • 多域名解析到一个 ip,充分利用 http/2 的多路复用
  • 多域名共用一个证书,减少证书验证的时间消耗

http/2 作为一个协议,本事是可以支持 HTTP 的,但浏览器厂商都没有去实现,仅支持 HTTPS。另外, http/2 也新增了一些功能:

Request & Response: 推送,该功能也是在 Nginx 层面完成的,只需要配置即可,以下两个报文仍处于 草案,如果不是服务器程序开发者,也无需关心。
Accept-Push-Policy / Push-Policy
对于应用程序开发者而言,若不想在服务器软件内配置,Link 首部其实可以作为推送的代替品(参见最上面基础首部的介绍),但推送并不是一定有益,可参考 这篇 文章适场景而用。

Response: 备选服务(草案),支持度 还不错
Alt-Svc: <service-list>; ma=<max-age>; persist=1, <service-list>; ma=<max-age>; persist=1

  • 通过改报文可提供一个或多个备选服务,设置不同的通信协议 / 服务端IP,浏览器会缓存这些配置;
  • max-age 内,若服务端不可用,浏览器会尝试使用这些备选服务发出请求,比如 google 就设置了该报文。
  • 另外还有一个 persist 值,是否在网络发生变化(如:wifi 变 4g)时,仍保持备选服务的有效性,默认为否。
  • 想像一下,是不是可以用国内服务器给未备案的域名加速呢,感觉云厂商也会防堵这个。不过,该报文对于提高服务可用性还是用处不小的,可尝试一下。另外,该技术是不是可以在一定程度上预防 DNS 投毒。

Request: 若客户端使用备选服务发出请求,会携带该报文告知所选用的主机
Alt-Used: uri-host [ ":" port ]

HTTPS 相关:
互联网已经基本进入了 HTTPS 时代(无论是使用 HTTP/1.1 还是 HTTP/2,现在绝大多数网站都开启了 HTTPS),但由于要对旧世界兼容,所以仍然支持 HTTP。这对不少场景造成了一些问题,表现尤为严重就是流量劫持、中间人攻击,下面几个报文主要是为了降低这种问题带来的危害。

Response: HSTS(HTTP Strict Transport Security,RFC6797),严格使用安全协议传输
Strict-Transport-Security: max-age=<expire-time>; includeSubDomains; preload

  • 启用 HTTPS 后,访问 HTTP 一般会 301 到 HTTPS,但在跳转前仍然为 HTTP 响应,面临着被劫持的风险。跳转后的 HTTPS 可发送该报头(该报头仅能用在 HTTPS 响应中),那么在 max-age时间内,用户再次访问 HTTP,浏览器不会发送请求,而是直接转而请求 HTTPS。可选:includeSubDomains,子域名是否也强制启用 https,启用该项,子域名首次被劫持风险也消除了。
  • 可以看到,使用该报文也无法避免首次被劫持的风险,所有 google 搞了一份清单,收录那些永久采用 HTTPS 的域名,这样首次访问也直接跳转,目前各浏览器厂商基本都支持了该清单。添加 preload 的意思就是同意加入该清单,但并不是添加了就一定会被收录,还有有一些其他 条件。在确定永久采用 HTTPS 前,不要添加该值,否则以后想使用 HTTP 时域名就废了,目前不晓得怎么从清单删除。

Request: 客户端支持优先使用 HTTPS 加载资源
Upgrade-Insecure-Requests: 1

  • HTTPS 响应页面中可能会包含 HTTP 资源。若客户端请求发送该报文,则表明客户端支持无痛替换页面内所有 HTTP 资源为 HTTPS 资源(是直接替换,不会访问 HTTP 了)。服务端可通过 Content-Security-Policy 首部告知客户端该如何处理(参阅“内容安全”章节,提到了该报文):block-all-mixed-content (不加载 HTTP 资源)upgrade-insecure-requests (升级 HTTP 为 HTTPS)
  • 浏览器厂商除了避免服务端被劫持,也要为用户考虑,所以为了提高大家替换为 HTTPS 的积极性,如果 HTTPS 页面包含 HTTP 资源,会提醒用户该网站不安全(不同浏览器的提示方式也不尽相同)。如果是程序开发商,不知道程序被实际使用时是否会采用 HTTPS,内嵌资源可以使用无协议 URL ( //domian.com),浏览器会自动使用其所在页面的协议。否则就要排查一切非 HTTPS 资源,包括 css/js/图片/字体/异步接口、甚至是 form action.
  • 思考题:有些 APP 会在手机内建一个 127.0.0.1 的内网 server,以便于自家的网络产品使用,那么在浏览器内访问该接口是否会触发不安全提示、该如何处理。

Response: 发送证书透明度检测报告 (草案
Expect-CT: max-age=<age>; enforce; report-uri="<uri>"

  • HTTPS 主要是为了加密传输,但也不是不可 解密。对于网络应用而言,避免中间人获取传输数据,最重要的就是保护好证书私钥。但这仍然不能保证完全安全,比如著名的 DigiNotar 事件,如果 CA 机构出现问题,对于使用其证书的公司很难第一时间得到预警,消除影响。
  • 于是有了 certificate transparency 用于监测审计证书透明度,以便第一时间消除影响。该机构推进证书厂商支持 CT,好消息是现在几乎所有证书颁发机构,包括 Let's Encrypt 都已支持 CT。网站运维人员可以在 googlecrt.sh 搜索查看证书的透明度报告。
  • 由 Google 主导的 RFC 6962 则是为了更进一步,服务端可通过 report-uri 提供一个 URL,浏览器收到该报文后,会在每个 max-age 时间后发送一份透明度检测报告到该 URL,以便管理员可以更及时应对。若指定为 enforce,浏览器会在不符合 CT 安全性要求的情况下拒绝建立连接(慎用)。

Response: HPKP 指令报文
Public-Key-PinsPublic-Key-Pins-Report-Only
这是 chrome 在 CT 规范之前尝试使用的一种证书验证方式,目前已移除 chrome,应该不会再有浏览器支持了,就不再细说了。

WebSocket 相关
还记得上面说的 Upgrade 首部吧,该首部作用就是将 HTTP/1.1 升级为其他协议,浏览器利用该特性新增了 WebSocket 协议以支持双向通信。

Request: 客户端发出请求
Connection: Upgrade
Upgrade: websocket (通知服务端要升级为 websocket 协议)
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== (客户端标识)
Sec-WebSocket-Version: 13 (使用的 websocket 协议版本)
Sec-WebSocket-Protocol: soap, wamp (不常用: 使用的 websocket 子协议)
Sec-WebSocket-Extensions: permessage-deflate (不常用: 客户端支持的 websocket 扩展)
Origin: https://domian.com (其他任意 HTTP 首部)

Response: 服务端响应
Sec-WebSocket-Accept: s3p= (由于 Sec-WebSocket-Key 计算的值,返回给客户端验证)

以上便完成了 HTTP 到 WebSocket 的升级。当然,升级过程对客户端应用程序开发者而言一般是透明的,即无需了解具体细节,比如浏览器提供了 相关API,微信小程序提供的 相关API,只需要直接使用 API 操作即可,无需关心通信是如何建立的。

但如果是需要以程序方式使用客户端,或创建服务端,则需要自行处理协议,服务器软件一般不提供。可参考 RFC 6455RFC 8441 标准,另外可以在 iana 查阅已注册的 websocket 子协议和扩展。具体的消息通信协议这里不做展开,网络上有很多资料。并且还有很多已经封装好、可以直接使用的 websocket 库,有兴趣的话可以自己写一个,其实并不难。

值得在这里一提的是:若想在不支持 websocket 的客户端完成双工通信,可考虑服务端提供一个 SSE 保持消息推送,客户端使用 AJAX 或 Fetch 等手段、通过 HTTP 协议向服务器发送信息。

Service Workers
一种可以构建离线应用的技术,可参考:渐进式 Web 应用PWA 应用实战Service Workers

这里的篇幅不足以解释的更清楚,仅是为了列举一个在这种技术下的的首部
Response: 设置离线服务的控制范围
Service-Worker-Allowed: <path>

DNS over HTTPS (DoH
RFC 8484 发布标准,该技术与网络开发者没什么关系。仅针对客户端开发者,一般指浏览器,但也包括如 cURL 等软件,目的是作为 DNS 的一种补充,保护 DNS 查询时的用户隐私,但也会带来一些 风险,有兴趣的可以看一下。

其他 : 以下都不属于标准协议,列举几个有意思的

Response: 这是 WordPress 定义的一个首部,用于不同博客间的引用通知,如果是开发博客类程序,可了解一下,这里有一篇 文章 对此作了说明
X-Pingback: <url>

Response: 针对搜索引擎一个首部,告知其索引规则,可 参阅
X-Robots-Tag: googlebot: nofollow

Response: 告知错误报告的发送地,也许可以用在自动测试的相关程序上,详情 参阅
NEL: { "report_to": "name_of_reporting_group", "max_age": 12345, "include_subdomains": false, "success_fraction": 0.0, "failure_fraction": 1.0 }

十、结尾

以上主要介绍 HTTP 报文、内容传输方面的,其实还有较为重要的 HTTP 请求方法 (扩展:IANA)和 HTTP 响应代码 (扩展:IANA),因为比较简单,就不再展开。

HTTP / 3 马上也要来了,好消息是对于网络应用而言,并无特别需要注意的地方,终于可以松一口气了。可以看到,HTTP 协议越来越复杂了,就在现在,还有各种 草案 在不断的提出、等候纳入标准,IANA 还整理一份 Header 清单;伴随着浏览器支持的接口越来越多,提供的基础能力越来越丰富,估计以后会越来越多。

不知道跟 google 有没有关系,反正感觉 Chrome 占据绝对份额后,WEB 发展就越来越快了。google 自然有绝对的利益驱动这件事,如果大家都不搞 APP,全部以 WEB 提供服务,google 恐怕做梦都会笑醒。但也要小心 Chrome 屠龙少年变恶龙,成为新时代的 IE6,比如 名场面,若是 google 无法推动某项技术成为标准,借助 Chrome 也能搞成既定标准,所以对于网络应用开发者,建议大家最好是尽可能让自己的产品兼容 Firefox 等其他浏览器。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容