package http3
import (
"context"
"errors"
"io"
"log/slog"
"net/http"
"runtime"
"strconv"
"time"
"github.com/quic-go/qpack"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/qlogwriter"
)
type RawServerConn struct {
rawConn rawConn
idleTimeout time .Duration
idleTimer *time .Timer
serverContext context .Context
requestHandler http .Handler
maxHeaderBytes int
decoder *qpack .Decoder
qlogger qlogwriter .Recorder
logger *slog .Logger
}
func newRawServerConn(
conn *quic .Conn ,
enableDatagrams bool ,
idleTimeout time .Duration ,
qlogger qlogwriter .Recorder ,
logger *slog .Logger ,
serverContext context .Context ,
requestHandler http .Handler ,
maxHeaderBytes int ,
) *RawServerConn {
c := &RawServerConn {
idleTimeout : idleTimeout ,
serverContext : serverContext ,
requestHandler : requestHandler ,
maxHeaderBytes : maxHeaderBytes ,
decoder : qpack .NewDecoder (),
qlogger : qlogger ,
logger : logger ,
}
c .rawConn = *newRawConn (conn , enableDatagrams , c .onStreamsEmpty , nil , qlogger , logger )
if idleTimeout > 0 {
c .idleTimer = time .AfterFunc (idleTimeout , c .onIdleTimer )
}
return c
}
func (c *RawServerConn ) onStreamsEmpty () {
if c .idleTimeout > 0 {
c .idleTimer .Reset (c .idleTimeout )
}
}
func (c *RawServerConn ) onIdleTimer () {
c .CloseWithError (quic .ApplicationErrorCode (ErrCodeNoError ), "idle timeout" )
}
func (c *RawServerConn ) CloseWithError (code quic .ApplicationErrorCode , msg string ) error {
if c .idleTimer != nil {
c .idleTimer .Stop ()
}
return c .rawConn .CloseWithError (code , msg )
}
func (c *RawServerConn ) HandleRequestStream (str *quic .Stream ) {
hstr := c .rawConn .TrackStream (str )
c .handleRequestStream (hstr )
}
func (c *RawServerConn ) requestMaxHeaderBytes () int {
if c .maxHeaderBytes <= 0 {
return http .DefaultMaxHeaderBytes
}
return c .maxHeaderBytes
}
func (c *RawServerConn ) openControlStream (settings *settingsFrame ) (*quic .SendStream , error ) {
return c .rawConn .openControlStream (settings )
}
func (c *RawServerConn ) handleRequestStream (str *stateTrackingStream ) {
if c .idleTimeout > 0 {
c .idleTimer .Stop ()
}
conn := &c .rawConn
qlogger := c .qlogger
decoder := c .decoder
connCtx := c .serverContext
maxHeaderBytes := c .requestMaxHeaderBytes ()
fp := &frameParser {closeConn : conn .CloseWithError , r : str , streamID : str .StreamID ()}
frame , err := fp .ParseNext (qlogger )
if err != nil {
str .CancelRead (quic .StreamErrorCode (ErrCodeRequestIncomplete ))
str .CancelWrite (quic .StreamErrorCode (ErrCodeRequestIncomplete ))
return
}
hf , ok := frame .(*headersFrame )
if !ok {
conn .CloseWithError (quic .ApplicationErrorCode (ErrCodeFrameUnexpected ), "expected first frame to be a HEADERS frame" )
return
}
if hf .Length > uint64 (maxHeaderBytes ) {
maybeQlogInvalidHeadersFrame (qlogger , str .StreamID (), hf .Length )
str .CancelRead (quic .StreamErrorCode (ErrCodeExcessiveLoad ))
c .rejectWithHeaderFieldsTooLarge (str )
return
}
headerBlock := make ([]byte , hf .Length )
if _ , err := io .ReadFull (str , headerBlock ); err != nil {
maybeQlogInvalidHeadersFrame (qlogger , str .StreamID (), hf .Length )
str .CancelRead (quic .StreamErrorCode (ErrCodeRequestIncomplete ))
str .CancelWrite (quic .StreamErrorCode (ErrCodeRequestIncomplete ))
return
}
decodeFn := decoder .Decode (headerBlock )
var hfs []qpack .HeaderField
if qlogger != nil {
hfs = make ([]qpack .HeaderField , 0 , 16 )
}
req , err := requestFromHeaders (decodeFn , maxHeaderBytes , &hfs )
if qlogger != nil {
qlogParsedHeadersFrame (qlogger , str .StreamID (), hf , hfs )
}
if err != nil {
if errors .Is (err , errHeaderTooLarge ) {
str .CancelRead (quic .StreamErrorCode (ErrCodeExcessiveLoad ))
c .rejectWithHeaderFieldsTooLarge (str )
return
}
errCode := ErrCodeMessageError
var qpackErr *qpackError
if errors .As (err , &qpackErr ) {
errCode = ErrCodeQPACKDecompressionFailed
}
str .CancelRead (quic .StreamErrorCode (errCode ))
str .CancelWrite (quic .StreamErrorCode (errCode ))
return
}
connState := conn .ConnectionState ().TLS
req .TLS = &connState
req .RemoteAddr = conn .RemoteAddr ().String ()
contentLength := int64 (-1 )
if _ , ok := req .Header ["Content-Length" ]; ok && req .ContentLength >= 0 {
contentLength = req .ContentLength
}
hstr := newStream (str , conn , nil , func (r io .Reader , hf *headersFrame ) error {
trailers , err := decodeTrailers (r , hf , maxHeaderBytes , decoder , qlogger , str .StreamID ())
if err != nil {
return err
}
req .Trailer = trailers
return nil
}, qlogger )
body := newRequestBody (hstr , contentLength , connCtx , conn .ReceivedSettings (), conn .Settings )
req .Body = body
if c .logger != nil {
c .logger .Debug ("handling request" , "method" , req .Method , "host" , req .Host , "uri" , req .RequestURI )
}
ctx , cancel := context .WithCancel (connCtx )
req = req .WithContext (ctx )
context .AfterFunc (str .Context (), cancel )
r := newResponseWriter (hstr , conn , req .Method == http .MethodHead , c .logger )
handler := c .requestHandler
if handler == nil {
handler = http .DefaultServeMux
}
var panicked bool
func () {
defer func () {
if p := recover (); p != nil {
panicked = true
if p == http .ErrAbortHandler {
return
}
const size = 64 << 10
buf := make ([]byte , size )
buf = buf [:runtime .Stack (buf , false )]
logger := c .logger
if logger == nil {
logger = slog .Default ()
}
logger .Error ("http3: panic serving" , "arg" , p , "trace" , string (buf ))
}
}()
handler .ServeHTTP (r , req )
}()
if r .wasStreamHijacked () {
return
}
if panicked {
str .CancelRead (quic .StreamErrorCode (ErrCodeInternalError ))
str .CancelWrite (quic .StreamErrorCode (ErrCodeInternalError ))
return
}
if !r .headerWritten {
if _ , haveCL := r .header ["Content-Length" ]; !haveCL {
r .header .Set ("Content-Length" , strconv .FormatInt (r .numWritten , 10 ))
}
}
r .Flush ()
r .flushTrailers ()
str .CancelRead (quic .StreamErrorCode (ErrCodeNoError ))
str .Close ()
}
func (c *RawServerConn ) rejectWithHeaderFieldsTooLarge (str *stateTrackingStream ) {
hstr := newStream (str , &c .rawConn , nil , nil , c .qlogger )
defer hstr .Close ()
r := newResponseWriter (hstr , &c .rawConn , false , c .logger )
r .WriteHeader (http .StatusRequestHeaderFieldsTooLarge )
r .Flush ()
}
func (c *RawServerConn ) HandleUnidirectionalStream (str *quic .ReceiveStream ) {
c .rawConn .handleUnidirectionalStream (str , true )
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .