forked from srfrog/go-relax
-
Notifications
You must be signed in to change notification settings - Fork 0
/
filter_gzip.go
114 lines (97 loc) · 3.17 KB
/
filter_gzip.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
// Copyright 2014 Codehack.com All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package relax
import (
"compress/gzip"
"strings"
)
// FilterGzip compresses the response with gzip encoding, if the client
// indicates support for it.
type FilterGzip struct {
// CompressionLevel specifies the level of compression used for gzip.
// Value must be between -1 (gzip.DefaultCompression) to 9 (gzip.BestCompression)
// A value of 0 (gzip.DisableCompression) will disable compression.
// Defaults to ``gzip.BestSpeed``
CompressionLevel int
// MinLength is the minimum content length, in bytes, required to do compression.
// Defaults to 100
MinLength int
}
/*
Run runs the filter and passes down the following Info:
ctx.Info.Get("content.gzip") // boolean; whether gzip actually happened.
The info passed is used by ETag to generate distinct entity-tags for gzip'ed
content.
*/
func (f *FilterGzip) Run(next HandlerFunc) HandlerFunc {
if f.CompressionLevel == 0 || f.CompressionLevel > gzip.BestCompression {
f.CompressionLevel = gzip.BestSpeed
}
if f.MinLength == 0 {
f.MinLength = 100
}
return func(ctx *Context) {
ctx.Info.Set("content.gzip", false)
ctx.Header().Add("Vary", "Accept-Encoding")
encodings := ctx.Request.Header.Get("Accept-Encoding")
if f.CompressionLevel == 0 || !(strings.Contains(encodings, "gzip") || encodings == "*") {
next(ctx)
return
}
// don't compress ranged responses.
if ctx.Request.Header.Get("If-Range") != "" {
next(ctx)
return
}
// Check for encoding preferences.
if prefs, err := ParsePreferences(encodings); err == nil && len(prefs) > 1 {
if xgzip, ok := prefs["x-gzip"]; ok {
prefs["gzip"] = xgzip
}
for _, value := range prefs {
// Client prefers another encoding better, we may support it in another
// filter. Let that filter handle it instead.
if value > prefs["gzip"] {
next(ctx)
return
}
}
}
next(ctx.Capture()) // start buffering
switch {
// this might happen when FilterETag runs after GZip
case ctx.Buffer.Status() == 304:
ctx.WriteHeader(304)
break
case ctx.Buffer.Status() == 204, ctx.Buffer.Status() > 299, ctx.Buffer.Status() < 200:
break
case ctx.Buffer.Header().Get("Content-Range") != "":
break
case strings.Contains(ctx.Buffer.Header().Get("Content-Encoding"), "gzip"):
break
case ctx.Buffer.Len() < f.MinLength:
break
default:
gz, err := gzip.NewWriterLevel(ctx.ResponseWriter, f.CompressionLevel)
if err != nil {
ctx.Release()
return
}
defer gz.Close()
// Only set if gzip actually happened.
ctx.Info.Set("content.gzip", true)
ctx.Buffer.Header().Add("Content-Encoding", "gzip")
// Check if ETag is set, alter it to reflect gzip content.
if etag := ctx.Buffer.Header().Get("ETag"); etag != "" && !strings.Contains(etag, "gzip") {
etagGzip := strings.TrimSuffix(etag, `"`) + `-gzip"`
ctx.Buffer.Header().Set("ETag", etagGzip)
}
ctx.Buffer.FlushHeader(ctx.ResponseWriter)
ctx.Buffer.WriteTo(gz)
ctx.Buffer.Free()
ctx.Buffer = nil // stop Context.Relase from flushing
}
ctx.Release() // finish buffering
}
}