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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 09:20:13