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