如何获取http.Request的发送字节数?HTTPS连接是否适用?
统计HTTP客户端发送的字节数(支持HTTPS)
方法一:统计HTTP协议层面的请求字节数
这种方法统计的是HTTP请求本身的总大小,包括请求行、请求头和请求体,适用于大多数需要管控HTTP请求大小的场景,且完美支持HTTPS。
核心思路是通过自定义RoundTripper包装默认的HTTP传输层,在发送请求前复制请求体避免被消耗,同时统计请求体和请求头/行的字节数:
import ( "bytes" "fmt" "io" "net/http" ) // CountingTransport 包装http.RoundTripper,用于统计请求发送的字节数 type CountingTransport struct { Transport http.RoundTripper OnRequestSent func(totalBytes int64) // 请求发送完成后的回调,返回总字节数 } // RoundTrip 实现http.RoundTripper接口 func (ct *CountingTransport) RoundTrip(req *http.Request) (*http.Response, error) { var bodyBytes int64 var clonedBody io.ReadCloser // 处理请求体:复制原始内容,避免读取后无法发送 if req.Body != nil { buf := &bytes.Buffer{} if _, err := io.Copy(buf, req.Body); err != nil { return nil, err } req.Body.Close() // 使用TeeReader在读取请求体时统计字节数 bodyReader := bytes.NewReader(buf.Bytes()) teeReader := io.TeeReader(bodyReader, &countingWriter{count: &bodyBytes}) clonedBody = io.NopCloser(teeReader) req.Body = clonedBody } // 计算请求行和请求头的字节数 headerBuf := &bytes.Buffer{} // 写入请求行(如GET /path HTTP/1.1\r\n) fmt.Fprintf(headerBuf, "%s %s %s\r\n", req.Method, req.URL.RequestURI(), req.Proto) // 写入请求头 if err := req.Header.Write(headerBuf); err != nil { return nil, err } // 写入请求头与体之间的空行 headerBuf.WriteString("\r\n") headerSize := int64(headerBuf.Len()) // 实际发送请求 resp, err := ct.Transport.RoundTrip(req) if err != nil { return resp, err } // 计算总发送字节数并触发回调 totalBytes := headerSize + bodyBytes if ct.OnRequestSent != nil { ct.OnRequestSent(totalBytes) } return resp, nil } // countingWriter 用于统计写入的字节数 type countingWriter struct { count *int64 } func (cw *countingWriter) Write(p []byte) (n int, err error) { n = len(p) *cw.count += int64(n) return n, nil }
使用示例
func main() { client := &http.Client{ Transport: &CountingTransport{ Transport: http.DefaultTransport, OnRequestSent: func(total int64) { fmt.Printf("HTTP请求总大小:%d 字节\n", total) }, }, } // 发送HTTPS请求也能正常统计 resp, err := client.Get("https://example.com") if err != nil { panic(err) } defer resp.Body.Close() }
方法二:统计实际网络传输的字节数(含TLS加密)
如果需要统计实际通过网卡发送的所有字节(包括TLS握手数据、加密后的HTTP内容),可以通过自定义net.Conn来拦截所有写入操作:
import ( "context" "fmt" "net" "net/http" "crypto/tls" ) // CountingConn 包装net.Conn,统计发送的字节数 type CountingConn struct { net.Conn bytesSent int64 onWrite func(int64) } func (cc *CountingConn) Write(b []byte) (n int, err error) { n, err = cc.Conn.Write(b) cc.bytesSent += int64(n) if cc.onWrite != nil { cc.onWrite(cc.bytesSent) } return n, err } // CountingDialer 自定义Dialer,返回带统计功能的Conn type CountingDialer struct { Dialer net.Dialer OnSent func(int64) } func (cd *CountingDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { conn, err := cd.Dialer.DialContext(ctx, network, addr) if err != nil { return nil, err } return &CountingConn{ Conn: conn, onWrite: cd.OnSent, }, nil } // NewCountingTransport 创建带字节统计的HTTP Transport func NewCountingTransport(onSent func(int64)) *http.Transport { dialer := &CountingDialer{ Dialer: net.Dialer{}, OnSent: onSent, } return &http.Transport{ DialContext: dialer.DialContext, TLSClientConfig: &tls.Config{InsecureSkipVerify: false}, // 根据需求配置 } }
使用示例
func main() { transport := NewCountingTransport(func(total int64) { fmt.Printf("实际网络发送字节数(含TLS):%d 字节\n", total) }) client := &http.Client{Transport: transport} resp, err := client.Post("https://example.com", "application/json", bytes.NewBufferString(`{"key": "value"}`)) if err != nil { panic(err) } defer resp.Body.Close() }
关键说明
- 方法一统计的是HTTP协议层面的请求大小,不包含TLS加密开销,适合管控业务请求的大小阈值。
- 方法二统计的是底层网络传输的所有字节,包括TLS握手、加密后的内容,适合精确统计网络流量。
- 两种方法都完全支持HTTPS请求,因为都是基于Go标准库的传输层扩展,不影响TLS握手和加密流程。
内容的提问来源于stack exchange,提问作者Spiros
相关产品推荐
相关产品推荐

