Skip to content

Commit f08b10c

Browse files
mruegbwplotka
andauthored
feat: Support zstd compression (#1496)
* 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]> * Update prometheus/promhttp/http.go Co-authored-by: Bartlomiej Plotka <[email protected]> Signed-off-by: Manuel Rüger <[email protected]> * Update prometheus/promhttp/http.go Co-authored-by: Bartlomiej Plotka <[email protected]> Signed-off-by: Manuel Rüger <[email protected]> * Update prometheus/promhttp/http.go Co-authored-by: Bartlomiej Plotka <[email protected]> Signed-off-by: Manuel Rüger <[email protected]> * Integrate review comments * String typed enum Signed-off-by: Manuel Rüger <[email protected]> * Test with gzip compression Signed-off-by: Manuel Rüger <[email protected]> * Update prometheus/promhttp/http.go Co-authored-by: Bartlomiej Plotka <[email protected]> Signed-off-by: Manuel Rüger <[email protected]> * Reorder error handling Signed-off-by: Manuel Rüger <[email protected]> * Apply suggestions from code review Co-authored-by: Bartlomiej Plotka <[email protected]> Signed-off-by: Manuel Rüger <[email protected]> * Include review suggestions Signed-off-by: Manuel Rüger <[email protected]> --------- Signed-off-by: Manuel Rüger <[email protected]> Co-authored-by: Bartlomiej Plotka <[email protected]>
1 parent 920e6f7 commit f08b10c

File tree

10 files changed

+697
-29
lines changed

10 files changed

+697
-29
lines changed

go.mod

+2
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.15.1

go.sum

+3
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=
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)