github.com/brycereitano/goa@v0.0.0-20170315073847-8ffa6c85e265/middleware/gzip/middleware.go (about)

     1  package gzip
     2  
     3  import (
     4  	"compress/gzip"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"strings"
     8  	"sync"
     9  
    10  	"golang.org/x/net/context"
    11  
    12  	"github.com/goadesign/goa"
    13  )
    14  
    15  // These compression constants are copied from the compress/gzip package.
    16  const (
    17  	encodingGzip = "gzip"
    18  
    19  	headerAcceptEncoding  = "Accept-Encoding"
    20  	headerContentEncoding = "Content-Encoding"
    21  	headerContentLength   = "Content-Length"
    22  	headerContentType     = "Content-Type"
    23  	headerVary            = "Vary"
    24  	headerSecWebSocketKey = "Sec-WebSocket-Key"
    25  )
    26  
    27  // gzipResponseWriter wraps the http.ResponseWriter to provide gzip
    28  // capabilities.
    29  type gzipResponseWriter struct {
    30  	http.ResponseWriter
    31  	gzw *gzip.Writer
    32  }
    33  
    34  // Write writes bytes to the gzip.Writer. It will also set the Content-Type
    35  // header using the net/http library content type detection if the Content-Type
    36  // header was not set yet.
    37  func (grw gzipResponseWriter) Write(b []byte) (int, error) {
    38  	if len(grw.Header().Get(headerContentType)) == 0 {
    39  		grw.Header().Set(headerContentType, http.DetectContentType(b))
    40  	}
    41  	return grw.gzw.Write(b)
    42  }
    43  
    44  // handler struct contains the ServeHTTP method
    45  type handler struct {
    46  	pool sync.Pool
    47  }
    48  
    49  // Middleware encodes the response using Gzip encoding and sets all the appropriate
    50  // headers. If the Content-Type is not set, it will be set by calling
    51  // http.DetectContentType on the data being written.
    52  func Middleware(level int) goa.Middleware {
    53  	gzipPool := sync.Pool{
    54  		New: func() interface{} {
    55  			gz, err := gzip.NewWriterLevel(ioutil.Discard, level)
    56  			if err != nil {
    57  				panic(err)
    58  			}
    59  			return gz
    60  		},
    61  	}
    62  	return func(h goa.Handler) goa.Handler {
    63  		return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) (err error) {
    64  			// Skip compression if the client doesn't accept gzip encoding, is
    65  			// requesting a WebSocket or the data is already compressed.
    66  			if !strings.Contains(req.Header.Get(headerAcceptEncoding), encodingGzip) ||
    67  				len(req.Header.Get(headerSecWebSocketKey)) > 0 ||
    68  				req.Header.Get(headerContentEncoding) == encodingGzip {
    69  				return h(ctx, rw, req)
    70  			}
    71  
    72  			// Set the appropriate gzip headers.
    73  			resp := goa.ContextResponse(ctx)
    74  			resp.Header().Set(headerContentEncoding, encodingGzip)
    75  			resp.Header().Set(headerVary, headerAcceptEncoding)
    76  
    77  			// Retrieve gzip writer from the pool. Reset it to use the ResponseWriter.
    78  			// This allows us to re-use an already allocated buffer rather than
    79  			// allocating a new buffer for every request.
    80  			gz := gzipPool.Get().(*gzip.Writer)
    81  
    82  			// Get the original http.ResponseWriter
    83  			w := resp.SwitchWriter(nil)
    84  			// Reset our gzip writer to use the http.ResponseWriter
    85  			gz.Reset(w)
    86  
    87  			// Wrap the original http.ResponseWriter with our gzipResponseWriter
    88  			grw := gzipResponseWriter{
    89  				ResponseWriter: w,
    90  				gzw:            gz,
    91  			}
    92  
    93  			// Set the new http.ResponseWriter
    94  			resp.SwitchWriter(grw)
    95  
    96  			// Call the next handler supplying the gzipResponseWriter instead of
    97  			// the original.
    98  			err = h(ctx, rw, req)
    99  			if err != nil {
   100  				return
   101  			}
   102  
   103  			// Delete the content length after we know we have been written to.
   104  			grw.Header().Del(headerContentLength)
   105  			gz.Close()
   106  			gzipPool.Put(gz)
   107  			return
   108  		}
   109  	}
   110  }