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