phttp: add generic support for RFC 2616 14.46 warnings headers
Signed-off-by: Monis Khan <mok@vmware.com>
This commit is contained in:
parent
1611cf681a
commit
9d4a932656
@ -44,5 +44,6 @@ func defaultTransport() *http.Transport {
|
|||||||
func defaultWrap(rt http.RoundTripper) http.RoundTripper {
|
func defaultWrap(rt http.RoundTripper) http.RoundTripper {
|
||||||
rt = safeDebugWrappers(rt, transport.DebugWrappers, func() bool { return plog.Enabled(plog.LevelTrace) })
|
rt = safeDebugWrappers(rt, transport.DebugWrappers, func() bool { return plog.Enabled(plog.LevelTrace) })
|
||||||
rt = transport.NewUserAgentRoundTripper(rest.DefaultKubernetesUserAgent(), rt)
|
rt = transport.NewUserAgentRoundTripper(rest.DefaultKubernetesUserAgent(), rt)
|
||||||
|
rt = warningWrapper(rt, getWarningHandler())
|
||||||
return rt
|
return rt
|
||||||
}
|
}
|
||||||
|
74
internal/net/phttp/warning.go
Normal file
74
internal/net/phttp/warning.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package phttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
"k8s.io/apimachinery/pkg/util/net"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
|
||||||
|
"go.pinniped.dev/internal/httputil/roundtripper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func warningWrapper(rt http.RoundTripper, handler rest.WarningHandler) http.RoundTripper {
|
||||||
|
return roundtripper.WrapFunc(rt, func(req *http.Request) (*http.Response, error) {
|
||||||
|
resp, err := rt.RoundTrip(req)
|
||||||
|
|
||||||
|
handleWarnings(resp, handler)
|
||||||
|
|
||||||
|
return resp, err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleWarnings(resp *http.Response, handler rest.WarningHandler) {
|
||||||
|
if resp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
warnings, _ := net.ParseWarningHeaders(resp.Header["Warning"]) // safe to ignore errors here
|
||||||
|
for _, warning := range warnings {
|
||||||
|
handler.HandleWarningHeader(warning.Code, warning.Agent, warning.Text) // client-go throws away the date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWarningHandler() rest.WarningHandler {
|
||||||
|
// the client-go rest.WarningHandlers all log warnings with non-empty message and code=299, agent is ignored
|
||||||
|
|
||||||
|
// no deduplication or color output when running from a non-terminal such as a pod
|
||||||
|
if isTerm := term.IsTerminal(int(os.Stderr.Fd())); !isTerm {
|
||||||
|
return rest.WarningLogger{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deduplicate and attempt color warnings when running from a terminal
|
||||||
|
return rest.NewWarningWriter(os.Stderr, rest.WarningWriterOptions{
|
||||||
|
Deduplicate: true,
|
||||||
|
Color: allowsColorOutput(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// allowsColorOutput returns true if the process environment indicates color output is supported and desired.
|
||||||
|
// Copied from k8s.io/kubectl/pkg/util/term.AllowsColorOutput.
|
||||||
|
func allowsColorOutput() bool {
|
||||||
|
// https://en.wikipedia.org/wiki/Computer_terminal#Dumb_terminals
|
||||||
|
if os.Getenv("TERM") == "dumb" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://no-color.org/
|
||||||
|
if _, nocolor := os.LookupEnv("NO_COLOR"); nocolor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// On Windows WT_SESSION is set by the modern terminal component.
|
||||||
|
// Older terminals have poor support for UTF-8, VT escape codes, etc.
|
||||||
|
if runtime.GOOS == "windows" && os.Getenv("WT_SESSION") == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
110
internal/net/phttp/warning_test.go
Normal file
110
internal/net/phttp/warning_test.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package phttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"go.pinniped.dev/internal/constable"
|
||||||
|
"go.pinniped.dev/internal/httputil/roundtripper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_warningWrapper(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
resp *http.Response
|
||||||
|
wantCodes []int
|
||||||
|
wantAgents []string
|
||||||
|
wantTexts []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil resp",
|
||||||
|
resp: nil,
|
||||||
|
wantCodes: nil,
|
||||||
|
wantAgents: nil,
|
||||||
|
wantTexts: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no warning",
|
||||||
|
resp: testResp(t, http.Header{"moon": {"white"}}), //nolint:bodyclose
|
||||||
|
wantCodes: nil,
|
||||||
|
wantAgents: nil,
|
||||||
|
wantTexts: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "malformed warning",
|
||||||
|
resp: testResp(t, http.Header{"Warning": {"wee"}}), //nolint:bodyclose
|
||||||
|
wantCodes: nil,
|
||||||
|
wantAgents: nil,
|
||||||
|
wantTexts: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "partial malformed warning",
|
||||||
|
resp: testResp(t, http.Header{"Warning": {`123 foo "bar"`, "wee"}}), //nolint:bodyclose
|
||||||
|
wantCodes: []int{123},
|
||||||
|
wantAgents: []string{"foo"},
|
||||||
|
wantTexts: []string{"bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "partial malformed warning other order",
|
||||||
|
resp: testResp(t, http.Header{"Warning": {"bar", `852 nah "dude"`, "wee"}}), //nolint:bodyclose
|
||||||
|
wantCodes: []int{852},
|
||||||
|
wantAgents: []string{"nah"},
|
||||||
|
wantTexts: []string{"dude"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple warnings",
|
||||||
|
resp: testResp(t, http.Header{"Warning": {`123 foo "bar"`, `222 good "day"`}}), //nolint:bodyclose
|
||||||
|
wantCodes: []int{123, 222},
|
||||||
|
wantAgents: []string{"foo", "good"},
|
||||||
|
wantTexts: []string{"bar", "day"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var rtCalled bool
|
||||||
|
|
||||||
|
staticErr := constable.Error("yay pandas")
|
||||||
|
|
||||||
|
rtFunc := roundtripper.Func(func(r *http.Request) (*http.Response, error) {
|
||||||
|
rtCalled = true
|
||||||
|
require.Nil(t, r)
|
||||||
|
return tt.resp, staticErr
|
||||||
|
})
|
||||||
|
|
||||||
|
h := &testWarningHandler{}
|
||||||
|
out := warningWrapper(rtFunc, h)
|
||||||
|
|
||||||
|
resp, err := out.RoundTrip(nil) //nolint:bodyclose
|
||||||
|
|
||||||
|
require.Equal(t, tt.resp, resp)
|
||||||
|
require.Equal(t, staticErr, err)
|
||||||
|
require.True(t, rtCalled)
|
||||||
|
|
||||||
|
require.Equal(t, tt.wantCodes, h.codes)
|
||||||
|
require.Equal(t, tt.wantAgents, h.agents)
|
||||||
|
require.Equal(t, tt.wantTexts, h.texts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testWarningHandler struct {
|
||||||
|
codes []int
|
||||||
|
agents []string
|
||||||
|
texts []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *testWarningHandler) HandleWarningHeader(code int, agent, text string) {
|
||||||
|
h.codes = append(h.codes, code)
|
||||||
|
h.agents = append(h.agents, agent)
|
||||||
|
h.texts = append(h.texts, text)
|
||||||
|
}
|
@ -21,6 +21,7 @@ import (
|
|||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/clock"
|
"k8s.io/apimachinery/pkg/util/clock"
|
||||||
|
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
|
||||||
kubeinformers "k8s.io/client-go/informers"
|
kubeinformers "k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/pkg/version"
|
"k8s.io/client-go/pkg/version"
|
||||||
@ -60,7 +61,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func startServer(ctx context.Context, shutdown *sync.WaitGroup, l net.Listener, handler http.Handler) {
|
func startServer(ctx context.Context, shutdown *sync.WaitGroup, l net.Listener, handler http.Handler) {
|
||||||
server := http.Server{Handler: handler}
|
server := http.Server{Handler: genericapifilters.WithWarningRecorder(handler)}
|
||||||
|
|
||||||
shutdown.Add(1)
|
shutdown.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
Loading…
Reference in New Issue
Block a user