@@ -40,7 +40,7 @@ type WriteFlusher interface {
40
40
}
41
41
42
42
type CompressResponseWriter struct {
43
- Header ServerHeader
43
+ Header * BufferedServerHeader
44
44
ControllerResponse * Response
45
45
OriginalWriter io.Writer
46
46
compressWriter WriteFlusher
@@ -51,19 +51,27 @@ type CompressResponseWriter struct {
51
51
closed bool
52
52
}
53
53
54
- // CompressFilter does compresssion of response body in gzip/deflate if
54
+ // CompressFilter does compression of response body in gzip/deflate if
55
55
// `results.compressed=true` in the app.conf
56
56
func CompressFilter (c * Controller , fc []Filter ) {
57
- if Config .BoolDefault ("results.compressed" , false ) {
57
+ if c . Response . ServerHeader != nil && Config .BoolDefault ("results.compressed" , false ) {
58
58
if c .Response .Status != http .StatusNoContent && c .Response .Status != http .StatusNotModified {
59
59
if found , compressType , compressWriter := detectCompressionType (c .Request , c .Response ); found {
60
- writer := CompressResponseWriter {c .Response .Out .Header (), c .Response , c .Response .Out .GetWriter (), compressWriter , compressType , false , make (chan bool , 1 ), nil , false }
61
- if w , ok := c .Response .Out .(http.CloseNotifier ); ok {
60
+ writer := CompressResponseWriter {
61
+ ControllerResponse :c .Response ,
62
+ OriginalWriter : c .Response .GetWriter (),
63
+ compressWriter : compressWriter ,
64
+ compressionType :compressType ,
65
+ headersWritten :false ,
66
+ closeNotify : make (chan bool , 1 ),
67
+ closed : false }
68
+ // Swap out the header with our own
69
+ writer .Header = NewBufferedServerHeader (c .Response .ServerHeader )
70
+ c .Response .ServerHeader = writer .Header
71
+ if w , ok := c .Response .GetWriter ().(http.CloseNotifier ); ok {
62
72
writer .parentNotify = w .CloseNotify ()
63
73
}
64
- c .Response .Out .SetWriter (& writer )
65
- // Block the results from writing there own header
66
- c .Response .headerWritten = true
74
+ c .Response .SetWriter (& writer )
67
75
}
68
76
} else {
69
77
TRACE .Printf ("Compression disabled for response status (%d)" , c .Response .Status )
@@ -101,6 +109,7 @@ func (c *CompressResponseWriter) prepareHeaders() {
101
109
c .compressionType = ""
102
110
}
103
111
}
112
+ c .Header .Release ()
104
113
}
105
114
106
115
func (c * CompressResponseWriter ) WriteHeader (status int ) {
@@ -110,12 +119,16 @@ func (c *CompressResponseWriter) WriteHeader(status int) {
110
119
}
111
120
112
121
func (c * CompressResponseWriter ) Close () error {
113
- if c .compressionType != "" {
122
+ if ! c .headersWritten {
123
+ c .prepareHeaders ()
124
+ }
125
+ if c .compressionType != "" {
114
126
c .Header .Del ("Content-Length" )
115
127
if err := c .compressWriter .Close (); err != nil {
116
- // TODO When writing directly to stream
128
+ // TODO When writing directly to stream, an error will be generated
117
129
ERROR .Println ("Error closing compress writer" ,c .compressionType , err )
118
130
}
131
+
119
132
}
120
133
// Non-blocking write to the closenotifier, if we for some reason should
121
134
// get called multiple times
@@ -128,6 +141,7 @@ func (c *CompressResponseWriter) Close() error {
128
141
}
129
142
130
143
func (c * CompressResponseWriter ) Write (b []byte ) (int , error ) {
144
+ println ("*** Write called" )
131
145
// Abort if parent has been closed
132
146
if c .parentNotify != nil {
133
147
select {
@@ -140,6 +154,7 @@ func (c *CompressResponseWriter) Write(b []byte) (int, error) {
140
154
if c .closed {
141
155
return 0 , io .ErrClosedPipe
142
156
}
157
+
143
158
if ! c .headersWritten {
144
159
c .prepareHeaders ()
145
160
c .headersWritten = true
@@ -154,7 +169,7 @@ func (c *CompressResponseWriter) Write(b []byte) (int, error) {
154
169
// from header "Accept-Encoding"
155
170
func detectCompressionType (req * Request , resp * Response ) (found bool , compressionType string , compressionKind WriteFlusher ) {
156
171
if Config .BoolDefault ("results.compressed" , false ) {
157
- acceptedEncodings := strings .Split (req .In . GetHeader (). Get ("Accept-Encoding" ), "," )
172
+ acceptedEncodings := strings .Split (req .HttpHeaderValue ("Accept-Encoding" ), "," )
158
173
159
174
largestQ := 0.0
160
175
chosenEncoding := len (compressionTypes )
@@ -223,12 +238,94 @@ func detectCompressionType(req *Request, resp *Response) (found bool, compressio
223
238
224
239
switch compressionType {
225
240
case "gzip" :
226
- compressionKind = gzip .NewWriter (resp .Out . GetWriter ())
241
+ compressionKind = gzip .NewWriter (resp .GetWriter ())
227
242
found = true
228
243
case "deflate" :
229
- compressionKind = zlib .NewWriter (resp .Out . GetWriter ())
244
+ compressionKind = zlib .NewWriter (resp .GetWriter ())
230
245
found = true
231
246
}
232
247
}
233
248
return
234
249
}
250
+
251
+
252
+ // This class will not send content out until the Released is called, from that point on it will act normally
253
+ // It implements all the ServerHeader
254
+ type BufferedServerHeader struct {
255
+ cookieList []string
256
+ headerMap map [string ][]string
257
+ status int
258
+ released bool
259
+ original ServerHeader
260
+ }
261
+ func NewBufferedServerHeader (o ServerHeader ) * BufferedServerHeader {
262
+ return & BufferedServerHeader {original :o ,headerMap :map [string ][]string {}}
263
+ }
264
+ func (bsh * BufferedServerHeader ) SetCookie (cookie string ) {
265
+ if bsh .released {
266
+ bsh .original .SetCookie (cookie )
267
+ } else {
268
+ bsh .cookieList = append (bsh .cookieList ,cookie )
269
+ }
270
+ }
271
+ func (bsh * BufferedServerHeader ) GetCookie (key string ) (value ServerCookie , err error ) {
272
+ return bsh .original .GetCookie (key )
273
+ }
274
+ func (bsh * BufferedServerHeader ) Set (key string , value string ){
275
+ if bsh .released {
276
+ bsh .original .Set (key ,value )
277
+ } else {
278
+ bsh .headerMap [key ]= []string {value }
279
+ }
280
+ }
281
+ func (bsh * BufferedServerHeader ) Add (key string , value string ) {
282
+ if bsh .released {
283
+ bsh .original .Set (key ,value )
284
+ } else {
285
+ old := []string {}
286
+ if v ,found := bsh .headerMap [key ];found {
287
+ old = v
288
+ }
289
+ bsh .headerMap [key ]= append (old ,value )
290
+ }
291
+
292
+ }
293
+ func (bsh * BufferedServerHeader ) Del (key string ){
294
+ if bsh .released {
295
+ bsh .original .Del (key )
296
+ } else {
297
+ delete (bsh .headerMap ,key )
298
+ }
299
+
300
+ }
301
+ func (bsh * BufferedServerHeader ) Get (key string ) (value string ){
302
+ if bsh .released {
303
+ value = bsh .original .Get (key )
304
+ } else {
305
+ if v ,found := bsh .headerMap [key ]; found && len (v )> 0 {
306
+ value = v [0 ]
307
+ } else {
308
+ value = bsh .original .Get (key )
309
+ }
310
+ }
311
+ return
312
+ }
313
+ func (bsh * BufferedServerHeader ) SetStatus (statusCode int ) {
314
+ if bsh .released {
315
+ bsh .original .SetStatus (statusCode )
316
+ } else {
317
+ bsh .status = statusCode
318
+ }
319
+ }
320
+ func (bsh * BufferedServerHeader ) Release () {
321
+ bsh .released = true
322
+ bsh .original .SetStatus (bsh .status )
323
+ for k ,v := range bsh .headerMap {
324
+ for _ ,r := range v {
325
+ bsh .original .Set (k , r )
326
+ }
327
+ }
328
+ for _ ,c := range bsh .cookieList {
329
+ bsh .original .SetCookie (c )
330
+ }
331
+ }
0 commit comments