h2c vs h2

已思考 1 秒


下面给出一套完整示例,包含:

1. 用 Python + Nginx 简单部署同时支持 **h2**(TLS 下的 HTTP/2)和 **h2c**(明文 HTTP/22. 绕过 Upgrade 的 h2c “Prior Knowledge” 访问示例
3. 通过 HTTP/1.1 `Upgrade: h2c` 正常升级到 h2c 的请求示例
4. HTTP/1.1 升级到 HTTP/2 的完整过程解析
5. 如何验证服务器是否支持 HTTP/2(h2)和 h2c

---

## 概览

* **部署**

  * Nginx 配置(含 TLS + http2,以及明文 http2)
  * Python 简易后端(Flask + Hypercorn 支持 h2/h2c)
* **访问示例**

  * Prior‑Knowledge h2c(直接发送 HTTP/2 preface)
  * Upgrade h2c(HTTP/1.1 → HTTP/2 协商)
* **升级流程解析**

  1. 客户端发起明文 HTTP/1.1 带 `Upgrade: h2c`
  2. 服务端返回 `101 Switching Protocols`
  3. 双方切换到 HTTP/2 二进制帧通信
* **验证方式**

  * `curl --http2` / `curl --http2-prior-knowledge`
  * `nghttp` / `h2load` / `nc`

---

## 1. 环境准备

* Nginx(主版本 ≥ 1.19)或 OpenResty/Tengine
* Python 3.8+
* Flask + Hypercorn

安装:

```bash
# Ubuntu/Debian
sudo apt update
sudo apt install nginx python3-pip
pip3 install flask hypercorn h2
```

---

## 2. Nginx 配置

在 `/etc/nginx/conf.d/h2c.conf` 加入:

```nginx
# =================================================================
# server A:HTTPS + h2(基于 TLS 的 HTTP/2)
# =================================================================
server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate     /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    location / {
        proxy_pass       http://127.0.0.1:5000;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

# =================================================================
# server B:HTTP + h2c (明文 HTTP/2)
# (需要 OpenResty / Tengine 或编译了 http2 模块的 Nginx)
# =================================================================
server {
    listen 8080 http2;          # http2 here means h2c on cleartext
    server_name localhost;

    location / {
        proxy_pass       http://127.0.0.1:5000;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}
```

> **注意**:官方 Nginx 二进制默认并不支持 `listen … http2;` 在 80/8080 明文端口上,你需要用 OpenResty/Tengine,或自己编译时加上 `--with-http_v2_module`。

重载 Nginx:

```bash
sudo nginx -t
sudo systemctl reload nginx
```

---

## 3. Python 简易后端

创建 `app.py`:

```python
from flask import Flask, request

app = Flask(__name__)

@app.route('/')
def index():
    proto = request.environ.get('SERVER_PROTOCOL')
    return f"Hello from Flask! 协议:{proto}\n"

if __name__ == '__main__':
    # Hypercorn 同时支持 h2 (TLS) & h2c(明文):
    #  启动命令请在终端执行,不在此处硬编码。
    app.run()
```

使用 Hypercorn 启动(支持 h2 & h2c):

```bash
# 明文 h2c
hypercorn app:app --bind 0.0.0.0:5000 --worker-connections 100 --use-reloader \
    --h2 --h2c

# 或者同时给出证书用于 h2(TLS)
hypercorn app:app \
    --bind 0.0.0.0:5000 \
    --certfile /path/to/fullchain.pem \
    --keyfile  /path/to/privkey.pem \
    --h2 --h2c
```

---

## 4. Prior‑Knowledge h2c 访问示例(绕过 Upgrade)

```bash
# 直接发送 HTTP/2 preface 到明文 8080(h2c)
echo -ne 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' | \
    nc -v -n localhost 8080 | hexdump -C | head -n 20
```

* 如果返回类似二进制帧(帧头以 0x000x01 开始),并且后面有 SETTINGS 帧,则表明 h2c 可用。
* 你也可以用 `curl`:

  ```bash
  curl -v --http2-prior-knowledge http://localhost:8080/
  ```

---

## 5. Upgrade: h2c 协商示例

客户端发送(HTTP/1.1 → HTTP/2):

```bash
printf 'GET / HTTP/1.1\r\n'\
'Host: localhost:8080\r\n'\
'Connection: Upgrade, HTTP2-Settings\r\n'\
'Upgrade: h2c\r\n'\
'HTTP2-Settings: \r\n'\
'\r\n' \
| nc localhost 8080 | sed -n '1,10p'
```

* `HTTP2-Settings:` 一般可以留空或填入 base64url 编码的 SETTINGS 帧。
* 服务端如果支持,就返回:

  ```
  HTTP/1.1 101 Switching Protocols
  Connection: Upgrade
  Upgrade: h2c

  <接着就是 HTTP/2 二进制帧:SETTINGS 等>
  ```
* 之后客户端和服务端切换到 HTTP/2 二进制帧层面通信。

---

## 6. HTTP/1.1 升级到 HTTP/2 的详细过程

1. **初始 HTTP/1.1 请求**

   ```
   GET / HTTP/1.1
   Host: example.com
   Connection: Upgrade, HTTP2-Settings
   Upgrade: h2c
   HTTP2-Settings: <base64url(SETTINGS帧)>
   ```
2. **服务端确认升级**

   ```
   HTTP/1.1 101 Switching Protocols
   Connection: Upgrade
   Upgrade: h2c
   ```
3. **客户端发送 HTTP/2 连接前言**

   ```
   PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
   ```
4. **双方开始交换 HTTP/2 帧**

   * 客户端:SETTINGS 帧
   * 服务端:SETTINGS、WINDOW\_UPDATE 等帧
   * 随后 HEADERS、DATA 等帧用于具体请求/响应

---

## 7. 验证是否支持 HTTP/2 & h2c

| 方法                   | 命令示例                                                               | 说明                    |
| -------------------- | ------------------------------------------------------------------ | --------------------- |
| curl HTTP/2 over TLS | `curl -v --http2 https://example.com/`                             | 验证 h2 (TLS 下的 HTTP/2) |
| curl HTTP/2 明文 h2c   | `curl -v --http2-prior-knowledge http://localhost:8080/`           | 验证 h2c                |
| netcat 发送 preface    | `echo -ne 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' \| nc localhost 8080` | 绕过 Upgrade,直接测试 h2c   |
| HTTP/1.1 Upgrade 流程  | 参见上面第 5 节 Upgrade 示例                                               | 测试标准的 Upgrade 协商      |
| nghttp / h2load      | `nghttp -nv http://localhost:8080/` 或 `h2load http://…`            | 专业 HTTP/2 客户端/压测工具    |

---

以上示例基本覆盖了 **h2 (TLS)**、**h2c (明文)** 的部署、访问、升级与验证流程。如有细节需求(如自定义 SETTINGS、窗口大小调优、混合协议监听等),可继续深入交流。

 

已思考若干秒


下面将从**原理**、**Nginx 配置示例**、**Python 客户端示例**、**升级过程详解**、**验证方法**五个部分,使用中文给出最全面的解答,并在每处关键配置或命令后附上参考文献,以帮助你快速理解和落地。

---

## 概览

1. **原理梳理**

   * HTTP/2 over TLS(`h2`)和 HTTP/2 Cleartext(`h2c`)的区别与联系
   * 升级流程:HTTP/1.1 → HTTP/2 Upgrade → h2c
   * “绕过升级”Preface 直连方式

2. **Nginx 配置示例**

   * 同时支持 `h2`(443/TLS)和 `h2c`(80/明文)
   * 使用 OpenResty/Tengine 实现 h2c

3. **Python 客户端示例**

   * 普通 `h2` 与 `h2c` 请求
   * Upgrade 流程与 Preface 直连两种方式

4. **HTTP/1.1 升级成 h2c 详解**

   * 请求头、HTTP2-Settings 生成、服务器响应
   * Preface 帧结构

5. **验证方法**

   * `curl` 多场景演示:HTTPS(`-k -X POST https://…`)与 HTTP(`-X POST http://…`)
   * 请求/响应头说明

---

## 一、原理梳理

### 1.1 h2 与 h2c 的区别

| 协议  | 端口  | 加密     | 用途              |
| --- | --- | ------ | --------------- |
| h2  | 443 | TLS 加密 | 公网服务、浏览器访问      |
| h2c | 80  | 明文     | 内部微服务、gRPC、测试场景 |

* **h2**:标准 HTTPS 上的 HTTP/2,需要 TLS。
* **h2c**:HTTP/2 Cleartext,不依赖 TLS,但必须明确升级或 Preface 直连。

### 1.2 升级流程

* **Upgrade 方式**(HTTP/1.1 → HTTP/2 Upgrade → h2c)

  1. 客户端发送 HTTP/1.1 普通请求,带上

     ```txt
     Connection: Upgrade, HTTP2-Settings
     Upgrade: h2c
     HTTP2-Settings: BASE64(HTTP2_SETTINGS_FRAME)
     ```
  2. 服务器返回 `101 Switching Protocols` 并带上 `Upgrade: h2c`。
  3. 随后客户端开始发送 HTTP/2 帧,正式进入 h2c 通信。

* **Preface 直连**

  1. 客户端直接发出 24 字节 Preface:

     ```
     PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
     ```
  2. 服务器若支持 h2c,会直接用 HTTP/2 帧响应,无需 HTTP/1.1 协议切换。

---

## 二、Nginx 配置示例

> **注意**:官方 Nginx 不支持明文 h2c,需要使用 OpenResty 或 Tengine 等变种。

### 2.1 同时支持 h2(TLS)和 h2c(明文)

```nginx
# nginx.conf

# 1. 明文 HTTP h2c(80 端口)
server {
    listen 80 http2;                   # http2 但不加 ssl
    server_name example.com;

    location / {
        root /usr/share/nginx/html;
        index index.html;
    }
}

# 2. HTTPS h2(443 端口)
server {
    listen 443 ssl http2;              # 开启 TLS + HTTP/2
    server_name example.com;

    ssl_certificate     /path/fullchain.pem;
    ssl_certificate_key /path/privkey.pem;

    location / {
        root /usr/share/nginx/html;
        index index.html;
    }
}
```

> 以上配置在 OpenResty/Tengine 下生效,官方 Nginx 会忽略明文 `http2`。

### 2.2 使用 OpenResty 支持 h2c

```bash
# 安装 OpenResty(Ubuntu 示例)
sudo apt-get install -y openresty
```

```nginx
# openresty 的 nginx.conf
worker_processes 1;

events { worker_connections 1024; }

http {
    server {
        listen 80 http2;            # 明文 h2c
        server_name _;

        location / {
            default_type text/html;
            return 200 "Hello h2c from OpenResty!";
        }
    }

    server {
        listen 443 ssl http2;       # TLS h2
        ssl_certificate     /path/fullchain.pem;
        ssl_certificate_key /path/privkey.pem;

        location / {
            default_type text/html;
            return 200 "Hello h2 over TLS!";
        }
    }
}
```

---

## 三、Python 客户端示例

### 3.1 普通 h2c Upgrade 请求

```python
import socket, base64
from h2.settings import SettingCodes, Settings

def build_settings():
    s = Settings()
    s[SettingCodes.MAX_CONCURRENT_STREAMS] = 100
    return base64.urlsafe_b64encode(s.to_bytes()).decode().rstrip('=')

def h2c_upgrade(host, port=80):
    pre = build_settings()
    req = (
        f"GET / HTTP/1.1\r\n"
        f"Host: {host}\r\n"
        "Connection: Upgrade, HTTP2-Settings\r\n"
        "Upgrade: h2c\r\n"
        f"HTTP2-Settings: {pre}\r\n\r\n"
    ).encode()
    sock = socket.create_connection((host, port))
    sock.sendall(req)
    print(sock.recv(4096).decode())
    sock.close()

h2c_upgrade("127.0.0.1", 80)
```

### 3.2 Preface 直连方式

```python
import socket

def h2c_preface(host, port=80):
    preface = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
    sock = socket.create_connection((host, port))
    sock.sendall(preface)
    print(sock.recv(4096).decode(errors='ignore'))
    sock.close()

h2c_preface("127.0.0.1", 80)
```

### 3.3 TLS 下的 h2(无需 Upgrade)

```python
import httpx

# httpx 自动使用 TLS+HTTP/2(需要安装 httpx)
resp = httpx.post("https://127.0.0.1:443/", verify=False)
print("HTTP Version:", resp.http_version)
print(resp.headers)
print(resp.text)
```

---

## 四、HTTP/1.1 升级成 h2c 详解

1. **客户端发送(示例)**

   ```
   GET /path HTTP/1.1
   Host: 127.0.0.1:80
   Connection: Upgrade, HTTP2-Settings
   Upgrade: h2c
   HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA  ← Base64 编码后的 Settings Frame
   ```

2. **服务器响应**

   ```
   HTTP/1.1 101 Switching Protocols
   Connection: Upgrade
   Upgrade: h2c
   ```

3. **之后通信**

   * 客户端紧跟发送 HTTP/2 Preface + Settings Frame
   * 双方按照 HTTP/2 帧格式收发数据

---

## 五、验证是否支持 HTTP/2(h2/h2c)

### 5.1 使用 curl 验证 h2(TLS)

```bash
curl -k -v -X POST https://127.0.0.1:443/DeployId/GetDeployId \
  -H "Content-Type: application/json"
```

* 响应头中应包含 `HTTP/2` 与 `:status` 字段:

  ```
  * Using HTTP2, server supports multi-use
  > POST /DeployId/GetDeployId HTTP/2
  < :status: 200
  < content-type: application/json
  ```

### 5.2 使用 curl 验证 h2c(明文 Upgrade)

```bash
curl -v --http2-prior-knowledge \
     -X POST http://127.0.0.1:80/DeployId/GetDeployId \
     -H "Content-Type: application/json"
```

* `--http2-prior-knowledge` 直接发 Preface。
* 成功时输出:

  ```
  * Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
  * Using HTTP2, server supports multi-use
  > POST /DeployId/GetDeployId HTTP/2
  < HTTP/2 200 
  < content-type: application/json
  ```

### 5.3 使用 nc 检查 Preface

```bash
printf 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' | nc -v 127.0.0.1 80
```

* 若支持 h2c,返回 HTTP/2 帧;否则无响应或 HTTP/1.x。

---

## 额外说明

* **h2 必须 SSL 吗?**

  * 浏览器环境下,HTTP/2 (`h2`) 几乎都要求 TLS;但服务器实现层面,使用 `listen 443 http2 ssl;` 即可。
  * 明文 HTTP/2(`h2c`)则不使用 SSL。

* **多请求头/响应头差异**

  * **Upgrade 方式**会先出现 `101 Switching Protocols`。
  * **Preface 方式**直接进入 HTTP/2 帧,不会有 `101`。

---

以上内容涵盖了从原理到落地的完整流程,包含 Nginx 配置、Python 客户端例子、升级过程详解和多种 curl 验证方法,足够帮助你快速掌握并验证 h2 与 h2c。如果还有更深层需求,欢迎继续交流!

 

posted on 2025-05-09 11:09  吃草的青蛙  阅读(153)  评论(0)    收藏  举报

导航