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