2
0
mirror of https://github.com/soheilhy/cmux.git synced 2024-11-14 11:31:28 +08:00

Return not-match on different field values in HTTP2

Retun as soon as we have the matched field in the HTTP2 matcher
regardless of weather the value is matched or not. Fixes #35.

Issue #35 reports that cmux leaks memory when the client is HTTP2
but does not sends the expected header field. For example, when
the non-gRPC client sends a large field in the header and we are
matching for gRPC, we waste a lot of memory in the sniff buffer.
This commit is contained in:
Soheil Hassas Yeganeh 2016-09-25 00:01:52 -04:00
parent 13f520d62c
commit 861c99e0fc
2 changed files with 83 additions and 8 deletions

View File

@ -15,6 +15,7 @@
package cmux package cmux
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -32,6 +33,7 @@ import (
"time" "time"
"golang.org/x/net/http2" "golang.org/x/net/http2"
"golang.org/x/net/http2/hpack"
) )
const ( const (
@ -394,6 +396,72 @@ func TestHTTP2(t *testing.T) {
} }
} }
func TestHTTP2MatchHeaderField(t *testing.T) {
defer leakCheck(t)()
errCh := make(chan error)
defer func() {
select {
case err := <-errCh:
t.Fatal(err)
default:
}
}()
name := "name"
value := "value"
writer, reader := net.Pipe()
go func() {
if _, err := io.WriteString(writer, http2.ClientPreface); err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
enc := hpack.NewEncoder(&buf)
if err := enc.WriteField(hpack.HeaderField{Name: name, Value: value}); err != nil {
t.Fatal(err)
}
framer := http2.NewFramer(writer, nil)
err := framer.WriteHeaders(http2.HeadersFrameParam{
StreamID: 1,
BlockFragment: buf.Bytes(),
EndStream: true,
EndHeaders: true,
})
if err != nil {
t.Fatal(err)
}
if err := writer.Close(); err != nil {
t.Fatal(err)
}
}()
l := newChanListener()
l.connCh <- reader
muxl := New(l)
// Register a bogus matcher that only reads one byte.
muxl.Match(func(r io.Reader) bool {
var b [1]byte
_, _ = r.Read(b[:])
return false
})
// Create a matcher that cannot match the response.
muxl.Match(HTTP2HeaderField(name, "another"+value))
// Then match with the expected field.
h2l := muxl.Match(HTTP2HeaderField(name, value))
go safeServe(errCh, muxl)
muxedConn, err := h2l.Accept()
close(l.connCh)
if err != nil {
t.Fatal(err)
}
var b [len(http2.ClientPreface)]byte
// We have the sniffed buffer first...
if _, err := muxedConn.Read(b[:]); err == io.EOF {
t.Fatal(err)
}
if string(b[:]) != http2.ClientPreface {
t.Errorf("got unexpected read %s, expected %s", b, http2.ClientPreface)
}
}
func TestHTTPGoRPC(t *testing.T) { func TestHTTPGoRPC(t *testing.T) {
defer leakCheck(t)() defer leakCheck(t)()
errCh := make(chan error) errCh := make(chan error)

View File

@ -144,11 +144,15 @@ func matchHTTP2Field(w io.Writer, r io.Reader, name, value string) (matched bool
return false return false
} }
done := false
framer := http2.NewFramer(w, r) framer := http2.NewFramer(w, r)
hdec := hpack.NewDecoder(uint32(4<<10), func(hf hpack.HeaderField) { hdec := hpack.NewDecoder(uint32(4<<10), func(hf hpack.HeaderField) {
if hf.Name == name && hf.Value == value { if hf.Name == name {
done = true
if hf.Value == value {
matched = true matched = true
} }
}
}) })
for { for {
f, err := framer.ReadFrame() f, err := framer.ReadFrame()
@ -161,17 +165,20 @@ func matchHTTP2Field(w io.Writer, r io.Reader, name, value string) (matched bool
if err := framer.WriteSettings(); err != nil { if err := framer.WriteSettings(); err != nil {
return false return false
} }
case *http2.ContinuationFrame:
if _, err := hdec.Write(f.HeaderBlockFragment()); err != nil {
return false
}
done = done || f.FrameHeader.Flags&http2.FlagHeadersEndHeaders != 0
case *http2.HeadersFrame: case *http2.HeadersFrame:
if _, err := hdec.Write(f.HeaderBlockFragment()); err != nil { if _, err := hdec.Write(f.HeaderBlockFragment()); err != nil {
return false return false
} }
if matched { done = done || f.FrameHeader.Flags&http2.FlagHeadersEndHeaders != 0
return true
} }
if f.FrameHeader.Flags&http2.FlagHeadersEndHeaders != 0 { if done {
return false return matched
}
} }
} }
} }