详解 HTTP 协议

超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的网络协议。是一种建立在 TCP 上的无状态连接,整个基本的工作流程是客户端发送一个 HTTP 请求,说明客户端想要访问的资源和请求的动作,服务端收到请求之后,服务端开始处理请求,并根据请求做出相应的动作访问服务器资源,最后通过发送 HTTP 响应把结果返回给客户端。

HTTP 发展史

HTTP 发展过程已经演化出了很多版本,它们中的大部分都是向下兼容的。其中主要的三个版本分别是:HTTP/1.0、HTTP/1.1、HTTP/2.0。

HTTP/1.0

HTTP/1.0 最大的改变是引入了 POST 方法,使得客户端通过 HTML 表单向服务器发送数据成为可能,这也是 WEB 应用程序的一个基础。

另一个巨大的改变是引入了 HTTP 头,使得 HTTP 不仅能返回错误代码,并且 HTTP 协议所传输的内容不仅限于纯文本,还可以是图片,动画等一系列格式。

除此之外,还允许保持连接,既一次 TCP 连接后,可以多次通信,虽然 HTTP/1.0 默认是传输一次数据后就关闭。

HTTP/1.1

2000 年 5 月,HTTP/1.1 确立。HTTP/1.1 并不像 HTTP/1.0 对于 HTTP/0.9 那样的革命性。但是也有很多增强。

变化如下:

  • 缓存处理:

    在 HTTP/1.0 中主要使用了 if-modified-since 和 expires,而在 HTTP/1.1 中出现了跟多的缓存控制策略,例如 Entity-tag、If-Unmodified-since、If-Match、if-none-match。

  • 带宽优化以及网络连接的使用:

    在 HTTP/1.0 中,存在着一些带宽浪费,譬如,一些用户只想请求某个对象的一部分内容,而服务器却将整个内容都发送了过来,并且不支持断点续传功能,而 HTTP/1.1 在请求头部引入了 range 头,它允许请求资源的某个部分,返回码为 206(Partial Content),此种做法方便了开发者调试以及合理的利用带宽资源

  • 错误通知的管理:

    在 HTTP/1.1 中新增了 24 个错误状态响应码,如 409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。

  • Host 头处理:

    在 HTTP/1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的 URL 并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个 IP 地址。HTTP/1.1 的请求消息和响应消息都应支持 Host 头域,且请求消息中如果没有 Host 头域会报告一个错误(400 Bad Request)

  • 长连接:

    HTTP/1.1 支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟,在 HTTP/1.1 中默认开启 Connection:keep-alive,一定程度上弥补了 HTTP/1.0 每次请求都要创建连接的缺点。

HTTP/2.0

增加的新特性:

  • 新的二进制格式(Binary Format)

    HTTP/1.x 的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认 0 和 1 的组合。基于这种考虑 HTTP/2.0 的协议解析决定采用二进制格式,实现方便且健壮。

  • 多路复用(MultiPlexing)

    即连接共享,即每一个 request 都是是用作连接共享机制的。一个 request 对应一个 id,这样一个连接上可以有多个 request,每个连接的 request 可以随机的混杂在一起,接收方可以根据 request 的 id 将 request 再归属到各自不同的服务端请求里面。

  • Header 压缩

    HTTP/1.x 的 header 带有大量信息,而且每次都要重复发送,要知道上行带宽是很珍贵的,HTTP2.0 使用 encoder 来减少需要传输的 header 大小,通讯双方各自 cache 一份 header fields 表,既避免了重复 header 的传输,又减小了需要传输的大小。

  • 服务端推送(Server Push)

    同 SPDY 一样,HTTP/2.0 也具有 server push 功能。

HTTP/2.0 在线测试:https://servertest.online/http2

HTTP 网络层次

先来个高清大图:

可以看出,HTTP 是基于传输层的 TCP 协议,而 TCP 是一个端到端的面向连接的协议。所谓的端到端可以理解为进程到进程之间的通信。所以 HTTP 在开始传输之前,首先需要建立 TCP 连接,而 TCP 连接的过程需要所谓的“三次握手”。

在 TCP 三次握手之后,建立了 TCP 连接,此时 HTTP 就可以进行传输了。一个重要的概念是面向连接,既 HTTP 在传输完成之前并不断开 TCP 连接。

HTTP 报文格式

HTTP 请求

HTTP 请求,即 Web 客户端向 Web 服务器发送信息,这个信息由如下三部分组成:

  1. 请求行
  2. HTTP 头,又分:
    1. 请求头(Request Header)
    2. 普通头(General header)
    3. 实体头(Entity header)
  3. 内容

HTTP 响应

当 Web 服务器收到 HTTP 请求后,会根据请求的信息做某些处理,相应的返回一个 HTTP 响应。HTTP 响应在结构上很类似于 HTTP 请求,也是由三部分组成,分别为:

  1. 状态行
  2. HTTP 头,又分:
    1. 响应头(Response Header)
    2. 普通头(General header)
    3. 实体头(Entity header)
  3. 返回内容

关于 HTTP 头

HTTP 头并不是严格要求的,仅仅是一个标签,如果浏览器可以解析就会按照某些标准(比如浏览器自身标准,W3C 的标准)去解释这个头,否则不识别的头就会被浏览器无视。对服务器也是同理。假如你编写一个浏览器,你可以将上面的头解释成任何你想要的效果。

通用头(General header)

通用头即可以包含在 HTTP 请求中,也可以包含在 HTTP 响应中。通用头的作用是描述 HTTP 协议本身。比如描述 HTTP 是否持久连接的 Connection 头,HTTP 发送日期的 Date 头,描述 HTTP 所在 TCP 连接时间的 Keep-Alive 头,用于缓存控制的 Cache-Control 头等。

实体头(Entity header)

实体头是那些描述 HTTP 信息的头。既可以出现在 HTTP POST 方法的请求中,也可以出现在 HTTP 响应中。比如 Content-Type 和 Content-length 都是描述实体的类型和大小的头都属于实体头。其它还有用于描述实体的 Content-Language,Content-MD5,Content-Encoding 以及控制实体缓存的 Expires 和 Last-Modifies 头等。

请求头(Request Header)

请求头是那些由客户端发往服务端以便帮助服务端更好的满足客户端请求的头。请求头只能出现在 HTTP 请求中。比如告诉服务器只接收某种响应内容的 Accept 头,发送 Cookies 的 Cookie 头,显示请求主机域的 HOST 头,用于缓存的 If-Match,If-Match-Since,If-None-Match 头。用于只取 HTTP 响应信息中部分信息的 Range 头,用于附属 HTML 相关请求引用的 Referer 头等。

响应头(Response Header)

HTTP 响应头是那些描述 HTTP 响应本身的头,这里面并不包含描述 HTTP 响应中第三部分也就是 HTTP 信息的头(这部分由实体头负责)。比如说定时刷新的 Refresh 头,当遇到 503 错误时自动重试的 Retry-After 头,显示服务器信息的 Server 头,设置 COOKIE 的 Set-Cookie 头,告诉客户端可以部分请求的 Accept-Ranges 头等。

Get 和 Post 区别

网上关于 Get 和 Post 的区别满天飞,但大多没有说到点子上。Get 和 Post 最大的区别就是 Post 有上面所说的第三部分:内容。而 Get 不存在这个内容。因此就像 Get 和 Post 其名称所示那样,Get 用于从服务器上取内容,虽然可以通过 QueryString 向服务器发信息,但这违背了 Get 的本意,QueryString 中的信息在 HTTP 看来仅仅是获取所取得内容的一个参数而已。而 Post 是由客户端向服务器端发送内容的方式。因此具有请求的第三部分:内容。

常见的状态码

200—OK/ 请求已经正常处理完毕

301—/ 请求永久重定向

302—/ 请求临时重定向

304—/ 请求被重定向到客户端本地缓存

400—/ 客户端请求存在语法错误

401—/ 客户端请求没有经过授权

403—/ 客户端的请求被服务器拒绝,一般为客户端没有访问权限

404—/ 客户端请求的 URL 在服务端不存在

500—/ 服务端永久错误

502—/ 网关或代理错误

503—/ 服务端发生临时错误

常见的请求头

Header 解释 示例
Accept 指定客户端能够接收的内容类型 Accept: text/plain, text/html
Accept-Charset 浏览器可以接受的字符编码集。 Accept-Charset: iso-8859-5
Accept-Encoding 指定浏览器可以支持的 web 服务器返回内容压缩编码类型。 Accept-Encoding: compress, gzip
Accept-Language 浏览器可接受的语言 Accept-Language: en,zh
Accept-Ranges 可以请求网页实体的一个或者多个子范围字段 Accept-Ranges: bytes
Authorization HTTP 授权的授权证书 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Cache-Control 指定请求和响应遵循的缓存机制 Cache-Control: no-cache
Connection 表示是否需要持久连接。(HTTP 1.1 默认进行持久连接) Connection: close
Cookie HTTP 请求发送时,会把保存在该请求域名下的所有 cookie 值一起发送给 web 服务器。 Cookie: $Version=1; Skin=new;
Content-Length 请求的内容长度 Content-Length: 348
Content-Type 请求的与实体对应的 MIME 信息 Content-Type: application/x-www-form-urlencoded
Date 请求发送的日期和时间 Date: Tue, 15 Nov 2010 08:12:31 GMT
Expect 请求的特定的服务器行为 Expect: 100-continue
From 发出请求的用户的 Email From: user@email.com
Host 指定请求的服务器的域名和端口号 Host: www.zhangzw.com
If-Match 只有请求内容与实体相匹配才有效 If-Match: “737060cd8c284d8af7ad3082f209582d”
If-Modified-Since 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回 304 代码 If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT
If-None-Match 如果内容未改变返回 304 代码,参数为服务器先前发送的 Etag,与服务器回应的 Etag 比较判断是否改变 If-None-Match: “737060cd8c284d8af7ad3082f209582d”
If-Range 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为 Etag If-Range: “737060cd8c284d8af7ad3082f209582d”
If-Unmodified-Since 只在实体在指定时间之后未被修改才请求成功 If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT
Max-Forwards 限制信息通过代理和网关传送的时间 Max-Forwards: 10
Pragma 用来包含实现特定的指令 Pragma: no-cache
Proxy-Authorization 连接到代理的授权证书 Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Range 只请求实体的一部分,指定范围 Range: bytes=500-999
Referer 先前网页的地址,当前请求网页紧随其后, 即来路 Referer: http://www.zhangzw.com
TE 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息 TE: trailers,deflate;q=0.5
Upgrade 向服务器指定某种传输协议以便服务器进行转换(如果支持) Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
User-Agent User-Agent 的内容包含发出请求的用户信息 User-Agent: Mozilla/5.0 (Linux; X11)
Via 通知中间网关或代理服务器地址,通信协议 Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
Warning 关于消息实体的警告信息 Warn: 199 Miscellaneous warning

三次握手 & 四次挥手

三次握手:

  1. 第一次握手:

    客户端发送 syn 包 (syn=x) 到服务器,并进入 SYN_SEND 状态,等待服务器确认;

  2. 第二次握手:

    服务器收到 syn 包,必须确认客户的 SYN(ack=x+1),同时自己也发送一个 SYN 包(syn=y),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态;

  3. 第三次握手:

    客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK(ack=y+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP 连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。

四次挥手:

  1. 第一次挥手:

    主动关闭方发送一个 FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了(当然,在 fin 包之前发送出去的数据,如果没有收到对应的 ack 确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可以接受数据。

  2. 第二次挥手:

    被动关闭方收到 FIN 包后,发送一个 ACK 给对方,确认序号为收到序号 +1(与 SYN 相同,一个 FIN 占用一个序号)。

  3. 第三次挥手:

    被动关闭方发送一个 FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。

  4. 第四次挥手:

    主动关闭方收到 FIN 后,发送一个 ACK 给被动关闭方,确认序号为收到序号 +1,至此,完成四次挥手。

参考资料

https://www.cnblogs.com/zhangtaotqy/p/9408834.html

https://blog.51cto.com/13570193/2108347

https://blog.csdn.net/zhangyexinaisurui/article/details/82711848

https://blog.csdn.net/ccpat/article/details/79413433

http://en.wikipedia.org/wiki/HTTP/2#Browser_support