---
url: https://sugarat.top/computerBase/Internet/http.md
---

# http协议

> 超文本传输协议(HyperText Transfer Protocol）),基于TCP实现的应用层协议

* 请求响应模型:客户端发送请求,服务端响应请求
* 无状态协议:不需要建立持久链接

## http工作过程

1. 地址解析
   * 协议
   * 端口
   * 主机名:DNS解析出主机IP地址
   * 对象路径
2. 封装Http请求数据包
3. 封装成tcp包,建立tcp连接(3次握手)
4. 客户端发送请求
5. 服务端响应
6. 关闭TCP连接(4次挥手)
   * 保持链接的方案:在请求/响应头中加入 `Connection:keep-alive`就可以保持链接打开状态

## http

请求由三部分构成:

* 请求行
* 首部
* 实体

响应:

* 协议/版本号 状态码 状态吗描述
* 首部
* 实体

### 请求行

`GET /images/logo.gif HTTP/1.1`基本由请求**方法**、**URL**、**协议版本**组成

### 请求方法

请求方法分为很多种，最常用的也就是 `Get` 和 `Post` 了。虽然请求方法有很多，但是更多的是**传达一个语义**，而不是说 Post 能做的事情 Get 就不能做了。

* Get:应该只被用于获取数据
* Post:用于将实体提交到指定的资源，通常导致在服务器上的状态变化或副作用.
* Put:求有效载荷替换目标资源的所有当前表示,即更新操作.
* Delete:删除指定的资源
* Patch:用于对资源应用部分修改
* Head:请求一个与GET请求的响应相同的响应，但没有响应体.
* Connect:建立一个到由目标资源标识的服务器的隧道
* Options:描述目标资源的通信选项
* Trace:沿着到目标资源的路径执行一个消息环回测试。

### URI

`Uniform Resource Identifier`--**统一资源标识符**,用于区分互联网上不同资源

`URI` 包含 `URL` 与 `URN`

URI只能使用ASCII, ASCII 之外的字符不支持显示,因此，URI 引入了编码机制，将所有非 ASCII 码字符和界定符转为十六进制字节值，然后在前面加个%

### URL

```
scheme://host:port/path?query
```

* scheme:协议,HTTP,HTTPS,FTP
* host:主机名,sugarat.top
* port:端口号,默认80,https默认443
* path:资源路径
* query:用于查询的参数

### 副作用和幂等

`副作用`指对服务器上的资源做改变，搜索是无副作用的，注册是副作用的

`幂等`指发送 M 和 N 次请求（两者不相同且都大于 1），服务器上资源的状态一致，比如注册 10 个和 11 个帐号是不幂等的，对文章进行更改 10 次和 11 次是幂等的。因为前者是多了一个账号（资源），后者只是更新同一个资源。

### Post 和 Get 的区别

从使用场景上来说:

* Get多用于无副作用,幂等:例如搜索
* Post多用于有副作用,不幂等:例如注册

从技术上说:

* Get会缓存,Post不能
* Post相对与Get安全一点(Get请求参数包含在Url中,也可以写在body里面),但抓包情况下都是一样的
* URL有长度限制，会影响 Get 请求，但是这个长度限制是浏览器规定的
* Post 支持更多的编码类型且不对数据类型限制,GET 只能进行 URL 编码，只能接收 ASCII 字符

### 首部

**注意:**

* 字段名不区分大小写
* 字段名不允许出现空格，不可以出现下划线`_`
* 字段名后面必须紧接着`:`

部分首部通用

* 请求首部
* 响应首部
* 实体首部

**通用首部**
|       字段        |                     作用                      |
| :---------------: | :-------------------------------------------: |
|   Cache-Control   |                控制缓存的行为                 |
|    Connection     | 浏览器想要优先使用的连接类型，比如 keep-alive |
|       Date        |                 创建报文时间                  |
|      Pragma       |                   报文指令                    |
|        Via        |              代理服务器相关信息               |
| Transfer-Encoding |                 传输编码方式                  |
|      Upgrade      |              要求客户端升级协议               |
|      Warning      |             在内容中可能存在错误              |

**请求首部**
|      字段       |                作用                 |
| :-------------: | :---------------------------------: |
|     Accept      |        能正确接收的媒体类型(application/json)         |
| Accept-Charset  |         能正确接收的字符集          |
| Accept-Encoding |   能正确接收的编码格式列表(gzip)    |
| Accept-Language | 能正确接收的语言列表(zh-CN, zh, en) |
|      Host       |        访问资源所在的主机名         |
|    If-Match     |  值与请求资源ETag相同才会处理请求   |
|  If-None-Match  | 值与请求资源ETag不相同才会处理请求  |
|   User-Agent    |        发送请求的客户端信息         |
|     Referer     |      浏览器所访问的前一个页面       |
**响应首部**
|        字段        |            作用            |
| :----------------: | :------------------------: |
|        Age         | 资源在代理缓存中存在的时间 |
|      Location      |   客户端重定向到某个 URL   |
| Proxy-Authenticate |  向代理服务器发送验证信息  |
|       Server       |         服务器名字         |

**实体首部**
|      字段      |               作用               |
| :------------: | :------------------------------: |
| Content-Length |        告知客户端资源长度        |
|    Expires     |      告知客户端资源失效日期      |
| Last-Modified  | 告知客户端资源最后一次修改的时间 |

**[cookie](./../../bigWeb/browser/storage.md#Cookie)**
|    字段    |           作用           |
| :--------: | :----------------------: |
|   Cookie   |     请求时添加cookie     |
| Set-Cookie | 响应时服务端传回的cookie |

**压缩**

```js
// 发送端
Content-Encoding: gzip
// 接收端
Accept-Encoding: gizp
```

### 实体

具体传输的数据，即body部分

* 请求报文:请求体
* 响应报文:响应体

### 状态码

**1xx协议处理的中间状态**

* `101` 在HTTP升级为WebSocket的时候，如果服务器同意变更，就会发送状态码 101

**2xx成功**

* `200` 客户端的请求被服务端正确处理
* `204` 请求成功但响应报文不包含实体的主体部分
* 206 范围请求,客户端进行了部分请求,服务端返回指定部分的内容
* 205 与204作用一致但要求请求方重置内容

**3xx重定向**

* `301` 永久性重定向,表示资源已被分配了新的url
* `302` 临时性重定向,表示资源临时被分配了新的url
* `304` 当客户端拥有可能过期的缓存时，会携带缓存的标识 etag、时间等信息询问服务器缓存是否仍可复用，而304是告诉客户端可以 复用缓存。
* 303 资源存在另一个url,服务端要求客户端使用get请求
* 307 临时重定向,向新的url发送同样的请求

**4XX客户端错误**

* `400` 请求报文存在语法错误
* `401` 发送的请求需要有通过 HTTP 认证的认证信息
* `403` 对请求资源的访问被服务器拒绝,资源允许访问,但请求不满足条件
* `404` 在服务器上没有找到请求的资源
* `405` 当前的请求方法不被允许
* `415` 不支持的媒体类型,检查Content-Type

**5XX 服务器错误**

* `500` 服务器端在收到请求后后，执行相关动作时发生了错误
* `502` bad gate无效网关
* 501 表示服务器不支持当前请求所需要的某个功能
* 503 表明服务器暂时处于超负载或正在停机维护，无法处理请求

### HTTP特点

* 请求-应答
* 无状态

### HTTP缺点

* 通信使用明文，可能被窃听
* 不验证通信方的身份，可能遭遇伪装
* 无法证明报文的完整性，有可能遭遇篡改

## HTTPS

> HTTP + 加密 + 认证 + 完整性保护 = HTTPS

* 基于HTTP协议，通过SSL或TLS提供加密处理数据、验证对方身份以及数据完整性保护
* HTTPS是在HTTP上建立SSL加密层，并对传输数据进行加密，是HTTP协议的安全版

**与HTTP区别**

* HTTP是明文传输的
* http(80) 和 https(443) 使用不同的连接方式
* HTTP 页面响应速度比 HTTPS 快，HTTP 使用 TCP 三次握手建立连接，客户端和服务器需要交换 3 个包
* HTTPS 是建构在 SSL/TLS 之上的 HTTP 协议,HTTPS 比 HTTP 要更耗费服务器资源
* HTTPS除了 TCP 的三个包，还要加上 ssl 握手需要的 9 个包,一共是 12 个包。
* HTTPS是在HTTP上建立SSL/TLS加密层，并对传输数据进行加密，是HTTP协议的安全版。
  ![图片](https://img.cdn.sugarat.top/mdImg/MTU4NDE5NTg5ODc3Mw==584195898773)

**特点**

* `通过抓包获取到的数据不是明文传输的`
* 内容加密：采用混合加密技术，中间者无法直接查看明文内容
* 验证身份：通过证书认证客户端访问的是自己的服务器
* 保护数据完整性：防止传输的内容被中间人冒充或者篡改

**缺点**

* 性能损耗
* 增加延时
* 消耗较多的CPU资源

**优化**

* CDN
* 回话缓存

## SSL

## TLS

> TLS是传输层加密协议，前身是SSL协议

HTTPS 还是通过了 HTTP 来传输信息，但是信息通过 TLS 协议进行了加密

TLS 协议位于传输层之上，应用层之下。

TLS/SSL的功能实现主要依赖于三类基本算法：

* `散列函数 Hash`:MD5,SHA,SHA256---完成校验
* `对称加密 1-1`:AES,DES,RC4---信息加密
* `非对称加密 1-N`:RSA,ECC,DH---身份验证秘钥协商

> 利用非对称加密实现身份认证和密钥协商，对称加密算法采用协商的密钥对数据加密，基于散列函数验证信息的完整性

**对称加密：**

对称加密就是两边拥有相同的秘钥，两边都知道如何将密文加密解密。

这种加密方式固然很好，但是问题就在于如何让双方知道秘钥。因为传输数据都是走的网络，如果将秘钥通过网络的方式传递的话，一旦秘钥被截获就没有加密的意义的。

**非对称加密：**

有公钥私钥之分，公钥所有人都可以知道，可以将数据用公钥加密，但是将数据解密必须使用私钥解密，私钥只有分发公钥的一方才知道。

这种加密方式就可以完美解决对称加密存在的问题。假设现在两端需要使用对称加密，那么在这之前，可以先使用非对称加密交换秘钥。

简单流程如下：首先服务端将公钥公布出去，那么客户端也就知道公钥了。接下来客户端创建一个秘钥，然后通过公钥加密并发送给服务端，服务端接收到密文以后通过私钥解密出正确的秘钥，这时候两端就都知道秘钥是什么了。

**TLS1.2握手过程**

1. 客户端发出请求:
   * 一个随机值(用于生成对话秘钥)
   * 支持的协议
   * 支持加密方式
   * 支持压缩的方法
2. 服务端收到客户端的请求，向客户端发出回应:
   * 一个随机值(用于生成对话秘钥)
   * 确定使用的协议
   * 确定使用的加密方式
   * 发送自己的证书（如果需要验证客户端证书需要说明）
3. 客户端收到服务端的证书并验证是否有效，如果证书没问题,向服务端发送一个请求:
   * 生成一个随机值(用证书公钥加密)
   * 编码改变通知(随后的信息将使用双方商定的加密方法和秘钥发送)
   * 客户端握手结束通知(前面发送的所有内容的hash值，用来提供给服务端校验)
4. 服务端最后响应
   * 服务器收到客户端的第三个随机数之后(用私钥解密)结合之前的两个明文随机数，计算生成本次会话所用的"会话密钥"
   * 编码改变通知(随后的信息都将用双方商定的加密方法和密钥发送)
   * 服务器握手结束通知(前面发送的所有内容的hash值，用来提供给客户端校验)

通过以上步骤可知，在 TLS 握手阶段，两端使用非对称加密的方式来通信，实现身份验证并协商对称加密使用的密钥,但是因为非对称加密损耗的性能比对称加密大，所以在正式传输数据时，两端使用对称加密的方式通信。不同的节点之间采用的对称密钥不同，从而可以保证信息只能通信双方获取

**TLS1.3**
TLS1.3 中废除了非常多的加密算法,如果私钥泄露,那么中间人可以破解所有密文

TLS1.3 在 TLS1.2 的基础上废除了大量的算法，提升了安全性。同时利用会话复用节省了重新生成密钥的时间，利用 PSK 做到了0-RTT连接。

## HTTP2

[感受http2](https://http2.akamai.com/demo)

HTTP/2 相比于 HTTP/1，可以说是大幅度提高了网页的性能。

在 HTTP/1 中,浏览器限制了同一个域名下的请求数量（Chrome 下一般是限制六个连接），当页面中需要请求很多资源的时候，队头阻塞（Head of line blocking）会导致在达到最大请求数量时，剩余的资源需要等待其他资源请求完成后才能发起请求。

* HTTP/2 中引入了多路复用的技术，这个技术可以只通过一个 TCP 连接就可以传输所有的请求数据。多路复用很好的解决了浏览器限制同一个域名下的请求数量的问题，同时也间接更容易实现全速传输
* 在之前的 HTTP 版本中，我们是通过文本的方式传输数据。在 HTTP/2 中引入了新的编码机制，所有传输的数据都会被分割，并采用二进制格式编码。

### 多路复用

在 HTTP/2 中，有两个非常重要的概念，分别是:

* **帧（frame）** 代表最小的数据单位，每个帧会标识出该帧属于哪个流
* **流（stream）** 是多个帧组成的数据流

多路复用，就是在一个 TCP 连接中可以存在多条流。换句话说，也就是可以发送多个请求，对端可以通过帧中的标识知道属于哪个请求。通过这个技术，可以避免 HTTP 旧版本中的队头阻塞问题，极大的提高传输性能。

### Header 压缩

在 HTTP/1 中，我们使用文本的形式传输 header，在 header 携带 cookie 的情况下，可能每次都需要重复传输几百到几千的字节。

在 HTTP /2 中，使用了 HPACK 压缩格式对传输的 header 进行编码，减少了 header 的大小。并在两端维护了索引表，用于记录出现过的 header ，后面在传输过程中就可以传输已经记录过的 header 的键名，对端收到数据后就可以通过键名找到对应的值。

### 服务端 Push

在 HTTP/2 中，服务端可以在客户端某个请求后，主动推送其他资源。

可以想象以下情况，某些资源客户端是一定会请求的，这时就可以采取服务端 push 的技术，提前给客户端推送必要的资源，这样就可以相对减少一点延迟时间。

### 设置请求优先级

![图片](https://img.cdn.sugarat.top/mdImg/MTU4NTcxMzIwNDc0Nw==585713204747)

### 如何使用?

* 使用Nginx搭建HTTP2服务器

### 使用条件

* [支持Http2的服务端与客户端](https://github.com/http2/http2-spec/wiki/Implementations)
* 域名就必须是https(基于TLS/1.2或以上版本的加密连接)
* 服务器的openssl版本必须大于1.0.2

## http/3

因为 HTTP/2 使用了多路复用，一般来说同一域名下只需要使用一个 TCP 连接。当这个连接中出现了丢包的情况，那就会导致 HTTP/2 的表现情况反倒不如 HTTP/1 了

* 出现丢包的情况下，整个 TCP 都要开始等待重传，也就导致了后面的所有数据都被阻塞了。
* 对于 HTTP/1 来说，可以开启多个 TCP 连接，出现这种情况反到只会影响其中一个连接，剩余的 TCP 连接还可以正常传输数据。

基于这个原因，Google 就更起炉灶搞了一个基于 UDP 协议的 QUIC 协议，并且使用在了 HTTP/3 上

## QUIC

QUIC 基于 UDP，在原本的基础上新增了很多功能，比如:

* 多路复用
* 0-RTT
* 使用 TLS1.3 加密
* 流量控制
* 有序交付
* 重传
* ...

> 一种全新的基于UDP的web开发协议。可以用一个公式大致概括：TCP + TLS + HTTP2 = UDP + QUIC + HTTP2’s API

> 从公式可看出：QUIC协议虽然是基于UDP，但它不但具有TCP的可靠性、拥塞控制、流量控制等，且在TCP协议的基础上做了一些改进，比如避免了队首阻塞；另外，QUIC协议具有TLS的安全传输特性，实现了TLS的保密功能，同时又使用更少的RTT建立安全的会话。

### 多路复用

> 无队头阻塞的多路复用

QUIC 原生实现了这个功能，并且传输的单个数据流可以保证有序交付且不会影响其他的数据流，这样的技术就解决了之前 TCP 存在的问题。

并且 QUIC 在移动端的表现也会比 TCP 好:

* 因为 TCP 是基于 IP 和端口去识别连接的，这种方式在多变的移动端网络环境下是很脆弱的。
* QUIC 是通过 ID 的方式去识别一个连接，不管你网络环境如何变化，只要 ID 不变，就能迅速重连上。

### 0-RTT

**与其它协议比较**

* TCP建立链接需要三次握手,每次链接需要额外的RTT
* 加入了TLS需要额外的RTT

**0-RTT情况**

通过使用类似 TCP 快速打开的技术，缓存当前会话的上下文，在下次恢复会话的时候，只需要将之前的缓存传递给服务端验证通过就可以进行传输了。

**1-RTT情况**
新的QUIC连接至少需要1 RTT才能完成握手

![图片](https://img.cdn.sugarat.top/mdImg/MTU4NDMyMzc2NTY2MA==584323765660)

### 纠错机制

假如说这次我要发送三个包，那么协议会算出这三个包的异或值并单独发出一个校验包，也就是总共发出了四个包。

当出现其中的非校验包丢包的情况时，可以通过另外三个包计算出丢失的数据包的内容。

当然**这种技术只能使用在丢失一个包的情况**下，如果出现丢失多个包就不能使用纠错机制了，只能使用重传的方式了。

## 总结

* HTTP/2 通过多路复用、二进制流、Header 压缩等等技术，极大地提高了性能，但是还是存在着问题的
* QUIC 基于 UDP 实现，是 HTTP/3 中的底层支撑协议，该协议基于 UDP，又取了 TCP 中的精华，实现了即快又可靠的协议

## HTTP1.1 如何解决 HTTP 的队头阻塞问题？

### 域名分片

使用多个指向同一服务器的二级域名

:::tip 参考
[MDN Http方法](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods)
[http](https://yuchengkai.cn/docs/cs/#post-%E5%92%8C-get-%E7%9A%84%E5%8C%BA%E5%88%AB)
[CSDN:QUIC（Quick UDP Internet Connections）协议：全新的基于UDP的web开发协议](https://blog.csdn.net/coloriy/article/details/88418258)
:::
