Skip to content

Commit 79addb5

Browse files
authored
Merge pull request kubernetes#128925 from richabanker/zpages-cleanup
Move zpages common code to an httputil package
2 parents 6ef2458 + ebe5bab commit 79addb5

File tree

6 files changed

+139
-116
lines changed

6 files changed

+139
-116
lines changed

staging/src/k8s.io/component-base/zpages/flagz/flagz.go

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,9 @@ import (
2323
"math/rand"
2424
"net/http"
2525
"sort"
26-
"strings"
2726
"sync"
2827

29-
"github.com/munnerz/goautoneg"
30-
28+
"k8s.io/component-base/zpages/httputil"
3129
"k8s.io/klog/v2"
3230
)
3331

@@ -40,8 +38,7 @@ Warning: This endpoint is not meant to be machine parseable, has no formatting c
4038
)
4139

4240
var (
43-
flagzSeparators = []string{":", ": ", "=", " "}
44-
errUnsupportedMediaType = fmt.Errorf("media type not acceptable, must be: text/plain")
41+
delimiters = []string{":", ": ", "=", " "}
4542
)
4643

4744
type registry struct {
@@ -64,8 +61,8 @@ func (reg *registry) installHandler(m mux, componentName string, flagReader Read
6461

6562
func (reg *registry) handleFlags(componentName string, flagReader Reader) http.HandlerFunc {
6663
return func(w http.ResponseWriter, r *http.Request) {
67-
if !acceptableMediaType(r) {
68-
http.Error(w, errUnsupportedMediaType.Error(), http.StatusNotAcceptable)
64+
if !httputil.AcceptableMediaType(r) {
65+
http.Error(w, httputil.ErrUnsupportedMediaType.Error(), http.StatusNotAcceptable)
6966
return
7067
}
7168

@@ -76,8 +73,8 @@ func (reg *registry) handleFlags(componentName string, flagReader Reader) http.H
7673
return
7774
}
7875

79-
randomIndex := rand.Intn(len(flagzSeparators))
80-
separator := flagzSeparators[randomIndex]
76+
randomIndex := rand.Intn(len(delimiters))
77+
separator := delimiters[randomIndex]
8178
// Randomize the delimiter for printing to prevent scraping of the response.
8279
printSortedFlags(&reg.response, flagReader.GetFlagz(), separator)
8380
})
@@ -90,29 +87,6 @@ func (reg *registry) handleFlags(componentName string, flagReader Reader) http.H
9087
}
9188
}
9289

93-
func acceptableMediaType(r *http.Request) bool {
94-
accepts := goautoneg.ParseAccept(r.Header.Get("Accept"))
95-
for _, accept := range accepts {
96-
if !mediaTypeMatches(accept) {
97-
continue
98-
}
99-
if len(accept.Params) == 0 {
100-
return true
101-
}
102-
if len(accept.Params) == 1 {
103-
if charset, ok := accept.Params["charset"]; ok && strings.EqualFold(charset, "utf-8") {
104-
return true
105-
}
106-
}
107-
}
108-
return false
109-
}
110-
111-
func mediaTypeMatches(a goautoneg.Accept) bool {
112-
return (a.Type == "text" || a.Type == "*") &&
113-
(a.SubType == "plain" || a.SubType == "*")
114-
}
115-
11690
func printSortedFlags(w io.Writer, flags map[string]string, separator string) {
11791
var sortedKeys []string
11892
for key := range flags {

staging/src/k8s.io/component-base/zpages/flagz/flagz_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Warning: This endpoint is not meant to be machine parseable, has no formatting c
3535

3636
func TestFlagz(t *testing.T) {
3737
componentName := "test-server"
38-
flagzSeparators = []string{"="}
38+
delimiters = []string{"="}
3939
wantHeaderLines := strings.Split(fmt.Sprintf(wantTmpl, componentName), "\n")
4040
tests := []struct {
4141
name string
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package httputil
18+
19+
import (
20+
"fmt"
21+
"net/http"
22+
"strings"
23+
24+
"github.com/munnerz/goautoneg"
25+
)
26+
27+
// ErrUnsupportedMediaType is the error returned when the request's
28+
// Accept header does not contain "text/plain".
29+
var ErrUnsupportedMediaType = fmt.Errorf("media type not acceptable, must be: text/plain")
30+
31+
// AcceptableMediaType checks if the request's Accept header contains
32+
// a supported media type with optional "charset=utf-8" parameter.
33+
func AcceptableMediaType(r *http.Request) bool {
34+
accepts := goautoneg.ParseAccept(r.Header.Get("Accept"))
35+
for _, accept := range accepts {
36+
if !mediaTypeMatches(accept) {
37+
continue
38+
}
39+
if len(accept.Params) == 0 {
40+
return true
41+
}
42+
if len(accept.Params) == 1 {
43+
if charset, ok := accept.Params["charset"]; ok && strings.EqualFold(charset, "utf-8") {
44+
return true
45+
}
46+
}
47+
}
48+
return false
49+
}
50+
51+
func mediaTypeMatches(a goautoneg.Accept) bool {
52+
return (a.Type == "text" || a.Type == "*") &&
53+
(a.SubType == "plain" || a.SubType == "*")
54+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package httputil
18+
19+
import (
20+
"net/http"
21+
"testing"
22+
)
23+
24+
func TestAcceptableMediaTypes(t *testing.T) {
25+
tests := []struct {
26+
name string
27+
reqHeader string
28+
want bool
29+
}{
30+
{
31+
name: "valid text/plain header",
32+
reqHeader: "text/plain",
33+
want: true,
34+
},
35+
{
36+
name: "valid text/* header",
37+
reqHeader: "text/*",
38+
want: true,
39+
},
40+
{
41+
name: "valid */plain header",
42+
reqHeader: "*/plain",
43+
want: true,
44+
},
45+
{
46+
name: "valid accept args",
47+
reqHeader: "text/plain; charset=utf-8",
48+
want: true,
49+
},
50+
{
51+
name: "invalid text/foo header",
52+
reqHeader: "text/foo",
53+
want: false,
54+
},
55+
{
56+
name: "invalid text/plain params",
57+
reqHeader: "text/plain; foo=bar",
58+
want: false,
59+
},
60+
}
61+
for _, tt := range tests {
62+
req, err := http.NewRequest(http.MethodGet, "http://example.com/statusz", nil)
63+
if err != nil {
64+
t.Fatalf("Unexpected error while creating request: %v", err)
65+
}
66+
67+
req.Header.Set("Accept", tt.reqHeader)
68+
got := AcceptableMediaType(req)
69+
70+
if got != tt.want {
71+
t.Errorf("Unexpected response from AcceptableMediaType(), want %v, got = %v", tt.want, got)
72+
}
73+
}
74+
}

staging/src/k8s.io/component-base/zpages/statusz/statusz.go

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,14 @@ import (
2222
"html/template"
2323
"math/rand"
2424
"net/http"
25-
"strings"
2625
"time"
2726

28-
"github.com/munnerz/goautoneg"
29-
27+
"k8s.io/component-base/zpages/httputil"
3028
"k8s.io/klog/v2"
3129
)
3230

3331
var (
34-
delimiters = []string{":", ": ", "=", " "}
35-
errUnsupportedMediaType = fmt.Errorf("media type not acceptable, must be: text/plain")
32+
delimiters = []string{":", ": ", "=", " "}
3633
)
3734

3835
const DefaultStatuszPath = "/statusz"
@@ -90,8 +87,8 @@ func initializeTemplates() (*template.Template, error) {
9087

9188
func handleStatusz(componentName string, dataTmpl *template.Template, reg statuszRegistry) http.HandlerFunc {
9289
return func(w http.ResponseWriter, r *http.Request) {
93-
if !acceptableMediaType(r) {
94-
http.Error(w, errUnsupportedMediaType.Error(), http.StatusNotAcceptable)
90+
if !httputil.AcceptableMediaType(r) {
91+
http.Error(w, httputil.ErrUnsupportedMediaType.Error(), http.StatusNotAcceptable)
9592
return
9693
}
9794

@@ -108,30 +105,6 @@ func handleStatusz(componentName string, dataTmpl *template.Template, reg status
108105
}
109106
}
110107

111-
// TODO(richabanker) : Move this to a common place to be reused for all zpages.
112-
func acceptableMediaType(r *http.Request) bool {
113-
accepts := goautoneg.ParseAccept(r.Header.Get("Accept"))
114-
for _, accept := range accepts {
115-
if !mediaTypeMatches(accept) {
116-
continue
117-
}
118-
if len(accept.Params) == 0 {
119-
return true
120-
}
121-
if len(accept.Params) == 1 {
122-
if charset, ok := accept.Params["charset"]; ok && strings.EqualFold(charset, "utf-8") {
123-
return true
124-
}
125-
}
126-
}
127-
return false
128-
}
129-
130-
func mediaTypeMatches(a goautoneg.Accept) bool {
131-
return (a.Type == "text" || a.Type == "*") &&
132-
(a.SubType == "plain" || a.SubType == "*")
133-
}
134-
135108
func populateStatuszData(tmpl *template.Template, reg statuszRegistry) (string, error) {
136109
if tmpl == nil {
137110
return "", fmt.Errorf("received nil template")

staging/src/k8s.io/component-base/zpages/statusz/statusz_test.go

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -152,58 +152,6 @@ func TestStatusz(t *testing.T) {
152152
}
153153
}
154154

155-
func TestAcceptableMediaTypes(t *testing.T) {
156-
tests := []struct {
157-
name string
158-
reqHeader string
159-
want bool
160-
}{
161-
{
162-
name: "valid text/plain header",
163-
reqHeader: "text/plain",
164-
want: true,
165-
},
166-
{
167-
name: "valid text/* header",
168-
reqHeader: "text/*",
169-
want: true,
170-
},
171-
{
172-
name: "valid */plain header",
173-
reqHeader: "*/plain",
174-
want: true,
175-
},
176-
{
177-
name: "valid accept args",
178-
reqHeader: "text/plain; charset=utf-8",
179-
want: true,
180-
},
181-
{
182-
name: "invalid text/foo header",
183-
reqHeader: "text/foo",
184-
want: false,
185-
},
186-
{
187-
name: "invalid text/plain params",
188-
reqHeader: "text/plain; foo=bar",
189-
want: false,
190-
},
191-
}
192-
for _, tt := range tests {
193-
req, err := http.NewRequest(http.MethodGet, "http://example.com/statusz", nil)
194-
if err != nil {
195-
t.Fatalf("Unexpected error while creating request: %v", err)
196-
}
197-
198-
req.Header.Set("Accept", tt.reqHeader)
199-
got := acceptableMediaType(req)
200-
201-
if got != tt.want {
202-
t.Errorf("Unexpected response from acceptableMediaType(), want %v, got = %v", tt.want, got)
203-
}
204-
}
205-
}
206-
207155
func parseVersion(t *testing.T, v string) *version.Version {
208156
parsed, err := version.ParseMajorMinor(v)
209157
if err != nil {

0 commit comments

Comments
 (0)