TUN-2796: Implement HTTP2 CONTINUATION headers correctly

This commit is contained in:
Areg Harutyunyan 2020-03-10 01:35:11 +00:00
parent a368fbbe9b
commit 80f387214c
2 changed files with 67 additions and 17 deletions

View File

@ -250,28 +250,52 @@ func (w *MuxWriter) encodeHeaders(headers []Header) ([]byte, error) {
// writeHeaders writes a block of encoded headers, splitting it into multiple frames if necessary. // writeHeaders writes a block of encoded headers, splitting it into multiple frames if necessary.
func (w *MuxWriter) writeHeaders(streamID uint32, headers []Header) error { func (w *MuxWriter) writeHeaders(streamID uint32, headers []Header) error {
encodedHeaders, err := w.encodeHeaders(headers) encodedHeaders, err := w.encodeHeaders(headers)
if err != nil { if err != nil || len(encodedHeaders) == 0 {
return err return err
} }
blockSize := int(w.maxFrameSize) blockSize := int(w.maxFrameSize)
endHeaders := len(encodedHeaders) == 0 // CONTINUATION is unnecessary; the headers fit within the blockSize
for !endHeaders && err == nil { if len(encodedHeaders) < blockSize {
blockFragment := encodedHeaders return w.f.WriteHeaders(http2.HeadersFrameParam{
if len(encodedHeaders) > blockSize { StreamID: streamID,
blockFragment = blockFragment[:blockSize] EndHeaders: true,
encodedHeaders = encodedHeaders[blockSize:] BlockFragment: encodedHeaders,
// Send CONTINUATION frame if the headers can't be fit into 1 frame })
err = w.f.WriteContinuation(streamID, endHeaders, blockFragment) }
} else {
endHeaders = true choppedHeaders := chopEncodedHeaders(encodedHeaders, blockSize)
err = w.f.WriteHeaders(http2.HeadersFrameParam{ // len(choppedHeaders) is at least 2
StreamID: streamID, if err := w.f.WriteHeaders(http2.HeadersFrameParam{StreamID: streamID, EndHeaders: false, BlockFragment: choppedHeaders[0]}); err != nil {
EndHeaders: endHeaders, return err
BlockFragment: blockFragment, }
}) for i := 1; i < len(choppedHeaders)-1; i++ {
if err := w.f.WriteContinuation(streamID, false, choppedHeaders[i]); err != nil {
return err
} }
} }
return err if err := w.f.WriteContinuation(streamID, true, choppedHeaders[len(choppedHeaders)-1]); err != nil {
return err
}
return nil
}
// Partition a slice of bytes into `len(slice) / blockSize` slices of length `blockSize`
func chopEncodedHeaders(headers []byte, chunkSize int) [][]byte {
var divided [][]byte
for i := 0; i < len(headers); i += chunkSize {
end := i + chunkSize
if end > len(headers) {
end = len(headers)
}
divided = append(divided, headers[i:end])
}
return divided
} }
func (w *MuxWriter) writeUseDictionary(dictRequest useDictRequest) error { func (w *MuxWriter) writeUseDictionary(dictRequest useDictRequest) error {

26
h2mux/muxwriter_test.go Normal file
View File

@ -0,0 +1,26 @@
package h2mux
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestChopEncodedHeaders(t *testing.T) {
mockEncodedHeaders := make([]byte, 5)
for i := range mockEncodedHeaders {
mockEncodedHeaders[i] = byte(i)
}
chopped := chopEncodedHeaders(mockEncodedHeaders, 4)
assert.Equal(t, 2, len(chopped))
assert.Equal(t, []byte{0, 1, 2, 3}, chopped[0])
assert.Equal(t, []byte{4}, chopped[1])
}
func TestChopEncodedEmptyHeaders(t *testing.T) {
mockEncodedHeaders := make([]byte, 0)
chopped := chopEncodedHeaders(mockEncodedHeaders, 3)
assert.Equal(t, 0, len(chopped))
}