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

为何WebTransport无法连接Rust中基于quinn构建的QUIC服务器?

问题:WebTransport无法连接Quinn搭建的QUIC服务器

我用Rust的quinn crate搭建了QUIC服务器,代码如下:

use std::net::SocketAddr;
use quinn::{Endpoint, ServerConfig};
use rcgen::Error;
use rustls::Certificate;

#[tokio::main]
async fn main() {
    println!("start");
    server().await;
    println!("stop")
}

static SERVER_NAME: &str = "localhost";

fn server_addr() -> SocketAddr {
    "127.0.0.1:5001".parse::<SocketAddr>().unwrap()
}

async fn server() {
    let (cert, key) = generate_self_signed_cert().unwrap();
    let config = configure_server(cert, key);
    // Bind this endpoint to a UDP socket on the given server address.
    let endpoint = Endpoint::server(config, server_addr()).unwrap();

    // Start iterating over incoming connections.
    while let Some(conn) = endpoint.accept().await {
        let mut _connection = conn.await.unwrap();
    }
}

fn configure_server(cert: Certificate, key: rustls::PrivateKey) -> ServerConfig {
    ServerConfig::with_single_cert(vec![cert], key).unwrap()
}

fn generate_self_signed_cert() -> Result<(rustls::Certificate, rustls::PrivateKey), Error> {
    let cert = rcgen::generate_simple_self_signed(vec![SERVER_NAME.to_string()])?;
    let key = rustls::PrivateKey(cert.serialize_private_key_der());
    Ok((rustls::Certificate(cert.serialize_der()?), key))
}

Cargo.toml配置:

[dependencies]
quinn = "*"
rustls = { version = "*", features = ["dangerous_configuration", "quic"] }
rcgen = "*"
tokio = { version = "1", features = ["rt", "macros", "rt-multi-thread"] }

运行cargo run后服务器正常启动,但用React代码连接时失败:

function App() {
  async function handle3() {
    let tran = new WebTransport("https://127.0.0.1:5001");
    await tran.ready;
  }
  return (
    <>
      <button onClick={async () => await handle3()}>
        CLICK
      </button>
    </>
  )
}

浏览器报错:

Uncaught (in promise) 
WebTransportError { source: "session", streamErrorCode: null, name: "WebTransportError", message: "WebTransport connection rejected", code: 0, result: 0, filename: "", lineNumber: 0, columnNumber: 0, data: null }

解决方案

核心问题分析

  1. Quinn默认不支持WebTransport协议:Quinn是底层QUIC传输库,仅处理QUIC层逻辑,而WebTransport基于HTTP/3协议,需要上层的HTTP/3会话管理和WebTransport特定握手逻辑。
  2. 证书域名不匹配:生成的证书仅包含localhost,但客户端用127.0.0.1连接,浏览器会判定证书无效,导致TLS握手失败。
  3. WebTransport路径与协议配置缺失:WebTransport需要通过HTTP/3的CONNECT请求建立会话,服务器未配置对应处理逻辑,客户端连接URL也未指向正确路径。

具体修改步骤

步骤1:更新Cargo.toml,添加HTTP/3与WebTransport依赖

[dependencies]
quinn = "*"
rustls = { version = "*", features = ["dangerous_configuration", "quic"] }
rcgen = "*"
tokio = { version = "1", features = ["rt", "macros", "rt-multi-thread"] }
quinn-h3 = "*"
http = "*"

步骤2:修改证书生成逻辑,添加IP备用名称

确保证书同时支持localhost和127.0.0.1:

fn generate_self_signed_cert() -> Result<(rustls::Certificate, rustls::PrivateKey), Error> {
    let mut params = rcgen::generate_simple_self_signed(vec![SERVER_NAME.to_string()])?;
    // 添加127.0.0.1作为IP备用名称
    params.subject_alt_names.push(rcgen::SanType::IpAddress(std::net::Ipv4Addr::new(127, 0, 0, 1).into()));
    let key = rustls::PrivateKey(params.serialize_private_key_der());
    Ok((rustls::Certificate(params.serialize_der()?), key))
}

步骤3:重构服务器代码,添加HTTP/3与WebTransport处理

use quinn_h3::{Server, Connection, webtransport::Session};
use http::{Method, StatusCode, Response};

async fn server() {
    let (cert, key) = generate_self_signed_cert().unwrap();
    let mut config = configure_server(cert, key);
    
    // 初始化HTTP/3服务器
    let mut server = Server::new(config);
    let (endpoint, mut incoming) = server.bind(server_addr()).unwrap();
    println!("Listening on {}", endpoint.local_addr().unwrap());

    // 处理 incoming 连接
    while let Some(conn) = incoming.next().await {
        let conn = conn.unwrap();
        tokio::spawn(async move {
            if let Err(e) = handle_connection(conn).await {
                eprintln!("Connection error: {}", e);
            }
        });
    }
}

fn configure_server(cert: Certificate, key: rustls::PrivateKey) -> ServerConfig {
    let mut config = ServerConfig::with_single_cert(vec![cert], key).unwrap();
    // 添加HTTP/3的ALPN协议标识(WebTransport依赖HTTP/3)
    config.transport = config.transport.with_alpn_protocols(vec![b"h3".to_vec()]);
    config
}

async fn handle_connection(mut conn: Connection) -> Result<(), Box<dyn std::error::Error>> {
    while let Some(req) = conn.accept_request().await {
        let (parts, _body) = req.into_parts();
        // 处理WebTransport的CONNECT请求
        if parts.method == Method::CONNECT && parts.uri.path() == "/webtransport" {
            let session = conn.accept_webtransport_session(parts).await?;
            println!("WebTransport session established");
            tokio::spawn(async move {
                if let Err(e) = handle_webtransport_session(session).await {
                    eprintln!("WebTransport session error: {}", e);
                }
            });
        } else {
            // 其他请求返回404
            let resp = Response::builder()
                .status(StatusCode::NOT_FOUND)
                .body(())?;
            conn.send_response(resp).await?;
        }
    }
    Ok(())
}

async fn handle_webtransport_session(mut session: Session) -> Result<(), Box<dyn std::error::Error>> {
    // 处理WebTransport会话的双向流
    while let Some(stream) = session.accept_bi().await {
        let (mut send, mut recv) = stream?;
        let mut buf = [0u8; 1024];
        while let Ok(n) = recv.read(&mut buf).await {
            if n == 0 {
                break;
            }
            // echo数据回客户端
            send.write_all(&buf[..n]).await?;
            send.flush().await?;
        }
    }
    Ok(())
}

步骤4:修改React客户端连接逻辑

指向正确的WebTransport会话路径,并添加错误处理:

async function handle3() {
  try {
    // 指向WebTransport会话路径
    let tran = new WebTransport("https://localhost:5001/webtransport");
    await tran.ready;
    console.log("WebTransport连接成功!");
    
    // 示例:创建双向流并发送数据
    let stream = await tran.createBidirectionalStream();
    let writer = stream.writable.getWriter();
    await writer.write(new TextEncoder().encode("Hello from WebTransport!"));
    writer.close();
    
    let reader = stream.readable.getReader();
    let result = await reader.read();
    console.log("收到服务器响应:", new TextDecoder().decode(result.value));
  } catch (e) {
    console.error("连接失败:", e);
  }
}

步骤5:信任自签名证书

将服务器生成的证书导出(可在代码中添加保存证书的逻辑),导入浏览器的信任证书列表,避免TLS握手时的证书验证错误。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 04:58:13