Skip to content

Commit 5f4456c

Browse files
committed
feat: Support zstd encoding
This allows endpoints to respond with zstd compressed metric data, if the requester supports it. I have imported a content-encoding parser from https://github.com/golang/gddo which is an archived repository to support different content-encoding headers. Signed-off-by: Manuel Rüger <[email protected]>
1 parent 36b9f46 commit 5f4456c

File tree

10 files changed

+440
-22
lines changed

10 files changed

+440
-22
lines changed

go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ go 1.20
55
require (
66
github.com/beorn7/perks v1.0.1
77
github.com/cespare/xxhash/v2 v2.3.0
8+
github.com/google/go-cmp v0.6.0
89
github.com/json-iterator/go v1.1.12
10+
github.com/klauspost/compress v1.17.8
911
github.com/prometheus/client_model v0.6.1
1012
github.com/prometheus/common v0.53.0
1113
github.com/prometheus/procfs v0.13.0
@@ -20,6 +22,7 @@ require (
2022
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
2123
github.com/modern-go/reflect2 v1.0.2 // indirect
2224
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
25+
github.com/stretchr/testify v1.4.0 // indirect
2326
golang.org/x/net v0.23.0 // indirect
2427
golang.org/x/oauth2 v0.18.0 // indirect
2528
golang.org/x/text v0.14.0 // indirect

go.sum

+6-1
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg
1212
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
1313
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
1414
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
15+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
1516
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
1617
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
1718
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
1819
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
1920
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
21+
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
22+
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
2023
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
2124
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
2225
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -40,8 +43,9 @@ github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43Z
4043
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
4144
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
4245
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
43-
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
4446
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
47+
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
48+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
4549
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
4650
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
4751
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
@@ -65,5 +69,6 @@ google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH
6569
google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
6670
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
6771
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
72+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
6873
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
6974
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Copyright (c) 2013 The Go Authors. All rights reserved.
2+
3+
Redistribution and use in source and binary forms, with or without
4+
modification, are permitted provided that the following conditions are
5+
met:
6+
7+
* Redistributions of source code must retain the above copyright
8+
notice, this list of conditions and the following disclaimer.
9+
* Redistributions in binary form must reproduce the above
10+
copyright notice, this list of conditions and the following disclaimer
11+
in the documentation and/or other materials provided with the
12+
distribution.
13+
* Neither the name of Google Inc. nor the names of its
14+
contributors may be used to endorse or promote products derived from
15+
this software without specific prior written permission.
16+
17+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This source code is a stripped down version from the archived repository https://github.com/golang/gddo and licensed under BSD.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Copyright 2013 The Go Authors. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file or at
5+
// https://developers.google.com/open-source/licenses/bsd.
6+
7+
// Package header provides functions for parsing HTTP headers.
8+
package header
9+
10+
import (
11+
"net/http"
12+
"strings"
13+
)
14+
15+
// Octet types from RFC 2616.
16+
var octetTypes [256]octetType
17+
18+
type octetType byte
19+
20+
const (
21+
isToken octetType = 1 << iota
22+
isSpace
23+
)
24+
25+
func init() {
26+
// OCTET = <any 8-bit sequence of data>
27+
// CHAR = <any US-ASCII character (octets 0 - 127)>
28+
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
29+
// CR = <US-ASCII CR, carriage return (13)>
30+
// LF = <US-ASCII LF, linefeed (10)>
31+
// SP = <US-ASCII SP, space (32)>
32+
// HT = <US-ASCII HT, horizontal-tab (9)>
33+
// <"> = <US-ASCII double-quote mark (34)>
34+
// CRLF = CR LF
35+
// LWS = [CRLF] 1*( SP | HT )
36+
// TEXT = <any OCTET except CTLs, but including LWS>
37+
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
38+
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
39+
// token = 1*<any CHAR except CTLs or separators>
40+
// qdtext = <any TEXT except <">>
41+
42+
for c := 0; c < 256; c++ {
43+
var t octetType
44+
isCtl := c <= 31 || c == 127
45+
isChar := 0 <= c && c <= 127
46+
isSeparator := strings.ContainsRune(" \t\"(),/:;<=>?@[]\\{}", rune(c))
47+
if strings.ContainsRune(" \t\r\n", rune(c)) {
48+
t |= isSpace
49+
}
50+
if isChar && !isCtl && !isSeparator {
51+
t |= isToken
52+
}
53+
octetTypes[c] = t
54+
}
55+
}
56+
57+
// AcceptSpec describes an Accept* header.
58+
type AcceptSpec struct {
59+
Value string
60+
Q float64
61+
}
62+
63+
// ParseAccept parses Accept* headers.
64+
func ParseAccept(header http.Header, key string) (specs []AcceptSpec) {
65+
loop:
66+
for _, s := range header[key] {
67+
for {
68+
var spec AcceptSpec
69+
spec.Value, s = expectTokenSlash(s)
70+
if spec.Value == "" {
71+
continue loop
72+
}
73+
spec.Q = 1.0
74+
s = skipSpace(s)
75+
if strings.HasPrefix(s, ";") {
76+
s = skipSpace(s[1:])
77+
if !strings.HasPrefix(s, "q=") {
78+
continue loop
79+
}
80+
spec.Q, s = expectQuality(s[2:])
81+
if spec.Q < 0.0 {
82+
continue loop
83+
}
84+
}
85+
specs = append(specs, spec)
86+
s = skipSpace(s)
87+
if !strings.HasPrefix(s, ",") {
88+
continue loop
89+
}
90+
s = skipSpace(s[1:])
91+
}
92+
}
93+
return
94+
}
95+
96+
func skipSpace(s string) (rest string) {
97+
i := 0
98+
for ; i < len(s); i++ {
99+
if octetTypes[s[i]]&isSpace == 0 {
100+
break
101+
}
102+
}
103+
return s[i:]
104+
}
105+
106+
func expectTokenSlash(s string) (token, rest string) {
107+
i := 0
108+
for ; i < len(s); i++ {
109+
b := s[i]
110+
if (octetTypes[b]&isToken == 0) && b != '/' {
111+
break
112+
}
113+
}
114+
return s[:i], s[i:]
115+
}
116+
117+
func expectQuality(s string) (q float64, rest string) {
118+
switch {
119+
case len(s) == 0:
120+
return -1, ""
121+
case s[0] == '0':
122+
q = 0
123+
case s[0] == '1':
124+
q = 1
125+
default:
126+
return -1, ""
127+
}
128+
s = s[1:]
129+
if !strings.HasPrefix(s, ".") {
130+
return q, s
131+
}
132+
s = s[1:]
133+
i := 0
134+
n := 0
135+
d := 1
136+
for ; i < len(s); i++ {
137+
b := s[i]
138+
if b < '0' || b > '9' {
139+
break
140+
}
141+
n = n*10 + int(b) - '0'
142+
d *= 10
143+
}
144+
return q + float64(n)/float64(d), s[i:]
145+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2013 The Go Authors. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file or at
5+
// https://developers.google.com/open-source/licenses/bsd.
6+
7+
package header
8+
9+
import (
10+
"net/http"
11+
"testing"
12+
13+
"github.com/google/go-cmp/cmp"
14+
)
15+
16+
var parseAcceptTests = []struct {
17+
s string
18+
expected []AcceptSpec
19+
}{
20+
{"text/html", []AcceptSpec{{"text/html", 1}}},
21+
{"text/html; q=0", []AcceptSpec{{"text/html", 0}}},
22+
{"text/html; q=0.0", []AcceptSpec{{"text/html", 0}}},
23+
{"text/html; q=1", []AcceptSpec{{"text/html", 1}}},
24+
{"text/html; q=1.0", []AcceptSpec{{"text/html", 1}}},
25+
{"text/html; q=0.1", []AcceptSpec{{"text/html", 0.1}}},
26+
{"text/html;q=0.1", []AcceptSpec{{"text/html", 0.1}}},
27+
{"text/html, text/plain", []AcceptSpec{{"text/html", 1}, {"text/plain", 1}}},
28+
{"text/html; q=0.1, text/plain", []AcceptSpec{{"text/html", 0.1}, {"text/plain", 1}}},
29+
{"iso-8859-5, unicode-1-1;q=0.8,iso-8859-1", []AcceptSpec{{"iso-8859-5", 1}, {"unicode-1-1", 0.8}, {"iso-8859-1", 1}}},
30+
{"iso-8859-1", []AcceptSpec{{"iso-8859-1", 1}}},
31+
{"*", []AcceptSpec{{"*", 1}}},
32+
{"da, en-gb;q=0.8, en;q=0.7", []AcceptSpec{{"da", 1}, {"en-gb", 0.8}, {"en", 0.7}}},
33+
{"da, q, en-gb;q=0.8", []AcceptSpec{{"da", 1}, {"q", 1}, {"en-gb", 0.8}}},
34+
{"image/png, image/*;q=0.5", []AcceptSpec{{"image/png", 1}, {"image/*", 0.5}}},
35+
36+
// bad cases
37+
{"value1; q=0.1.2", []AcceptSpec{{"value1", 0.1}}},
38+
{"da, en-gb;q=foo", []AcceptSpec{{"da", 1}}},
39+
}
40+
41+
func TestParseAccept(t *testing.T) {
42+
for _, tt := range parseAcceptTests {
43+
header := http.Header{"Accept": {tt.s}}
44+
actual := ParseAccept(header, "Accept")
45+
if !cmp.Equal(actual, tt.expected) {
46+
t.Errorf("ParseAccept(h, %q)=%v, want %v", tt.s, actual, tt.expected)
47+
}
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2013 The Go Authors. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file or at
5+
// https://developers.google.com/open-source/licenses/bsd.
6+
7+
package httputil
8+
9+
import (
10+
"net/http"
11+
12+
"github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header"
13+
)
14+
15+
// NegotiateContentEncoding returns the best offered content encoding for the
16+
// request's Accept-Encoding header. If two offers match with equal weight and
17+
// then the offer earlier in the list is preferred. If no offers are
18+
// acceptable, then "" is returned.
19+
func NegotiateContentEncoding(r *http.Request, offers []string) string {
20+
bestOffer := "identity"
21+
bestQ := -1.0
22+
specs := header.ParseAccept(r.Header, "Accept-Encoding")
23+
for _, offer := range offers {
24+
for _, spec := range specs {
25+
if spec.Q > bestQ &&
26+
(spec.Value == "*" || spec.Value == offer) {
27+
bestQ = spec.Q
28+
bestOffer = offer
29+
}
30+
}
31+
}
32+
if bestQ == 0 {
33+
bestOffer = ""
34+
}
35+
return bestOffer
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2013 The Go Authors. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file or at
5+
// https://developers.google.com/open-source/licenses/bsd.
6+
7+
package httputil
8+
9+
import (
10+
"net/http"
11+
"testing"
12+
)
13+
14+
var negotiateContentEncodingTests = []struct {
15+
s string
16+
offers []string
17+
expect string
18+
}{
19+
{"", []string{"identity", "gzip"}, "identity"},
20+
{"*;q=0", []string{"identity", "gzip"}, ""},
21+
{"gzip", []string{"identity", "gzip"}, "gzip"},
22+
{"gzip,zstd", []string{"identity", "zstd"}, "zstd"},
23+
{"zstd,gzip", []string{"gzip", "zstd"}, "gzip"},
24+
{"gzip,zstd", []string{"gzip", "zstd"}, "gzip"},
25+
{"gzip,zstd", []string{"zstd", "gzip"}, "zstd"},
26+
{"gzip;q=0.1,zstd;q=0.5", []string{"gzip", "zstd"}, "zstd"},
27+
{"gzip;q=1.0, identity; q=0.5, *;q=0", []string{"identity", "gzip"}, "gzip"},
28+
{"gzip;q=1.0, identity; q=0.5, *;q=0", []string{"identity", "zstd"}, "identity"},
29+
{"zstd", []string{"identity", "gzip"}, "identity"},
30+
}
31+
32+
func TestNegotiateContentEncoding(t *testing.T) {
33+
for _, tt := range negotiateContentEncodingTests {
34+
r := &http.Request{Header: http.Header{"Accept-Encoding": {tt.s}}}
35+
actual := NegotiateContentEncoding(r, tt.offers)
36+
if actual != tt.expect {
37+
t.Errorf("NegotiateContentEncoding(%q, %#v)=%q, want %q", tt.s, tt.offers, actual, tt.expect)
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)