# HTTP 请求的完整过程

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

## HTTP 起源

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

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

* 提出 HTTP (Hypertext Transfer Protocol) 超文本传输协议，允许用户通过单击超链接访问资源。
* 提出使用HTML超文本标记语言(Hypertext Markup Language)作为创建网页的标准。
* 创建了统一资源定位器 URL (Uniform Resource Locator)作为网站地址系统，就是沿用至今的 <http://www> URL 格式。
* 创建第一个 **Web 浏览器**，称为万维网浏览器，这也是一个 Web 编辑器。
* 创建第一个 [Web 服务器](http://info.cern.ch)以及描述项目本身的第一个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 四层模型如下：

![](https://blog.tommyyang.cn/img/protocol/tcpip-four-model.png)

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

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

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

![](https://blog.tommyyang.cn/img/protocol/tcpip-3times-shakehands.png)

**过程说明**：

* 客户端发送位码为 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 请求报文。结构如下：

![](https://blog.tommyyang.cn/img/protocol/http-req.png)

### 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），表示报文主体结束。

结构如下：

![](https://blog.tommyyang.cn/img/protocol/http-resp.png)

### 响应状态码

|     |           类别           |      原因短语     |
| :-: | :--------------------: | :-----------: |
| 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 断开连接过程 --- 四次挥手结构图如下：

![](https://blog.tommyyang.cn/img/protocol/tcpip-4times-wave.png)

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

**注意注意注意，重要的问题说三次**：为什么断开连接是四次挥手呢？

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

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

## 拓展

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://tommyyang.gitbook.io/javainterview/web/http-processing.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
