source: code/trunk/vendor/github.com/andybalholm/brotli/http.go@ 145

Last change on this file since 145 was 145, checked in by Izuru Yakumo, 22 months ago

Updated the Makefile and vendored depedencies

Signed-off-by: Izuru Yakumo <yakumo.izuru@…>

File size: 4.4 KB
Line 
1package brotli
2
3import (
4 "compress/gzip"
5 "io"
6 "net/http"
7 "strings"
8)
9
10// HTTPCompressor chooses a compression method (brotli, gzip, or none) based on
11// the Accept-Encoding header, sets the Content-Encoding header, and returns a
12// WriteCloser that implements that compression. The Close method must be called
13// before the current HTTP handler returns.
14//
15// Due to https://github.com/golang/go/issues/31753, the response will not be
16// compressed unless you set a Content-Type header before you call
17// HTTPCompressor.
18func HTTPCompressor(w http.ResponseWriter, r *http.Request) io.WriteCloser {
19 if w.Header().Get("Content-Type") == "" {
20 return nopCloser{w}
21 }
22
23 if w.Header().Get("Vary") == "" {
24 w.Header().Set("Vary", "Accept-Encoding")
25 }
26
27 encoding := negotiateContentEncoding(r, []string{"br", "gzip"})
28 switch encoding {
29 case "br":
30 w.Header().Set("Content-Encoding", "br")
31 return NewWriter(w)
32 case "gzip":
33 w.Header().Set("Content-Encoding", "gzip")
34 return gzip.NewWriter(w)
35 }
36 return nopCloser{w}
37}
38
39// negotiateContentEncoding returns the best offered content encoding for the
40// request's Accept-Encoding header. If two offers match with equal weight and
41// then the offer earlier in the list is preferred. If no offers are
42// acceptable, then "" is returned.
43func negotiateContentEncoding(r *http.Request, offers []string) string {
44 bestOffer := "identity"
45 bestQ := -1.0
46 specs := parseAccept(r.Header, "Accept-Encoding")
47 for _, offer := range offers {
48 for _, spec := range specs {
49 if spec.Q > bestQ &&
50 (spec.Value == "*" || spec.Value == offer) {
51 bestQ = spec.Q
52 bestOffer = offer
53 }
54 }
55 }
56 if bestQ == 0 {
57 bestOffer = ""
58 }
59 return bestOffer
60}
61
62// acceptSpec describes an Accept* header.
63type acceptSpec struct {
64 Value string
65 Q float64
66}
67
68// parseAccept parses Accept* headers.
69func parseAccept(header http.Header, key string) (specs []acceptSpec) {
70loop:
71 for _, s := range header[key] {
72 for {
73 var spec acceptSpec
74 spec.Value, s = expectTokenSlash(s)
75 if spec.Value == "" {
76 continue loop
77 }
78 spec.Q = 1.0
79 s = skipSpace(s)
80 if strings.HasPrefix(s, ";") {
81 s = skipSpace(s[1:])
82 if !strings.HasPrefix(s, "q=") {
83 continue loop
84 }
85 spec.Q, s = expectQuality(s[2:])
86 if spec.Q < 0.0 {
87 continue loop
88 }
89 }
90 specs = append(specs, spec)
91 s = skipSpace(s)
92 if !strings.HasPrefix(s, ",") {
93 continue loop
94 }
95 s = skipSpace(s[1:])
96 }
97 }
98 return
99}
100
101func skipSpace(s string) (rest string) {
102 i := 0
103 for ; i < len(s); i++ {
104 if octetTypes[s[i]]&isSpace == 0 {
105 break
106 }
107 }
108 return s[i:]
109}
110
111func expectTokenSlash(s string) (token, rest string) {
112 i := 0
113 for ; i < len(s); i++ {
114 b := s[i]
115 if (octetTypes[b]&isToken == 0) && b != '/' {
116 break
117 }
118 }
119 return s[:i], s[i:]
120}
121
122func expectQuality(s string) (q float64, rest string) {
123 switch {
124 case len(s) == 0:
125 return -1, ""
126 case s[0] == '0':
127 q = 0
128 case s[0] == '1':
129 q = 1
130 default:
131 return -1, ""
132 }
133 s = s[1:]
134 if !strings.HasPrefix(s, ".") {
135 return q, s
136 }
137 s = s[1:]
138 i := 0
139 n := 0
140 d := 1
141 for ; i < len(s); i++ {
142 b := s[i]
143 if b < '0' || b > '9' {
144 break
145 }
146 n = n*10 + int(b) - '0'
147 d *= 10
148 }
149 return q + float64(n)/float64(d), s[i:]
150}
151
152// Octet types from RFC 2616.
153var octetTypes [256]octetType
154
155type octetType byte
156
157const (
158 isToken octetType = 1 << iota
159 isSpace
160)
161
162func init() {
163 // OCTET = <any 8-bit sequence of data>
164 // CHAR = <any US-ASCII character (octets 0 - 127)>
165 // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
166 // CR = <US-ASCII CR, carriage return (13)>
167 // LF = <US-ASCII LF, linefeed (10)>
168 // SP = <US-ASCII SP, space (32)>
169 // HT = <US-ASCII HT, horizontal-tab (9)>
170 // <"> = <US-ASCII double-quote mark (34)>
171 // CRLF = CR LF
172 // LWS = [CRLF] 1*( SP | HT )
173 // TEXT = <any OCTET except CTLs, but including LWS>
174 // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
175 // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
176 // token = 1*<any CHAR except CTLs or separators>
177 // qdtext = <any TEXT except <">>
178
179 for c := 0; c < 256; c++ {
180 var t octetType
181 isCtl := c <= 31 || c == 127
182 isChar := 0 <= c && c <= 127
183 isSeparator := strings.ContainsRune(" \t\"(),/:;<=>?@[]\\{}", rune(c))
184 if strings.ContainsRune(" \t\r\n", rune(c)) {
185 t |= isSpace
186 }
187 if isChar && !isCtl && !isSeparator {
188 t |= isToken
189 }
190 octetTypes[c] = t
191 }
192}
Note: See TracBrowser for help on using the repository browser.