[145] | 1 | package brotli
|
---|
| 2 |
|
---|
| 3 | import (
|
---|
| 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.
|
---|
| 18 | func 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.
|
---|
| 43 | func 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.
|
---|
| 63 | type acceptSpec struct {
|
---|
| 64 | Value string
|
---|
| 65 | Q float64
|
---|
| 66 | }
|
---|
| 67 |
|
---|
| 68 | // parseAccept parses Accept* headers.
|
---|
| 69 | func parseAccept(header http.Header, key string) (specs []acceptSpec) {
|
---|
| 70 | loop:
|
---|
| 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 |
|
---|
| 101 | func 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 |
|
---|
| 111 | func 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 |
|
---|
| 122 | func 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.
|
---|
| 153 | var octetTypes [256]octetType
|
---|
| 154 |
|
---|
| 155 | type octetType byte
|
---|
| 156 |
|
---|
| 157 | const (
|
---|
| 158 | isToken octetType = 1 << iota
|
---|
| 159 | isSpace
|
---|
| 160 | )
|
---|
| 161 |
|
---|
| 162 | func 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 | }
|
---|