forked from quic-go/quic-go
Currently, it's not possible to send informational responses such as 103 Early Hints or 102 Processing. This patch allows calling WriteHeader() multiple times in order to send informational responses before the final one. It follows the patch for HTTP/1 (golang/go#42597) and HTTP/2 (golang/net#96). In conformance with RFC 8297, if the status code is 103 the current content of the header map is also sent. Its content is not removed after the call to WriteHeader() because the headers must also be included in the final response. The Chrome and Fastly teams are starting a large-scale experiment to measure the real-life impact of the 103 status code. Using Early Hints is proposed as a (partial) alternative to Server Push, which are going to be removed from Chrome: https://groups.google.com/a/chromium.org/g/blink-dev/c/K3rYLvmQUBY/m/21anpFhxAQAJ Being able to send this status code from servers implemented using Go would help to see if implementing it in browsers is worth it.
136 lines
3.2 KiB
Go
136 lines
3.2 KiB
Go
package http3
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/lucas-clemente/quic-go"
|
|
"github.com/lucas-clemente/quic-go/internal/utils"
|
|
"github.com/marten-seemann/qpack"
|
|
)
|
|
|
|
// DataStreamer lets the caller take over the stream. After a call to DataStream
|
|
// the HTTP server library will not do anything else with the connection.
|
|
//
|
|
// It becomes the caller's responsibility to manage and close the stream.
|
|
//
|
|
// After a call to DataStream, the original Request.Body must not be used.
|
|
type DataStreamer interface {
|
|
DataStream() quic.Stream
|
|
}
|
|
|
|
type responseWriter struct {
|
|
stream quic.Stream // needed for DataStream()
|
|
bufferedStream *bufio.Writer
|
|
|
|
header http.Header
|
|
status int // status code passed to WriteHeader
|
|
headerWritten bool
|
|
dataStreamUsed bool // set when DataSteam() is called
|
|
|
|
logger utils.Logger
|
|
}
|
|
|
|
var (
|
|
_ http.ResponseWriter = &responseWriter{}
|
|
_ http.Flusher = &responseWriter{}
|
|
_ DataStreamer = &responseWriter{}
|
|
)
|
|
|
|
func newResponseWriter(stream quic.Stream, logger utils.Logger) *responseWriter {
|
|
return &responseWriter{
|
|
header: http.Header{},
|
|
stream: stream,
|
|
bufferedStream: bufio.NewWriter(stream),
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
func (w *responseWriter) Header() http.Header {
|
|
return w.header
|
|
}
|
|
|
|
func (w *responseWriter) WriteHeader(status int) {
|
|
if w.headerWritten {
|
|
return
|
|
}
|
|
|
|
if status < 100 || status >= 200 {
|
|
w.headerWritten = true
|
|
}
|
|
w.status = status
|
|
|
|
var headers bytes.Buffer
|
|
enc := qpack.NewEncoder(&headers)
|
|
enc.WriteField(qpack.HeaderField{Name: ":status", Value: strconv.Itoa(status)})
|
|
|
|
for k, v := range w.header {
|
|
for index := range v {
|
|
enc.WriteField(qpack.HeaderField{Name: strings.ToLower(k), Value: v[index]})
|
|
}
|
|
}
|
|
|
|
buf := &bytes.Buffer{}
|
|
(&headersFrame{Length: uint64(headers.Len())}).Write(buf)
|
|
w.logger.Infof("Responding with %d", status)
|
|
if _, err := w.bufferedStream.Write(buf.Bytes()); err != nil {
|
|
w.logger.Errorf("could not write headers frame: %s", err.Error())
|
|
}
|
|
if _, err := w.bufferedStream.Write(headers.Bytes()); err != nil {
|
|
w.logger.Errorf("could not write header frame payload: %s", err.Error())
|
|
}
|
|
if !w.headerWritten {
|
|
w.Flush()
|
|
}
|
|
}
|
|
|
|
func (w *responseWriter) Write(p []byte) (int, error) {
|
|
if !w.headerWritten {
|
|
w.WriteHeader(200)
|
|
}
|
|
if !bodyAllowedForStatus(w.status) {
|
|
return 0, http.ErrBodyNotAllowed
|
|
}
|
|
df := &dataFrame{Length: uint64(len(p))}
|
|
buf := &bytes.Buffer{}
|
|
df.Write(buf)
|
|
if _, err := w.bufferedStream.Write(buf.Bytes()); err != nil {
|
|
return 0, err
|
|
}
|
|
return w.bufferedStream.Write(p)
|
|
}
|
|
|
|
func (w *responseWriter) Flush() {
|
|
if err := w.bufferedStream.Flush(); err != nil {
|
|
w.logger.Errorf("could not flush to stream: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
func (w *responseWriter) usedDataStream() bool {
|
|
return w.dataStreamUsed
|
|
}
|
|
|
|
func (w *responseWriter) DataStream() quic.Stream {
|
|
w.dataStreamUsed = true
|
|
w.Flush()
|
|
return w.stream
|
|
}
|
|
|
|
// copied from http2/http2.go
|
|
// bodyAllowedForStatus reports whether a given response status code
|
|
// permits a body. See RFC 2616, section 4.4.
|
|
func bodyAllowedForStatus(status int) bool {
|
|
switch {
|
|
case status >= 100 && status <= 199:
|
|
return false
|
|
case status == 204:
|
|
return false
|
|
case status == 304:
|
|
return false
|
|
}
|
|
return true
|
|
}
|