| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- package l7
- import (
- "encoding/binary"
- "net/http"
- "strconv"
- "strings"
- "time"
- "golang.org/x/net/http2"
- "golang.org/x/net/http2/hpack"
- )
- const (
- http2FrameHeaderLength = 9
- http2DecoderGcInterval = uint64(10 * time.Minute)
- )
- type Http2FrameHeader struct {
- Type http2.FrameType
- Flags http2.Flags
- Length int
- StreamId uint32
- }
- type Http2Request struct {
- Method string
- Path string
- Scheme string
- Status Status
- Duration time.Duration
- kernelTime uint64
- }
- type Http2Parser struct {
- clientDecoder *hpack.Decoder
- serverDecoder *hpack.Decoder
- activeRequests map[uint32]*Http2Request
- lastGcTime uint64
- }
- func NewHttp2Parser() *Http2Parser {
- return &Http2Parser{
- clientDecoder: hpack.NewDecoder(4096, nil),
- serverDecoder: hpack.NewDecoder(4096, nil),
- activeRequests: map[uint32]*Http2Request{},
- }
- }
- func (p *Http2Parser) Parse(method Method, payload []byte, kernelTime uint64) []Http2Request {
- if method == MethodHttp2ClientFrames {
- l := len(http2.ClientPreface)
- if len(payload) >= l && string(payload[:l]) == http2.ClientPreface {
- payload = payload[l:]
- }
- }
- if len(payload) == 0 {
- return nil
- }
- var decoder *hpack.Decoder
- statuses := map[uint32]Status{}
- offset := 0
- for {
- if len(payload)-offset < http2FrameHeaderLength {
- break
- }
- h := Http2FrameHeader{
- Length: int(binary.BigEndian.Uint32(payload[offset:]) >> 8),
- Type: http2.FrameType(payload[offset+3]),
- Flags: http2.Flags(payload[offset+4]),
- StreamId: binary.BigEndian.Uint32(payload[offset+5:]) & (1<<31 - 1),
- }
- offset += http2FrameHeaderLength
- if h.Type != http2.FrameHeaders {
- if len(payload)-offset < h.Length {
- break
- }
- offset += h.Length
- continue
- }
- switch method {
- case MethodHttp2ClientFrames:
- req := p.activeRequests[h.StreamId]
- if req == nil {
- req = &Http2Request{kernelTime: kernelTime}
- p.activeRequests[h.StreamId] = req
- }
- decoder = p.clientDecoder
- decoder.SetEmitFunc(func(hf hpack.HeaderField) {
- switch hf.Name {
- case ":method":
- if req.Method == "" && isHttpMethod(hf.Value) {
- req.Method = hf.Value
- }
- case ":path":
- if req.Path == "" && isHttpPath(hf.Value) {
- req.Path = hf.Value
- }
- case ":scheme":
- if req.Scheme == "" && isHttpScheme(hf.Value) {
- req.Scheme = hf.Value
- }
- }
- })
- case MethodHttp2ServerFrames:
- if _, ok := statuses[h.StreamId]; !ok {
- statuses[h.StreamId] = 0
- }
- decoder = p.serverDecoder
- decoder.SetEmitFunc(func(hf hpack.HeaderField) {
- if hf.Name == ":status" {
- s, _ := strconv.Atoi(hf.Value)
- statuses[h.StreamId] = Status(s)
- }
- })
- }
- next := offset + h.Length
- if next > len(payload) {
- next = len(payload)
- }
- if _, err := decoder.Write(payload[offset:next]); err != nil {
- continue
- }
- offset = next
- }
- var res []Http2Request
- for streamId, status := range statuses {
- r := p.activeRequests[streamId]
- if r == nil {
- continue
- }
- r.Status = status
- r.Duration = time.Duration(kernelTime - r.kernelTime)
- res = append(res, *r)
- delete(p.activeRequests, streamId)
- }
- // GC
- if kernelTime-p.lastGcTime > http2DecoderGcInterval {
- if p.lastGcTime > 0 {
- for streamId, r := range p.activeRequests {
- if kernelTime-r.kernelTime > http2DecoderGcInterval {
- delete(p.activeRequests, streamId)
- }
- }
- }
- p.lastGcTime = kernelTime
- }
- return res
- }
- func isHttpMethod(s string) bool {
- switch s {
- case http.MethodGet,
- http.MethodHead,
- http.MethodPost,
- http.MethodPut,
- http.MethodPatch,
- http.MethodDelete,
- http.MethodConnect,
- http.MethodOptions,
- http.MethodTrace:
- return true
- }
- return false
- }
- func isHttpPath(s string) bool {
- return strings.HasPrefix(s, "/") || s == "*"
- }
- func isHttpScheme(s string) bool {
- return s == "http" || s == "https"
- }
|