超文本传输协议(HyperText Transfer Protocol), 是一种用于分布式、协作式和超媒体信息系统的应用层协议, 是万维网的数据通信的基础(维基百科)。这里我们主要关注一下什么是应用层,以及网络是如何分层的.
网络的分层
网络为什么要分层[1]?
- 各层之间相互独立, 某一层并不需要知道它下一层是如何实现的,而仅仅需要知道该层通过层间的接口所提供的服务。由于每一层只实现一种相对独立的功能,因而可以将一个难以处理的复杂问题分解为若干个较容易处理的更小问题,这样,整个问题的复杂度就下降了。
- 灵活性, 当任何一层发生变化时,只要层间接口关系保持不变,则在这层以上或以下各层均不受影响,此外,对某一层提供的服务还可以进行修改。当某层提供的服务不再需要时,甚至可以将这层取消
- 结构上可分割开。各层都可以采用最合适的技术来实现。
- 易于实现和维护。这种结构使得实现和调试一个庞大而又复杂的系统变得易于处理,因为整个系统已被分解为若干个相对独立的子系统。
- 能促进标准化工作。因为每一层的功能及其所提供的服务都已有了精确的说明。
OSI七层模型
开放式系统互联通信参考模型,由国际标准化组织提出。
OSI将网络分为七层: 应用层、表示层、会话层、传输层、网络层、链路层、物理层。
TCP/IP五层模型
TCP/IP是实际的标准, 分为五层: 应用层、传输层、网络层、链路层、物理层。
TCP/IP与OSI的分层的对应关系, 如下图所示:
TCP/IP也可以说是四层, 如果是四层的话就是把链路层与物理层统称为网络接口层。
HTTP的过程
让我们从一次请求过程来了解HTTP。
请求的准备
- 在输入
URL之后,首先需要DNS来将域名解析为IP地址。 - 建立
TCP连接(三次握手),建立连接的时候是需要发送IP包的。 - 如果是
HTTPS会进行TLS/SSL的握手。
请求的构建
连接建立以后,就是要向服务器发送请求,那么请求格式是怎么样的呢?
HTTP1.1为明文传输,所以我们很容易能够在Chrome的Network中看到,请求的格式如下图所示:
从上图可以看出,请求分为了三部分:请求行,首部,实体。
首部与实体之间使用空行分隔。
请求行
请求行由三部分构成:方法,URL,HTTP版本号,以空格隔开。
方法
主要的方法有:GET POST OPTIONS HEAD 等
GET
如果有参数,会将其放在URL中:
优点:
- 请求的
URL可以被缓存 - 可以手动输入,并保存参数
- 相对较快(会在
TCP第三次握手时将报文随握手包发送)。 缺点: - 参数有大小限制(受限于
URL的长度) - 参数可见,相对不安全
GET请求的过程[2]:
- 浏览器请求
TCP连接(第一次握手) - 服务器答应进行
TCP连接(第二次握手) - 浏览器确认,并发送
GET请求头和数据 - 服务器返回
200 OK响应
POST
参数存在于实体中: 优点:
- 能发送更多的数据。
- 参数不直接可见,相对安全(相对
GET,抓包除外)。 缺点: - 相对较慢(在首部中相对
GET多了几个用于协商的首部,且需要待第三次握手后再发送报文)。
POST请求的过程[2]:
- 浏览器请求
TCP连接(第一次握手) - 服务器答应进行
TCP连接(第二次握手) - 浏览器确认,并发送
POST请求头(第三次握手) - 服务器返回
100 Continue响应 - 浏览器发送数据
- 服务器返回
200 OK响应
OPTIONS
HEAD
请求资源的首部信息, 并且这些首部与GET方法请求时返回的一致。响应不应包含响应实体,即使包含了实体也必须忽略掉。
URL
URL(Uniform Resource Locator), 统一资源定位符, 是因特网上标准的资源的地址。
完整格式:协议类型:[//[访问资源需要的凭证信息@]服务器地址[:端口号]][/资源层级UNIX文件路径]文件名[?查询][#片段ID]
另外,与URL相关的定义还有URI和URN。
URI(Uniform Resource Identifier),统一资源标识符,用于标识某一互联网资源名称的字符串。
URN(Uniform Resource Name),统一资源名称,一种为资源提供持久的、位置无关的标识方式。
URL与URN 是URI的子集,三者关系,如下图所示:
举个栗子:
urn:isbn:0-486-27557-4这是一个资源,它是URN,也是URI,但不是URL。
HTTP 版本
主要版本:0.9、1.0、1.1、2
首部
首部是key: value形式,通过冒号空格分隔,为客户端和服务器分别处理请求和相应提供所需要的信息。
首部分为四类:请求首部,响应首部,通用首部,实体首部。
请求首部
顾名思义,只会在请求报文中存在。
Accept-Charset:客户端支持的字符集,例:utf-8。Accept-Encoding:客户端可以接受的内容编码形式,例:gzip。Referer:对请求中URL的原始获取方。Host:请求资源所在服务器。- ......
通用首部
Accept-Language:提示用户期望获得的自然语言的优先顺序。User-Agent:用来识别发送请求的浏览器。Cache-Control:缓存控制。- ......
实体首部
Content-Encoding:实体的编码方式。Content-Language:实体的自然语言。Content-Type:实体的媒体类型。- ......
响应首部
Location:令客户端重定向至指定URL。Retry-After:对再次发起请求的时机要求。Server:HTTP服务器的安装信息。ETag:资源匹配信息(缓存相关)。- ......
一切准备完成后就是发送请求。
请求的发送与接收
请求发送接收的过程如下图所示:
请求的发送
- 浏览器将
HTTP构建完成后,通过网络线程将请求报文交给TCP。 TCP将请求的报文进行分割,并将各个报文包入TCP的头部,交给IP。IP将TCP报文包入IP的头部,交个链路层。- 链路层报上以太网首部通过物理层发送报文(待后续补充)。
请求的接收
- 网卡接收到请求后,会先查看目标
MAC地址是否为自己的MAC地址,如果是会把以太网头部去除,交个上层协议。 IP收到链路层发送的数据后,会检查目标IP是否为自己的IP,如果是把IP头部去除,交给上层协议。TCP收到数据后,去掉TCP头部,交给浏览器。
请求的响应
响应的构建
响应与请求除了状态行外结构基本相同,同样分为 3 部分:状态行,首部,实体。
状态行
状态行分为 3 部分:HTTP版本,状态码,短语,以空格分隔,其中短语为对状态码的解释。
状态码
主要状态码介绍
- 200 OK:表示从客户端发送来的请求在服务器端被正常处理了。对应请求资源的实体主体随报文首部作为响应返回(HEAD 方法不会返回实体,即使返回也会被忽略)。
- 204 Not Content:服务器接收的请求已成功处理,但是再返回的响应报文中不包含实体的主体部分。另外,也不允许返回任何实体的主体。(MDN:使用惯例是,在
PUT请求中进行资源更新,但是不需要改变当前展示给用户的页面,那么返回204 No Content。例如:提交表单后,不进行页面跳转)。 - 206 Partial Content:客户端进行了范围请求,而服务器成功执行了这部分的
GET请求。响应报文中包含由Content-Range指定范围的的实体内容。 - 301 Moved Permanently:永久重定向,表示请求的资源已被分配了新的
URL,以后请使用资源现在所指的URL。搜索引擎会根据该响应修正。 - 302 Found:临时重定向,表示请求的资源已被分配了新的
URL,希望用户(本次)能使用新的URL访问。已改变,且将来还有可能发生改变。 - 303 See Other:请求对应的资源存在着另一个
URL,应使用GET方法定向获取请求的资源。(301、302、303响应状态码返回时几乎所有的浏览器都会把POST改成GET,并删除请求报文的主体,之后请求会自动再次发送。)。 - 304 Not Modified:服务器端资源未改变,可直接使用客户端未过期的缓存,返回结果不包含任何响应的主体部分。
- 307 Temporary Redirect:临时重定向。与
302之间的唯一区别在于,当发送重定向请求的时候,307状态码可以确保请求方法和消息主体不会发生变化。 - 400 Bad Request:请求报文中存在语法错误。当错误发生时,需修改请求的内容后再次发送请求。另外,浏览器会像
200 OK一样对待该状态码。 - 401 Unauthorized:表示由于缺乏目标资源要求的身份验证凭证,发送的请求未得到满足。这个状态码会与
WWW-Authenticate首部一起发送,其中包含有如何进行验证的信息。 - 403 Forbidden:表示服务器端有能力处理该请求,但是拒绝授权访问(无权限)。
- 404 Not Found:表示服务器上无法找到请求的资源。
- 500 Internal Server Error:表示服务器端在执行请求时发生了错误,也有可能是 Web 应用存在的 Bug 或某些临时的故障。
- 503 Service Unavailable:表示服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。如果事先得知解除以上状况需要的时间,最好写入
Retry-After首部字段再返回给客户端。
在完成了响应的构建之后,会按照与发送相同的方式将响应发送给客户端。最后TCP四次回收断开连接。
整个请求过程如下图所示:
HTTP版本
HTTP/0.9 单行协议
HTTP/0.9非常简单,请求仅由单行构成,以唯一可用方法GET开头,其后跟目标资源的路径。
GET /index.html
响应也非常简单仅包含响应文档本身,如果出现错误会将错误信息以文档形式返回。
HTTP/1.0 构建可扩展性
主要引入了头部,及状态码,并支持多种文件格式。
HTTP/1.1 标准化的协议
HTTP/1.1 相对 1.0 的不同:
- 持久连接:默认都开启了
Keep-Alive,所有连接都被保持,除非在请求头或响应头中指明要关闭:Connection: Close。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件中设定这个时间(实际是在1.0版本引入,但并不会默认开启)。 - 管线化:无需等待上次请求返回也可直接发送下一个请求(实际使用受限,浏览器默认不开启)。
- 缓存:增加了新的缓存控制首部,如:
Cache-Control等。 - Host:请求头指明了服务器的域名(对于虚拟主机来说),以及(可选的)服务器监听的
TCP端口号。 - 内容协商机制: 包括语言,编码,类型等,并允许客户端和服务器之间约定以最合适的内容进行交换。
HTTP/2 为了更优异的表现
与 HTTP/1.1的区别:
- 二进制协议:在不改变方法,首部的基础上,转换为二进制协议,称为二进制分帧层。
- 多路复用:即在一个
TCP连接中可以同时发送多个请求。 - 流控制:是一种阻止发送方向接收方发送大量数据的机制,以免超出后者的需求或处理能力:发送方可能非常繁忙、处于较高的负载之下,也可能仅仅希望为特定数据流分配固定量的资源。
- 服务端推送:服务端推送,服务器可以对一个客户端请求发送多个响应。
- 头部压缩。
缓存控制
浏览器在首次对资源进行请求时,会记录缓存相关的首部,在后续请求中根据记录的首部进行相应的资源读取操作,如读取缓存,资源验证等。 缓存的作用:减少请求次数,减少带宽;增加加载速度,减少白屏时间。
在说明缓存控制原理之前,先了解下缓存相关的首部。
缓存相关首部
Cache-Control
控制缓存的有效时间,及缓存行为。
主要值:
- max-age:缓存有效时间,即从相应时间后多长时间缓存过期。
- no-cache:不缓存,并非不对资源进行缓存,而是每次请求都需要向源资源服务器验证资源。
- no-store:禁止缓存,禁止浏览器对资源进行缓存,每次请求都要重新请求资源。
- public:允许所有用户缓存,包括浏览器,代理服务器等。
- private:仅允许单个用户缓存,不允许代理服务器缓存。
- ......
对于 Cache-Control: no-cache, max-age=900 这种情况,no-cache 与 max-age 的优先级与先后顺序有关。
Expires
为HTTP/1.0提出,表示缓存过期时间,超过这个时间即缓存过期。如果响应中有max-age或s-maxage会被覆盖。
expires: Sun, 02 Sep 2018 14:36:18 GMT
Last-modified
资源的最后修改时间,主要用于在服务器验证缓存是否被修改时使用。
last-modified: Fri, 18 May 2018 01:10:24 GMT
ETag
资源实体标识,由服务器分配,资源更新时ETge随之改变,分为强ETag与弱ETag。
**弱ETag**很容易生成,但不利于比较。**强ETag**是比较的理想选择,但很难有效地生成。
ETag: W/"5a323f72-152"(弱) ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"(强)
缓存请求过程
- 判断该资源是否有缓存。
- 如有缓存,判断缓存是否过期,如果缓存没过期,直接读取缓存,状态码
200 (from memory/disk cache)。 - 如果缓存过期,判断是否有
ETag及Last-Modified,如果存在,则在请求首部中发送对应的首部If-None-Match及If-Modified-Since,如果没有则不发送这两个首部。 - 服务器端接收到请求后,判断根据
If-None-Match及If-Modified-Since来比较资源的ETag如果改变了比较Last-Modified,如果匹配则返回304,如果不匹配,将资源随实体返回,状态码200。
from memory cache 与 from disk cache 为 Chrome 的缓存优化机制,但对于该采用什么方式,并没有找到明确答案。
强缓存与协商缓存
强缓存:不会向服务器发送请求,直接读取缓存的方式,在上述过程中直接返回状态码200 (from memory/disk cache)。
协商缓存:请求资源时会向服务器发送If-None-Match及If-Modified-Since(如果存在ETag及Last-Modified),服务器验证后,返回304或200。
HTTPS
在学习HTTPS之前,我们了解下HTTP的缺点:
- 明文通信(不加密),不安全。
- 不验证通信方身份,可能遭遇伪装。
- 无法保证报文的完整性,可能在途中遭到了篡改。
而HTTPS解决了上述缺点(HTTPS并非新协议,而是HTTP + TLS)
对称加密
发送端和接收端使用相同的密钥。发送端使用密钥加密明文,接收端接收后使用密钥解析加密信息,得到明文。
对称加密最大的问题就是在一对多的时候的密钥传输问题,所以为了保证安全对称加密的密钥是绝对不能公开的。
非对称加密
非对称加密有两个密钥,一个是公钥,一个是私钥,公钥加密后的密文只能使用私钥解密,私钥加密后的密文只能使用公钥解密。那么只需要对外展示公钥就可以了。
但是,非对称加密的解密过程速度较慢(对称加密主要是位运算,而非对称加密包含了很多乘法或大数模)。
混合加密
充分利用对称及非对称加密的优势,使用非对称加密的方式传输对称加密的密钥,即有非对称加密的安全又有对称加密的速度。
HTTPS(或者说TLS)就是采用了这种方式。
证书与CA
在拿到公钥后,能不能就直接确定这个公钥是值得信任的?答案是肯定不能,如果公钥是某个黑客伪造的,他就可以修改从发送端接到的请求,在发给服务器了。所以,在拿到公钥后,首先要验证公钥是不是可以信任的,那么谁能保证公钥是可以信任的,那就必须要是一个你信任的机构,这个机构就是CA,而每个站点的公钥实际都是由CA签发的。所以CA可以验证公钥是否属于这个站点的。
签发及验证过程
- 服务器将公钥交给
CA CA使用自己的私钥向服务器的公钥部署数字签名,并颁发公钥证书- 客户端在接收到公钥证书后,客户端使用自己信任的
CA的公钥(存在于客户端证书信任列表中)去验证签名是否与公钥匹配。 - 若匹配则认为公钥是可以信任的。
连接过程
- 在
TCP三次握手后,客户端发送请求安全连接(Client Hello),报文中包含一个随机字符串,并列出客户端支持的加密套件,用于协商对称加密的加密方式。 - 服务器端回复(Server Hello),报文中包含服务端选择的加密方式;同时将证书和公钥发送给客户端,同时报文中包含一个随机字符串;最后发送完成握手协商结束(Server Hello Done)。
- 客户端接收到证书后,根据证书上的
CA,使用该CA的公钥解密证书,来验证公钥是否安全,如果CA的证书由上级CA提供且不在信任列表内,则需要一直向上找到客户端信任的CA为止。 - 验证公钥安全后,客户端会使用公钥加密并发送一段叫做
Pre-master secret的随机字符串,客户端及服务器端使用上面提到的三个随机字符串,并使用协商好的加密算法计算出对称加密的密钥。 - 客户端发送
Change Cipher Spec报文,以提示服务器后续通信将采用计算好的对称加密密钥进行通信 - 客户端继续发送
Finished报文,报文中包含连接至今全部报文的整体校验值,如果服务器端能够正确解密该报文,则握手成功 - 服务器端发送
Change Cipher Spec报文 - 服务器端发送
Finished报文,连接建立。
WebSocket
WebSocket是可以实现客户端与服务器端双向通信的新协议,除了借助HTTP完成一次握手外,与HTTP没有关系。
连接过程
TCP三次握手。- 客户端发送升级请求,请求首部如下:
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
上述首部中主要是用到了Upgrade首部,用于通知服务器切换协议到WebSocket。
3. 服务器端接收到升级协议的请求后,如果支持WebSocket会响应该请求。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
响应状态码:101,表示服务器端应客户端升级协议的请求正在升级协议。
4. WebSocket握手完成,后续通信将使用WebSocket协议。
参考:
[1]《计算机网络(第五版)》(谢希仁)
[2] http GET 和 POST 请求的优缺点、区别以及误区
[3] TLS 握手优化详解