-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
internal/http3: add request/response body transfer
For golang/go#70914 Change-Id: I372458214fe73f8156e0ec291168b043c10221e6 Reviewed-on: https://go-review.googlesource.com/c/net/+/644915 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Damien Neil <dneil@google.com> Reviewed-by: Jonathan Amsterdam <jba@google.com>
- Loading branch information
Showing
5 changed files
with
707 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// Copyright 2025 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
//go:build go1.24 | ||
|
||
package http3 | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io" | ||
"sync" | ||
) | ||
|
||
// A bodyWriter writes a request or response body to a stream | ||
// as a series of DATA frames. | ||
type bodyWriter struct { | ||
st *stream | ||
remain int64 // -1 when content-length is not known | ||
flush bool // flush the stream after every write | ||
name string // "request" or "response" | ||
} | ||
|
||
func (w *bodyWriter) Write(p []byte) (n int, err error) { | ||
if w.remain >= 0 && int64(len(p)) > w.remain { | ||
return 0, &streamError{ | ||
code: errH3InternalError, | ||
message: w.name + " body longer than specified content length", | ||
} | ||
} | ||
w.st.writeVarint(int64(frameTypeData)) | ||
w.st.writeVarint(int64(len(p))) | ||
n, err = w.st.Write(p) | ||
if w.remain >= 0 { | ||
w.remain -= int64(n) | ||
} | ||
if w.flush && err == nil { | ||
err = w.st.Flush() | ||
} | ||
if err != nil { | ||
err = fmt.Errorf("writing %v body: %w", w.name, err) | ||
} | ||
return n, err | ||
} | ||
|
||
func (w *bodyWriter) Close() error { | ||
if w.remain > 0 { | ||
return errors.New(w.name + " body shorter than specified content length") | ||
} | ||
return nil | ||
} | ||
|
||
// A bodyReader reads a request or response body from a stream. | ||
type bodyReader struct { | ||
st *stream | ||
|
||
mu sync.Mutex | ||
remain int64 | ||
err error | ||
} | ||
|
||
func (r *bodyReader) Read(p []byte) (n int, err error) { | ||
// The HTTP/1 and HTTP/2 implementations both permit concurrent reads from a body, | ||
// in the sense that the race detector won't complain. | ||
// Use a mutex here to provide the same behavior. | ||
r.mu.Lock() | ||
defer r.mu.Unlock() | ||
if r.err != nil { | ||
return 0, r.err | ||
} | ||
defer func() { | ||
if err != nil { | ||
r.err = err | ||
} | ||
}() | ||
if r.st.lim == 0 { | ||
// We've finished reading the previous DATA frame, so end it. | ||
if err := r.st.endFrame(); err != nil { | ||
return 0, err | ||
} | ||
} | ||
// Read the next DATA frame header, | ||
// if we aren't already in the middle of one. | ||
for r.st.lim < 0 { | ||
ftype, err := r.st.readFrameHeader() | ||
if err == io.EOF && r.remain > 0 { | ||
return 0, &streamError{ | ||
code: errH3MessageError, | ||
message: "body shorter than content-length", | ||
} | ||
} | ||
if err != nil { | ||
return 0, err | ||
} | ||
switch ftype { | ||
case frameTypeData: | ||
if r.remain >= 0 && r.st.lim > r.remain { | ||
return 0, &streamError{ | ||
code: errH3MessageError, | ||
message: "body longer than content-length", | ||
} | ||
} | ||
// Fall out of the loop and process the frame body below. | ||
case frameTypeHeaders: | ||
// This HEADERS frame contains the message trailers. | ||
if r.remain > 0 { | ||
return 0, &streamError{ | ||
code: errH3MessageError, | ||
message: "body shorter than content-length", | ||
} | ||
} | ||
// TODO: Fill in Request.Trailer. | ||
if err := r.st.discardFrame(); err != nil { | ||
return 0, err | ||
} | ||
return 0, io.EOF | ||
default: | ||
if err := r.st.discardUnknownFrame(ftype); err != nil { | ||
return 0, err | ||
} | ||
} | ||
} | ||
// We are now reading the content of a DATA frame. | ||
// Fill the read buffer or read to the end of the frame, | ||
// whichever comes first. | ||
if int64(len(p)) > r.st.lim { | ||
p = p[:r.st.lim] | ||
} | ||
n, err = r.st.Read(p) | ||
if r.remain > 0 { | ||
r.remain -= int64(n) | ||
} | ||
return n, err | ||
} | ||
|
||
func (r *bodyReader) Close() error { | ||
// Unlike the HTTP/1 and HTTP/2 body readers (at the time of this comment being written), | ||
// calling Close concurrently with Read will interrupt the read. | ||
r.st.stream.CloseRead() | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.