http.go 4.4 KB

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