Skip to content

Commit

Permalink
align upstream quic-go to v0.47.0
Browse files Browse the repository at this point in the history
  • Loading branch information
imroc committed Sep 11, 2024
1 parent fcc942e commit c0fb6d6
Show file tree
Hide file tree
Showing 15 changed files with 1,614 additions and 725 deletions.
1 change: 0 additions & 1 deletion internal/dump/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ func NewDumper(opt Options) *Dumper {

func (d *Dumper) SetOptions(opt Options) {
d.Options = opt
return
}

func (d *Dumper) Clone() *Dumper {
Expand Down
135 changes: 67 additions & 68 deletions internal/http3/body.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,78 +2,95 @@ package http3

import (
"context"
"errors"
"io"
"net"

"github.com/quic-go/quic-go"
)

// The HTTPStreamer allows taking over a HTTP/3 stream. The interface is implemented by:
// * for the server: the http.Request.Body
// * for the client: the http.Response.Body
// On the client side, the stream will be closed for writing, unless the DontCloseRequestStream RoundTripOpt was set.
// When a stream is taken over, it's the caller's responsibility to close the stream.
type HTTPStreamer interface {
HTTPStream() Stream
}

type StreamCreator interface {
// Context returns a context that is cancelled when the underlying connection is closed.
Context() context.Context
OpenStream() (quic.Stream, error)
OpenStreamSync(context.Context) (quic.Stream, error)
OpenUniStream() (quic.SendStream, error)
OpenUniStreamSync(context.Context) (quic.SendStream, error)
LocalAddr() net.Addr
RemoteAddr() net.Addr
ConnectionState() quic.ConnectionState
}

var _ StreamCreator = quic.Connection(nil)

// A Hijacker allows hijacking of the stream creating part of a quic.Session from a http.Response.Body.
// It is used by WebTransport to create WebTransport streams after a session has been established.
type Hijacker interface {
StreamCreator() StreamCreator
Connection() Connection
}

// The body of a http.Request or http.Response.
var errTooMuchData = errors.New("peer sent too much data")

// The body is used in the requestBody (for a http.Request) and the responseBody (for a http.Response).
type body struct {
str quic.Stream
str *stream

wasHijacked bool // set when HTTPStream is called
remainingContentLength int64
violatedContentLength bool
hasContentLength bool
}

var (
_ io.ReadCloser = &body{}
_ HTTPStreamer = &body{}
)

func newRequestBody(str Stream) *body {
return &body{str: str}
func newBody(str *stream, contentLength int64) *body {
b := &body{str: str}
if contentLength >= 0 {
b.hasContentLength = true
b.remainingContentLength = contentLength
}
return b
}

func (r *body) HTTPStream() Stream {
r.wasHijacked = true
return r.str
}
func (r *body) StreamID() quic.StreamID { return r.str.StreamID() }

func (r *body) wasStreamHijacked() bool {
return r.wasHijacked
func (r *body) checkContentLengthViolation() error {
if !r.hasContentLength {
return nil
}
if r.remainingContentLength < 0 || r.remainingContentLength == 0 && r.str.hasMoreData() {
if !r.violatedContentLength {
r.str.CancelRead(quic.StreamErrorCode(ErrCodeMessageError))
r.str.CancelWrite(quic.StreamErrorCode(ErrCodeMessageError))
r.violatedContentLength = true
}
return errTooMuchData
}
return nil
}

func (r *body) Read(b []byte) (int, error) {
return r.str.Read(b)
if err := r.checkContentLengthViolation(); err != nil {
return 0, err
}
if r.hasContentLength {
b = b[:min(int64(len(b)), r.remainingContentLength)]
}
n, err := r.str.Read(b)
r.remainingContentLength -= int64(n)
if err := r.checkContentLengthViolation(); err != nil {
return n, err
}
return n, maybeReplaceError(err)
}

func (r *body) Close() error {
r.str.CancelRead(quic.StreamErrorCode(ErrCodeRequestCanceled))
return nil
}

type hijackableBody struct {
type requestBody struct {
body
conn quic.Connection // only needed to implement Hijacker
connCtx context.Context
rcvdSettings <-chan struct{}
getSettings func() *Settings
}

var _ io.ReadCloser = &requestBody{}

func newRequestBody(str *stream, contentLength int64, connCtx context.Context, rcvdSettings <-chan struct{}, getSettings func() *Settings) *requestBody {
return &requestBody{
body: *newBody(str, contentLength),
connCtx: connCtx,
rcvdSettings: rcvdSettings,
getSettings: getSettings,
}
}

type hijackableBody struct {
body body

// only set for the http.Response
// The channel is closed when the user is done with this response:
Expand All @@ -82,31 +99,21 @@ type hijackableBody struct {
reqDoneClosed bool
}

var (
_ Hijacker = &hijackableBody{}
_ HTTPStreamer = &hijackableBody{}
)
var _ io.ReadCloser = &hijackableBody{}

func newResponseBody(str Stream, conn quic.Connection, done chan<- struct{}) *hijackableBody {
func newResponseBody(str *stream, contentLength int64, done chan<- struct{}) *hijackableBody {
return &hijackableBody{
body: body{
str: str,
},
body: *newBody(str, contentLength),
reqDone: done,
conn: conn,
}
}

func (r *hijackableBody) StreamCreator() StreamCreator {
return r.conn
}

func (r *hijackableBody) Read(b []byte) (int, error) {
n, err := r.str.Read(b)
n, err := r.body.Read(b)
if err != nil {
r.requestDone()
}
return n, err
return n, maybeReplaceError(err)
}

func (r *hijackableBody) requestDone() {
Expand All @@ -119,17 +126,9 @@ func (r *hijackableBody) requestDone() {
r.reqDoneClosed = true
}

func (r *body) StreamID() quic.StreamID {
return r.str.StreamID()
}

func (r *hijackableBody) Close() error {
r.requestDone()
// If the EOF was read, CancelRead() is a no-op.
r.str.CancelRead(quic.StreamErrorCode(ErrCodeRequestCanceled))
r.body.str.CancelRead(quic.StreamErrorCode(ErrCodeRequestCanceled))
return nil
}

func (r *hijackableBody) HTTPStream() Stream {
return r.str
}
Loading

0 comments on commit c0fb6d6

Please sign in to comment.