use our own serve mux that directs how we want
Kubernetes-commit: c837c7fb1a227ef8c772ad15b08ff47593379543
This commit is contained in:
parent
76ad921357
commit
585812f439
|
@ -479,7 +479,15 @@ func (c completedConfig) buildHandlers(s *GenericAPIServer, delegate http.Handle
|
|||
}
|
||||
}
|
||||
|
||||
installAPI(s, c.Config, delegate)
|
||||
installAPI(s, c.Config)
|
||||
if delegate != nil {
|
||||
s.FallThroughHandler.NotFoundHandler(delegate)
|
||||
} else if c.EnableIndex {
|
||||
s.FallThroughHandler.NotFoundHandler(routes.IndexLister{
|
||||
StatusCode: http.StatusNotFound,
|
||||
PathProvider: s.listedPathProvider,
|
||||
})
|
||||
}
|
||||
|
||||
s.Handler = c.BuildHandlerChainFunc(s.HandlerContainer.ServeMux, c.Config)
|
||||
|
||||
|
@ -500,15 +508,9 @@ func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
|
|||
return handler
|
||||
}
|
||||
|
||||
func installAPI(s *GenericAPIServer, c *Config, delegate http.Handler) {
|
||||
switch {
|
||||
case c.EnableIndex:
|
||||
routes.Index{}.Install(s.listedPathProvider, c.FallThroughHandler, delegate)
|
||||
|
||||
case delegate != nil:
|
||||
// if we have a delegate, allow it to handle everything that's unmatched even if
|
||||
// the index is disabled.
|
||||
s.FallThroughHandler.UnlistedHandleFunc("/", delegate.ServeHTTP)
|
||||
func installAPI(s *GenericAPIServer, c *Config) {
|
||||
if c.EnableIndex {
|
||||
routes.Index{}.Install(s.listedPathProvider, c.FallThroughHandler)
|
||||
}
|
||||
if c.SwaggerConfig != nil && c.EnableSwaggerUI {
|
||||
routes.SwaggerUI{}.Install(s.FallThroughHandler)
|
||||
|
|
|
@ -192,7 +192,7 @@ type emptyDelegate struct {
|
|||
}
|
||||
|
||||
func (s emptyDelegate) UnprotectedHandler() http.Handler {
|
||||
return http.NotFoundHandler()
|
||||
return nil
|
||||
}
|
||||
func (s emptyDelegate) PostStartHooks() map[string]postStartHookEntry {
|
||||
return map[string]postStartHookEntry{}
|
||||
|
|
|
@ -31,6 +31,7 @@ go_library(
|
|||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters:go_default_library",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -21,18 +21,25 @@ import (
|
|||
"net/http"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// PathRecorderMux wraps a mux object and records the registered exposedPaths.
|
||||
type PathRecorderMux struct {
|
||||
lock sync.Mutex
|
||||
pathToHandler map[string]http.Handler
|
||||
lock sync.Mutex
|
||||
notFoundHandler http.Handler
|
||||
pathToHandler map[string]http.Handler
|
||||
prefixToHandler map[string]http.Handler
|
||||
|
||||
// mux stores an *http.ServeMux and is used to handle the actual serving
|
||||
// mux stores a pathHandler and is used to handle the actual serving.
|
||||
// Turns out, we want to accept trailing slashes, BUT we don't care about handling
|
||||
// everything under them. This does exactly matches only unless its explicitly requested to
|
||||
// do something different
|
||||
mux atomic.Value
|
||||
|
||||
// exposedPaths is the list of paths that should be shown at /
|
||||
|
@ -43,16 +50,38 @@ type PathRecorderMux struct {
|
|||
pathStacks map[string]string
|
||||
}
|
||||
|
||||
// pathHandler is an http.Handler that will satify requests first by exact match, then by prefix,
|
||||
// then by notFoundHandler
|
||||
type pathHandler struct {
|
||||
// pathToHandler is a map of exactly matching request to its handler
|
||||
pathToHandler map[string]http.Handler
|
||||
|
||||
// this has to be sorted by most slashes then by length
|
||||
prefixHandlers []prefixHandler
|
||||
|
||||
// notFoundHandler is the handler to use for satisfying requests with no other match
|
||||
notFoundHandler http.Handler
|
||||
}
|
||||
|
||||
// prefixHandler holds the prefix it should match and the handler to use
|
||||
type prefixHandler struct {
|
||||
// prefix is the prefix to test for a request match
|
||||
prefix string
|
||||
// handler is used to satisfy matching requests
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
// NewPathRecorderMux creates a new PathRecorderMux with the given mux as the base mux.
|
||||
func NewPathRecorderMux() *PathRecorderMux {
|
||||
ret := &PathRecorderMux{
|
||||
pathToHandler: map[string]http.Handler{},
|
||||
mux: atomic.Value{},
|
||||
exposedPaths: []string{},
|
||||
pathStacks: map[string]string{},
|
||||
pathToHandler: map[string]http.Handler{},
|
||||
prefixToHandler: map[string]http.Handler{},
|
||||
mux: atomic.Value{},
|
||||
exposedPaths: []string{},
|
||||
pathStacks: map[string]string{},
|
||||
}
|
||||
|
||||
ret.mux.Store(http.NewServeMux())
|
||||
ret.mux.Store(&pathHandler{notFoundHandler: http.NotFoundHandler()})
|
||||
return ret
|
||||
}
|
||||
|
||||
|
@ -74,12 +103,38 @@ func (m *PathRecorderMux) trackCallers(path string) {
|
|||
// refreshMuxLocked creates a new mux and must be called while locked. Otherwise the view of handlers may
|
||||
// not be consistent
|
||||
func (m *PathRecorderMux) refreshMuxLocked() {
|
||||
mux := http.NewServeMux()
|
||||
newMux := &pathHandler{
|
||||
pathToHandler: map[string]http.Handler{},
|
||||
prefixHandlers: []prefixHandler{},
|
||||
notFoundHandler: http.NotFoundHandler(),
|
||||
}
|
||||
if m.notFoundHandler != nil {
|
||||
newMux.notFoundHandler = m.notFoundHandler
|
||||
}
|
||||
for path, handler := range m.pathToHandler {
|
||||
mux.Handle(path, handler)
|
||||
newMux.pathToHandler[path] = handler
|
||||
}
|
||||
|
||||
m.mux.Store(mux)
|
||||
keys := sets.StringKeySet(m.prefixToHandler).List()
|
||||
sort.Sort(sort.Reverse(byPrefixPriority(keys)))
|
||||
for _, prefix := range keys {
|
||||
newMux.prefixHandlers = append(newMux.prefixHandlers, prefixHandler{
|
||||
prefix: prefix,
|
||||
handler: m.prefixToHandler[prefix],
|
||||
})
|
||||
}
|
||||
|
||||
m.mux.Store(newMux)
|
||||
}
|
||||
|
||||
// NotFoundHandler sets the handler to use if there's no match for a give path
|
||||
func (m *PathRecorderMux) NotFoundHandler(notFoundHandler http.Handler) {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
|
||||
m.notFoundHandler = notFoundHandler
|
||||
|
||||
m.refreshMuxLocked()
|
||||
}
|
||||
|
||||
// Unregister removes a path from the mux.
|
||||
|
@ -88,6 +143,7 @@ func (m *PathRecorderMux) Unregister(path string) {
|
|||
defer m.lock.Unlock()
|
||||
|
||||
delete(m.pathToHandler, path)
|
||||
delete(m.prefixToHandler, path)
|
||||
delete(m.pathStacks, path)
|
||||
for i := range m.exposedPaths {
|
||||
if m.exposedPaths[i] == path {
|
||||
|
@ -114,13 +170,7 @@ func (m *PathRecorderMux) Handle(path string, handler http.Handler) {
|
|||
// HandleFunc registers the handler function for the given pattern.
|
||||
// If a handler already exists for pattern, Handle panics.
|
||||
func (m *PathRecorderMux) HandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
m.trackCallers(path)
|
||||
|
||||
m.exposedPaths = append(m.exposedPaths, path)
|
||||
m.pathToHandler[path] = http.HandlerFunc(handler)
|
||||
m.refreshMuxLocked()
|
||||
m.Handle(path, http.HandlerFunc(handler))
|
||||
}
|
||||
|
||||
// UnlistedHandle registers the handler for the given pattern, but doesn't list it.
|
||||
|
@ -137,15 +187,79 @@ func (m *PathRecorderMux) UnlistedHandle(path string, handler http.Handler) {
|
|||
// UnlistedHandleFunc registers the handler function for the given pattern, but doesn't list it.
|
||||
// If a handler already exists for pattern, Handle panics.
|
||||
func (m *PathRecorderMux) UnlistedHandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) {
|
||||
m.UnlistedHandle(path, http.HandlerFunc(handler))
|
||||
}
|
||||
|
||||
// HandlePrefix is like Handle, but matches for anything under the path. Like a standard golang trailing slash.
|
||||
func (m *PathRecorderMux) HandlePrefix(path string, handler http.Handler) {
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
panic(fmt.Sprintf("%q must end in a trailing slash", path))
|
||||
}
|
||||
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
m.trackCallers(path)
|
||||
|
||||
m.pathToHandler[path] = http.HandlerFunc(handler)
|
||||
m.exposedPaths = append(m.exposedPaths, path)
|
||||
m.prefixToHandler[path] = handler
|
||||
m.refreshMuxLocked()
|
||||
}
|
||||
|
||||
// UnlistedHandlePrefix is like UnlistedHandle, but matches for anything under the path. Like a standard golang trailing slash.
|
||||
func (m *PathRecorderMux) UnlistedHandlePrefix(path string, handler http.Handler) {
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
panic(fmt.Sprintf("%q must end in a trailing slash", path))
|
||||
}
|
||||
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
m.trackCallers(path)
|
||||
|
||||
m.prefixToHandler[path] = handler
|
||||
m.refreshMuxLocked()
|
||||
}
|
||||
|
||||
// ServeHTTP makes it an http.Handler
|
||||
func (m *PathRecorderMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
m.mux.Load().(*http.ServeMux).ServeHTTP(w, r)
|
||||
m.mux.Load().(*pathHandler).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// ServeHTTP makes it an http.Handler
|
||||
func (h *pathHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if exactHandler, ok := h.pathToHandler[r.URL.Path]; ok {
|
||||
exactHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
for _, prefixHandler := range h.prefixHandlers {
|
||||
if strings.HasPrefix(r.URL.Path, prefixHandler.prefix) {
|
||||
prefixHandler.handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
h.notFoundHandler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// byPrefixPriority sorts url prefixes by the order in which they should be tested by the mux
|
||||
// this has to be sorted by most slashes then by length so that we can iterate straight
|
||||
// through to match the "best" one first.
|
||||
type byPrefixPriority []string
|
||||
|
||||
func (s byPrefixPriority) Len() int { return len(s) }
|
||||
func (s byPrefixPriority) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s byPrefixPriority) Less(i, j int) bool {
|
||||
lhsNumParts := strings.Count(s[i], "/")
|
||||
rhsNumParts := strings.Count(s[j], "/")
|
||||
if lhsNumParts != rhsNumParts {
|
||||
return lhsNumParts < rhsNumParts
|
||||
}
|
||||
|
||||
lhsLen := len(s[i])
|
||||
rhsLen := len(s[j])
|
||||
if lhsLen != rhsLen {
|
||||
return lhsLen < rhsLen
|
||||
}
|
||||
|
||||
return strings.Compare(s[i], s[j]) < 0
|
||||
}
|
||||
|
|
|
@ -67,3 +67,69 @@ func TestUnregisterHandlers(t *testing.T) {
|
|||
assert.Equal(t, second, 1)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
||||
}
|
||||
|
||||
func TestPrefixHandlers(t *testing.T) {
|
||||
c := NewPathRecorderMux()
|
||||
s := httptest.NewServer(c)
|
||||
defer s.Close()
|
||||
|
||||
secretPrefixCount := 0
|
||||
c.UnlistedHandlePrefix("/secretPrefix/", http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
secretPrefixCount = secretPrefixCount + 1
|
||||
}))
|
||||
publicPrefixCount := 0
|
||||
c.HandlePrefix("/publicPrefix/", http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
publicPrefixCount = publicPrefixCount + 1
|
||||
}))
|
||||
precisePrefixCount := 0
|
||||
c.HandlePrefix("/publicPrefix/but-more-precise/", http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
precisePrefixCount = precisePrefixCount + 1
|
||||
}))
|
||||
exactMatchCount := 0
|
||||
c.Handle("/publicPrefix/exactmatch", http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
exactMatchCount = exactMatchCount + 1
|
||||
}))
|
||||
slashMatchCount := 0
|
||||
c.Handle("/otherPublic/exactmatchslash/", http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
slashMatchCount = slashMatchCount + 1
|
||||
}))
|
||||
fallThroughCount := 0
|
||||
c.NotFoundHandler(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
fallThroughCount = fallThroughCount + 1
|
||||
}))
|
||||
|
||||
assert.NotContains(t, c.ListedPaths(), "/secretPrefix/")
|
||||
assert.Contains(t, c.ListedPaths(), "/publicPrefix/")
|
||||
|
||||
resp, _ := http.Get(s.URL + "/fallthrough")
|
||||
assert.Equal(t, 1, fallThroughCount)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
||||
resp, _ = http.Get(s.URL + "/publicPrefix")
|
||||
assert.Equal(t, 2, fallThroughCount)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
||||
|
||||
http.Get(s.URL + "/publicPrefix/")
|
||||
assert.Equal(t, 1, publicPrefixCount)
|
||||
http.Get(s.URL + "/publicPrefix/something")
|
||||
assert.Equal(t, 2, publicPrefixCount)
|
||||
http.Get(s.URL + "/publicPrefix/but-more-precise")
|
||||
assert.Equal(t, 3, publicPrefixCount)
|
||||
http.Get(s.URL + "/publicPrefix/but-more-precise/")
|
||||
assert.Equal(t, 1, precisePrefixCount)
|
||||
http.Get(s.URL + "/publicPrefix/but-more-precise/more-stuff")
|
||||
assert.Equal(t, 2, precisePrefixCount)
|
||||
|
||||
http.Get(s.URL + "/publicPrefix/exactmatch")
|
||||
assert.Equal(t, 1, exactMatchCount)
|
||||
http.Get(s.URL + "/publicPrefix/exactmatch/")
|
||||
assert.Equal(t, 4, publicPrefixCount)
|
||||
http.Get(s.URL + "/otherPublic/exactmatchslash")
|
||||
assert.Equal(t, 3, fallThroughCount)
|
||||
http.Get(s.URL + "/otherPublic/exactmatchslash/")
|
||||
assert.Equal(t, 1, slashMatchCount)
|
||||
|
||||
http.Get(s.URL + "/secretPrefix/")
|
||||
assert.Equal(t, 1, secretPrefixCount)
|
||||
http.Get(s.URL + "/secretPrefix/something")
|
||||
assert.Equal(t, 2, secretPrefixCount)
|
||||
}
|
||||
|
|
|
@ -50,20 +50,20 @@ func (p ListedPathProviders) ListedPaths() []string {
|
|||
type Index struct{}
|
||||
|
||||
// Install adds the Index webservice to the given mux.
|
||||
func (i Index) Install(pathProvider ListedPathProvider, mux *mux.PathRecorderMux, delegate http.Handler) {
|
||||
mux.UnlistedHandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
status := http.StatusOK
|
||||
if r.URL.Path != "/" && r.URL.Path != "/index.html" {
|
||||
// Since "/" matches all paths, handleIndex is called for all paths for which there is no handler api.Registry.
|
||||
// if we have a delegate, we should call to it and simply return
|
||||
if delegate != nil {
|
||||
delegate.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
func (i Index) Install(pathProvider ListedPathProvider, mux *mux.PathRecorderMux) {
|
||||
handler := IndexLister{StatusCode: http.StatusOK, PathProvider: pathProvider}
|
||||
|
||||
// If we have no delegate, we want to return a 404 status with a list of all valid paths, incase of an invalid URL request.
|
||||
status = http.StatusNotFound
|
||||
}
|
||||
responsewriters.WriteRawJSON(status, metav1.RootPaths{Paths: pathProvider.ListedPaths()}, w)
|
||||
})
|
||||
mux.UnlistedHandle("/", handler)
|
||||
mux.UnlistedHandle("/index.html", handler)
|
||||
}
|
||||
|
||||
// IndexLister lists the available indexes with the status code provided
|
||||
type IndexLister struct {
|
||||
StatusCode int
|
||||
PathProvider ListedPathProvider
|
||||
}
|
||||
|
||||
// ServeHTTP serves the available paths.
|
||||
func (i IndexLister) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
responsewriters.WriteRawJSON(i.StatusCode, metav1.RootPaths{Paths: i.PathProvider.ListedPaths()}, w)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
|
||||
"k8s.io/apiserver/pkg/server/mux"
|
||||
|
@ -27,7 +28,8 @@ type Profiling struct{}
|
|||
|
||||
// Install adds the Profiling webservice to the given mux.
|
||||
func (d Profiling) Install(c *mux.PathRecorderMux) {
|
||||
c.UnlistedHandleFunc("/debug/pprof/", pprof.Index)
|
||||
c.UnlistedHandle("/debug/pprof", http.HandlerFunc(pprof.Index))
|
||||
c.UnlistedHandlePrefix("/debug/pprof/", http.HandlerFunc(pprof.Index))
|
||||
c.UnlistedHandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
c.UnlistedHandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
c.UnlistedHandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
|
|
|
@ -36,5 +36,5 @@ func (l SwaggerUI) Install(c *mux.PathRecorderMux) {
|
|||
Prefix: "third_party/swagger-ui",
|
||||
})
|
||||
prefix := "/swagger-ui/"
|
||||
c.Handle(prefix, http.StripPrefix(prefix, fileServer))
|
||||
c.HandlePrefix(prefix, http.StripPrefix(prefix, fileServer))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue