107 lines
2.4 KiB
Go
107 lines
2.4 KiB
Go
package http2
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"net"
|
|
"sync"
|
|
)
|
|
|
|
const (
|
|
frameTypeRSTStream byte = 0x3
|
|
frameTypeGoAway byte = 0x7
|
|
)
|
|
|
|
type frameDirection uint8
|
|
|
|
const (
|
|
frameRead frameDirection = iota
|
|
frameWrite
|
|
)
|
|
|
|
// observedConn keeps the HTTP/2 implementation opaque while exposing the
|
|
// small amount of frame-level telemetry that x/net/http2 does not provide.
|
|
// It parses only the nine-byte frame header and skips payload bytes without
|
|
// buffering them, so large DATA frames cannot increase telemetry memory.
|
|
type observedConn struct {
|
|
net.Conn
|
|
onFrame func(frameDirection, byte)
|
|
readParser frameParser
|
|
writeParser frameParser
|
|
writeMu sync.Mutex
|
|
}
|
|
|
|
func (c *observedConn) Read(buffer []byte) (int, error) {
|
|
n, err := c.Conn.Read(buffer)
|
|
if n > 0 {
|
|
c.readParser.observe(buffer[:n], frameRead, c.onFrame)
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
func (c *observedConn) Write(buffer []byte) (int, error) {
|
|
c.writeMu.Lock()
|
|
defer c.writeMu.Unlock()
|
|
n, err := c.Conn.Write(buffer)
|
|
if n > 0 {
|
|
c.writeParser.observe(buffer[:n], frameWrite, c.onFrame)
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
// observedTLSConn preserves the connection state hook used by x/net/http2 so
|
|
// handlers still receive Request.TLS after frame observation is added.
|
|
type observedTLSConn struct {
|
|
*observedConn
|
|
state tls.ConnectionState
|
|
}
|
|
|
|
func (c *observedTLSConn) ConnectionState() tls.ConnectionState { return c.state }
|
|
|
|
type frameParser struct {
|
|
prefaceRemain int
|
|
header [9]byte
|
|
headerBytes int
|
|
payloadRemain int
|
|
frameType byte
|
|
}
|
|
|
|
func (p *frameParser) observe(buffer []byte, direction frameDirection, onFrame func(frameDirection, byte)) {
|
|
for len(buffer) > 0 {
|
|
if p.prefaceRemain > 0 {
|
|
skip := p.prefaceRemain
|
|
if skip > len(buffer) {
|
|
skip = len(buffer)
|
|
}
|
|
p.prefaceRemain -= skip
|
|
buffer = buffer[skip:]
|
|
continue
|
|
}
|
|
if p.payloadRemain > 0 {
|
|
skip := p.payloadRemain
|
|
if skip > len(buffer) {
|
|
skip = len(buffer)
|
|
}
|
|
p.payloadRemain -= skip
|
|
buffer = buffer[skip:]
|
|
if p.payloadRemain > 0 {
|
|
continue
|
|
}
|
|
onFrame(direction, p.frameType)
|
|
continue
|
|
}
|
|
|
|
copied := copy(p.header[p.headerBytes:], buffer)
|
|
p.headerBytes += copied
|
|
buffer = buffer[copied:]
|
|
if p.headerBytes < len(p.header) {
|
|
continue
|
|
}
|
|
p.payloadRemain = int(p.header[0])<<16 | int(p.header[1])<<8 | int(p.header[2])
|
|
p.frameType = p.header[3]
|
|
p.headerBytes = 0
|
|
if p.payloadRemain == 0 {
|
|
onFrame(direction, p.frameType)
|
|
}
|
|
}
|
|
}
|