如何在hyper服务器中使用未加密HTTP/2(H2C)?相关报错求助
Tonic h2c 服务器HTTP/2直连验证失败问题
问题现象
- curl命令报错
执行curl -v --http2-prior-knowledge http://localhost:8081后触发协议错误:
curl -v --http2-prior-knowledge http://localhost:8081 * Trying 127.0.0.1:8081... * Connected to localhost (127.0.0.1) port 8081 (#0) * h2h3 [:method: GET] * h2h3 [:path: /] * h2h3 [:scheme: http] * h2h3 [:authority: localhost:8081] * h2h3 [user-agent: curl/7.88.1] * h2h3 [accept: */*] * Using Stream ID: 1 (easy handle 0x56425e0e6c80) > GET / HTTP/2 > Host: localhost:8081 > user-agent: curl/7.88.1 > accept: */* > * HTTP/2 stream 1 was not closed cleanly: PROTOCOL_ERROR (err 1) * Connection #0 to host localhost left intact curl: (92) HTTP/2 stream 1 was not closed cleanly: PROTOCOL_ERROR (err 1)
- 示例代码执行报错
使用Tonic官方h2c示例代码时,以下代码行抛出错误:
let upgraded_io = hyper::upgrade::on(&mut req).await.unwrap();
错误信息:
called `Result::unwrap()` on an `Err` value: hyper::Error(User(NoUpgrade))
- Postman连接问题
- 启用TLS连接失败(符合h2c明文协议预期)
- 不启用TLS时收到
Received RST_STREAM with code 1错误
解决方案
核心原因
h2c(明文HTTP/2)支持两种启动模式:
- HTTP/1.1 Upgrade:客户端先发HTTP/1.1请求并携带
Upgrade: h2c头,服务器升级至HTTP/2 - Prior Knowledge:客户端直接发送HTTP/2帧(即
curl --http2-prior-knowledge的调用方式)
Tonic官方示例仅处理了Upgrade模式,未适配Prior Knowledge模式,因此直接用--http2-prior-knowledge会触发协议错误,且hyper::upgrade::on仅在存在Upgrade请求时有效,直接发HTTP/2帧会返回NoUpgrade错误。
修复步骤
修改服务器逻辑,同时兼容两种模式:
use hyper::{Body, Request, Response, Server, StatusCode}; use hyper::service::{make_service_fn, service_fn}; use hyper::server::conn::Http; use hyper::upgrade::Upgraded; use tonic::transport::server::Connected; use std::convert::Infallible; #[tokio::main] async fn main() { let addr = ([127, 0, 0, 1], 8081).into(); let make_svc = make_service_fn(|conn| { let is_h2 = conn.is_http2(); async move { Ok::<_, Infallible>(service_fn(move |req| handle_request(req, is_h2))) } }); let server = Server::bind(&addr).serve(make_svc); if let Err(e) = server.await { eprintln!("server error: {}", e); } } async fn handle_request(req: Request<Body>, is_h2: bool) -> Result<Response<Body>, Infallible> { if is_h2 { // 处理HTTP/2 Prior Knowledge模式请求 handle_tonic_request(req).await } else { // 处理HTTP/1.1 Upgrade到h2c的请求 if req.headers().contains_key("upgrade") && req.headers()["upgrade"].eq_ignore_ascii_case("h2c") { let resp = Response::builder() .status(StatusCode::SWITCHING_PROTOCOLS) .header("upgrade", "h2c") .header("connection", "upgrade") .body(Body::empty()) .unwrap(); tokio::spawn(async move { if let Ok(upgraded) = hyper::upgrade::on(req).await { handle_upgraded_connection(upgraded).await; } }); Ok(resp) } else { Ok(Response::builder() .status(StatusCode::BAD_REQUEST) .body("仅支持h2c Upgrade或HTTP/2 Prior Knowledge模式".into()) .unwrap()) } } } async fn handle_upgraded_connection(upgraded: Upgraded) { let mut conn = Http::new() .http2_only(true) .serve_connection(upgraded, service_fn(handle_tonic_request)); if let Err(e) = conn.await { eprintln!("connection error: {}", e); } } // 替换为你的Tonic服务处理逻辑 async fn handle_tonic_request(req: Request<Body>) -> Result<Response<Body>, Infallible> { Ok(Response::new("Hello from h2c server".into())) }
验证方式
- Prior Knowledge模式:执行
curl -v --http2-prior-knowledge http://localhost:8081,应正常返回响应 - Upgrade模式:执行
curl -v http://localhost:8081 --header "Upgrade: h2c" --header "Connection: upgrade",应成功升级至HTTP/2
内容的提问来源于stack exchange,提问作者pocesar
相关产品推荐
相关产品推荐

