Files
quic-go/http3/response_writer.go
Kévin Dunglas 3b916f2e80 allow sending 1xx responses (#3047)
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.
2021-03-05 10:03:31 +08:00

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
}