wrap_writer.go

  1package middleware
  2
  3// The original work was derived from Goji's middleware, source:
  4// https://github.com/zenazn/goji/tree/master/web/middleware
  5
  6import (
  7	"bufio"
  8	"io"
  9	"net"
 10	"net/http"
 11)
 12
 13// NewWrapResponseWriter wraps an http.ResponseWriter, returning a proxy that allows you to
 14// hook into various parts of the response process.
 15func NewWrapResponseWriter(w http.ResponseWriter, protoMajor int) WrapResponseWriter {
 16	_, fl := w.(http.Flusher)
 17
 18	bw := basicWriter{ResponseWriter: w}
 19
 20	if protoMajor == 2 {
 21		_, ps := w.(http.Pusher)
 22		if fl && ps {
 23			return &http2FancyWriter{bw}
 24		}
 25	} else {
 26		_, hj := w.(http.Hijacker)
 27		_, rf := w.(io.ReaderFrom)
 28		if fl && hj && rf {
 29			return &httpFancyWriter{bw}
 30		}
 31		if fl && hj {
 32			return &flushHijackWriter{bw}
 33		}
 34		if hj {
 35			return &hijackWriter{bw}
 36		}
 37	}
 38
 39	if fl {
 40		return &flushWriter{bw}
 41	}
 42
 43	return &bw
 44}
 45
 46// WrapResponseWriter is a proxy around an http.ResponseWriter that allows you to hook
 47// into various parts of the response process.
 48type WrapResponseWriter interface {
 49	http.ResponseWriter
 50	// Status returns the HTTP status of the request, or 0 if one has not
 51	// yet been sent.
 52	Status() int
 53	// BytesWritten returns the total number of bytes sent to the client.
 54	BytesWritten() int
 55	// Tee causes the response body to be written to the given io.Writer in
 56	// addition to proxying the writes through. Only one io.Writer can be
 57	// tee'd to at once: setting a second one will overwrite the first.
 58	// Writes will be sent to the proxy before being written to this
 59	// io.Writer. It is illegal for the tee'd writer to be modified
 60	// concurrently with writes.
 61	Tee(io.Writer)
 62	// Unwrap returns the original proxied target.
 63	Unwrap() http.ResponseWriter
 64	// Discard causes all writes to the original ResponseWriter be discarded,
 65	// instead writing only to the tee'd writer if it's set.
 66	// The caller is responsible for calling WriteHeader and Write on the
 67	// original ResponseWriter once the processing is done.
 68	Discard()
 69}
 70
 71// basicWriter wraps a http.ResponseWriter that implements the minimal
 72// http.ResponseWriter interface.
 73type basicWriter struct {
 74	http.ResponseWriter
 75	tee         io.Writer
 76	code        int
 77	bytes       int
 78	wroteHeader bool
 79	discard     bool
 80}
 81
 82func (b *basicWriter) WriteHeader(code int) {
 83	if code >= 100 && code <= 199 && code != http.StatusSwitchingProtocols {
 84		if !b.discard {
 85			b.ResponseWriter.WriteHeader(code)
 86		}
 87	} else if !b.wroteHeader {
 88		b.code = code
 89		b.wroteHeader = true
 90		if !b.discard {
 91			b.ResponseWriter.WriteHeader(code)
 92		}
 93	}
 94}
 95
 96func (b *basicWriter) Write(buf []byte) (n int, err error) {
 97	b.maybeWriteHeader()
 98	if !b.discard {
 99		n, err = b.ResponseWriter.Write(buf)
100		if b.tee != nil {
101			_, err2 := b.tee.Write(buf[:n])
102			// Prefer errors generated by the proxied writer.
103			if err == nil {
104				err = err2
105			}
106		}
107	} else if b.tee != nil {
108		n, err = b.tee.Write(buf)
109	} else {
110		n, err = io.Discard.Write(buf)
111	}
112	b.bytes += n
113	return n, err
114}
115
116func (b *basicWriter) maybeWriteHeader() {
117	if !b.wroteHeader {
118		b.WriteHeader(http.StatusOK)
119	}
120}
121
122func (b *basicWriter) Status() int {
123	return b.code
124}
125
126func (b *basicWriter) BytesWritten() int {
127	return b.bytes
128}
129
130func (b *basicWriter) Tee(w io.Writer) {
131	b.tee = w
132}
133
134func (b *basicWriter) Unwrap() http.ResponseWriter {
135	return b.ResponseWriter
136}
137
138func (b *basicWriter) Discard() {
139	b.discard = true
140}
141
142// flushWriter ...
143type flushWriter struct {
144	basicWriter
145}
146
147func (f *flushWriter) Flush() {
148	f.wroteHeader = true
149	fl := f.basicWriter.ResponseWriter.(http.Flusher)
150	fl.Flush()
151}
152
153var _ http.Flusher = &flushWriter{}
154
155// hijackWriter ...
156type hijackWriter struct {
157	basicWriter
158}
159
160func (f *hijackWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
161	hj := f.basicWriter.ResponseWriter.(http.Hijacker)
162	return hj.Hijack()
163}
164
165var _ http.Hijacker = &hijackWriter{}
166
167// flushHijackWriter ...
168type flushHijackWriter struct {
169	basicWriter
170}
171
172func (f *flushHijackWriter) Flush() {
173	f.wroteHeader = true
174	fl := f.basicWriter.ResponseWriter.(http.Flusher)
175	fl.Flush()
176}
177
178func (f *flushHijackWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
179	hj := f.basicWriter.ResponseWriter.(http.Hijacker)
180	return hj.Hijack()
181}
182
183var _ http.Flusher = &flushHijackWriter{}
184var _ http.Hijacker = &flushHijackWriter{}
185
186// httpFancyWriter is a HTTP writer that additionally satisfies
187// http.Flusher, http.Hijacker, and io.ReaderFrom. It exists for the common case
188// of wrapping the http.ResponseWriter that package http gives you, in order to
189// make the proxied object support the full method set of the proxied object.
190type httpFancyWriter struct {
191	basicWriter
192}
193
194func (f *httpFancyWriter) Flush() {
195	f.wroteHeader = true
196	fl := f.basicWriter.ResponseWriter.(http.Flusher)
197	fl.Flush()
198}
199
200func (f *httpFancyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
201	hj := f.basicWriter.ResponseWriter.(http.Hijacker)
202	return hj.Hijack()
203}
204
205func (f *http2FancyWriter) Push(target string, opts *http.PushOptions) error {
206	return f.basicWriter.ResponseWriter.(http.Pusher).Push(target, opts)
207}
208
209func (f *httpFancyWriter) ReadFrom(r io.Reader) (int64, error) {
210	if f.basicWriter.tee != nil {
211		n, err := io.Copy(&f.basicWriter, r)
212		f.basicWriter.bytes += int(n)
213		return n, err
214	}
215	rf := f.basicWriter.ResponseWriter.(io.ReaderFrom)
216	f.basicWriter.maybeWriteHeader()
217	n, err := rf.ReadFrom(r)
218	f.basicWriter.bytes += int(n)
219	return n, err
220}
221
222var _ http.Flusher = &httpFancyWriter{}
223var _ http.Hijacker = &httpFancyWriter{}
224var _ http.Pusher = &http2FancyWriter{}
225var _ io.ReaderFrom = &httpFancyWriter{}
226
227// http2FancyWriter is a HTTP2 writer that additionally satisfies
228// http.Flusher, and io.ReaderFrom. It exists for the common case
229// of wrapping the http.ResponseWriter that package http gives you, in order to
230// make the proxied object support the full method set of the proxied object.
231type http2FancyWriter struct {
232	basicWriter
233}
234
235func (f *http2FancyWriter) Flush() {
236	f.wroteHeader = true
237	fl := f.basicWriter.ResponseWriter.(http.Flusher)
238	fl.Flush()
239}
240
241var _ http.Flusher = &http2FancyWriter{}