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