use our own serve mux that directs how we want

Kubernetes-commit: c837c7fb1a227ef8c772ad15b08ff47593379543
This commit is contained in:
deads2k 2017-04-13 15:25:17 -04:00 committed by Kubernetes Publisher
parent 76ad921357
commit 585812f439
8 changed files with 233 additions and 48 deletions

View File

@ -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)

View File

@ -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{}

View File

@ -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",
],
)

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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))
}