Skip to content

Commit 363018c

Browse files
authored
transport: Avoid buffer copies when reading Data frames (#8657)
This change incorporates changes from golang/go#73560 to split reading HTTP/2 frame headers and payloads. If the frame is not a Data frame, it's read through the standard library framer as before. For Data frames, the payload is read directly into a buffer from the buffer pool to avoid copying it from the framer's buffer. ## Testing For 1 MB payloads, this results in ~4% improvement in throughput. ```sh # test command go run benchmark/benchmain/main.go -benchtime=60s -workloads=streaming \ -compression=off -maxConcurrentCalls=120 -trace=off \ -reqSizeBytes=1000000 -respSizeBytes=1000000 -networkMode=Local -resultFile="${RUN_NAME}" # comparison go run benchmark/benchresult/main.go streaming-before streaming-after Title Before After Percentage TotalOps 87536 91120 4.09% SendOps 0 0 NaN% RecvOps 0 0 NaN% Bytes/op 4074102.92 4070489.30 -0.09% Allocs/op 83.60 76.55 -8.37% ReqT/op 11671466666.67 12149333333.33 4.09% RespT/op 11671466666.67 12149333333.33 4.09% 50th-Lat 78.209875ms 75.159943ms -3.90% 90th-Lat 117.764228ms 107.8697ms -8.40% 99th-Lat 146.935704ms 139.069685ms -5.35% Avg-Lat 82.310691ms 79.073282ms -3.93% GoVersion go1.24.7 go1.24.7 GrpcVersion 1.77.0-dev 1.77.0-dev ``` For smaller payloads, the difference in minor. ```sh go run benchmark/benchmain/main.go -benchtime=60s -workloads=streaming \ -compression=off -maxConcurrentCalls=120 -trace=off \ -reqSizeBytes=100 -respSizeBytes=100 -networkMode=Local -resultFile="${RUN_NAME}" go run benchmark/benchresult/main.go streaming-before streaming-after Title Before After Percentage TotalOps 21490752 21477822 -0.06% SendOps 0 0 NaN% RecvOps 0 0 NaN% Bytes/op 1902.92 1902.94 0.00% Allocs/op 29.21 29.21 0.00% ReqT/op 286543360.00 286370960.00 -0.06% RespT/op 286543360.00 286370960.00 -0.06% 50th-Lat 352.505µs 352.247µs -0.07% 90th-Lat 433.446µs 434.907µs 0.34% 99th-Lat 536.445µs 539.759µs 0.62% Avg-Lat 333.403µs 333.457µs 0.02% GoVersion go1.24.7 go1.24.7 GrpcVersion 1.77.0-dev 1.77.0-dev ``` RELEASE NOTES: * transport: Avoid a buffer copy when reading data.
1 parent 64cba2d commit 363018c

File tree

22 files changed

+300
-62
lines changed

22 files changed

+300
-62
lines changed

examples/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ require (
7474
go.opentelemetry.io/otel/trace v1.38.0 // indirect
7575
go.yaml.in/yaml/v2 v2.4.3 // indirect
7676
golang.org/x/crypto v0.43.0 // indirect
77-
golang.org/x/net v0.46.0 // indirect
77+
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect
7878
golang.org/x/sync v0.17.0 // indirect
7979
golang.org/x/sys v0.37.0 // indirect
8080
golang.org/x/text v0.30.0 // indirect

examples/go.sum

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4126,8 +4126,9 @@ golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
41264126
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
41274127
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
41284128
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
4129-
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
41304129
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
4130+
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo=
4131+
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
41314132
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
41324133
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
41334134
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=

gcp/observability/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ require (
5151
go.opentelemetry.io/otel/metric v1.38.0 // indirect
5252
go.opentelemetry.io/otel/trace v1.38.0 // indirect
5353
golang.org/x/crypto v0.43.0 // indirect
54-
golang.org/x/net v0.46.0 // indirect
54+
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect
5555
golang.org/x/sync v0.17.0 // indirect
5656
golang.org/x/sys v0.37.0 // indirect
5757
golang.org/x/text v0.30.0 // indirect

gcp/observability/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3814,8 +3814,8 @@ golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
38143814
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
38153815
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
38163816
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
3817-
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
3818-
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
3817+
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo=
3818+
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
38193819
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
38203820
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
38213821
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ require (
1818
go.opentelemetry.io/otel/sdk v1.38.0
1919
go.opentelemetry.io/otel/sdk/metric v1.38.0
2020
go.opentelemetry.io/otel/trace v1.38.0
21-
golang.org/x/net v0.46.0
21+
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82
2222
golang.org/x/oauth2 v0.32.0
2323
golang.org/x/sync v0.17.0
2424
golang.org/x/sys v0.37.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr
5757
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
5858
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
5959
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
60-
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
61-
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
60+
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo=
61+
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
6262
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
6363
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
6464
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=

internal/transport/http2_client.go

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ func NewHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts
336336
writerDone: make(chan struct{}),
337337
goAway: make(chan struct{}),
338338
keepaliveDone: make(chan struct{}),
339-
framer: newFramer(conn, writeBufSize, readBufSize, opts.SharedWriteBuffer, maxHeaderListSize),
339+
framer: newFramer(conn, writeBufSize, readBufSize, opts.SharedWriteBuffer, maxHeaderListSize, opts.BufferPool),
340340
fc: &trInFlow{limit: uint32(icwz)},
341341
scheme: scheme,
342342
activeStreams: make(map[uint32]*ClientStream),
@@ -1170,7 +1170,7 @@ func (t *http2Client) updateFlowControl(n uint32) {
11701170
})
11711171
}
11721172

1173-
func (t *http2Client) handleData(f *http2.DataFrame) {
1173+
func (t *http2Client) handleData(f *parsedDataFrame) {
11741174
size := f.Header().Length
11751175
var sendBDPPing bool
11761176
if t.bdpEst != nil {
@@ -1214,22 +1214,15 @@ func (t *http2Client) handleData(f *http2.DataFrame) {
12141214
t.closeStream(s, io.EOF, true, http2.ErrCodeFlowControl, status.New(codes.Internal, err.Error()), nil, false)
12151215
return
12161216
}
1217+
dataLen := f.data.Len()
12171218
if f.Header().Flags.Has(http2.FlagDataPadded) {
1218-
if w := s.fc.onRead(size - uint32(len(f.Data()))); w > 0 {
1219+
if w := s.fc.onRead(size - uint32(dataLen)); w > 0 {
12191220
t.controlBuf.put(&outgoingWindowUpdate{s.id, w})
12201221
}
12211222
}
1222-
// TODO(bradfitz, zhaoq): A copy is required here because there is no
1223-
// guarantee f.Data() is consumed before the arrival of next frame.
1224-
// Can this copy be eliminated?
1225-
if len(f.Data()) > 0 {
1226-
pool := t.bufferPool
1227-
if pool == nil {
1228-
// Note that this is only supposed to be nil in tests. Otherwise, stream is
1229-
// always initialized with a BufferPool.
1230-
pool = mem.DefaultBufferPool()
1231-
}
1232-
s.write(recvMsg{buffer: mem.Copy(f.Data(), pool)})
1223+
if dataLen > 0 {
1224+
f.data.Ref()
1225+
s.write(recvMsg{buffer: f.data})
12331226
}
12341227
}
12351228
// The server has closed the stream without sending trailers. Record that
@@ -1659,7 +1652,7 @@ func (t *http2Client) reader(errCh chan<- error) {
16591652
// loop to keep reading incoming messages on this transport.
16601653
for {
16611654
t.controlBuf.throttle()
1662-
frame, err := t.framer.fr.ReadFrame()
1655+
frame, err := t.framer.readFrame()
16631656
if t.keepaliveEnabled {
16641657
atomic.StoreInt64(&t.lastRead, time.Now().UnixNano())
16651658
}
@@ -1674,7 +1667,7 @@ func (t *http2Client) reader(errCh chan<- error) {
16741667
if s != nil {
16751668
// use error detail to provide better err message
16761669
code := http2ErrConvTab[se.Code]
1677-
errorDetail := t.framer.fr.ErrorDetail()
1670+
errorDetail := t.framer.errorDetail()
16781671
var msg string
16791672
if errorDetail != nil {
16801673
msg = errorDetail.Error()
@@ -1692,8 +1685,9 @@ func (t *http2Client) reader(errCh chan<- error) {
16921685
switch frame := frame.(type) {
16931686
case *http2.MetaHeadersFrame:
16941687
t.operateHeaders(frame)
1695-
case *http2.DataFrame:
1688+
case *parsedDataFrame:
16961689
t.handleData(frame)
1690+
frame.data.Free()
16971691
case *http2.RSTStreamFrame:
16981692
t.handleRSTStream(frame)
16991693
case *http2.SettingsFrame:

internal/transport/http2_server.go

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ func NewServerTransport(conn net.Conn, config *ServerConfig) (_ ServerTransport,
169169
if config.MaxHeaderListSize != nil {
170170
maxHeaderListSize = *config.MaxHeaderListSize
171171
}
172-
framer := newFramer(conn, writeBufSize, readBufSize, config.SharedWriteBuffer, maxHeaderListSize)
172+
framer := newFramer(conn, writeBufSize, readBufSize, config.SharedWriteBuffer, maxHeaderListSize, config.BufferPool)
173173
// Send initial settings as connection preface to client.
174174
isettings := []http2.Setting{{
175175
ID: http2.SettingMaxFrameSize,
@@ -670,7 +670,7 @@ func (t *http2Server) HandleStreams(ctx context.Context, handle func(*ServerStre
670670
}()
671671
for {
672672
t.controlBuf.throttle()
673-
frame, err := t.framer.fr.ReadFrame()
673+
frame, err := t.framer.readFrame()
674674
atomic.StoreInt64(&t.lastRead, time.Now().UnixNano())
675675
if err != nil {
676676
if se, ok := err.(http2.StreamError); ok {
@@ -707,8 +707,9 @@ func (t *http2Server) HandleStreams(ctx context.Context, handle func(*ServerStre
707707
})
708708
continue
709709
}
710-
case *http2.DataFrame:
710+
case *parsedDataFrame:
711711
t.handleData(frame)
712+
frame.data.Free()
712713
case *http2.RSTStreamFrame:
713714
t.handleRSTStream(frame)
714715
case *http2.SettingsFrame:
@@ -788,7 +789,7 @@ func (t *http2Server) updateFlowControl(n uint32) {
788789

789790
}
790791

791-
func (t *http2Server) handleData(f *http2.DataFrame) {
792+
func (t *http2Server) handleData(f *parsedDataFrame) {
792793
size := f.Header().Length
793794
var sendBDPPing bool
794795
if t.bdpEst != nil {
@@ -833,22 +834,15 @@ func (t *http2Server) handleData(f *http2.DataFrame) {
833834
t.closeStream(s, true, http2.ErrCodeFlowControl, false)
834835
return
835836
}
837+
dataLen := f.data.Len()
836838
if f.Header().Flags.Has(http2.FlagDataPadded) {
837-
if w := s.fc.onRead(size - uint32(len(f.Data()))); w > 0 {
839+
if w := s.fc.onRead(size - uint32(dataLen)); w > 0 {
838840
t.controlBuf.put(&outgoingWindowUpdate{s.id, w})
839841
}
840842
}
841-
// TODO(bradfitz, zhaoq): A copy is required here because there is no
842-
// guarantee f.Data() is consumed before the arrival of next frame.
843-
// Can this copy be eliminated?
844-
if len(f.Data()) > 0 {
845-
pool := t.bufferPool
846-
if pool == nil {
847-
// Note that this is only supposed to be nil in tests. Otherwise, stream is
848-
// always initialized with a BufferPool.
849-
pool = mem.DefaultBufferPool()
850-
}
851-
s.write(recvMsg{buffer: mem.Copy(f.Data(), pool)})
843+
if dataLen > 0 {
844+
f.data.Ref()
845+
s.write(recvMsg{buffer: f.data})
852846
}
853847
}
854848
if f.StreamEnded() {

internal/transport/http_util.go

Lines changed: 132 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
"fmt"
2626
"io"
2727
"math"
28-
"net"
2928
"net/http"
3029
"net/url"
3130
"strconv"
@@ -37,6 +36,7 @@ import (
3736
"golang.org/x/net/http2"
3837
"golang.org/x/net/http2/hpack"
3938
"google.golang.org/grpc/codes"
39+
"google.golang.org/grpc/mem"
4040
)
4141

4242
const (
@@ -300,11 +300,11 @@ type bufWriter struct {
300300
buf []byte
301301
offset int
302302
batchSize int
303-
conn net.Conn
303+
conn io.Writer
304304
err error
305305
}
306306

307-
func newBufWriter(conn net.Conn, batchSize int, pool *sync.Pool) *bufWriter {
307+
func newBufWriter(conn io.Writer, batchSize int, pool *sync.Pool) *bufWriter {
308308
w := &bufWriter{
309309
batchSize: batchSize,
310310
conn: conn,
@@ -388,15 +388,34 @@ func toIOError(err error) error {
388388
return ioError{error: err}
389389
}
390390

391+
type parsedDataFrame struct {
392+
http2.FrameHeader
393+
data mem.Buffer
394+
}
395+
396+
func (df *parsedDataFrame) StreamEnded() bool {
397+
return df.FrameHeader.Flags.Has(http2.FlagDataEndStream)
398+
}
399+
391400
type framer struct {
392-
writer *bufWriter
393-
fr *http2.Framer
401+
writer *bufWriter
402+
fr *http2.Framer
403+
reader io.Reader
404+
dataFrame parsedDataFrame // Cached data frame to avoid heap allocations.
405+
pool mem.BufferPool
406+
errDetail error
394407
}
395408

396409
var writeBufferPoolMap = make(map[int]*sync.Pool)
397410
var writeBufferMutex sync.Mutex
398411

399-
func newFramer(conn net.Conn, writeBufferSize, readBufferSize int, sharedWriteBuffer bool, maxHeaderListSize uint32) *framer {
412+
func newFramer(conn io.ReadWriter, writeBufferSize, readBufferSize int, sharedWriteBuffer bool, maxHeaderListSize uint32, memPool mem.BufferPool) *framer {
413+
if memPool == nil {
414+
// Note that this is only supposed to be nil in tests. Otherwise, stream
415+
// is always initialized with a BufferPool.
416+
memPool = mem.DefaultBufferPool()
417+
}
418+
400419
if writeBufferSize < 0 {
401420
writeBufferSize = 0
402421
}
@@ -412,6 +431,8 @@ func newFramer(conn net.Conn, writeBufferSize, readBufferSize int, sharedWriteBu
412431
f := &framer{
413432
writer: w,
414433
fr: http2.NewFramer(w, r),
434+
reader: r,
435+
pool: memPool,
415436
}
416437
f.fr.SetMaxReadFrameSize(http2MaxFrameLen)
417438
// Opt-in to Frame reuse API on framer to reduce garbage.
@@ -422,6 +443,111 @@ func newFramer(conn net.Conn, writeBufferSize, readBufferSize int, sharedWriteBu
422443
return f
423444
}
424445

446+
// readFrame reads a single frame. The returned Frame is only valid
447+
// until the next call to readFrame.
448+
func (f *framer) readFrame() (any, error) {
449+
f.errDetail = nil
450+
fh, err := f.fr.ReadFrameHeader()
451+
if err != nil {
452+
f.errDetail = f.fr.ErrorDetail()
453+
return nil, err
454+
}
455+
// Read the data frame directly from the underlying io.Reader to avoid
456+
// copies.
457+
if fh.Type == http2.FrameData {
458+
err = f.readDataFrame(fh)
459+
return &f.dataFrame, err
460+
}
461+
fr, err := f.fr.ReadFrameForHeader(fh)
462+
if err != nil {
463+
f.errDetail = f.fr.ErrorDetail()
464+
return nil, err
465+
}
466+
return fr, err
467+
}
468+
469+
// errorDetail returns a more detailed error of the last error
470+
// returned by framer.readFrame. For instance, if readFrame
471+
// returns a StreamError with code PROTOCOL_ERROR, errorDetail
472+
// will say exactly what was invalid. errorDetail is not guaranteed
473+
// to return a non-nil value.
474+
// errorDetail is reset after the next call to readFrame.
475+
func (f *framer) errorDetail() error {
476+
return f.errDetail
477+
}
478+
479+
func (f *framer) readDataFrame(fh http2.FrameHeader) (err error) {
480+
if fh.StreamID == 0 {
481+
// DATA frames MUST be associated with a stream. If a
482+
// DATA frame is received whose stream identifier
483+
// field is 0x0, the recipient MUST respond with a
484+
// connection error (Section 5.4.1) of type
485+
// PROTOCOL_ERROR.
486+
f.errDetail = errors.New("DATA frame with stream ID 0")
487+
return http2.ConnectionError(http2.ErrCodeProtocol)
488+
}
489+
// Converting a *[]byte to a mem.SliceBuffer incurs a heap allocation. This
490+
// conversion is performed by mem.NewBuffer. To avoid the extra allocation
491+
// a []byte is allocated directly if required and cast to a mem.SliceBuffer.
492+
var buf []byte
493+
// poolHandle is the pointer returned by the buffer pool (if it's used.).
494+
var poolHandle *[]byte
495+
useBufferPool := !mem.IsBelowBufferPoolingThreshold(int(fh.Length))
496+
if useBufferPool {
497+
poolHandle = f.pool.Get(int(fh.Length))
498+
buf = *poolHandle
499+
defer func() {
500+
if err != nil {
501+
f.pool.Put(poolHandle)
502+
}
503+
}()
504+
} else {
505+
buf = make([]byte, int(fh.Length))
506+
}
507+
if fh.Flags.Has(http2.FlagDataPadded) {
508+
if fh.Length == 0 {
509+
return io.ErrUnexpectedEOF
510+
}
511+
// This initial 1-byte read can be inefficient for unbuffered readers,
512+
// but it allows the rest of the payload to be read directly to the
513+
// start of the destination slice. This makes it easy to return the
514+
// original slice back to the buffer pool.
515+
if _, err := io.ReadFull(f.reader, buf[:1]); err != nil {
516+
return err
517+
}
518+
padSize := buf[0]
519+
buf = buf[:len(buf)-1]
520+
if int(padSize) > len(buf) {
521+
// If the length of the padding is greater than the
522+
// length of the frame payload, the recipient MUST
523+
// treat this as a connection error.
524+
// Filed: https://github.com/http2/http2-spec/issues/610
525+
f.errDetail = errors.New("pad size larger than data payload")
526+
return http2.ConnectionError(http2.ErrCodeProtocol)
527+
}
528+
if _, err := io.ReadFull(f.reader, buf); err != nil {
529+
return err
530+
}
531+
buf = buf[:len(buf)-int(padSize)]
532+
} else if _, err := io.ReadFull(f.reader, buf); err != nil {
533+
return err
534+
}
535+
536+
f.dataFrame.FrameHeader = fh
537+
if useBufferPool {
538+
// Update the handle to point to the (potentially re-sliced) buf.
539+
*poolHandle = buf
540+
f.dataFrame.data = mem.NewBuffer(poolHandle, f.pool)
541+
} else {
542+
f.dataFrame.data = mem.SliceBuffer(buf)
543+
}
544+
return nil
545+
}
546+
547+
func (df *parsedDataFrame) Header() http2.FrameHeader {
548+
return df.FrameHeader
549+
}
550+
425551
func getWriteBufferPool(size int) *sync.Pool {
426552
writeBufferMutex.Lock()
427553
defer writeBufferMutex.Unlock()

0 commit comments

Comments
 (0)