119 lines
2.6 KiB
Go
119 lines
2.6 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"
|
||
|
)
|
||
|
|
||
|
type responseWriter struct {
|
||
|
conn quic.Connection
|
||
|
bufferedStr *bufio.Writer
|
||
|
|
||
|
header http.Header
|
||
|
status int // status code passed to WriteHeader
|
||
|
headerWritten bool
|
||
|
|
||
|
logger utils.Logger
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
_ http.ResponseWriter = &responseWriter{}
|
||
|
_ http.Flusher = &responseWriter{}
|
||
|
_ Hijacker = &responseWriter{}
|
||
|
)
|
||
|
|
||
|
func newResponseWriter(str quic.Stream, conn quic.Connection, logger utils.Logger) *responseWriter {
|
||
|
return &responseWriter{
|
||
|
header: http.Header{},
|
||
|
conn: conn,
|
||
|
bufferedStr: bufio.NewWriter(str),
|
||
|
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.bufferedStr.Write(buf.Bytes()); err != nil {
|
||
|
w.logger.Errorf("could not write headers frame: %s", err.Error())
|
||
|
}
|
||
|
if _, err := w.bufferedStr.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.bufferedStr.Write(buf.Bytes()); err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
return w.bufferedStr.Write(p)
|
||
|
}
|
||
|
|
||
|
func (w *responseWriter) Flush() {
|
||
|
if err := w.bufferedStr.Flush(); err != nil {
|
||
|
w.logger.Errorf("could not flush to stream: %s", err.Error())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (w *responseWriter) StreamCreator() StreamCreator {
|
||
|
return w.conn
|
||
|
}
|
||
|
|
||
|
// 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
|
||
|
}
|