316 lines
7.1 KiB
Go
316 lines
7.1 KiB
Go
package httpx
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
)
|
|
|
|
// trackingWriter does not buffer response bodies. Each wrapper has its own
|
|
// commit flag, so a standard middleware may safely wrap an already tracked writer.
|
|
type trackingWriter struct {
|
|
http.ResponseWriter
|
|
context *Context
|
|
committed bool
|
|
}
|
|
|
|
func (w *trackingWriter) Unwrap() http.ResponseWriter { return w.ResponseWriter }
|
|
func (w *trackingWriter) inheritCommit(status int) { w.mark(status) }
|
|
func (w *trackingWriter) mark(status int) {
|
|
w.committed = true
|
|
w.context.responded = true
|
|
w.context.status = status
|
|
}
|
|
func (w *trackingWriter) WriteHeader(status int) {
|
|
if w.committed {
|
|
return
|
|
}
|
|
// Let the server validate the status before updating the observation.
|
|
w.ResponseWriter.WriteHeader(status)
|
|
if status == 101 || status >= 200 {
|
|
w.mark(status)
|
|
}
|
|
}
|
|
func (w *trackingWriter) Write(p []byte) (int, error) {
|
|
if !w.committed {
|
|
w.mark(http.StatusOK)
|
|
}
|
|
return w.ResponseWriter.Write(p)
|
|
}
|
|
|
|
type flushWriter struct{ *trackingWriter }
|
|
|
|
func (w flushWriter) Flush() { _ = w.FlushError() }
|
|
func (w flushWriter) FlushError() error {
|
|
if !w.committed {
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
return http.NewResponseController(w.ResponseWriter).Flush()
|
|
}
|
|
|
|
type hijackWriter struct{ *trackingWriter }
|
|
|
|
func (w hijackWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
conn, rw, err := w.ResponseWriter.(http.Hijacker).Hijack()
|
|
if err == nil {
|
|
w.context.responded = true
|
|
w.committed = true
|
|
}
|
|
return conn, rw, err
|
|
}
|
|
|
|
type pushWriter struct{ *trackingWriter }
|
|
|
|
func (w pushWriter) Push(target string, options *http.PushOptions) error {
|
|
return w.ResponseWriter.(http.Pusher).Push(target, options)
|
|
}
|
|
|
|
type readFromWriter struct{ *trackingWriter }
|
|
|
|
func (w readFromWriter) ReadFrom(r io.Reader) (int64, error) {
|
|
// A writer-only view makes every write observable, including partial failures,
|
|
// while io.Copy retains bounded streaming memory and source WriterTo support.
|
|
return io.Copy(struct{ io.Writer }{w.trackingWriter}, r)
|
|
}
|
|
|
|
type closeNotifyWriter struct{ *trackingWriter }
|
|
|
|
func (w closeNotifyWriter) CloseNotify() <-chan bool {
|
|
return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
|
}
|
|
|
|
func trackWriter(writer http.ResponseWriter, c *Context) http.ResponseWriter {
|
|
w := &trackingWriter{ResponseWriter: writer, context: c}
|
|
mask := 0
|
|
if _, ok := writer.(http.Flusher); ok {
|
|
mask |= 1
|
|
} else if _, ok := writer.(interface{ FlushError() error }); ok {
|
|
mask |= 1
|
|
}
|
|
if _, ok := writer.(http.Hijacker); ok {
|
|
mask |= 2
|
|
}
|
|
if _, ok := writer.(http.Pusher); ok {
|
|
mask |= 4
|
|
}
|
|
if _, ok := writer.(io.ReaderFrom); ok {
|
|
mask |= 8
|
|
}
|
|
if _, ok := writer.(http.CloseNotifier); ok {
|
|
mask |= 16
|
|
}
|
|
switch mask {
|
|
case 1:
|
|
return struct {
|
|
*trackingWriter
|
|
flushCapability
|
|
}{w, flushWriter{w}}
|
|
case 2:
|
|
return struct {
|
|
*trackingWriter
|
|
http.Hijacker
|
|
}{w, hijackWriter{w}}
|
|
case 3:
|
|
return struct {
|
|
*trackingWriter
|
|
flushCapability
|
|
http.Hijacker
|
|
}{w, flushWriter{w}, hijackWriter{w}}
|
|
case 4:
|
|
return struct {
|
|
*trackingWriter
|
|
http.Pusher
|
|
}{w, pushWriter{w}}
|
|
case 5:
|
|
return struct {
|
|
*trackingWriter
|
|
flushCapability
|
|
http.Pusher
|
|
}{w, flushWriter{w}, pushWriter{w}}
|
|
case 6:
|
|
return struct {
|
|
*trackingWriter
|
|
http.Hijacker
|
|
http.Pusher
|
|
}{w, hijackWriter{w}, pushWriter{w}}
|
|
case 7:
|
|
return struct {
|
|
*trackingWriter
|
|
flushCapability
|
|
http.Hijacker
|
|
http.Pusher
|
|
}{w, flushWriter{w}, hijackWriter{w}, pushWriter{w}}
|
|
case 8:
|
|
return struct {
|
|
*trackingWriter
|
|
io.ReaderFrom
|
|
}{w, readFromWriter{w}}
|
|
case 9:
|
|
return struct {
|
|
*trackingWriter
|
|
flushCapability
|
|
io.ReaderFrom
|
|
}{w, flushWriter{w}, readFromWriter{w}}
|
|
case 10:
|
|
return struct {
|
|
*trackingWriter
|
|
http.Hijacker
|
|
io.ReaderFrom
|
|
}{w, hijackWriter{w}, readFromWriter{w}}
|
|
case 11:
|
|
return struct {
|
|
*trackingWriter
|
|
flushCapability
|
|
http.Hijacker
|
|
io.ReaderFrom
|
|
}{w, flushWriter{w}, hijackWriter{w}, readFromWriter{w}}
|
|
case 12:
|
|
return struct {
|
|
*trackingWriter
|
|
http.Pusher
|
|
io.ReaderFrom
|
|
}{w, pushWriter{w}, readFromWriter{w}}
|
|
case 13:
|
|
return struct {
|
|
*trackingWriter
|
|
flushCapability
|
|
http.Pusher
|
|
io.ReaderFrom
|
|
}{w, flushWriter{w}, pushWriter{w}, readFromWriter{w}}
|
|
case 14:
|
|
return struct {
|
|
*trackingWriter
|
|
http.Hijacker
|
|
http.Pusher
|
|
io.ReaderFrom
|
|
}{w, hijackWriter{w}, pushWriter{w}, readFromWriter{w}}
|
|
case 15:
|
|
return struct {
|
|
*trackingWriter
|
|
flushCapability
|
|
http.Hijacker
|
|
http.Pusher
|
|
io.ReaderFrom
|
|
}{w, flushWriter{w}, hijackWriter{w}, pushWriter{w}, readFromWriter{w}}
|
|
case 16:
|
|
return struct {
|
|
*trackingWriter
|
|
http.CloseNotifier
|
|
}{w, closeNotifyWriter{w}}
|
|
case 17:
|
|
return struct {
|
|
*trackingWriter
|
|
flushCapability
|
|
http.CloseNotifier
|
|
}{w, flushWriter{w}, closeNotifyWriter{w}}
|
|
case 18:
|
|
return struct {
|
|
*trackingWriter
|
|
http.Hijacker
|
|
http.CloseNotifier
|
|
}{w, hijackWriter{w}, closeNotifyWriter{w}}
|
|
case 19:
|
|
return struct {
|
|
*trackingWriter
|
|
flushCapability
|
|
http.Hijacker
|
|
http.CloseNotifier
|
|
}{w, flushWriter{w}, hijackWriter{w}, closeNotifyWriter{w}}
|
|
case 20:
|
|
return struct {
|
|
*trackingWriter
|
|
http.Pusher
|
|
http.CloseNotifier
|
|
}{w, pushWriter{w}, closeNotifyWriter{w}}
|
|
case 21:
|
|
return struct {
|
|
*trackingWriter
|
|
flushCapability
|
|
http.Pusher
|
|
http.CloseNotifier
|
|
}{w, flushWriter{w}, pushWriter{w}, closeNotifyWriter{w}}
|
|
case 22:
|
|
return struct {
|
|
*trackingWriter
|
|
http.Hijacker
|
|
http.Pusher
|
|
http.CloseNotifier
|
|
}{w, hijackWriter{w}, pushWriter{w}, closeNotifyWriter{w}}
|
|
case 23:
|
|
return struct {
|
|
*trackingWriter
|
|
flushCapability
|
|
http.Hijacker
|
|
http.Pusher
|
|
http.CloseNotifier
|
|
}{w, flushWriter{w}, hijackWriter{w}, pushWriter{w}, closeNotifyWriter{w}}
|
|
case 24:
|
|
return struct {
|
|
*trackingWriter
|
|
io.ReaderFrom
|
|
http.CloseNotifier
|
|
}{w, readFromWriter{w}, closeNotifyWriter{w}}
|
|
case 25:
|
|
return struct {
|
|
*trackingWriter
|
|
flushCapability
|
|
io.ReaderFrom
|
|
http.CloseNotifier
|
|
}{w, flushWriter{w}, readFromWriter{w}, closeNotifyWriter{w}}
|
|
case 26:
|
|
return struct {
|
|
*trackingWriter
|
|
http.Hijacker
|
|
io.ReaderFrom
|
|
http.CloseNotifier
|
|
}{w, hijackWriter{w}, readFromWriter{w}, closeNotifyWriter{w}}
|
|
case 27:
|
|
return struct {
|
|
*trackingWriter
|
|
flushCapability
|
|
http.Hijacker
|
|
io.ReaderFrom
|
|
http.CloseNotifier
|
|
}{w, flushWriter{w}, hijackWriter{w}, readFromWriter{w}, closeNotifyWriter{w}}
|
|
case 28:
|
|
return struct {
|
|
*trackingWriter
|
|
http.Pusher
|
|
io.ReaderFrom
|
|
http.CloseNotifier
|
|
}{w, pushWriter{w}, readFromWriter{w}, closeNotifyWriter{w}}
|
|
case 29:
|
|
return struct {
|
|
*trackingWriter
|
|
flushCapability
|
|
http.Pusher
|
|
io.ReaderFrom
|
|
http.CloseNotifier
|
|
}{w, flushWriter{w}, pushWriter{w}, readFromWriter{w}, closeNotifyWriter{w}}
|
|
case 30:
|
|
return struct {
|
|
*trackingWriter
|
|
http.Hijacker
|
|
http.Pusher
|
|
io.ReaderFrom
|
|
http.CloseNotifier
|
|
}{w, hijackWriter{w}, pushWriter{w}, readFromWriter{w}, closeNotifyWriter{w}}
|
|
case 31:
|
|
return struct {
|
|
*trackingWriter
|
|
flushCapability
|
|
http.Hijacker
|
|
http.Pusher
|
|
io.ReaderFrom
|
|
http.CloseNotifier
|
|
}{w, flushWriter{w}, hijackWriter{w}, pushWriter{w}, readFromWriter{w}, closeNotifyWriter{w}}
|
|
}
|
|
return w
|
|
}
|
|
|
|
type flushCapability interface {
|
|
Flush()
|
|
FlushError() error
|
|
}
|