blob: 0892b51e9ac24ee5d4d83f9a3b8511b5fefefe75 [file] [log] [blame]
// 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.
package http3
import (
"context"
"net/http"
"net/url"
"strconv"
"sync"
"golang.org/x/net/http/httpguts"
"golang.org/x/net/quic"
)
// A Server is an HTTP/3 server.
// The zero value for Server is a valid server.
type Server struct {
// Handler to invoke for requests, http.DefaultServeMux if nil.
Handler http.Handler
// Config is the QUIC configuration used by the server.
// The Config may be nil.
//
// ListenAndServe may clone and modify the Config.
// The Config must not be modified after calling ListenAndServe.
Config *quic.Config
initOnce sync.Once
}
func (s *Server) init() {
s.initOnce.Do(func() {
s.Config = initConfig(s.Config)
if s.Handler == nil {
s.Handler = http.DefaultServeMux
}
})
}
// ListenAndServe listens on the UDP network address addr
// and then calls Serve to handle requests on incoming connections.
func (s *Server) ListenAndServe(addr string) error {
s.init()
e, err := quic.Listen("udp", addr, s.Config)
if err != nil {
return err
}
return s.Serve(e)
}
// Serve accepts incoming connections on the QUIC endpoint e,
// and handles requests from those connections.
func (s *Server) Serve(e *quic.Endpoint) error {
s.init()
for {
qconn, err := e.Accept(context.Background())
if err != nil {
return err
}
go newServerConn(qconn, s.Handler)
}
}
type serverConn struct {
qconn *quic.Conn
genericConn // for handleUnidirectionalStream
enc qpackEncoder
dec qpackDecoder
handler http.Handler
}
func newServerConn(qconn *quic.Conn, handler http.Handler) {
sc := &serverConn{
qconn: qconn,
handler: handler,
}
sc.enc.init()
// Create control stream and send SETTINGS frame.
// TODO: Time out on creating stream.
controlStream, err := newConnStream(context.Background(), sc.qconn, streamTypeControl)
if err != nil {
return
}
controlStream.writeSettings()
controlStream.Flush()
sc.acceptStreams(sc.qconn, sc)
}
func (sc *serverConn) handleControlStream(st *stream) error {
// "A SETTINGS frame MUST be sent as the first frame of each control stream [...]"
// https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4-2
if err := st.readSettings(func(settingsType, settingsValue int64) error {
switch settingsType {
case settingsMaxFieldSectionSize:
_ = settingsValue // TODO
case settingsQPACKMaxTableCapacity:
_ = settingsValue // TODO
case settingsQPACKBlockedStreams:
_ = settingsValue // TODO
default:
// Unknown settings types are ignored.
}
return nil
}); err != nil {
return err
}
for {
ftype, err := st.readFrameHeader()
if err != nil {
return err
}
switch ftype {
case frameTypeCancelPush:
// "If a server receives a CANCEL_PUSH frame for a push ID
// that has not yet been mentioned by a PUSH_PROMISE frame,
// this MUST be treated as a connection error of type H3_ID_ERROR."
// https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.3-8
return &connectionError{
code: errH3IDError,
message: "CANCEL_PUSH for unsent push ID",
}
case frameTypeGoaway:
return errH3NoError
default:
// Unknown frames are ignored.
if err := st.discardUnknownFrame(ftype); err != nil {
return err
}
}
}
}
func (sc *serverConn) handleEncoderStream(*stream) error {
// TODO
return nil
}
func (sc *serverConn) handleDecoderStream(*stream) error {
// TODO
return nil
}
func (sc *serverConn) handlePushStream(*stream) error {
// "[...] if a server receives a client-initiated push stream,
// this MUST be treated as a connection error of type H3_STREAM_CREATION_ERROR."
// https://www.rfc-editor.org/rfc/rfc9114.html#section-6.2.2-3
return &connectionError{
code: errH3StreamCreationError,
message: "client created push stream",
}
}
func (sc *serverConn) parseRequest(st *stream) (*http.Request, error) {
req := &http.Request{
URL: &url.URL{},
Proto: "HTTP/3.0",
ProtoMajor: 3,
RemoteAddr: sc.qconn.RemoteAddr().String(),
}
ftype, err := st.readFrameHeader()
if err != nil {
return nil, err
}
if ftype != frameTypeHeaders {
return nil, err
}
req.Header = make(http.Header)
var dec qpackDecoder
if err := dec.decode(st, func(_ indexType, name, value string) error {
switch name {
case ":method":
req.Method = value
case ":scheme":
req.URL.Scheme = value
case ":path":
req.URL.Path = value
case ":authority":
req.URL.Host = value
default:
req.Header.Add(name, value)
}
return nil
}); err != nil {
return nil, err
}
if err := st.endFrame(); err != nil {
return nil, err
}
req.Body = &bodyReader{
st: st,
remain: -1,
}
return req, nil
}
func (sc *serverConn) handleRequestStream(st *stream) error {
req, err := sc.parseRequest(st)
if err != nil {
return err
}
defer req.Body.Close()
rw := &responseWriter{
st: st,
headers: make(http.Header),
isHeadResp: req.Method == "HEAD",
bw: &bodyWriter{
st: st,
remain: -1,
flush: false,
name: "response",
},
}
defer rw.close()
// TODO: handle panic coming from the HTTP handler.
sc.handler.ServeHTTP(rw, req)
return nil
}
// abort closes the connection with an error.
func (sc *serverConn) abort(err error) {
if e, ok := err.(*connectionError); ok {
sc.qconn.Abort(&quic.ApplicationError{
Code: uint64(e.code),
Reason: e.message,
})
} else {
sc.qconn.Abort(err)
}
}
type responseWriter struct {
st *stream
bw *bodyWriter
mu sync.Mutex
headers http.Header
// TODO: support 1xx status
wroteHeader bool // Non-1xx header has been (logically) written.
isHeadResp bool // response is for a HEAD request.
}
func (rw *responseWriter) Header() http.Header {
return rw.headers
}
// Caller must hold rw.mu. If rw.wroteHeader is true, calling this method is a
// no-op.
func (rw *responseWriter) writeHeaderLockedOnce(statusCode int) {
// TODO: support trailer header.
if rw.wroteHeader {
return
}
enc := &qpackEncoder{}
enc.init()
encHeaders := enc.encode(func(f func(itype indexType, name, value string)) {
f(mayIndex, ":status", strconv.Itoa(statusCode))
for name, values := range rw.headers {
if !httpguts.ValidHeaderFieldName(name) {
continue
}
for _, val := range values {
if !httpguts.ValidHeaderFieldValue(val) {
continue
}
// Issue #71374: Consider supporting never-indexed fields.
f(mayIndex, name, val)
}
}
})
rw.st.writeVarint(int64(frameTypeHeaders))
rw.st.writeVarint(int64(len(encHeaders)))
rw.st.Write(encHeaders)
rw.wroteHeader = true
}
func (rw *responseWriter) WriteHeader(statusCode int) {
rw.mu.Lock()
defer rw.mu.Unlock()
rw.writeHeaderLockedOnce(statusCode)
}
func (rw *responseWriter) Write(b []byte) (int, error) {
rw.mu.Lock()
defer rw.mu.Unlock()
rw.writeHeaderLockedOnce(http.StatusOK)
if rw.isHeadResp {
return 0, nil
}
return rw.bw.Write(b)
}
func (rw *responseWriter) Flush() {
rw.bw.st.Flush()
}
func (rw *responseWriter) close() error {
rw.mu.Lock()
defer rw.mu.Unlock()
rw.writeHeaderLockedOnce(http.StatusOK)
return rw.st.stream.Close()
}