http3: add basic server-side qlog support (#5367)

* add Conn.QlogTrace

* http3: add basic qlog support on the server side
This commit is contained in:
Marten Seemann
2025-10-10 18:35:58 +08:00
committed by GitHub
parent fb232c81ee
commit ca05442ab9
20 changed files with 672 additions and 72 deletions

View File

@@ -11,6 +11,8 @@ import (
"time"
"github.com/quic-go/qpack"
"github.com/quic-go/quic-go/http3/qlog"
"golang.org/x/net/http/httpguts"
)
@@ -181,6 +183,13 @@ func (w *responseWriter) doWrite(p []byte) (int, error) {
df := &dataFrame{Length: l}
w.buf = w.buf[:0]
w.buf = df.Append(w.buf)
if w.str.qlogger != nil {
w.str.qlogger.RecordEvent(qlog.FrameCreated{
StreamID: w.str.StreamID(),
Raw: qlog.RawInfo{Length: len(w.buf) + int(l), PayloadLength: int(l)},
Frame: qlog.Frame{Frame: qlog.DataFrame{}},
})
}
if _, err := w.str.writeUnframed(w.buf); err != nil {
return 0, maybeReplaceError(err)
}
@@ -202,11 +211,15 @@ func (w *responseWriter) doWrite(p []byte) (int, error) {
}
func (w *responseWriter) writeHeader(status int) error {
var headerFields []qlog.HeaderField // only used for qlog
var headers bytes.Buffer
enc := qpack.NewEncoder(&headers)
if err := enc.WriteField(qpack.HeaderField{Name: ":status", Value: strconv.Itoa(status)}); err != nil {
return err
}
if w.str.qlogger != nil {
headerFields = append(headerFields, qlog.HeaderField{Name: ":status", Value: strconv.Itoa(status)})
}
// Handle trailer fields
if vals, ok := w.header["Trailer"]; ok {
@@ -229,9 +242,14 @@ func (w *responseWriter) writeHeader(status int) error {
continue
}
for index := range v {
if err := enc.WriteField(qpack.HeaderField{Name: strings.ToLower(k), Value: v[index]}); err != nil {
name := strings.ToLower(k)
value := v[index]
if err := enc.WriteField(qpack.HeaderField{Name: name, Value: value}); err != nil {
return err
}
if w.str.qlogger != nil {
headerFields = append(headerFields, qlog.HeaderField{Name: name, Value: value})
}
}
}
@@ -239,6 +257,10 @@ func (w *responseWriter) writeHeader(status int) error {
buf = (&headersFrame{Length: uint64(headers.Len())}).Append(buf)
buf = append(buf, headers.Bytes()...)
if w.str.qlogger != nil {
qlogCreatedHeadersFrame(w.str.qlogger, w.str.StreamID(), len(buf), headers.Len(), headerFields)
}
_, err := w.str.writeUnframed(buf)
return err
}
@@ -311,6 +333,7 @@ func (w *responseWriter) writeTrailers() error {
}
var b bytes.Buffer
var headerFields []qlog.HeaderField
enc := qpack.NewEncoder(&b)
for trailer := range w.trailers {
trailerName := strings.ToLower(strings.TrimPrefix(trailer, http.TrailerPrefix))
@@ -319,6 +342,9 @@ func (w *responseWriter) writeTrailers() error {
if err := enc.WriteField(qpack.HeaderField{Name: trailerName, Value: val}); err != nil {
return err
}
if w.str.qlogger != nil {
headerFields = append(headerFields, qlog.HeaderField{Name: trailerName, Value: val})
}
}
}
}
@@ -326,6 +352,9 @@ func (w *responseWriter) writeTrailers() error {
buf := make([]byte, 0, frameHeaderLen+b.Len())
buf = (&headersFrame{Length: uint64(b.Len())}).Append(buf)
buf = append(buf, b.Bytes()...)
if w.str.qlogger != nil {
qlogCreatedHeadersFrame(w.str.qlogger, w.str.StreamID(), len(buf), b.Len(), headerFields)
}
_, err := w.str.writeUnframed(buf)
w.trailerWritten = true
return err