TCP/IP 网络分层模型 | 说明 | OSI 网络分层模型 | 说明 |
---|---|---|---|
应用层 (application layer) | 因为下面的三层把基础打得很是好,因此在这一层就百花齐放了,有各类面向具体应用的协议。例如 Telnet、SSH、FTP、SMTP 等等,固然还有咱们的 HTTP。传输单位则是消息或报文(message)。 | 应用层 (application layer) | 面向具体的应用传输数据。 |
- | - | 表示层(presentation layer) | 把数据转换为合适、可理解的语法和语义。 |
- | - | 会话层(session layer) | 维护网络中的链接状态,即保持会话和同步。 |
传输层(transport layer) | 这个层次协议的职责是保证数据在 IP 地址标记的两点之间可靠地传输,是 TCP 协议工做的层次,另外还有它的一个小伙伴 UDP。传输单位是段(segment)。 | 传输层(transport layer) | 至关于 TCP/IP 里的传输层。 |
网际层(internet layer) | IP 协议就处在这一层。由于 IP 协议定义了 IP 地址 的概念,因此就能够在 连接层 的基础上,用 IP 地址取代 MAC 地址 ,把许许多多的局域网、广域网链接成一个虚拟的巨大网络,在这个网络里找设备时只要把 IP 地址再「翻译」成 MAC 地址就能够了。传输单位是包(packet)。 | 网络层(network layer) | 至关于 TCP/IP 里的网际层。 |
连接层 (link layer/MAC) | 负责在以太网、WiFi 这样的底层网络上发送原始数据包,工做在网卡这个层次,使用 MAC 地址来标记网络上的设备 ,因此有时候也叫 MAC 层。传输单位是帧(frame)。 | 数据链路层(data link layer) | 至关于 TCP/IP 的连接层。 |
物理层(physical layer) | 网络的物理形式,例如电缆、光纤、网卡、集线器等等。 |
HTTP 是什么?
超文本传输协议(HTTP)是一个用于传输超媒体文档(例如 HTML)的应用层协议。javascript
它是为 Web 浏览器与 Web 服务器之间的通讯而设计的,但也能够用于其余目的。css
HTTP 遵循经典的客户端-服务端模型,客户端打开一个链接以发出请求,而后等待直到收到服务器端响应。html
HTTP 是无状态协议,这意味着服务器不会在两个请求之间保留任何数据(状态)。java
HTTP 协议的请求报文和响应报文的结构基本相同,由三大部分组成:git
请求报文的起始行叫作请求行(request line),它简要地描述了 客户端想要如何操做服务器端的资源 。github
请求方法(Method -> GET/POST)+ 请求目标(Path -> URI)+ 版本号(Version of the protocol -> HTTP 1.0 / 1.1 / 2.0)
这三个部分一般使用空格(space)来分隔,最后要用 CRLF
换行表示结束。web
目前 HTTP/1.1 规定了八种方法,单词 都必须是大写的形式 ,我先简单地列把它们列出来,后面再详细讲解。面试
GET 和 POST 的区别
做用算法
GET 用于获取资源,而 POST 用于传输实体主体。数据库
参数
GET 和 POST 的请求都能使用额外的参数,可是 GET 的参数是以查询字符串出如今 URL 中,而 POST 的参数存储在实体主体中。
由于 URL 只支持 ASCII 码,所以 GET 的参数中若是存在中文等字符就须要先进行编码。
http://www.baidu.com/?百度一下,你就知道 http://www.baidu.com/?%E7%99%BE%E5%BA%A6%E4%B8%80%E4%B8%8B%EF%BC%8C%E4%BD%A0%E5%B0%B1%E7%9F%A5%E9%81%93
安全
在 HTTP 协议里,所谓的 安全 是指请求方法不会「破坏」服务器上的资源,即不会对服务器上的资源形成实质的修改。
GET 方法是安全的,而 POST 却不是,由于 POST 的目的是传送实体主体内容,这个内容多是用户上传的表单数据,上传成功以后,服务器可能把这个数据存储到数据库中,所以状态也就发生了改变。
幂等性
所谓的 幂等(Idempotent) 其实是一个数学用语,被借用到了 HTTP 协议里,意思是屡次执行相同的操做,结果也都是相同的,即屡次幂后结果相等。
GET /pageX HTTP/1.1 是幂等的,连续调用屡次,客户端接收到的结果都是同样的:
GET /pageX HTTP/1.1 GET /pageX HTTP/1.1 GET /pageX HTTP/1.1 GET /pageX HTTP/1.1
POST /add_row HTTP/1.1 不是幂等的,若是调用屡次,就会增长多行记录:
POST /add_row HTTP/1.1 -> Adds a 1nd row POST /add_row HTTP/1.1 -> Adds a 2nd row POST /add_row HTTP/1.1 -> Adds a 3rd row
缓存
GET是可缓存的,POST不可缓存。
XMLHttpRequest
XMLHttpRequest 是一个 API,它为客户端提供了在客户端和服务器之间传输数据的功能。它提供了一个经过 URL 来获取数据的简单方式,而且不会使整个页面刷新。这使得网页只更新一部分页面而不会打扰到用户。XMLHttpRequest 在 AJAX 中被大量使用。
URI(Uniform Resource Identifier) 本质上是一个字符串,这个字符串的做用是 惟一地标记资源的位置或者名字 。
http
、https
。后面必须是 三个特定的字符 ://
。host:port
,端口可省略。/
开始,也就是必须包含 /
。https://search.jd.com/Search?keyword=openresty&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=openresty&psort=3&click=0 // scheme -> https , authority -> search.jd.com , path -> /Search... , query -> keyword=openresty...
URI只能支持ASCII,对于ASCII 码之外的字符集和特殊字符,URI经过百分号编码( Percent Encoding)进行转义,转义规则是
直接把非 ASCII 码或特殊字符转换成十六进制字节值 ,而后前面再加上一个
%
。
http://www.baidu.com/?百度一下,你就知道 http://www.baidu.com/?%E7%99%BE%E5%BA%A6%E4%B8%80%E4%B8%8B%EF%BC%8C%E4%BD%A0%E5%B0%B1%E7%9F%A5%E9%81%93
响应报文的起始行叫作状态行(status line),意思是 服务器响应的状态。
版本号(Version of the protocol -> HTTP 1.0 / 1.1 / 2.0)+ 状态码(Status code -> 200 / 404)+ 缘由(Status message -> 数字状态码补充)
这三个部分一样使用空格(space)来分隔,最后要用 CRLF
换行表示结束。
状态码 | 类别 | 含义 |
---|---|---|
1XX | Informational(信息性状态码) | 接收的请求正在处理 |
2XX | Success(成功状态码) | 请求正常处理完毕 |
3XX | Redirection(重定向状态码) | 须要进行附加操做以完成请求 |
4XX | Client Error(客户端错误状态码) | 服务器没法处理请求 |
5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
1XX 信息
2XX 成功
HEAD
请求,服务器返回的响应头都会有 body 数据。3XX 重定向
302 Found :临时重定向,意思是请求的资源还在,但须要暂时用另外一个 URI 来访问。
301 和 302 都会在响应头里使用字段 Location
,指明后续要跳转的 URI,浏览器会重定向到新的 URI。
304 Not Modified :若是请求报文首部包含一些条件,例如:If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,若是不知足条件,则服务器会返回 304 状态码。
4XX 客户端错误
剩下的错误代码较明确地说明了错误的缘由:
5XX 服务器错误
请求行 + 头部字段 = 请求头 ; 状态行 + 头部字段 = 响应头。
请求头和响应头的结构是基本同样的,惟一的区别是起始行,因此请求头和响应头里的字段能够放在一块儿介绍。
头部字段是 key-value
的形式,key
和 value
之间用 :
分隔,最后用 CRLF
换行表示字段结束。
HTTP 头字段很是灵活,不只能够使用标准里的 Host、Connection 等已有头,也能够 任意添加自定义头 ,这就给 HTTP 协议带来了无限的扩展可能。
不过使用头字段须要注意下面几点:
Host
也能够写成 host
,但首字母大写的可读性更好;-
,但不能使用下划线 _
。例如,test-name
是合法的字段名,而 test name
、test_name
是不正确的字段名;:
,不能有空格,而 :
后的字段值前能够有多个空格;Set-Cookie
。头部字段名 | 说明 |
---|---|
Cache-Control | 控制缓存的行为 |
Connection | 控制再也不转发给代理的首部字段、管理持久链接 |
Date | 建立报文的日期时间 |
Pragma | 报文指令 |
Trailer | 报文末端的首部一览 |
Transfer-Encoding | 指定报文主体的传输编码方式 |
Upgrade | 升级为其余协议 |
Via | 代理服务器的相关信息 |
Warning | 错误通知 |
头部字段名 | 说明 |
---|---|
Accept | 用户代理可处理的媒体类型 |
Accept-Charset | 优先的字符集 |
Accept-Encoding | 优先的内容编码 |
Accept-Language | 优先的语言(天然语言) |
Authorization | Web 认证信息 |
Expect | 期待服务器的特定行为 |
From | 用户的电子邮箱地址 |
Host | 请求资源所在服务器 |
If-Match | 比较实体标记(ETag) |
If-Modified-Since | 比较资源的更新时间 |
If-None-Match | 比较实体标记(与 If-Match 相反) |
If-Range | 资源未更新时发送实体 Byte 的范围请求 |
If-Unmodified-Since | 比较资源的更新时间(与 If-Modified-Since 相反) |
Max-Forwards | 最大传输逐跳数 |
Proxy-Authorization | 代理服务器要求客户端的认证信息 |
Range | 实体的字节范围请求 |
Referer | 对请求中 URI 的原始获取方 |
TE | 传输编码的优先级 |
User-Agent | HTTP 客户端程序的信息 |
头部字段名 | 说明 |
---|---|
Accept-Ranges | 是否接受字节范围请求 |
Age | 推算资源建立通过时间 |
ETag | 资源的匹配信息 |
Location | 令客户端重定向至指定 URI |
Proxy-Authenticate | 代理服务器对客户端的认证信息 |
Retry-After | 对再次发起请求的时机要求 |
Server | HTTP 服务器的安装信息 |
Vary | 代理服务器缓存的管理信息 |
WWW-Authenticate | 服务器对客户端的认证信息 |
头部字段名 | 说明 |
---|---|
Allow | 资源可支持的 HTTP 方法 |
Content-Encoding | 实体主体适用的编码方式 |
Content-Language | 实体主体的天然语言 |
Content-Length | 实体主体的大小 |
Content-Location | 替代对应资源的 URI |
Content-MD5 | 实体主体的报文摘要 |
Content-Range | 实体主体的位置范围 |
Content-Type | 实体主体的媒体类型 |
Expires | 实体主体过时的日期时间 |
Last-Modified | 资源的最后修改日期时间 |
空行,也就是 「CRLF」,十六进制的「0D0A」。
实际传输的数据,它不必定是纯文本,能够是图片、视频等二进制数据。与 header 对应,被称为 body 。
多用途互联网邮件扩展(Multipurpose Internet Mail Extensions),简称为 MIME。HTTP 取了其中的一部分,用来标记 body 的数据类型 ,这就是咱们日常总能听到的 MIME type。
经常使用的MIME type:
text/html
(超文本文档)、text/plain
(纯文本)、text/css
(样式表) 等。image/gif
、image/jpeg
、image/png
等。audio/video
:音频和视频数据,例如 audio/mpeg
、video/mp4
等。application/json
,application/javascript
、application/pdf
等,另外,若是实在是不知道数据是什么类型,像刚才说的黑盒,就会是 application/octet-stream
,即不透明的二进制数据 。Accept: text/html,application/xml,image/webp,image/png //客户端可接收类型,","分隔符列出多个类型。 Content-Type: text/html //实体数据的真实类型。
HTTP 在传输时为了节约带宽,有时候还会 压缩数据 ,经过Encoding type解压缩。
经常使用的Encoding type只有下面三种:
Accept-Encoding: gzip, deflate, br //客户端支持的压缩格式,若是没有就表示客户端不支持压缩数据。 Content-Encoding: gzip //实体数据使用的压缩格式,若是没有就表示相应数据没被压缩。
为了解决语言文字国际化的问题,又引入了两个概念:语言类型和字符集。
Accept-Language: zh-CN, zh, en //请求头字段,表示客户端可理解的天然语言,用`,` 作分隔符列出多个类型。 Content-Language: zh-CN //实体头字段,告诉客户端实体数据使用的语言类型。(不会发送) Accept-Charset: gbk, utf-8 //请求头字段,表示浏览器请求的字符集。(不会发送) Content-Type: text/html; charset=utf-8 //通用头字段里面包含服务器返回的实体数据字符集。
浏览器都支持多种字符集,一般不会发送 Accept-Charset
,而服务器也不会发送 Content-Language
,由于使用的语言彻底能够由字符集推断出来,因此在请求头里通常只会有 Accept-Language
字段,响应头里只会有 Content-Type
字段。
Accept: text/html,application/xml;q=0.9,*/*;q=0.8 Vary: Accept-Encoding,User-Agent,Accept
在 HTTP 协议里用 Accept、Accept-Encoding、Accept-Language 等请求头字段进行内容协商的时候,还能够用一种特殊的 q
参数表示权重来设定优先级,这里的 q
是 quality factor
的意思。
权重的最大值是 1,最小值是 0.01,默认值是 1,若是值是 0 就表示拒绝。具体的形式是在数据类型或语言代码后面加一个 ;
,而后是 q=value
。
这里要提醒的是 ;
的用法,在大多数编程语言里 ;
的断句语气要强于 ,
,而在 HTTP 的内容协商里却刚好反了过来,;
的意义是小于 ,
的。
这个 Vary 字段表示服务器依据了 Accept-Encoding、User-Agent 和 Accept 这三个头字段,而后决定了发回的响应报文。
Vary 字段能够认为是响应报文的一个特殊的 版本标记 。每当 Accept 等请求头变化时,Vary 也会随着响应报文一块儿变化。也就是说,同一个 URI 可能会有多个不一样的「版本」,主要用在传输链路中间的代理服务器实现缓存服务 ,这个以后讲 HTTP 缓存 时还会再提到。
Accept-Encoding: gzip, deflate, br //客户端支持的压缩格式,若是没有就表示客户端不支持压缩数据。 Content-Encoding: gzip //实体数据使用的压缩格式,若是没有就表示相应数据没被压缩。
Transfer-Encoding: chunked //通用头字段,body 分红了许多的块(chunk)逐个发送。
Transfer-Encoding: chunked
和 Content-Length
这两个字段是 互斥的 ,也就是说响应报文里这两个字段不能同时出现,一个响应报文的传输要么是长度已知,要么是长度未知(chunked)。
CRLF
(回车换行,即 \r\n
)结尾的一行明文,用 16 进制数字表示长度;CRLF
结尾,但数据不包含 CRLF
;0\r\n\r\n
。HTTP/1.1 200 OK Content-Type: text/plain Transfer-Encoding: chunked 7\r\n //length 是以十六进制的形式表示。\r\n 是 CRLF Mozilla\r\n 9\r\n Developer\r\n 7\r\n Network\r\n 0\r\n \r\n
在视频中拖动进度条,这其实是想获取一个大文件其中的片断数据 ,而分块传输并无这个能力。
HTTP 协议为了知足这样的需求,提出了 范围请求 (range requests)的概念,容许客户端在请求头里使用专用字段来表示只获取文件的一部分 。
Accept-Ranges: none //不支持范围请求 Accept-Ranges: bytes = x - y //支持范围请求, x 和 y 是字节为单位的数据范围,表示的是偏移量。 //假设文件是100字节 Accept-Ranges: bytes = 0- //表示从文档起点到文档终点,至关于 0-99 ,即整个文件; Accept-Ranges: bytes = 10- //是从第 10 个字节开始到文档末尾,至关于 10-99; Accept-Ranges: bytes = -1 //是文档最后一个字节,至关于 99-99; Accept-Ranges: bytes = -10 //是从文档末尾倒数 10 个字节,至关于 90-99。
服务器收到 Range 字段后,须要作四件事:
HTTP/1.1 206 Partial Content Date: Wed, 15 Nov 2015 06:25:24 GMT Last-Modified: Wed, 15 Nov 2015 04:58:08 GMT Content-Range: bytes 21010-47021/47022 Content-Length: 26012 Content-Type: image/gif ... 26012 bytes of partial image data ...
Range: bytes=0-9, 20-29, 30-39 //请求头
响应头使用的MIME是multipart/byteranges
,还有个参数boundary=xxx
是用来分隔字节序列的。
每一个字节序列还须要用 Content-Type
和 Content-Range
标记这段数据的类型和所在范围。
HTTP/1.1 206 Partial Content Content-Type: multipart/byteranges; boundary=00001111 Content-Length: 189 Connection: keep-alive Accept-Ranges: bytes --00001111 Content-Type: text/plain Content-Range: bytes 0-9/96 // this is --00001111 Content-Type: text/plain Content-Range: bytes 20-29/96 ext json d --00001111--
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问的 HTML 页面资源,还会请求图片资源。若是每进行一次 HTTP 通讯就要新建一个 TCP 链接,那么开销会很大。
长链接只须要创建一次 TCP 链接就能进行屡次 HTTP 通讯。
Connection : close
;Connection : Keep-Alive
。队头阻塞与短链接和长链接无关 ,而是由 HTTP 基本的 请求 - 应答 模型所致使的。
由于 HTTP 规定报文必须是 一发一收 ,这就造成了一个先进先出的 串行队列 。队列里的请求没有优先级,只有入队的前后顺序,排在最前面的请求被优先处理。若是队首的请求处理的慢,后面的全部请求就要一直等待。这就致使队头阻塞。
同时对一个域名发起多个长链接,用数量来解决质量的问题 。但这种方式也存在缺陷。若是每一个客户端都想本身快,创建不少个链接,用户数×并发数就会是个天文数字。服务器的资源根本就扛不住,或者被服务器认为是恶意攻击,反而会形成拒绝服务。
HTTP 协议建议客户端使用并发,但不能「滥用」并发。
HTTP 协议和浏览器不是限制并发链接数量吗?好,那我就多开几个域名,好比 shard1.chrono.com
、shard2.chrono.com
,而这些域名都指向同一台服务器 www.chrono.com
,这样实际长链接的数量就又上去了,也就解决了队头阻塞问题。
https://im.qq.com/download/
在 QQ 主页点一下 下载 链接,会发生什么呢?
浏览器解析文字里的 URI -> 用这个 URI 发起一个新的 HTTP 请求 -> 得到响应报文,渲染出新 URI 指向的页面。
这样由浏览器使用者发起的跳转叫 主动跳转,由服务器发起的跳转,被叫作 重定向(Redirection)。
Location:https://im.qq.com/download/ //绝对URI。 Location:/index.html //相对URI,Location是响应头部字段。
重定向的相关问题
A=>B=>C=>A
的无限循环。浏览器必须具备检测 循环跳转 的能力,在发现这种状况时应当中止发送请求并给出错误提示。HTTP 协议是无状态的,主要是为了让 HTTP 协议尽量简单,使得它可以处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。
Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器以后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。因为以后每次请求都会须要携带 Cookie 数据,所以会带来额外的性能开销(尤为是在移动环境下)。
Cookie 曾一度用于客户端数据的存储,由于当时并无其它合适的存储办法而做为惟一的存储手段,但如今随着现代浏览器开始支持各类各样的存储方式,Cookie 渐渐被淘汰。新的浏览器 API 已经容许开发者直接将数据存储到本地,如使用 Web storage API(本地存储和会话存储)或 IndexedDB。
Cookie: name=value; name2=value2; name3=value3 //请求头 Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly...
Set-Cookie:Expires=Wed, 21 Oct 2015 07:28:00 GMT; Max-Age=10; //Max-Age优先级更高
Domain 标识指定了哪些主机能够接受 Cookie。若是不指定,默认为当前文档的主机(不包含子域名)。若是指定了 Domain,则通常包含子域名。例如,若是设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如 developer.mozilla.org)。
Set-Cookie: Domain = mozilla.org;
Path 标识指定了主机下的哪些路径能够接受 Cookie(该 URL 路径必须存在于请求 URL 中)。以字符 %x2F ("/") 做为路径分隔符,子路径也会被匹配。例如,设置 Path=/docs,则如下地址都会匹配:
/docs /docs/Web/ /docs/Web/HTTP
大多数状况只用一个/
或者直接省略,表示域名下任意路径都容许使用Cookie。
document.cookie
等一切相关的 API。这样就会阻止 跨站脚本(XSS)。Secure 表示这个 Cookie 仅能用 HTTPS 协议加密传输 ,明文的 HTTP 协议会禁止发送。但 Cookie 自己不是加密的,浏览器里仍是以明文的形式存在。
Cookie 主要用于如下三个方面:
缓存(cache)是一种保存资源副本并在下次请求时直接使用该副本的技术。当 web 缓存发现请求的资源已经被存储,它会拦截请求,返回该资源的拷贝,而不会去源服务器从新下载。
不须要发送请求到服务端,直接读取浏览器本地缓存,在 Chrome 的 Network 中显示的 HTTP 状态码是 200 ,在 Chrome 中,强缓存又分为 Disk Cache(存放在硬盘中)和 Memory Cache(存放在内存中),存放的位置是由浏览器控制的。是否强缓存由 Expires
、Cache-Control
和 Pragma
3 个 Header 属性共同来控制。
Pragma:no-cache; //禁用缓存,只用于HTTP1.0的状况。响应头字段不支持这个属性。
Expires所定义的缓存时间是相对服务器上的时间而言的,其定义的是资源“失效时刻”,若是客户端上的时间跟服务器上的时间不一致(特别是用户修改了本身电脑的系统时间),那缓存时间可能就没啥意义了。
Expires:Wed, 21 Oct 2015 07:28:00 GMT;//http1.0 指定缓存的过时时间。实体头字段。
针对上述的“Expires时间是相对服务器而言,没法保证和客户端时间统一”的问题,HTTP1.1
新增了 Cache-Control
来定义缓存过时时间。
//可缓存性 Cache-Control:no-cache;//至关于`max-age=0,must-revalidate`即资源被缓存,可是缓存马上过时。同时下次访问时强制验证资源有效性。 Cache-Control:no-store;//请求和响应都不缓存。通用头字段。 Cache-Control:public;//代表响应能够被发送请求的客户端,代理服务器等缓存。响应头字段。 Cache-Control:private;//代表响应只能被单个用户缓存,不能做为共享缓存(即代理服务器不能缓存它)。响应头字段。 //到期 Cache-Control:max-age=<seconds>;//缓存资源,可是在指定时间(单位为秒)后过时。时间是相对于请求的时间。通用头字段。 Cache-Control:s-maxage=<seconds>;//覆盖max-age或者Expires头,可是仅适用于共享缓存(好比各个代理),私有缓存会忽略它。响应头字段。 Cache-Control:max-stale[=<seconds>];//指定时间内,即便缓存过时,资源依然有效。请求头字段。 Cache-Control:min-fresh=<seconds>;//缓存的资源至少要保持指定时间的新鲜期。请求头字段。 //从新验证和从新加载 Cache-Control:must-revalidate;//一旦资源过时(好比已经超过max-age),在成功向原始服务器验证以前,缓存不能用该资源响应后续请求。响应头字段。 Cache-Control:proxy-revalidate;//与must-revalidate做用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略。响应头字段。 //其余 Cache-Control:no-transform;//强制要求代理服务器不要对资源进行转换,禁止代理服务器对 Content-Encoding、Content-Range、Content-Type 字段的修改(所以代理的 gzip 压缩将不被容许)。请求头字段。 Cache-Control:only-if-cached;//仅仅返回已经缓存的资源,不访问网络,若无缓存则返回504。请求头字段。
假设所请求资源于4月5日缓存, 且在4月12日过时。
当max-age
与 max-stale
和 min-fresh
同时使用时, 它们的设置相互之间独立生效, 最为保守的缓存策略老是有效。
这意味着,若是max-age = 10 days
,max-stale = 2 days
,min-fresh = 3 days
, 那么:
max-age
的设置,覆盖原缓存周期,缓存资源将在4月15日失效( 5 + 10 = 15);max-stale
的设置, 缓存过时后两天依然有效,此时响应将返回110(Response is stale)状态码, 缓存资源将在4月14日失效( 12 + 2 = 14);min-fresh
的设置,至少要留有3天的新鲜期,缓存资源将在4月9日失效( 12 - 3 = 9);因为客户端老是采用最保守的缓存策略, 所以, 4月9往后,对于该资源的请求将从新向服务器发起验证。
当客户端上某个资源保存的缓存时间过时了,但这时候服务器并无更新过这个资源,若是这个资源很大,咱们有必要再把这个资源从新发一遍吗?
答案是否认的。为了让客户端与服务器之间能实现缓存文件是否更新的验证、提高缓存的复用率,HTTP1.1新增了几个首部字段来作这件事情。
Last-Modifie:GMT
的形式加在实体头字段上一块儿返回给客户端。客户端为资源标记上该信息,下次再次请求时,会把该信息附带在请求报文中一并带给服务器去作检查。
Last-Modified
用于标记请求资源的最后一次修改时间, 格式为GMT(格林尼治标准时间)。
If-Modified-Since
缓存校验字段, 其值为上次响应头的Last-Modified
值,若与请求资源当前的Last-Modified
值相同,那么将返回304状态码的响应,反之, 将返回200状态码响应。请求头字段。最多见的应用场景:
ETag
标签的缓存实体。If-Unmodified-Since
缓存校验字段,其值为上次响应头的Last-Modified值,表示资源在指定的时间以后未修改则正常执行更新,不然返回412(Precondition Failed)状态码的响应。请求头字段。经常使用于以下两种场景:
Wiki
文档,文档未修改时才执行更新。If-Range
字段同时使用时, 能够用来保证新的片断请求来自一个未修改的文档。Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT;//精确度比ETag低,备用机制。HTTP1.0。 If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT;//当与 If-None-Match 一同出现时,它(If-Modified-Since)会被忽略掉。 If-Unmodified-Since: Wed, 21 Oct 2015 07:28:00 GMT;
Last-Modified
存在的问题:
Last-Modified
标注的最后修改只能精确到秒级,若是某些文件在 1 秒钟之内,被修改屡次的话,它将不能准确标注文件的新鲜度;Last-Modified
却改变了,致使文件无法使用缓存;为了解决上述Last-Modified
可能存在的的问题,HTTP1.1还推出了 ETag 实体头字段。
服务器经过算法,给资源计算得出一个惟一标识符,在把资源响应给客户端的时候,会在实体头字段加上ETag:惟一标识符
一块儿返回给客户端。例如:
ETag: "x234dff";
客户端会保留该ETag
字段,并在下一次请求时将其一并带过去给服务器。服务器只要对比客户端(做为If-None-Match字段的值一块儿发送)传来的ETag
和本身服务器上该资源的ETag
是否一致,就能很好的判断资源相对客户端而言是否被修改过了。
ETag
匹配不上,那么直接以常规GET
200回包形式将新资源(固然也包括了新的ETag
)发给客户端。ETag
一致,则直接返回304知会客户端直接使用本地缓存便可。ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"//强缓存 ETag 要求资源在字节级别必须彻底相符。 ETag: W/"0815"//弱ETag 只要求资源在语义上没变化,但内部可能会发生了变化(HTML里的标签顺序调整,多出了几个空格)。 If-None-Match: "bfc13a64729c4290ef5b2c2730249c88ca92d82d"//请求头字段。 If-None-Match: W/"67ab43", "54ed21", "7892dd" If-None-Match: * //星号是一个特殊值,能够表明任意资源。它只用在进行资源上传时,一般是采用 PUT 方法,来检测拥有相同识别ID的资源是否已经上传过了。 If-Match: "bfc13a64729c4290ef5b2c2730249c88ca92d82d"//请求头字段。 If-Match: W/"67ab43", "54ed21", "7892dd" If-Match: * //星号指代任意资源。
If-None-Match
经常使用于判断缓存资源是否有效
在请求方法为 GET 和 HEAD 的状况下
ETag
列表不匹配,服务器返回状态码200的响应。ETag
列表匹配,服务器返回状态码304的响应。PUT
,将If-None-Match
used 的值设置为 *,用来生成事先并不知道是否存在的文件,能够确保先前并无进行过相似的上传操做,防止以前操做数据的丢失。当与 If-Modified-Since
一同使用的时候,If-None-Match
优先级更高(假如服务器支持的话)。
If-Match
经常使用于判断条件是否知足
GET
和 HEAD
方法,搭配 Range头字段 使用,能够用来保证新请求的范围与以前请求的范围是对同一份资源的请求。若是 ETag
没法匹配,那么须要返回 416 (Range Not Satisfiable,范围请求没法知足) 响应。PUT
, If-Match
头字段能够用来避免更新丢失问题。它能够用来检测用户想要上传的不会覆盖获取原始资源以后作出的更新。若是请求的条件不知足,那么须要返回 412 (Precondition Failed,先决条件失败) 响应。头部 | 优点和特色 | 劣势和问题 |
---|---|---|
Expires | 一、HTTP 1.0 产物,能够在HTTP 1.0和1.1中使用,简单易用。 二、以时刻标识失效时间。 | 一、时间是由服务器发送的(UTC),若是服务器时间和客户端时间存在不一致,可能会出现问题。 二、存在版本问题,到期以前的修改客户端是不可知的。 |
Cache-Control | 一、HTTP 1.1 产物,以时间间隔标识失效时间,解决了Expires服务器和客户端相对时间的问题。 二、比Expires多了不少选项设置。 | 一、HTTP 1.1 才有的内容,不适用于HTTP 1.0 。 二、存在版本问题,到期以前的修改客户端是不可知的。 |
Last-Modified | 一、不存在版本问题,每次请求都会去服务器进行校验。服务器对比最后修改时间若是相同则返回304,不一样返回200以及资源内容。 | 一、只要资源修改,不管内容是否发生实质性的变化,都会将该资源返回客户端。例如周期性重写,这种状况下该资源包含的数据实际上同样的。 二、以时刻做为标识,没法识别一秒内进行屡次修改的状况。 三、某些服务器不能精确的获得文件的最后修改时间。 |
ETag | 一、能够更加精确的判断资源是否被修改,能够识别一秒内屡次修改的状况。 二、不存在版本问题,每次请求都回去服务器进行校验。 | 一、计算ETag值须要性能损耗。 二、分布式服务器存储的状况下,计算ETag的算法若是不同,会致使浏览器从一台服务器上得到页面内容后到另一台服务器上进行验证时发现ETag不匹配的状况。 |
Pragma > Cache-Control > Expires
。
ETag
> Last-Modified
(同时存在的时候,Last-Modified
是 ETag
备份)。
浏览器能够在内存、硬盘中开辟一个空间用于保存请求资源副本。
请求一个资源时,会按照Service Worker
-> Memory Cache
-> Disk Cache
-> Push Cache
依次查找缓存。
200 from memory cache 表示不访问服务器,直接从内存中读取缓存。 储存较小的文件,关闭进程后缓存资源随之销毁。
200 from disk cache 表示不访问服务器,直接从硬盘中读取缓存。存储大文件,关闭进程后,缓存资源依然存在。
200 from prefetch cache 在 preload
(预加载)或 prefetch
(预先载入)的资源加载时,二者也是均存储在 http cache
,当资源加载完成后,若是资源是能够被缓存的,那么其被存储在 http cache
中等待后续使用;若是资源不可被缓存,那么其在被使用前均存储在 memory cache
。
代理在客户端和服务器本来的通讯链路中插入的一个中间环节 ,也是服务器,但提供的是代理服务。
所谓的代理服务是指服务自己不产生内容,而是处于中间位置 转发上下游的请求和响应,具备双重身份:面向下游的用户时,表现为服务器,表明源服务器响应客户端的请求;而面向上游的源服务器时,又表现为客户端,表明客户端发送请求。
这里主要说的是最多见的反向代理。
Via 代理服务器用它标明代理的身份。Via 是一个通用头字段,请求头或响应头里均可以出现。
有多个代理节点的状况下,客户端发送请求和服务器返回响应via头字段顺序不同:
Via 字段只解决了 客户端和源服务器判断是否存在代理的问题,还不能知道对方的真实信息 。
但服务器的 IP 地址应该是保密的,关系到企业的内网安全,因此通常不会让客户端知道。不过反过来,一般服务器须要知道客户端的真实 IP 地址,方便作访问控制、用户画像、统计分析 。
惋惜的是 HTTP 标准里并无为此定义头字段 ,但已经出现了不少 事实上的标准 ,最经常使用的两个头字段是 X-Forwarded-For 和 X-Real-IP 。
X-Forwarded-For
:链式存储
字面意思是为 谁而转发 ,形式上和 Via
差很少,也是每通过一个代理节点就会在字段里追加一个信息,但 Via 追加的是代理主机名(或者域名),而 X-Forwarded-For
追加的是请求方的 IP 地址。因此,在字段里最左边的 IP 地址就客户端的地址。
X-Real-IP
:只有客户端 IP 地址
是另外一种获取客户端真实 IP 的手段,它的做用很简单,就是记录客户端 IP 地址,没有中间的代理信息。
若是客户端和源服务器之间只有一个代理,那么这两个字段的值就是相同的。
X-Forwarded-For缺点:
经过 X-Forwarded-For
操做代理信息 必需要解析 HTTP 报文头 ,会下降代理的转发性能 。
另外一个问题是 X-Forwarded-For
等头 必需要修改原始报文 ,而有些状况下是不容许甚至不可能的(好比使用 HTTPS 通讯被加密 )。
因此就出现了一个专门的 代理协议 (The PROXY protocol) ,它由知名的代理软件 HAProxy 所定义,也是一个 事实标准 ,被普遍采用(注意并非 RFC)。
代理协议有 v1 和 v2 两个版本,v1 和 HTTP 差很少,也是明文,而 v2 是二进制格式。
v1版本在HTTP 报文前增长了一行 ASCII 码文本,这一行文本其实很是简单,开头必须是 PROXY
五个大写字母,而后是 TCP4
或者 TCP6
,表示客户端的 IP 地址类型,再后面是请求方地址、应答方地址、请求方端口号、应答方端口号,最后用一个回车换行(\r\n)结束。
PROXY TCP4 1.1.1.1 2.2.2.2 55555 80\r\n GET / HTTP/1.1\r\n Host: www.xxx.com\r\n \r\n
你以为代理有什么缺点?实际应用时如何避免?
代理的缺点是增长链路长度,会增长响应耗时,应尽可能减小在代理商所作的的一些与业务无关的复杂耗时操做。
缓存控制 + 代理服务 = 缓存代理
加入了缓存后代理服务收到源服务器发来的响应数据后须要作两件事:
Cache
里。下一次再有相同的请求,代理服务器就能够直接发送 304 或者缓存数据,没必要再从源服务器那里获取。这样就下降了客户端的等待时间,同时节约了源服务器的网络带宽。
数据是否容许代理缓存:
private
:表示缓存只能在客户端保存,是用户 私有 的。public
:容许缓存失效后从新验证:proxy-revalidate
代理缓存过时后必须验证,对应的是客户端的(must-revalidate
)。
缓存的生存时间:s-maxage
限制在代理服务器上能缓存多久。
private, max-age = 5; public, max-age = 5, s-maxage = 10; max-age = 30, proxy-revalidate, no-transform;
only-if-cached
表示 只接受代理缓存的数据 ,不接受源服务器的响应。若是代理上没有缓存或者缓存过时,就应该给客户端返回一个 504(Gateway Timeout)。
Vary: Accept-Encoding
对于服务器而言,资源文件可能不止一个版本,好比说压缩和未压缩,针对不一样的客户端,一般须要返回不一样的资源版本。好比说老式的浏览器可能不支持解压缩,这个时候,就须要返回一个未压缩的版本; 对于新的浏览器,支持压缩,返回一个压缩的版本,有利于节省带宽,提高体验。 那么怎么区分这个版本呢, 这个时候就须要Vary了。
服务器经过指定Vary: Accept-Encoding
, 告知代理服务器, 对于这个资源, 须要缓存两个版本: 压缩和未压缩。这样老式浏览器和新的浏览器,经过代理,就分别拿到了未压缩和压缩版本的资源,避免了都拿同一个资源的尴尬 。
Vary:Accept-Encoding,User-Agent;
Age 和 Date
出现此字段, 表示命中代理服务器的缓存。它指的是代理服务器对于请求资源的已缓存时间, 单位为秒。以下:
Age:2383321 Date:Wed, 08 Mar 2017 16:12:42 GMT
以上指的是,代理服务器在2017年3月8日16:12:42
时向源服务器发起了对该资源的请求, 目前已缓存了该资源2383321秒。
Date 指的是响应生成的时间。 请求通过代理服务器时,返回的Date未必是最新的, 一般这个时候,代理服务器将增长一个Age字段告知该资源已缓存了多久。
永远不会修改的内容:JS 和 CSS 文件,图片,和任何类型的二进制文件都属于这个类目。
永远,我确实说的是永远。为静态资源指定版本号是很通用的作法。它们不管何时改动了,它们的 URL 就改变了。
这里是一些针对静态资源的简单的规则:
<link rel = "Stylesheet" href="http://static.xxx.com/a_f02bc2.css"> <script src = "http://static.xxx.com/a_13cfa51.js"
Cache-Control: public, max-age=31536000 Expires: (一年后的今天) ETag: (基于内容生成) Last-Modified: (过去某个时间) Vary: Accept-Encoding
针对应用程序私密性和新鲜度方面需求的不一样,咱们应该使用不一样的缓存控制设置。
对于非私密性和常常性变更的资源(想像一下股票信息),咱们应该使用下面这些:
Cache-Control: public, max-age=0 Expires: (当前时间) ETag: (基于内容生成) Last-Modified: (过去某个时间) Vary: Accept-Encoding
这些设置的效果是:
你若是须要更严格的控制,须要告知浏览器即便当用户点击了「返回/前进」按钮,也须要从新检查这些资源文件,那么能够使用:
Cache-Control: public, no-cache, no-store
不是全部的动态资源都会立刻变成过期的资源。若是它们能够保持至少5分钟的时效,能够使用:
Cache-Control: public, max-age=300
通过这样的设置,浏览器只会在5分钟以后才从新检查。在这以前,缓存的内容会被直接使用。若是在5分钟后,这些过期的内容须要严格控制,你能够添加 must-revalidate
字段:
Cache-Control: public, max-age=300, must-revalidate
对于私密或者针对用户的内容,须要把 public
替换为 private
以免内容被代理缓存。
Cache-Control: private, …
简单、灵活和易于扩展;
header+body
,头部信息也是简单的文本格式,用的也都是常见的英文单词。HTTPS 实际上是一个「很是简单」的协议,里面规定了 新的协议名「https」,默认端口号 443 ,至于其余的请求 - 应答模式、报文结构、请求方法、URI、头字段、链接管理等等都彻底沿用 HTTP。
HTTPS = HTTP + SSL/TLS
对称加密(Symmetric-Key Encryption),就是指加密和解密时使用的 密钥都是同一个 ,是 对称 的。只要保证了密钥的安全,那整个通讯过程就能够说具备了机密性。
目前经常使用的有:
对称算法还有一个 分组模式 的概念,它可让算法用固定长度的密钥加密任意长度的明文 ,把小秘密(即密钥)转化为大秘密(即密文)。
AEAD(Authenticated Encryption with Associated Data) ,在加密的同时增长了认证的功能,经常使用的是 GCM、CCM 和 Poly1305。
它有两个密钥,一个叫 公钥(public key),一个叫 私钥(private key)。两个密钥是不一样的(不对称) ,公钥能够公开给任何人使用,而私钥必须严格保密。
公钥和私钥有个特别的 单向 性,虽然均可以用来加密解密,但 公钥加密后只能用私钥解密 ,反过来,私钥加密后也只能用公钥解密 。
非对称加密能够解决 密钥交换 的问题。
目前经常使用的有:
虽然非对称密匙没有 密匙交换 问题,可是它的运行速度很慢。若是仅用非对称加密,虽然保证了安全,但通讯速度有如蜗牛,实用性就变成了零。
因此就产生了 把对称加密和非对称加密结合起来 的 混合加密,二者互相取长补短,即能高效地加密解密,又能安全地密钥交换。
咱们把消息加密了就能保证安全吗?
考虑下面的状况:
非对称加密的算法都是公开的,全部人均可以本身生成一对公钥私钥。
当服务端向客户端返回公钥 A1 的时候,中间人将其替换成本身的公钥 B1 传送给浏览器。
而浏览器此时一无所知,傻乎乎地使用公钥 B1 加密了密钥 K 发送出去,又被中间人截获,中间人利用本身的私钥 B2 解密,获得密钥 K,再使用服务端的公钥 A1 加密传送给服务端,完成了通讯链路,而服务端和客户端毫无感知。
要保证安全,咱们不只要加密,还要保证消息的完整性以及身份认证。
摘要算法(Digest Algorithm),也就是常说的散列函数、哈希函数(Hash Function)。把一个大空间映射到小空间,因为对输入具备 单向性 和 雪崩效应,能够用来作数据的完整性校验。
TLS 推荐的摘要算法是 SHA-2,SHA-2 其实是一系列摘要算法的统称,总共有 6 种,经常使用的有 SHA22四、SHA25六、SHA384,分别可以生成 28 字节、32 字节、48 字节的摘要。
可是它不具有机密性,在混合加密系统里用 会话密钥加密消息和摘要,这个术语叫作 哈希消息认证码(HMAC)。
如今还有个漏洞,通讯的两个端点(endpoint) 也就是你怎么证实是你?服务器怎么证实是服务器?
非对称加密里的 私钥 ,使用私钥再加上摘要算法,就可以实现 数字签名 ,同时实现 身份认证 和 不能否认 。
但又由于非对称加密效率过低,因此私钥只加密原文的摘要
这里的私钥是你本身须要有一个 私钥 ,服务器也须要有一个 私钥,大家互相交换公钥,除非大家的私钥被泄密,不然身份认证和不能否认就能保证。
到如今,综合使用对称加密、非对称加密和摘要算法,咱们已经实现了安全的四大特性,是否是已经完美了呢?
不是的,这里还有一个 公钥的信任 问题。由于谁均可以发布公钥,如何保证公钥不是伪造的?
找一个 公认的可信第三方 ,让它做为「信任的起点,递归的终点」,构建起公钥的信任链。这就是 CA(Certificate Authority,证书认证机构),使用 CA 的私钥对你的 公钥进行签名(包含序列号、用途、颁发者、有效时间等等和你的公钥打包再签名),造成 数字证书(Certificate)。
那么 CA 怎么证实本身呢?这仍是信任链的问题。小一点的 CA 可让大 CA 签名认证 ,但链条的最后,也就是 Root CA ,就只能本身证实本身了。
也就是说,个人公钥是 CA 的私钥签名的,那么我须要拿到该 CA 的公钥进行解密,解密成功才能证实没有被伪造,那么最后仍是信任链的问题,最终解决办法就是 Root CA,这就叫 自签名证书(Self-Signed Certificate)或者 根证书(Root Certificate),有了这个证书体系,操做系统和浏览器都内置了各大 CA 的根证书
也就是说,若是你的公钥不是 CA 颁发的,那么想要浏览器认为是安全的,就必须将它安装到系统的根证书存储区里。
记录协议(Record Protocol)规定了 TLS 收发数据的基本单位:记录(record)。它有点像是 TCP 里的 segment,全部的其余子协议都须要经过记录协议发出。但多个记录数据能够在一个 TCP 包里一次性发出,也并不须要像 TCP 那样返回 ACK。
警报协议(Alert Protocol)的职责是向对方发出警报信息,有点像是 HTTP 协议里的状态码。好比,protocol_version 就是不支持旧版本,bad_certificate 就是证书有问题,收到警报后另外一方能够选择继续,也能够当即终止链接。
握手协议(Handshake Protocol)是 TLS 里最复杂的子协议,要比 TCP 的 SYN/ACK 复杂的多,浏览器和服务器会在握手过程当中协商 TLS 版本号、随机数、密码套件等信息,而后交换证书和密钥参数,最终双方协商获得会话密钥,用于后续的混合加密系统。
最后一个是 变动密码规范协议(Change Cipher Spec Protocol),它很是简单,就是一个「通知」,告诉对方,后续的数据都将使用加密保护。那么反过来,在它以前,数据都是明文的。 (在 TLS 1.3 中这个协议已经删除,为了兼容 TLS 老版本,可能还会存在)。
Heartbeat 扩展为 TLS/DTLS 提供了一种新的协议,容许在不须要重协商的状况下,使用 keep-alive 功能。Heartbeat 扩展也为 path MTU (PMTU) 发现提供了基础(这个是 TLS 1.3 新加的,TLS 1.3 以前的版本没有这个协议。)。
在 TCP 创建链接以后,浏览器会首先发一个 Client Hello 消息,也就是跟服务器打招呼。里面有客户端的TLS版本号、支持的密码套件,还有一个 随机数(Client Random) ,用于后续生成会话密钥。
服务器接收到,当即返回 server_random
,确认TLS版本号和使用的密码套件 ECDHE。
浏览器接收,先验证数字证书和签名。
master_secret = PRF(pre_master_secret, "master secret", ClientHello.random + ServerHello.random) //Master Secret 公式
这里的 PRF
就是伪随机数函数,它基于密码套件里的最后一个参数,好比此次的 SHA384,经过摘要算法来再一次强化 Master Secret
的随机性。
主密钥有 48 字节,但它也不是最终用于通讯的会话密钥,还会再用 PRF 扩展出更多的密钥,好比客户端发送用的会话密钥(client_write_key)、服务器发送用的会话密钥(server_write_key)等等,避免只用一个密钥带来的安全隐患。
有了主密钥和派生的会话密钥,握手就快结束了。客户端发一个 Change Cipher Spec ,而后再发一个 Finished 消息,把以前全部发送的数据作个摘要,再加密一下,让服务器作个验证。
密码套件
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 //密钥交换算法 + 签名算法 + 对称加密算法 + 摘要算法
大致的流程没有变,只是 Pre-Master
再也不须要用算法生成,而是客户端直接生成随机数,而后用服务器的公钥加密,经过 Client Key Exchange 消息发给服务器。服务器再用私钥解密,这样双方也实现了共享三个随机数,就能够生成主密钥。
服务端发送 CA 证书给客户端。
发送的是一个 CA 链,不包含 ROOT 证书。
客户端验证该证书的可靠性。
解析 CA 链,用链条上的 CA 进行验证,一层层地验证,直到找到根证书,就可以肯定证书是可信的。
k*。
k*
发送给服务端。k*
后用本身的私钥解密获得 k。k*
加密信息。密码套件名 | 代码 |
---|---|
TLS_AES_128_GCM_SHA256 | {0x13,0x01} |
TLS_AES_256_GCM_SHA384 | {0x13,0x02} |
TLS_CHACHA20_POLY1305_SHA256 | {0x13,0x03} |
TLS_AES_128_GCM_SHA256 | {0x13,0x04} |
TLS_AES_128_GCM_8_SHA256 | {0x13,0x05} |
浏览器首先仍是发一个 Client Hello 。
服务器收到 Client Hello
一样返回 Server Hello
消息,仍是要给出一个 随机数(Server Random)和选定密码套件。
这时只交换了两条消息,客户端和服务器就拿到了四个共享信息:Client Random 和 Server Random 、Client Params 和 Server Params ,两边就能够各自用 ECDHE 算出 Pre-Master ,再用 HKDF 生成主密钥 Master Secret ,效率比 TLS1.2 提升了一大截。
这里 TLS1.3 还有一个安全强化措施,多了个 Certificate Verify 消息,用服务器的私钥把前面的曲线、套件、参数等握手数据加了签名,做用和 Finished 消息差很少。但因为是私钥签名,因此强化了身份认证和和防窜改。
这两个 Hello
消息以后,客户端验证服务器证书,再发 Finished
消息,就正式完成了握手,开始收发 HTTP 报文。
HTTP/1.x 实现简单是以牺牲性能为代价的:
HTTP/1.1 的首部带有大量信息,并且每次都要重复发送。
HTTP/2.0 要求客户端和服务器同时维护和更新一个包含以前见过的头字段表,从而避免了重复传输。
HTTP/2 并无使用传统的压缩算法,而是开发了专门的 HPACK 算法,在客户端和服务器两端创建「字典」,用索引号表示重复的字符串,还釆用 Huffman 编码来压缩整数和字符串,能够达到 50%~90% 的高压缩率。
HTTP/2.0 将报文分红 HEADERS 帧和 DATA 帧,它们都是二进制格式的。
在通讯过程当中,只会有一个 TCP 链接存在,它承载了任意数量的双向数据流(Stream)。
HTTP/2.0 在客户端请求一个资源时,会把相关的资源一块儿发送给客户端,客户端就不须要再次发起请求了。例如客户端请求 page.html 页面,服务端就把 script.js 和 style.css 等与之相关的资源一块儿发给客户端。
当一个资源从与该资源自己所在的服务器不一样的域或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。
出于安全缘由,浏览器限制从脚本内发起的跨源HTTP请求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非使用CORS头文件。
(译者注:跨域并不必定是浏览器限制了发起跨站请求,也多是跨站请求能够正常发起,可是返回结果被浏览器拦截了。最好的例子是 CSRF 跨站攻击原理,请求是发送到了后端服务器不管是否跨域!注意:有些浏览器不容许从 HTTPS 的域跨域访问 HTTP,好比 Chrome 和 Firefox,这些浏览器在请求还未发出的时候就会拦截请求,这是一个特例。)
隶属于 W3C 的 Web 应用工做组推荐了一种新的机制,即跨源资源共享(Cross-Origin Resource Sharing ) CORS。这种机制让Web应用服务器能支持跨站访问控制,从而使得安全地进行跨站数据传输成为可能。须要特别注意的是,这个规范是针对API容器的(好比说XMLHttpReques 或者 Fetch),以减轻跨域HTTP请求的风险。CORS 须要客户端和服务器同时支持。目前,全部浏览器都支持该机制。
跨域资源共享标准( cross-origin sharing standard )容许在下列场景中使用跨域 HTTP 请求:
把CORS分为:简单请求、预请求和附带凭证信息的请求。
某些请求不会触发 CORS 预检请求。本文称这样的请求为“简单请求”,请注意,该术语并不属于 Fetch (其中定义了 CORS)规范。若请求知足全部下述条件,则该请求可视为“简单请求”:
(1). 使用下列方法之一:
(2). Fetch 规范定义了对 CORS 安全的首部字段集合,不得人为设置该集合以外的其余首部字段。该集合为:
Accept
Accept-Language
Content-Language
Content-Type (须要注意额外的限制)
DPR
Downlink
Save-Data
Viewport-Width
Width
(3). Content-Type 的值仅限于下列三者之一:
(4). 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象能够使用 XMLHttpRequest.upload 属性访问。
(5). 请求中没有使用 ReadableStream 对象。
简单来讲,重点须要记住的就是两点:
**(1)只使用 GET, HEAD 或者 POST 请求方法。若是使用 POST 向服务器端传送数据,则数据类型(Content-Type)只能是 application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一种。
(2)不会使用自定义请求头(相似于 X-Modified 这种)。**
举例:
//好比说,假如站点 http://foo.example 的网页应用想要访问 http://bar.other 的资源。如下的 JavaScript 代 //码应该会在 foo.example 上执行: var invocation = new XMLHttpRequest(); var url = 'http://bar.other/resources/public-data/'; function callOtherDomain() { if(invocation) { invocation.open('GET', url, true); invocation.onreadystatechange = handler; invocation.send(); } }
//让咱们看看,在这个场景中,浏览器会发送什么的请求到服务器,而服务器又会返回什么给浏览器: GET /resources/public-data/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Referer: http://foo.example/examples/access-control/simpleXSInvocation.html Origin: http://foo.example //该请求来自于 http://foo.exmaple。 //以上是浏览器发送请求 HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 00:23:53 GMT Server: Apache/2.0.61 Access-Control-Allow-Origin: * //这代表服务器接受来自任何站点的跨站请求。若是设置为http://foo.example。其它站点就不能跨站访问 http://bar.other 的资源了。 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Transfer-Encoding: chunked Content-Type: application/xml //以上是服务器返回信息给浏览器
如下状况,请求会返回相关响应信息
与前述简单请求不一样,“需预检的请求”要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否容许该实际请求。"预检请求“的使用,能够避免跨域请求对服务器的用户数据产生未预期的影响。
当请求知足下述任一条件时,即应首先发送预检请求:
(1). 使用了下面任一 HTTP 方法:
PUT
DELETE
CONNECT
OPTIONS
TRACE
PATCH
(2). 人为设置了对 CORS 安全的首部字段集合以外的其余首部字段。该集合为:
Accept
Accept-Language
Content-Language
Content-Type (but note the additional requirements below)
DPR
Downlink
Save-Data
Viewport-Width
Width
(3). Content-Type 的值不属于下列之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
(4). 请求中的XMLHttpRequestUpload 对象注册了任意多个事件监听器。
(5). 请求中使用了ReadableStream对象。
不一样于上面讨论的简单请求,“预请求”要求必须先发送一个 OPTIONS 请求给目的站点,来查明这个跨站请求对于目的站点是否是安全可接受的。这样作,是由于跨站请求可能会对目的站点的数据形成破坏。 当请求具有如下条件,就会被当成预请求处理:
**(1)请求以 GET, HEAD 或者 POST 之外的方法发起请求。或者,使用 POST,但请求数据为 application/x-www-form-urlencoded, multipart/form-data 或者 text/plain 之外的数据类型。好比说,用 POST 发送数据类型为 application/xml 或者 text/xml 的 XML 数据的请求。
(2)使用自定义请求头(好比添加诸如 X-PINGOTHER)**
举个例子:
var invocation = new XMLHttpRequest(); var url = 'http://bar.other/resources/post-here/'; var body = '{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}Arun'; function callOtherDomain(){ if(invocation){ invocation.open('POST', url, true); invocation.setRequestHeader('X-PINGOTHER', 'pingpong'); invocation.setRequestHeader('Content-Type', 'application/xml'); invocation.onreadystatechange = handler; invocation.send(body); } }
如上,以 XMLHttpRequest 建立了一个 POST 请求,为该请求添加了一个自定义请求头(X-PINGOTHER: pingpong),并指定数据类型为 application/xml。因此,该请求是一个“预请求”形式的跨站请求。浏览器使用一个 OPTIONS 发送了一个“预请求”。Firefox 3.1 根据请求参数,决定须要发送一个“预请求”,来探明服务器端是否接受后续真正的请求。 OPTIONS 是 HTTP/1.1 里的方法,用来获取更多服务器端的信息,是一个不该该对服务器数据形成影响的方法。 随同 OPTIONS 请求,如下两个请求头一块儿被发送:
Access-Control-Request-Method: POST Access-Control-Request-Headers: X-PINGOTHER
假设服务器成功响应返回部分信息以下:
Access-Control-Allow-Origin: http://foo.example //代表服务器容许http://foo.example的请求 Access-Control-Allow-Methods: POST, GET, OPTIONS //代表服务器能够接受POST, GET和 OPTIONS的请求方法 Access-Control-Allow-Headers: X-PINGOTHER //传递一个可接受的自定义请求头列表。服务器也须要设置一个与浏览器对应。不然会报 Request header field X-Requested-With is not allowed by Access-Control-Allow-Headers in preflight response 的错误 Access-Control-Max-Age: 1728000 //告诉浏览器,本次“预请求”的响应结果有效时间是多久。在上面的例子里,1728000秒表明着20天内,浏览器在处理针对该服务器的跨站请求,均可以无需再发送“预请求”,只需根据本次结果进行判断处理。
Fetch 与 CORS 的一个有趣的特性是,能够基于 HTTP cookies 和 HTTP 认证信息发送身份凭证。通常而言,对于跨域 XMLHttpRequest 或 Fetch 请求,浏览器不会发送身份凭证信息。若是要发送凭证信息,须要设置 XMLHttpRequest 的某个特殊标志位。
本例中,http://foo.example 的某脚本向 http://bar.other 发起一个GET 请求,并设置 Cookies:
var invocation = new XMLHttpRequest(); var url = 'http://bar.other/resources/credentialed-content/'; function callOtherDomain(){ if(invocation) { invocation.open('GET', url, true); invocation.withCredentials = true; invocation.onreadystatechange = handler; invocation.send(); } }
第 7 行将 XMLHttpRequest 的 withCredentials 标志设置为 true,从而向服务器发送 Cookies。由于这是一个简单 GET 请求,因此浏览器不会对其发起“预检请求”。可是,若是服务器端的响应中未携带 Access-Control-Allow-Credentials: true ,浏览器将不会把响应内容返回给请求的发送者。
假设服务器成功响应返回部分信息以下:
Access-Control-Allow-Origin: http://foo.example Access-Control-Allow-Credentials: true Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
若是 bar.other 的响应头里没有 Access-Control-Allow-Credentials:true,则响应会被忽略.。特别注意: 给一个带有withCredentials的请求发送响应的时候,服务器端必须指定容许请求的域名,不能使用“”。上面这个例子中,若是响应头是这样的 Access-Control-Allow-Origin: ,则响应会失败。在这个例子里,由于Access-Control-Allow-Origin的值是 http://foo.example 这个指定的请求域名,因此客户端把带有凭证信息的内容被返回给了客户端。另外注意,更多的cookie信息也被建立了。