Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support graceful shutdown in HTTP/3 server #3402

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
190 changes: 171 additions & 19 deletions http3/frames.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,48 @@ import (
// FrameType is the frame type of a HTTP/3 frame
type FrameType uint64

const (
// dataFrameType is the type of the DATA frame.
//
// See https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-data
dataFrameType = 0x00

// headersFrameType is the type of the HEADERS frame.
//
// See https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-headers
headersFrameType = 0x01

// cancelPushFrameType is the type of the CANCEL_PUSH frame.
//
// See https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-cancel_push
cancelPushFrameType = 0x03

// settingsFrameType is the type of the SETTINGS frame.
//
// See https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-settings
settingsFrameType = 0x04

// pushPromiseFrameType is the type of the PUSH_PROMISE frame.
//
// See https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-push_promise
pushPromiseFrameType = 0x05

// goawayFrameType is the type of the GOAWAY frame.
//
// See https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-goaway
goawayFrameType = 0x07

// maxPushIDFrameType is the type of the MAX_PUSH_ID frame.
//
// See https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-max_push_id
maxPushIDFrameType = 0x0d
)

type unknownFrameHandlerFunc func(FrameType) (processed bool, err error)

type frame interface{}
type frame interface {
frame()
}

var errHijacked = errors.New("hijacked")

Expand Down Expand Up @@ -45,19 +84,24 @@ func parseNextFrame(r io.Reader, unknownFrameHandler unknownFrameHandlerFunc) (f
}

switch t {
case 0x0:
case dataFrameType:
return &dataFrame{Length: l}, nil
case 0x1:
case headersFrameType:
return &headersFrame{Length: l}, nil
case 0x4:
case cancelPushFrameType:
return parseCancelPushFrame(r, l)
case settingsFrameType:
return parseSettingsFrame(r, l)
case 0x3: // CANCEL_PUSH
case 0x5: // PUSH_PROMISE
case 0x7: // GOAWAY
case 0xd: // MAX_PUSH_ID
case pushPromiseFrameType:
return parsePushPromiseFrame(r, l)
case goawayFrameType:
return parseGoawayFrame(r, l)
case maxPushIDFrameType:
return parseMaxPushIDFrame(r, l)
}
// skip over unknown frames
if _, err := io.CopyN(ioutil.Discard, qr, int64(l)); err != nil {

// Skip over unsupported or reserved frames.
if err := skipFramePayload(qr, l); err != nil {
return nil, err
}
}
Expand All @@ -68,19 +112,40 @@ type dataFrame struct {
}

func (f *dataFrame) Write(b *bytes.Buffer) {
quicvarint.Write(b, 0x0)
quicvarint.Write(b, dataFrameType)
quicvarint.Write(b, f.Length)
}

func (f *dataFrame) frame() {}

type headersFrame struct {
Length uint64
}

func (f *headersFrame) Write(b *bytes.Buffer) {
quicvarint.Write(b, 0x1)
quicvarint.Write(b, headersFrameType)
quicvarint.Write(b, f.Length)
}

func (f *headersFrame) frame() {}

// cancelPushFrame represents the CANCEL_PUSH frame.
//
// See https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-cancel_push
//
// TODO: currently not implemented.
type cancelPushFrame struct{}

// parseCancelPushFrame parses the cancelPushFrame of size l from r.
func parseCancelPushFrame(r io.Reader, l uint64) (*pushPromiseFrame, error) {
if err := skipFramePayload(r, l); err != nil {
return nil, err
}
return &pushPromiseFrame{}, nil
}

func (f *cancelPushFrame) frame() {}

const settingDatagram = 0xffd277

type settingsFrame struct {
Expand All @@ -92,15 +157,14 @@ func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) {
if l > 8*(1<<10) {
return nil, fmt.Errorf("unexpected size for SETTINGS frame: %d", l)
}
buf := make([]byte, l)
if _, err := io.ReadFull(r, buf); err != nil {
if err == io.ErrUnexpectedEOF {
return nil, io.EOF
}

b, err := readFramePayload(r, l)
if err != nil {
return nil, err
}

frame := &settingsFrame{}
b := bytes.NewReader(buf)

var readDatagram bool
for b.Len() > 0 {
id, err := quicvarint.Read(b)
Expand Down Expand Up @@ -132,11 +196,12 @@ func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) {
frame.Other[id] = val
}
}

return frame, nil
}

func (f *settingsFrame) Write(b *bytes.Buffer) {
quicvarint.Write(b, 0x4)
quicvarint.Write(b, settingsFrameType)
var l protocol.ByteCount
for id, val := range f.Other {
l += quicvarint.Len(id) + quicvarint.Len(val)
Expand All @@ -154,3 +219,90 @@ func (f *settingsFrame) Write(b *bytes.Buffer) {
quicvarint.Write(b, val)
}
}

func (f *settingsFrame) frame() {}

// pushPromiseFrame represents the PUSH_PROMISE frame.
//
// See https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-push_promise
//
// TODO: currently not implemented.
type pushPromiseFrame struct{}

// parsePushPromiseFrame parses the pushPromiseFrame of size l from r.
func parsePushPromiseFrame(r io.Reader, l uint64) (*pushPromiseFrame, error) {
if err := skipFramePayload(r, l); err != nil {
return nil, err
}
return &pushPromiseFrame{}, nil
}

func (f *pushPromiseFrame) frame() {}

// goawayFrame represents the GOAWAY frame.
//
// See https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-goaway
type goawayFrame struct {
StreamID protocol.StreamID
}

// parseGoawayFrame parses the goawayFrame of size l from r.
func parseGoawayFrame(r io.Reader, l uint64) (*goawayFrame, error) {
b, err := readFramePayload(r, l)
if err != nil {
return nil, err
}

streamID, err := quicvarint.Read(b)
if err != nil {
return nil, err
}

return &goawayFrame{
StreamID: protocol.StreamID(streamID),
}, nil
}

// Write encodes the GOAWAY frame to the given buffer.
func (f *goawayFrame) Write(b *bytes.Buffer) {
quicvarint.Write(b, goawayFrameType)
quicvarint.Write(b, uint64(quicvarint.Len(uint64(f.StreamID))))
quicvarint.Write(b, uint64(f.StreamID))
}

func (f *goawayFrame) frame() {}

// maxPushIDFrame represents the MAX_PUSH_ID frame.
//
// See https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-max_push_id
//
// TODO: parsing is currently not implemented.
type maxPushIDFrame struct{}

// parseMaxPushIDFrame parses the maxPushIDFrame of size l from r.
func parseMaxPushIDFrame(r io.Reader, l uint64) (*maxPushIDFrame, error) {
if err := skipFramePayload(r, l); err != nil {
return nil, err
}
return &maxPushIDFrame{}, nil
}

func (f *maxPushIDFrame) frame() {}

// readFramePayload reads l fram payload bytes from r.
func readFramePayload(r io.Reader, l uint64) (*bytes.Buffer, error) {
buf := make([]byte, l)
if _, err := io.ReadFull(r, buf); err != nil {
if err == io.ErrUnexpectedEOF {
return nil, io.EOF
}
return nil, err
}
return bytes.NewBuffer(buf), nil
}

// skipFramePayload discards l frame payload bytes from r.
func skipFramePayload(r io.Reader, l uint64) error {
_, err := io.CopyN(ioutil.Discard, r, int64(l))
return err
}