使用quic-go写了个http3的本地demo,浏览器访问不通是怎么回事,显示无法连接?

使用自签名证书,在端口4433和4431用两种方式启动了http3服务,在8443启动了HTTP2服务

用go写的客户端能正常访问http3服务,用浏览器firefo和chrome打开https://localhost:4431/都是显示无法连接是怎么回事?打开http2 https://localhost:8443/浏览器都会提示证书不信任,点忽略后正常。

为什么http3不行?

代码如下:

server.go:

package main

import (
    "crypto/tls"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net/http"
    "time"

    "github.com/quic-go/quic-go"
    "github.com/quic-go/quic-go/http3"
)

func streamHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/x-ndjson")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Alt-Svc", `h3=":4433"; ma=86400`)

    flusher, ok := w.(http.Flusher)
    if !ok {
        http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
        return
    }

    for i := 0; i < 5; i++ {
        msg := map[string]interface{}{
            "token": fmt.Sprintf("h3-word%d", i),
            "proto": r.Proto, // 应为 "HTTP/3.0"
            "index": i,
        }
        json.NewEncoder(w).Encode(msg)
        flusher.Flush()
        time.Sleep(600 * time.Millisecond)
    }
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Alt-Svc", `h3=":4433"; ma=86400`)

        w.Write([]byte("ok\n"))
        w.Write([]byte(r.Proto))
    })
    mux.HandleFunc("/stream", streamHandler)

    certPath := "cert.pem"

    keyPath := "key.pem"

    // 使用 quic-go 提供的 HTTP/3 服务器
    server := &http3.Server{
        Handler:    mux,
        Addr:       ":4433",
        TLSConfig:  http3.ConfigureTLSConfig(&tls.Config{}),
        QUICConfig: &quic.Config{},
    }

    go func() {
        log.Println("HTTP/3 server running on https://localhost:4433")
        log.Fatal(server.ListenAndServeTLS(certPath, keyPath))
    }()

    go func() {
        mux := http.NewServeMux()
        mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            io.WriteString(w, fmt.Sprintf("ok, http proto=%s", r.Proto))
        })
        // If mux is nil, the http.DefaultServeMux is used.
        log.Println("HTTP/3 server running on https://localhost:4431")
        log.Fatal(http3.ListenAndServeQUIC("0.0.0.0:4431", certPath, keyPath, mux))
    }()

    // 同时启动 HTTP/2 (TCP)

    log.Println("HTTP/2 on TCP :8443")

    log.Fatal(http.ListenAndServeTLS(":8443", certPath, keyPath, mux))
}

client.go:

package main

import (
    "crypto/tls"
    "io"
    "log"
    "net/http"

    "github.com/quic-go/quic-go"
    "github.com/quic-go/quic-go/http3"
)

func main() {
    tr := &http3.Transport{
        // set a TLS client config, if desired
        TLSClientConfig: &tls.Config{
            NextProtos:         []string{http3.NextProtoH3}, // set the ALPN for HTTP/3
            InsecureSkipVerify: true,
        },
        QUICConfig: &quic.Config{}, // QUIC connection options
    }
    defer tr.Close()
    client := &http.Client{
        Transport: tr,
    }

    urls := []string{
        "https://127.0.0.1:4433",
        "https://127.0.0.1:4433/stream",
        "https://127.0.0.1:4431",
    }

    for _, url := range urls {
        rsp, err := client.Get(url)
        if err != nil {
            log.Printf("req error: %s", err)
            return
        }
        defer rsp.Body.Close()

        rb, err := io.ReadAll(rsp.Body)
        if err != nil {
            log.Println("read body err: ", err)
            return
        }
        log.Println("req:", url, "-- response:", string(rb))
    }
}

2025-11-22更新:

折腾一番搞定了:https://360us.net/article/109.html 里面拉到最底部。

阅读 1.1k
avatarAI BotBETA

问题分析:浏览器无法访问 HTTP/3 服务的根本原因

关键原因:浏览器对 HTTP/3 的 ALPN 协商要求更严格

HTTP/3 的协议协商依赖 TLS 层的 ALPN(Application-Layer Protocol Negotiation)扩展。浏览器在检测到自签名证书时,通常会直接终止协商过程,不会像 Go 客户端那样继续建立 QUIC 连接。

具体问题点:

  1. 自签名证书未包含 SAN (Subject Alternative Name)

    • 浏览器要求证书必须包含 DNS:localhost 的 SAN 条目
    • 现代浏览器不再信任仅有 Common Name 的自签名证书
  2. ALPN 配置缺失

    • 服务端代码中 TLSConfig 未显式设置 ALPN 协议列表
    • 缺少 h3 (HTTP/3) 协议声明:TLSConfig.NextProtos = []string{"h3"}
  3. Alt-Svc 头部使用不当

    • 浏览器首次访问需要通过 HTTP/2 响应头的 Alt-Svc 发现 HTTP/3 端点
    • 直接访问 HTTP/3 端口不会触发自动发现机制

修复方案

第一步:生成符合浏览器标准的自签名证书

# 创建包含 SAN 的证书(关键!)
openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem \
  -days 365 -subj '/CN=localhost' \
  -addext "subjectAltName=DNS:localhost"

第二步:修改服务端代码

// 在 server.go 中添加以下修改

// 创建 TLS 配置时添加 ALPN 支持
tlsConfig := &tls.Config{
    NextProtos: []string{"h3"}, // 声明支持的 ALPN 协议
    MinVersion: tls.VersionTLS13,
}

// 修改 HTTP/3 服务器配置
server := &http3.Server{
    Handler:   mux,
    Addr:      ":4433",
    TLSConfig: tlsConfig, // 使用修正后的 TLSConfig
}

// 修改 ListenAndServeQUIC 调用
http3.ListenAndServeQUIC("localhost:4431", certPath, keyPath, nil)

第三步:优化 HTTP/2 服务

// 在 HTTP/2 处理器中强化 Alt-Svc 声明
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Alt-Svc", `h3=":4433"; ma=86400, h3-29=":4433"; ma=86400`)
    w.Header().Set("Access-Control-Allow-Origin", "*")
    
    if r.Proto == "HTTP/2.0" {
        w.Write([]byte("HTTP/2 OK (Alt-Svc points to HTTP/3 at :4433)"))
        return
    }
    w.Write([]byte(r.Proto))
})

验证流程

  1. 先访问 HTTP/2 服务

    https://localhost:8443/
    • 点击"忽略证书警告"(只做一次)
    • 检查响应头中的 Alt-Svc 值是否正确
  2. 刷新页面(触发升级)

    • 浏览器应自动切换到 HTTP/3 协议
    • 在开发者工具的 Network 标签检查 Protocol 列,会显示 h3
  3. 直接访问 HTTP/3(仅用于测试)

    chrome://flags/#enable-quic → Enable

    然后访问:

    https://localhost:4433/stream?access-raw-h3=1

浏览器兼容说明

浏览器启用步骤调试工具关键字
Chrome 115+默认启用h3
Firefox 110+默认启用HTTP/3
Safari 16.4+设置 → 高级 → Experimental Features → HTTP/3h3
浏览器开发者工具中需手动启用协议显示:Network 标签 → 右键列标题 → 勾选 Protocol

最后确认代码中所有监听的地址应为 localhost 而不是 0.0.0.0(避免浏览器安全策略限制)。

1 个回答

为了方面这里使用了一个工具:mkcert - https://github.com/FiloSottile/mkcert

使用方法:

mkcert -install # 安装CA

mkcert localhost 127.0.0.1 ::1 #生成本地测试的证书文件

mkcert -uninstall # 卸载CA

用上面生成的证书文件来启动http3本地服务器。

浏览器直接访问还没用,不管是firefox还是chrome都不会用http3来连接,会提示连接不了。

然后得让浏览器强制使用http3协议来连接。

win系统下面的命令:

"C:\Program Files\Google\Chrome\Application\chrome.exe" --origin-to-force-quic-on=127.0.0.1:443 https://127.0.0.1:443

强制chrome使用http3连接才行,edge浏览器参数一样。firefox没找到相关启动参数,就没法了。

在142版的chrome和edge测试通过。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进