0%

学习 HTTP 协议笔记

网络模型

五层网络模型:
classic-five-layer-model.jpg

16fb20a7abacc050.png
OSI 七层模型是国际标准化组织的标准,由于前者过于复杂且指定周期长,在整套标准推出之前,TCP/IP 模型就已经在全球范围内被广泛使用了,所以TCP/IP模型成了事实上的国际标准。我们在学习和开发中,将两者结合,这就是五层网络模型。

协议举例及功能

应用层

功能:为应用软件提供了很多服务。

协议: HTTP、DNS、FTP(文件传输)、SSH、Telnet、SMTP(电子邮件)

传输层

功能:向用户提供可靠的端到端服务。

协议: TCP、UDP

网络层

功能:寻址、路由。

协议:IPV4、IPV6、RARP、ICMP(V4、V6)

设备:路由器、三层交换机

数据链路层

功能:在通信实体间建立数据链路连接。

协议:以太网、Wi-Fi

设备:网卡、交换机

物理层

功能:定义物理设备如何传输数据。

设备:光纤、集线器、RJ-45接头

HTTP 协议发展历史

HTTP/0.9

HTTP 最早版本是1991年发布的0.9版本。该版本有如下特点:

  1. 只有一个命令GET
  2. 没有HEADER等描述数据的信息。
  3. 协议规定,服务器只能回应HTML格式的字符串,不能回应别的格式。
  4. 服务器发送完毕,就关闭TCP连接。

HTTP/1.0

1996年5月,HTTP/1.0发布,内容大大增加:

  1. 任何格式的内容都可以发送。
  2. 新增POSTHEAD命令。
  3. 每次通信都必须包含头信息(HTTP header)。
  4. 新增状态码(status code)。
  5. 新增多字符集支持、多部分发送(multi-part type)、权限(authorization)、缓存(cache)、内容编码(content encoding)等。

HTTP/1.1

1997年1月,HTTP/1.1 版本发布,至今(2020年)仍是最流行的版本。新增的功能有:

1. 持久连接(persistent connection)

即TCP连接默认不关闭,可以被多个请求复用。默认开启Connection: keep-alive

客户端和服务器发现对方一段时间没有活动,就可以主动关闭连接。不过,规范的做法是,客户端在最后一个请求时,发送Connection:close,明确要求服务器关闭TCP连接。

目前,对于同一个域名,大多数浏览器允许同时建立6个持久连接。

2. 管道机制(pipelining)

即在同一个TCP连接里面,客户端可以同时发送多个请求。

举例来说,客户端需要请求两个资源。以前的做法是,在同一个TCP连接里面,先发送A请求,然后等待服务器做出回应,收到后再发出B请求。管道机制则是允许浏览器同时发出A请求和B请求,但是服务器还是按照顺序,先回应A请求,完成后再回应B请求。

3. 其他

新增动词方法:PUTPATCHHEADOPTIONSDELETE

客户端请求头新增Host字段,用来指定服务器的域名。

1
Host: www.example.com

有了Host字段,就可以将请求发往同一台服务器上的不同网站。虚拟主机技术就是利用的这一点。在apache,ngnix环境下,直接通过配置文件cfg文件来配置不同的域名和网站根目录之间的映射关系。

HTTP/2

  1. 所有数据以二进制传输
  2. 多工。双向的、实时的通信,就叫做多工。举例来说,在一个TCP连接里面,服务器同时收到了A请求和B请求,于是先回应A请求,结果发现处理过程非常耗时,于是就发送A请求已经处理好的部分, 接着回应B请求,完成后,再发送A请求剩下的部分。
  3. 头信息压缩以及推动等提高效率的功能。

三次握手

进行三次握手的目的是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。三次握手由客户端发起,第一次握手, 客户端创建一个请求数据包,指明客户端打算连接服务器的端口,发送完毕进入SYN_SEND状态;二次握手,服务器端发送确认包,进入SYN_RCVD状态;三次握手,客户端再次发送确认包。发送完毕后,客户端进入ESTABILISHED状态,服务器收到后也进入ESTABLISHED状态。

二次握手后客户端和服务器端本来已经相互通信,交换信息了,本来可以直接建立连接了。但是为什么要进行三次握手呢?

这是服务器端为防止服务器端误会客户端的意思,服务器端开启无用连接,而浪费资源。

这就好比,一个你一直有好感的女孩向你发了个信息,“我喜欢你”。收到消息,你激动不已,你发了封信息“我也爱你,咱们交往吧”,然后就辗转反侧等消息,整夜没睡。但是第二天女孩说,不好意思,人家是玩的真心话大冒险。所以说,二次握手,不可靠。就是这个道理。

URI、URL和URN的区别

URI(Uniform resource identifier) 统一资源标识符

URL(uniform resource locator) 统一资源定位器(网址)
URN(uniform resource name) 统一资源命名(ISBN 编码)

URL 是 URI,URN 也是 URI。URL 和 URN 两种兼备也是 URI。
uri-url-urn.png
URN定义某事物的身份,而URL提供查找该事物的方法。

用于标识唯一书目的ISBN系统是一个典型的URN使用范例。例如,ISBN 0-486-27557-4无二义性地标识出莎士比亚的戏剧《罗密欧与朱丽叶》的某一特定版本。为获得该资源并阅读该书,人们需要它的位置,也就是一个URL地址。在类Unix操作系统中,一个典型的URL地址可能是一个文件目录,例如file:///home/username/RomeoAndJuliet.pdf。该URL标识出存储于本地硬盘中的电子书文件。因此,URL和URN有着互补的作用。

HTTP 报文格式

请求报文:

1
2
3
4
GET /index.txt HTTP/1.0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5)
Accept: */*
Accept-Language: en

相应报文:

1
2
3
4
5
6
7
8
9
10
HTTP/1.0 200 OK 
Content-Type: text/plain
Content-Length: 137582
Expires: Thu, 05 Dec 1997 16:00:00 GMT
Last-Modified: Wed, 5 August 1996 15:55:28 GMT
Server: Apache 0.84

<html>
<body>Hello World</body>
</html>

TIP:其中 GETPOST等命令是语义化的定义,GET请求一个数据,POST创建一个数据,但是他们都只是协议上规定的,不是强约束的,可以手动修改其行为,有点像HTML5标签。

HTTP 状态码

响应分为五类:
100-199 服务器收到请求
200-299 成功响应
300-399 重定向
400-499 客户端错误
500-599 服务器错误

常见的状态码:
200 请求成功
301 永久重定向(浏览器会记住返回301的网址,下次直接访问另一个网址)
302 临时重定向(下次再访问旧网址,还是要先访问旧网址,再根据 location 访问新网址)
304 资源未被修改
403 没有权限
404 资源未找到
500 服务器遇到了不知道如何处理的问题
504 网关超时

跨域

同源政策:协议、域名、端口号都相同则同源。如果不同源有如下限制:

  1. 当前域下的 JS 脚本不能访问其他作用域的 cookie、localStorage 和 indexDB。
  2. 当前域下的 JS 脚本不能操作访问其他域下的 DOM。
  3. 当前域下的 ajax 无法发送跨域请求。

通过 http://127.0.0.1:8888 获取到文件index.html,如果文件中有一段js脚本,请求http://127.0.0.1:8887的数据,也就是不同源的数据,浏览器会向8887服务器发送请求,并且接收返回内容,但是会检查是否有Access-Control-Allow-Origin头,并且设置为允许,就会将响应内容忽略掉,并返回错误。

解决办法:

  1. CORS。8887的服务端响应时可以添加"Access-Control-Allow-Origin":"http://127.0.0.1:8888"消息头,允许跨域请求。通过Access-Control-Allow-Headers:"*"允许跨域请求时添加的某些请求头。
  2. JSONP。浏览器允许<link><img><script> 在标签上写 URL 加载内容允许跨域。JSONP 就是在<script>上加载一个链接,向服务器请求JSON数据,服务器收到请求后,将数据放在一个指定名字的回调函数里传回来,客户端修改返回的内容。
  3. WebSocket。该协议不实行同源政策,只要服务器支持,就可以实现跨域。
  4. window.postMessage。HTML5的跨文档通信API,允许跨窗口通信,不论这两个窗口是否同源。
  5. 一级域名相同的域名,可以通过设置document.domain="example.com"来共享Cookie。

以上办法解决的跨域限制是不同的,例如:CORS、JSONP和WebSocket解决了AJAX跨域的问题。而document.domain解决了Cookie跨域的问题,window.postMessage解决了DOM跨域的问题。DOM跨域,典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。

HTTP 缓存

Cache-Control 头

HTTP/1.1 定义的Cache-Control头用来区分对缓存机制的支持情况,请求头和响应头都支持这个属性。通过它提供的不同值来定义缓存策略。*表示浏览器端常用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// *缓存中不得存储任何关于客户端和服务端响应的内容。
Cache-Control: no-store

// *每次有请求发出时,缓存会将此请求发到服务器,服务器验证是否过期,
// 若未过期返回304,缓存才使用本地缓存副本。
Cache-Control: no-cache

// *可以被任何中间人(中间代理、CDN等)缓存。
Cache-Control: public

// *中间人不能缓存此响应,该响应只能应用于浏览器私有缓存中。
Cache-Control: private

// *表示资源能够被缓存的最大时间。
Cache-Control: max-age=31536000

// 中间人的资源缓存时间将不使用max-age,而使用s-maxage。
Cache-Control: s-maxage=31536000

// *保证已过期的缓存不被使用,必须从服务端重新获取请求。
Cache-Control: must-revalidate

Cache-Control: proxy-revalidate

// 浏览器特定时间内仍可使用过期的缓存。
Cache-Control: max-stale = 31536000

// 禁止中间人进行压缩、格式转换等。
Cache-Control: no-transform

缓存验证

Expire

Expire 是 HttpHeader 中代表资源的过期时间,由服务器端设置。如果带有 Expire ,则在 Expire 过期前不会发生 Http 请求,直接从缓存中读取。用户强制刷新例外(Windows:ctrl+F5,Mac:command+option+R),强制刷新是指无论如何都从服务器重新获取数据。

Last-Modified

Last-Modified 是 HttpHeader 中的资源的上次修改时间,如果带有 Last-Modified ,下一次发送 Http 请求时,将会发生带 If-modified-since 的 HttpHeader。与服务器端进行对比,如果没有过期,将会收到 304 的响应,从缓存中读取。

Etag

Etag 是 HttpHeader 中代表资源的标签,在服务器端生成。如果带有 Etag ,下一次发送带 Etag 的请求,如果 Etag 没有变化将收到 304 的响应,从缓存中读取。
Etag 在使用时要注意相同资源多台 Web 服务器的 Etag 的一致性。所以分布式环境(比如CDN)很少使用ETag。

区别:
Expire 兼容 HTTP/1.0 浏览器。
Etag 更加严格的验证,可以可靠的比较指纹。
Last-Modified 不验证实际页面内容是否更改,对页面频繁更改的网页更加有利。

Cookie 在服务端返回数据的时候通过Set-Cookie设置,浏览器获取Cookie后保存在本地,下次请求会自动带上,Cookie 是键值对,可以设置多个。max-ageexpires设置过期时间,HttpOnly无法通过document.cookie访问。Session与之类似。
不过Session与Cookie不同之处在于,Cookie把数据放在客户端,而Session把数据放在服务器端。
将登陆信息等重要信息存放为Session,其他信息放在Cookie中。

Cookie, LocalStorage 与 SessionStorage 的区别:
cookie-localstorage-sessionstorage.png

长连接和短连接

TCP 本身并没有长短连接的区别,长短与否,完全取决于我们怎么用它。

  • 短连接:每次通信时,创建 Socket;一次通信结束,调用 socket.close()。这就是一般意义上的短连接,短连接的好处是管理起来比较简单,存在的连接都是可用的连接,不需要额外的控制手段。
  • 长连接:每次通信完毕后,不会关闭连接,这样可以做到连接的复用。长连接的好处是省去了创建连接的耗时。

短连接和长连接的优势,分别是对方的劣势。想要图简单,不追求高性能,使用短连接合适,这样我们就不需要操心连接状态的管理;想要追求性能,使用长连接,我们就需要担心各种问题:比如 端对端连接的维护,连接的保活。

HTTP1.1规定了默认保持长连接(HTTP persistent connection ,也有翻译为持久连接),数据传输完成了保持TCP连接不断开(不发RST包、不四次握手),等待在同域名下继续用这个通道传输数据;相反的就是短连接。

参考文献