Go语言如何从net.Conn的h2c请求提取路径且不影响后续使用
问题解决思路与实现代码
你的核心问题是直接读取net.Conn后,已读取的帧数据被消费,导致后续连接无法复用这些数据。解决关键是缓存已读取的帧数据,并返回一个包装后的连接,让后续操作能先读取缓存数据,再读取原连接的新数据。
实现方案
1. 自定义带缓存的连接结构体
这个结构体实现net.Conn接口,内部维护一个缓存缓冲区,优先返回缓存数据,缓存耗尽后再读取原连接:
import ( "bytes" "net" "time" ) type CachedConn struct { original net.Conn buffer *bytes.Buffer } func NewCachedConn(conn net.Conn, cachedData []byte) *CachedConn { buf := bytes.NewBuffer(cachedData) return &CachedConn{ original: conn, buffer: buf, } } // Read 优先读取缓存,缓存耗尽后读取原连接 func (c *CachedConn) Read(b []byte) (int, error) { if c.buffer.Len() > 0 { return c.buffer.Read(b) } return c.original.Read(b) } // 以下方法直接委托给原连接,满足net.Conn接口要求 func (c *CachedConn) Write(b []byte) (int, error) { return c.original.Write(b) } func (c *CachedConn) Close() error { return c.original.Close() } func (c *CachedConn) LocalAddr() net.Addr { return c.original.LocalAddr() } func (c *CachedConn) RemoteAddr() net.Addr { return c.original.RemoteAddr() } func (c *CachedConn) SetDeadline(t time.Time) error { return c.original.SetDeadline(t) } func (c *CachedConn) SetReadDeadline(t time.Time) error { return c.original.SetReadDeadline(t) } func (c *CachedConn) SetWriteDeadline(t time.Time) error { return c.original.SetWriteDeadline(t) }
2. 修改路径提取函数
读取帧时完整缓存所有已读取的数据,找到Headers帧后返回路径和带缓存的连接:
import ( "bufio" "bytes" "io" "net/http2" "golang.org/x/net/http2/hpack" ) // ExtractPathFromH2Request 提取h2c请求路径,同时返回可继续使用的连接 func ExtractPathFromH2Request(conn net.Conn) (string, net.Conn, error) { bufReader := bufio.NewReader(conn) var cachedData bytes.Buffer var path string for { // 读取帧头部,计算完整帧长度 frameHeader, err := http2.ReadFrameHeader(bufReader) if err != nil { return "", conn, err } totalFrameLen := 9 + int(frameHeader.Length) frameData := make([]byte, totalFrameLen) // 重构完整帧数据并缓存 copy(frameData[:9], frameHeader[:]) if _, err := io.ReadFull(bufReader, frameData[9:]); err != nil { return "", conn, err } cachedData.Write(frameData) // 解析帧内容 frame, err := http2.NewFrameReader(bytes.NewReader(frameData)).ReadFrame() if err != nil { return "", conn, err } // 处理Headers帧 if headersFrame, ok := frame.(*http2.HeadersFrame); ok { decoder := hpack.NewDecoder(4096, func(hf hpack.HeaderField) {}) headerList, err := decoder.DecodeFull(headersFrame.HeaderBlockFragment()) if err != nil { return "", conn, err } // 查找:path字段 for _, hf := range headerList { if hf.Name == ":path" { path = hf.Value break } } // 返回路径和带缓存的连接 cachedConn := NewCachedConn(conn, cachedData.Bytes()) return path, cachedConn, nil } // 非Headers帧继续循环,直到找到第一个Headers帧 } }
方案说明
- 读取帧时会完整缓存所有已读取的字节(包括非Headers帧),确保后续连接能获取到所有原始数据
- 返回的
CachedConn会优先读取缓存数据,缓存耗尽后再读取原连接,完全兼容net.Conn接口 - 解决了原代码中读取后帧数据丢失的问题,同时完成路径提取需求
内容的提问来源于stack exchange,提问作者Fangliding
相关产品推荐
相关产品推荐

