Back to
top
一次完整的 HTTP 请求过程 - 杨挺的博客 | Tommy's Blog

一次完整的 HTTP 请求过程

"HTTP 请求过程"

Posted by Tommy on January 28, 2020

HTTP 请求的完整过程

一次 HTTP 请求的整个过程包括:DNS 解析、建立 TCP 连接、客户端请求、服务端响应、断开 TCP 连接。 本文主要从以上几个方面来讲解一次完整的 HTTP 请求。

HTTP 起源

今天我们能够在网络中畅游,都得益于一位计算机科学家蒂姆·伯纳斯·李的构想。1991 年 8 月 6 日,蒂姆·伯纳斯·李在位于欧洲粒子物理研究所(CERN)的 NeXT 计算机上,正式公开运行世界上第一个Web网站,建立起基本的互联网基础概念和技术体系,由此开启了网络信息时代的序幕。

伯纳斯·李的提案包含了网络的基本概念并逐步建立了所有必要的工具:

  • 提出 HTTP (Hypertext Transfer Protocol) 超文本传输协议,允许用户通过单击超链接访问资源。
  • 提出使用HTML超文本标记语言(Hypertext Markup Language)作为创建网页的标准。
  • 创建了统一资源定位器 URL (Uniform Resource Locator)作为网站地址系统,就是沿用至今的 http://www URL 格式。
  • 创建第一个 Web 浏览器,称为万维网浏览器,这也是一个 Web 编辑器。
  • 创建第一个 Web 服务器以及描述项目本身的第一个Web页面。

HTTP 协议一共有五大特点:

  • 支持客户/服务器模式。
  • 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。
  • 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由 Content-Type(Content-Type是HTTP包中用来表示内容类型的标识)加以标记。
  • 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
  • 无状态:无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。即我们给服务器发送 HTTP 请求之后,服务器根据请求,会给我们发送数据过来,但是,发送完,不会记录任何信息(Cookie 和 Session 孕育而生,后期再讲)。

DNS 解析

DNS( Domain Name System) 是“域名系统”的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,它用于 TCP/IP 网络,它所提供的服务是用来将主机名和域名转换为 IP 地址的工作。

关于 DNS 的获取流程:

DNS 是应用层协议,事实上他是为其他应用层协议工作的,包括不限于 HTTP 和 SMTP 以及 FTP,用于将用户提供的主机名解析为 ip 地址。具体过程如下:

  • 用户主机上运行着 DNS 的客户端,就是我们的 PC 机或者手机客户端运行着 DNS 客户端。
  • 浏览器将接收到的 url 中抽取出域名字段,就是访问的主机名,比如 http://www.baidu.com/,并将这个主机名传送给 DNS 应用的客户端。
  • DNS 客户机端向 DNS 服务器端发送一份查询报文,报文中包含着要访问的主机名字段(中间包括一些列缓存查询以及分布式 DNS 集群的工作)。
  • 该 DNS 客户机最终会收到一份回答报文,其中包含有该主机名对应的 IP 地址。
  • 一旦该浏览器收到来自 DNS 的 IP 地址,就可以向该IP地址定位的 HTTP 服务器发起 TCP 连接。

建立 TCP 连接

相信大家都知道 HTTP 是一个基于 TCP/IP 协议簇来传递数据。TCP/IP 协议在进行连接的时候都需要进行三次握手,所以 HTTP 在连接服务器的时候也需要进行三次握手。

TCP/IP 是互联网相关的各类协议簇的总称。也有另一种说法 TCP/IP 是 TCP 和 IP 两种协议。 TCP/IP 四层模型如下:

TCP 报文包 = TCP 头信息 + TCP 数据体,而在 TCP 头信息中包含了 6 种控制位(上图红色框中),这六种标志位就代表着 TCP 连接的状态:

  • SYN:表示请求建立一个连接(同步序号)
  • URG:紧急数据(urgent data)—这是一条紧急信息
  • ACK:确认已收到(确认序号)
  • PSH:尽可能快地将数据送往接收进程
  • RST:表示要求对方重新建立连接
  • FIN:表示通知对方本端已经完成数据发送,要关闭连接了

TCP 建立连接过程 --- 三次握手

过程说明

  • 客户端发送位码为 syn=1,随机产生 seq number=1234567 的数据包到服务器,服务器由SYN=1知道客户端要求建立联机(客户端:我要连接你)
  • 服务器收到请求后要确认联机信息,向 A 发送ack number=(客户端的seq+1),syn=1,ack=1,随机产生seq=7654321的包(服务器:好的,你来连吧)
  • 客户端收到后检查 ack number 是否正确,即第一次发送的 seq number+1 ,以及位码 ack 是否为1,若正确,客户端会再发送 ack number=(服务器的seq+1),ack=1,服务器收到后确认seq值与ack=1则连接建立成功。(客户端:好的,我来了)

注意注意注意,重要的问题说三次:为什么 http 建立连接需要三次握手,不是两次或四次?

个人理解:确认双方信道可以实现最低限度的全双工(可以合并第 2,3 步),三次是最少的安全次数,两次不安全,四次浪费资源。

如果觉得理解不对的,可以下方留言或者在 issue 里面去讨论。

客户端请求

三次握手之后,客户端和服务端的连接就已经建立好了,客户端就可以向服务器端发送 HTTP 请求。

HTTP 请求报文结构

TCP 报文包 = TCP 头信息 + TCP 数据体,TCP 头信息的结构如下:

TCP 数据体,也就是 HTTP 请求报文。结构如下:

HTTP 请求实例

GET① /settings/user_has_gravatar② HTTP/1.1③


Host: github.com
Connection: keep-alive
Accept: application/json
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Referer: https://github.com/settings/profile
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Cookie: _octo=GH1.1.503958834.1571276454; _ga=GA1.2.104159404.1571276456; _device_id=d0247b2a88a0126139fad221e62f2c91; user_session=Z3ZgN7swYstKY35aXI_GD_u4A3Jk-pZ-5bVBXRCBpPmrjfV9; __Host-user_session_same_site=Z3ZgN7swYstKY35aXI_GD_u4A3Jk-pZ-5bVBXRCBpPmrjfV9; logged_in=yes; dotcom_user=joyang1; has_recent_activity=1; tz=Asia%2FShanghai; _gat=1
④

username=tommyyang&userid=168168⑤

  • ①是请求方法,HTTP/1.1 定义的请求方法有8种:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE,最常的两种 GET 和 POST,如果是 RESTFUL 接口的话一般会用到 GET、POST、DELETE、PUT。
  • ②为请求对应的URL地址,它和报文头的Host属性组成完整的请求URL
  • ③是协议名称及版本号
  • ④是HTTP的报文头,报文头包含若干个属性,格式为“属性名:属性值”,服务端据此获取客户端的信息
  • ⑤是报文体,GET 方法 username=tommyyang&userid=168168 通过请求 URL 传递参数,如“/settings/user_has_gravatar?username=tommyyang&userid=168168”的方式传递请求参数。

参数说明如下: Host: 域名。 Connection: 连接状态。 User-Agent:客户端使用的操作系统和浏览器的名称和版本,有些网站会限制请求浏览器。 Referer:跳转到该网页的地址,表示此请求来自哪里,有些网站会限制请求来源。

服务端响应

服务器在收到客户端请求,然后对请求处理完后需要响应并返回给客户端,而 HTTP 响应报文结构与请求结构体一致。

HTTP 响应报文结构

HTTP 响应报文结构与请求报文结构类似,包括:

  • 报文首部。
  • 空行(CR + LF),表示报文主体开始。
  • 报文主体。
  • 空行(CR + LF),表示报文主体结束。

结构如下:

响应状态码

| | 类别 | 原因短语 | | :----: | :----: | :----: | | 1XX | Informational(信息状态码)| 接受的请求正在处理 | | 2XX | Success(成功状态码) | 请求正常处理完毕 | | 3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 | | 4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 | | 5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |

HTTP 响应实例

HTTP/1.1① 200 OK②

③
Server: GitHub.com
Date: Mon, 20 Jan 2020 11:17:40 GMT
Status: 304 Not Modified
Vary: X-PJAX
Cache-Control: max-age=0, private, must-revalidate
Set-Cookie: user_session=Z3ZgN7swYstKY35aXI_GD_u4A3Jk-pZ-5bVBXRCBpPmrjfV9; path=/; expires=Mon, 03 Feb 2020 11:17:40 -0000; secure; HttpOnly
Set-Cookie: __Host-user_session_same_site=Z3ZgN7swYstKY35aXI_GD_u4A3Jk-pZ-5bVBXRCBpPmrjfV9; path=/; expires=Mon, 03 Feb 2020 11:17:40 -0000; secure; HttpOnly; SameSite=Strict
Set-Cookie: has_recent_activity=1; path=/; expires=Mon, 20 Jan 2020 12:17:40 -0000
X-Request-Id: 1bc86002-496d-4d6a-a028-9fd129fac631
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
Expect-CT: max-age=2592000, report-uri="https://api.github.com/_private/browser/errors"
Content-Security-Policy: default-src 'none'; base-uri 'self'; block-all-mixed-content; connect-src 'self' uploads.github.com www.githubstatus.com collector.githubapp.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com wss://live.github.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com; frame-ancestors 'none'; frame-src render.githubusercontent.com; img-src 'self' data: github.githubassets.com identicons.github.com collector.githubapp.com github-cloud.s3.amazonaws.com *.githubusercontent.com; manifest-src 'self'; media-src 'none'; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com
X-GitHub-Request-Id: BEF1:4193:BBF9FF:1933DED:5E258C54
Content-Type: application/json; charset=utf-8
ETag: W/"b086cd16a5d1e1190981cda623503729"
X-Frame-Options: deny
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Content-Encoding: gzip

④
6f
{"has_gravatar":false}
0
  • ① 报文协议及版本
  • ② 状态码及状态描述
  • ③ 响应头
  • ④ 响应体

断开连接

在服务器响应完毕后,一次会话就结束了,这时候连接会断开么?

长短连接

是否断开,我们需要根据 HTTP 版本来确定:

  • HTTP/1.0 版本的时候,客户端与服务器完成一个请求/响应之后,会将之前建立的 TCP 连接断开,下次请求的时候又要重新建立 TCP 连接, 这也就是短连接
  • 在 HTTP1.0 发布仅半年后(1997年1月) ,HTTP/1.1 版本发布并带来一个新的功能:在客户端与服务器完成一次请求/响应之后,允许不断开 TCP 连接, 这意味着下次请求就直接使用这个 TCP 连接而不需要重新握手建立新连接,这也被称为长连接

tips:长连接是指一次TCP连接允许多次HTTP会话,HTTP永远都是一次请求/响应,会话结束,HTTP本身不存在长连接之说

早在 1999 年 HTTP1.1 就推广普及,现在浏览器在请求时请求头中都会携带一个参数:Connection:keep-alive,这表示浏览器要求与服务器建立长连接, 而服务器也可以设置是否愿意建立长连接。

长连接的优缺点

  • 优点:当网站中有大量静态资源(图片、css、js等)被请求时就可以开启长连接,这些静态资源就可以通过一次 TCP 连接发送。
  • 缺点:当客户端请求一次就不再请求时,服务器却一直开着长连接,资源被占用着,严重浪费资源。

断开连接过程

在建立 TCP 连接时是三次握手,而断开 TCP 连接是四次挥手

TCP 断开连接过程 --- 四次挥手结构图如下:

在 TCP 连接建立的时候,讲到了标志位:FIN 表示通知对方自身要断开连接了

注意注意注意,重要的问题说三次:为什么断开连接是四次挥手呢?

个人理解:由于 TCP 要支持半关闭连接。在建立连接的时候是全双工的,A <=> B 双方都可以读写。断开的时候需要支持半关闭,意味着 TCP 支持客户端和服务端双方独立关闭通道;因此会有两次独立的关闭写通道的请求。一次关闭请求(FIN),对应一个 ACK,也就有了四次挥手。

如果觉得理解不对的,可以下方留言或者在 issue 里面去讨论。

拓展

  • 了解下 HTTP2.0。
  • HTTP & RPC,了解下 HTTP 与 RPC 的区别,为什么要使用 RPC?
  • HTTP & HTTPS,了解学习为什么现在应用更多的使用 HTTPS?