已思考 1 秒
下面给出一套完整示例,包含:
1. 用 Python + Nginx 简单部署同时支持 **h2**(TLS 下的 HTTP/2)和 **h2c**(明文 HTTP/2)
2. 绕过 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
```
* 如果返回类似二进制帧(帧头以 0x00 或 0x01 开始),并且后面有 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。如果还有更深层需求,欢迎继续交流!