···8 "strings"
9)
1011-var httpAcceptEncodingRegexp = regexp.MustCompile(`` +
12 // token optionally prefixed by whitespace
13 `^[ \t]*([a-zA-Z0-9$!#$%&'*+.^_\x60|~-]+)` +
14 // quality value prefixed by a semicolon optionally surrounded by whitespace
···17 `[ \t]*(?:,|$)`,
18)
1920-type httpEncoding struct {
21 code string
22 qval float64
23}
2425-type httpEncodings struct {
26- encodings []httpEncoding
27}
2829-func parseHTTPEncodings(headerValue string) (result httpEncodings) {
30 for headerValue != "" {
31- matches := httpAcceptEncodingRegexp.FindStringSubmatch(headerValue)
32 if matches == nil {
33- return httpEncodings{}
34 }
35- enc := httpEncoding{strings.ToLower(matches[1]), 1.0}
36 if matches[2] != "" {
37 enc.qval, _ = strconv.ParseFloat(matches[2], 64)
38 }
···5152// Negotiate returns the most preferred encoding that is acceptable by the
53// client, or an empty string if no encodings are acceptable.
54-func (e *httpEncodings) Negotiate(codes ...string) string {
55- prefs := make(map[string]float64, len(codes))
56- for _, code := range codes {
57 prefs[code] = 0
58 }
59 implicitIdentity := true
···73 if _, ok := prefs["identity"]; ok && implicitIdentity {
74 prefs["identity"] = -1 // sort last
75 }
76- encs := make([]httpEncoding, len(codes))
77- for idx, code := range codes {
78- encs[idx] = httpEncoding{code, prefs[code]}
79 }
80- slices.SortStableFunc(encs, func(a, b httpEncoding) int {
81 return -cmp.Compare(a.qval, b.qval)
82 })
83 for _, enc := range encs {
···8 "strings"
9)
1011+var httpAcceptRegexp = regexp.MustCompile(`` +
12 // token optionally prefixed by whitespace
13 `^[ \t]*([a-zA-Z0-9$!#$%&'*+.^_\x60|~-]+)` +
14 // quality value prefixed by a semicolon optionally surrounded by whitespace
···17 `[ \t]*(?:,|$)`,
18)
1920+type httpAcceptOffer struct {
21 code string
22 qval float64
23}
2425+type HTTPEncodings struct {
26+ encodings []httpAcceptOffer
27}
2829+func ParseHTTPAcceptEncoding(headerValue string) (result HTTPEncodings) {
30 for headerValue != "" {
31+ matches := httpAcceptRegexp.FindStringSubmatch(headerValue)
32 if matches == nil {
33+ return HTTPEncodings{}
34 }
35+ enc := httpAcceptOffer{strings.ToLower(matches[1]), 1.0}
36 if matches[2] != "" {
37 enc.qval, _ = strconv.ParseFloat(matches[2], 64)
38 }
···5152// Negotiate returns the most preferred encoding that is acceptable by the
53// client, or an empty string if no encodings are acceptable.
54+func (e *HTTPEncodings) Negotiate(offers ...string) string {
55+ prefs := make(map[string]float64, len(offers))
56+ for _, code := range offers {
57 prefs[code] = 0
58 }
59 implicitIdentity := true
···73 if _, ok := prefs["identity"]; ok && implicitIdentity {
74 prefs["identity"] = -1 // sort last
75 }
76+ encs := make([]httpAcceptOffer, len(offers))
77+ for idx, code := range offers {
78+ encs[idx] = httpAcceptOffer{code, prefs[code]}
79 }
80+ slices.SortStableFunc(encs, func(a, b httpAcceptOffer) int {
81 return -cmp.Compare(a.qval, b.qval)
82 })
83 for _, enc := range encs {
+8-6
src/pages.go
···214215 // we only offer `/.git-pages/archive.tar` and not the `.tar.gz`/`.tar.zst` variants
216 // because HTTP can already request compression using the `Content-Encoding` mechanism
217- acceptedEncodings := parseHTTPEncodings(r.Header.Get("Accept-Encoding"))
218 negotiated := acceptedEncodings.Negotiate("zstd", "gzip", "identity")
219 if negotiated != "" {
220 w.Header().Set("Content-Encoding", negotiated)
···322 defer closer.Close()
323 }
324325- acceptedEncodings := parseHTTPEncodings(r.Header.Get("Accept-Encoding"))
0326 negotiatedEncoding := true
327 switch entry.GetTransform() {
328 case Transform_Identity:
329- switch acceptedEncodings.Negotiate("identity") {
0330 case "identity":
331 serveEncodingCount.
332 With(prometheus.Labels{"transform": "identity", "negotiated": "identity"}).
···338 Inc()
339 }
340 case Transform_Zstd:
341- supported := []string{"zstd", "identity"}
342 if entry.ContentType == nil {
343 // If Content-Type is unset, `http.ServeContent` will try to sniff
344 // the file contents. That won't work if it's compressed.
345- supported = []string{"identity"}
346 }
347- switch acceptedEncodings.Negotiate(supported...) {
348 case "zstd":
349 // Set Content-Length ourselves since `http.ServeContent` only sets
350 // it if Content-Encoding is unset or if it's a range request.
···214215 // we only offer `/.git-pages/archive.tar` and not the `.tar.gz`/`.tar.zst` variants
216 // because HTTP can already request compression using the `Content-Encoding` mechanism
217+ acceptedEncodings := ParseHTTPAcceptEncoding(r.Header.Get("Accept-Encoding"))
218 negotiated := acceptedEncodings.Negotiate("zstd", "gzip", "identity")
219 if negotiated != "" {
220 w.Header().Set("Content-Encoding", negotiated)
···322 defer closer.Close()
323 }
324325+ offeredEncodings := []string{}
326+ acceptedEncodings := ParseHTTPAcceptEncoding(r.Header.Get("Accept-Encoding"))
327 negotiatedEncoding := true
328 switch entry.GetTransform() {
329 case Transform_Identity:
330+ offeredEncodings = []string{"identity"}
331+ switch acceptedEncodings.Negotiate(offeredEncodings...) {
332 case "identity":
333 serveEncodingCount.
334 With(prometheus.Labels{"transform": "identity", "negotiated": "identity"}).
···340 Inc()
341 }
342 case Transform_Zstd:
343+ offeredEncodings = []string{"zstd", "identity"}
344 if entry.ContentType == nil {
345 // If Content-Type is unset, `http.ServeContent` will try to sniff
346 // the file contents. That won't work if it's compressed.
347+ offeredEncodings = []string{"identity"}
348 }
349+ switch acceptedEncodings.Negotiate(offeredEncodings...) {
350 case "zstd":
351 // Set Content-Length ourselves since `http.ServeContent` only sets
352 // it if Content-Encoding is unset or if it's a range request.