193 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			193 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
package brotli
 | 
						|
 | 
						|
import (
 | 
						|
	"compress/gzip"
 | 
						|
	"io"
 | 
						|
	"net/http"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
// HTTPCompressor chooses a compression method (brotli, gzip, or none) based on
 | 
						|
// the Accept-Encoding header, sets the Content-Encoding header, and returns a
 | 
						|
// WriteCloser that implements that compression. The Close method must be called
 | 
						|
// before the current HTTP handler returns.
 | 
						|
//
 | 
						|
// Due to https://github.com/golang/go/issues/31753, the response will not be
 | 
						|
// compressed unless you set a Content-Type header before you call
 | 
						|
// HTTPCompressor.
 | 
						|
func HTTPCompressor(w http.ResponseWriter, r *http.Request) io.WriteCloser {
 | 
						|
	if w.Header().Get("Content-Type") == "" {
 | 
						|
		return nopCloser{w}
 | 
						|
	}
 | 
						|
 | 
						|
	if w.Header().Get("Vary") == "" {
 | 
						|
		w.Header().Set("Vary", "Accept-Encoding")
 | 
						|
	}
 | 
						|
 | 
						|
	encoding := negotiateContentEncoding(r, []string{"br", "gzip"})
 | 
						|
	switch encoding {
 | 
						|
	case "br":
 | 
						|
		w.Header().Set("Content-Encoding", "br")
 | 
						|
		return NewWriter(w)
 | 
						|
	case "gzip":
 | 
						|
		w.Header().Set("Content-Encoding", "gzip")
 | 
						|
		return gzip.NewWriter(w)
 | 
						|
	}
 | 
						|
	return nopCloser{w}
 | 
						|
}
 | 
						|
 | 
						|
// negotiateContentEncoding returns the best offered content encoding for the
 | 
						|
// request's Accept-Encoding header. If two offers match with equal weight and
 | 
						|
// then the offer earlier in the list is preferred. If no offers are
 | 
						|
// acceptable, then "" is returned.
 | 
						|
func negotiateContentEncoding(r *http.Request, offers []string) string {
 | 
						|
	bestOffer := "identity"
 | 
						|
	bestQ := -1.0
 | 
						|
	specs := parseAccept(r.Header, "Accept-Encoding")
 | 
						|
	for _, offer := range offers {
 | 
						|
		for _, spec := range specs {
 | 
						|
			if spec.Q > bestQ &&
 | 
						|
				(spec.Value == "*" || spec.Value == offer) {
 | 
						|
				bestQ = spec.Q
 | 
						|
				bestOffer = offer
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if bestQ == 0 {
 | 
						|
		bestOffer = ""
 | 
						|
	}
 | 
						|
	return bestOffer
 | 
						|
}
 | 
						|
 | 
						|
// acceptSpec describes an Accept* header.
 | 
						|
type acceptSpec struct {
 | 
						|
	Value string
 | 
						|
	Q     float64
 | 
						|
}
 | 
						|
 | 
						|
// parseAccept parses Accept* headers.
 | 
						|
func parseAccept(header http.Header, key string) (specs []acceptSpec) {
 | 
						|
loop:
 | 
						|
	for _, s := range header[key] {
 | 
						|
		for {
 | 
						|
			var spec acceptSpec
 | 
						|
			spec.Value, s = expectTokenSlash(s)
 | 
						|
			if spec.Value == "" {
 | 
						|
				continue loop
 | 
						|
			}
 | 
						|
			spec.Q = 1.0
 | 
						|
			s = skipSpace(s)
 | 
						|
			if strings.HasPrefix(s, ";") {
 | 
						|
				s = skipSpace(s[1:])
 | 
						|
				if !strings.HasPrefix(s, "q=") {
 | 
						|
					continue loop
 | 
						|
				}
 | 
						|
				spec.Q, s = expectQuality(s[2:])
 | 
						|
				if spec.Q < 0.0 {
 | 
						|
					continue loop
 | 
						|
				}
 | 
						|
			}
 | 
						|
			specs = append(specs, spec)
 | 
						|
			s = skipSpace(s)
 | 
						|
			if !strings.HasPrefix(s, ",") {
 | 
						|
				continue loop
 | 
						|
			}
 | 
						|
			s = skipSpace(s[1:])
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func skipSpace(s string) (rest string) {
 | 
						|
	i := 0
 | 
						|
	for ; i < len(s); i++ {
 | 
						|
		if octetTypes[s[i]]&isSpace == 0 {
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return s[i:]
 | 
						|
}
 | 
						|
 | 
						|
func expectTokenSlash(s string) (token, rest string) {
 | 
						|
	i := 0
 | 
						|
	for ; i < len(s); i++ {
 | 
						|
		b := s[i]
 | 
						|
		if (octetTypes[b]&isToken == 0) && b != '/' {
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return s[:i], s[i:]
 | 
						|
}
 | 
						|
 | 
						|
func expectQuality(s string) (q float64, rest string) {
 | 
						|
	switch {
 | 
						|
	case len(s) == 0:
 | 
						|
		return -1, ""
 | 
						|
	case s[0] == '0':
 | 
						|
		q = 0
 | 
						|
	case s[0] == '1':
 | 
						|
		q = 1
 | 
						|
	default:
 | 
						|
		return -1, ""
 | 
						|
	}
 | 
						|
	s = s[1:]
 | 
						|
	if !strings.HasPrefix(s, ".") {
 | 
						|
		return q, s
 | 
						|
	}
 | 
						|
	s = s[1:]
 | 
						|
	i := 0
 | 
						|
	n := 0
 | 
						|
	d := 1
 | 
						|
	for ; i < len(s); i++ {
 | 
						|
		b := s[i]
 | 
						|
		if b < '0' || b > '9' {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		n = n*10 + int(b) - '0'
 | 
						|
		d *= 10
 | 
						|
	}
 | 
						|
	return q + float64(n)/float64(d), s[i:]
 | 
						|
}
 | 
						|
 | 
						|
// Octet types from RFC 2616.
 | 
						|
var octetTypes [256]octetType
 | 
						|
 | 
						|
type octetType byte
 | 
						|
 | 
						|
const (
 | 
						|
	isToken octetType = 1 << iota
 | 
						|
	isSpace
 | 
						|
)
 | 
						|
 | 
						|
func init() {
 | 
						|
	// OCTET      = <any 8-bit sequence of data>
 | 
						|
	// CHAR       = <any US-ASCII character (octets 0 - 127)>
 | 
						|
	// CTL        = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
 | 
						|
	// CR         = <US-ASCII CR, carriage return (13)>
 | 
						|
	// LF         = <US-ASCII LF, linefeed (10)>
 | 
						|
	// SP         = <US-ASCII SP, space (32)>
 | 
						|
	// HT         = <US-ASCII HT, horizontal-tab (9)>
 | 
						|
	// <">        = <US-ASCII double-quote mark (34)>
 | 
						|
	// CRLF       = CR LF
 | 
						|
	// LWS        = [CRLF] 1*( SP | HT )
 | 
						|
	// TEXT       = <any OCTET except CTLs, but including LWS>
 | 
						|
	// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
 | 
						|
	//              | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
 | 
						|
	// token      = 1*<any CHAR except CTLs or separators>
 | 
						|
	// qdtext     = <any TEXT except <">>
 | 
						|
 | 
						|
	for c := 0; c < 256; c++ {
 | 
						|
		var t octetType
 | 
						|
		isCtl := c <= 31 || c == 127
 | 
						|
		isChar := 0 <= c && c <= 127
 | 
						|
		isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
 | 
						|
		if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
 | 
						|
			t |= isSpace
 | 
						|
		}
 | 
						|
		if isChar && !isCtl && !isSeparator {
 | 
						|
			t |= isToken
 | 
						|
		}
 | 
						|
		octetTypes[c] = t
 | 
						|
	}
 | 
						|
}
 |