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

HTTP/2实现中PRI方法引发问题:原因及处理方式咨询

问题分析与解决方案

首先,咱们得搞清楚你为什么会看到PRI请求——这根本不是客户端发起的实际业务请求,而是HTTP/2连接初始化的前缀标识,核心问题出在你用了错误的工具去解析HTTP/2连接。

为什么会收到PRI?

HTTP/2是二进制帧协议,和HTTP/1.x的文本格式完全不同。当客户端通过ALPN协商确定使用h2协议后,首先会发送一段固定的连接前缀:

PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n

这段内容的作用是触发HTTP/1.x服务器报错(以此确认服务器是否真的支持HTTP/2),但对于正确的HTTP/2服务器来说,它是连接初始化的一部分,需要被HTTP/2的专用解析器处理,而不是当成HTTP/1的请求来解析。

你现在用http.ReadRequest来读取请求,这个方法是专门为HTTP/1.x设计的文本请求解析器,它会把这段二进制协议的前缀当成一个畸形的HTTP/1请求,所以才会读出PRI这个不存在的请求方法。

你不需要手动处理SETTINGS帧或PRI前缀

Go标准库已经内置了完整的HTTP/2支持,完全不需要你手动处理底层的帧交换(包括SETTINGS帧的发送、接收和确认)。问题的根源是你没有使用Go的HTTP/2运行时来处理连接,而是试图用HTTP/1的方式手动解析请求。

正确的实现方式

方案1:直接使用标准库http.Server(推荐)

只要你的TLS配置中NextProtos包含"h2",http.Server会自动处理ALPN协商,自动区分HTTP/1和HTTP/2连接,并且用对应的协议栈处理请求。你只需要像平时写HTTP服务一样定义Handler即可:

package main

import (
    "crypto/rand"
    "crypto/tls"
    "net/http"
    "time"
)

func main() {
    // 加载你的证书(假设cert已经是tls.Certificate类型)
    cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
    if err != nil {
        panic(err)
    }

    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{cert},
        ServerName:   "mysrvr",
        NextProtos:   []string{"h2", "http/1.1", "http/1.0"},
        Time:         time.Now,
        Rand:         rand.Reader,
    }

    server := &http.Server{
        Addr:      ":8000",
        TLSConfig: tlsConfig,
        Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // 这里处理你的请求,不管是HTTP/1还是HTTP/2,接口完全一致
            w.WriteHeader(http.StatusOK)
            w.Write([]byte("Hello from " + r.Proto))
        }),
    }

    // 启动TLS服务,因为TLSConfig已经包含证书,所以可以传空字符串
    if err := server.ListenAndServeTLS("", ""); err != nil {
        panic(err)
    }
}

用这个代码启动服务后,再用你的curl命令测试:

$ curl -v https://127.0.0.1:8000 -k --http2

你会发现请求被正常处理,Handler里拿到的r.Proto会是HTTP/2.0,而且完全看不到PRI前缀——因为底层的HTTP/2栈已经帮你处理了连接初始化的所有细节。

方案2:手动管理连接时使用http2.Server

如果你因为某些原因必须手动管理连接(比如自定义监听逻辑),那应该用golang.org/x/net/http2包的Server来处理HTTP/2连接,而不是用http.ReadRequest:

package main

import (
    "crypto/rand"
    "crypto/tls"
    "net"
    "net/http"
    "time"

    "golang.org/x/net/http2"
)

func yourHandler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("Hello from " + r.Proto))
}

func main() {
    cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
    if err != nil {
        panic(err)
    }

    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{cert},
        ServerName:   "mysrvr",
        NextProtos:   []string{"h2", "http/1.1", "http/1.0"},
        Time:         time.Now,
        Rand:         rand.Reader,
    }

    ln, err := tls.Listen("tcp", ":8000", tlsConfig)
    if err != nil {
        panic(err)
    }
    defer ln.Close()

    http2Server := &http2.Server{}
    http1Server := &http.Server{Handler: http.HandlerFunc(yourHandler)}

    for {
        conn, err := ln.Accept()
        if err != nil {
            // 处理错误
            continue
        }
        tlsConn := conn.(*tls.Conn)
        if err := tlsConn.Handshake(); err != nil {
            conn.Close()
            continue
        }
        // 获取协商后的协议
        proto := tlsConn.ConnectionState().NegotiatedProtocol
        go func(c net.Conn) {
            defer c.Close()
            if proto == "h2" {
                // 用HTTP/2栈处理连接
                http2Server.ServeConn(c, &http2.ServeConnOpts{Handler: http.HandlerFunc(yourHandler)})
            } else {
                // 用HTTP/1栈处理连接
                http1Server.Serve(c)
            }
        }(conn)
    }
}

总结

  • 看到PRI请求是因为你用HTTP/1的解析器处理了HTTP/2连接,这是错误的使用方式
  • 不需要手动解析PRI前缀或发送SETTINGS帧,Go的HTTP/2运行时会自动完成这些底层操作
  • 推荐直接使用标准库的http.Server,它已经无缝支持HTTP/1和HTTP/2的自动切换

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:14:18