-
Notifications
You must be signed in to change notification settings - Fork 2
/
responsewriter.go
132 lines (122 loc) · 3.41 KB
/
responsewriter.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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package mango
import (
"compress/flate"
"compress/gzip"
"fmt"
"io"
"net/http"
"strings"
)
// NewResponseWriter returns an initialized instance of a ResponseWriter.
// If compMinLen is set above zero, then responses may be compressed if:
//
// a) the body is longer than compMinLen bytes,
// b) a suitable format has been requested in the accept-encoding header
// (currently this must be gzip or deflate),
// c) headers have not already been sent using the WriteHeader method
func NewResponseWriter(w http.ResponseWriter, acceptedEncoding string, compMinLen int) *ResponseWriter {
wr := ResponseWriter{
rw: w,
status: 200,
acceptedEncoding: acceptedEncoding,
compMinLength: compMinLen,
}
return &wr
}
// ResponseWriter implements the http.ResponseWriter interface and
// wraps the ResponseWriter provided to the ServeHTTP method. It's
// primary purpose is to collect data on the information written to provide
// more informative logging, but is used also for response compression.
type ResponseWriter struct {
rw http.ResponseWriter
byteCount int
status int
readonly bool
responded bool
headersSent bool
compMinLength int
acceptedEncoding string
}
// Header returns the header map that will be sent by
// WriteHeader.
// See http.ResponseWriter interface for more information.
func (r *ResponseWriter) Header() http.Header {
if r.headersSent || r.readonly {
// return a copy of the map
h := http.Header{}
origMap := map[string][]string(r.rw.Header())
for k, s := range origMap {
for _, v := range s {
h.Add(k, v)
}
}
return h
}
return r.rw.Header()
}
// WriteHeader sends an HTTP response header with status code
// to the underlying http.ResponseWriter. Status code is
// recorded to provide more informative logging.
// See http.ResponseWriter interface for more information.
func (r *ResponseWriter) WriteHeader(status int) {
if r.headersSent || r.readonly {
return
}
r.headersSent = true
r.responded = true
r.status = status
r.rw.WriteHeader(status)
}
func (r *ResponseWriter) compressor(w io.Writer, l int) io.WriteCloser {
// If headers sent then we're too late for compression.
if r.headersSent {
return nil
}
if r.compMinLength == 0 || l < r.compMinLength {
return nil
}
e := strings.Split(r.acceptedEncoding, ",")
for _, ae := range e {
switch strings.TrimSpace(ae) {
case "gzip":
c := gzip.NewWriter(w)
r.rw.Header().Set("Content-Encoding", "gzip")
return c
case "deflate":
c, err := flate.NewWriter(w, flate.DefaultCompression)
if err != nil {
return nil
}
r.rw.Header().Set("Content-Encoding", "deflate")
return c
}
}
return nil
}
// Write writes the data to the underlying http.ResponseWriter
// connection as part of an HTTP reply. The cumulative number
// of bytes is recorded to provide more informative logging.
// See http.ResponseWriter interface for more information.
func (r *ResponseWriter) Write(b []byte) (int, error) {
if r.readonly {
return 0, fmt.Errorf("write method has been called already")
}
reader, writer := io.Pipe()
go func() {
defer writer.Close()
c := r.compressor(writer, len(b))
if c != nil {
defer c.Close()
c.Write(b)
} else {
writer.Write(b)
}
}()
// TODO: check the error before updating anything
bc, err := io.Copy(r.rw, reader)
i := int(bc)
r.byteCount += i
r.headersSent = true
r.responded = true
return i, err
}