You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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)支持两种启动模式:

  1. HTTP/1.1 Upgrade:客户端先发HTTP/1.1请求并携带Upgrade: h2c头,服务器升级至HTTP/2
  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()))
}

验证方式

  1. Prior Knowledge模式:执行curl -v --http2-prior-knowledge http://localhost:8081,应正常返回响应
  2. Upgrade模式:执行curl -v http://localhost:8081 --header "Upgrade: h2c" --header "Connection: upgrade",应成功升级至HTTP/2

内容的提问来源于stack exchange,提问作者pocesar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 09:15:26