diff --git a/hack/make-rules/test-cmd-util.sh b/hack/make-rules/test-cmd-util.sh index 60f4cb8671a..20436113ef3 100644 --- a/hack/make-rules/test-cmd-util.sh +++ b/hack/make-rules/test-cmd-util.sh @@ -2943,7 +2943,7 @@ runTests() { # Make sure the UI can be proxied start-proxy - check-curl-proxy-code /ui 301 + check-curl-proxy-code /ui 307 check-curl-proxy-code /api/ui 404 check-curl-proxy-code /api/v1/namespaces 200 if kube::test::if_supports_resource "${metrics}" ; then @@ -2962,7 +2962,7 @@ runTests() { # Custom paths let you see everything. start-proxy /custom - check-curl-proxy-code /custom/ui 301 + check-curl-proxy-code /custom/ui 307 if kube::test::if_supports_resource "${metrics}" ; then check-curl-proxy-code /custom/metrics 200 fi diff --git a/pkg/routes/ui.go b/pkg/routes/ui.go index fd7baf1f394..40f288ab2ae 100644 --- a/pkg/routes/ui.go +++ b/pkg/routes/ui.go @@ -28,7 +28,9 @@ const dashboardPath = "/api/v1/namespaces/kube-system/services/kubernetes-dashbo type UIRedirect struct{} func (r UIRedirect) Install(c *mux.PathRecorderMux) { - c.HandleFunc("/ui/", func(w http.ResponseWriter, r *http.Request) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, dashboardPath, http.StatusTemporaryRedirect) }) + c.Handle("/ui", handler) + c.HandlePrefix("/ui/", handler) } diff --git a/staging/src/k8s.io/apiserver/pkg/server/config.go b/staging/src/k8s.io/apiserver/pkg/server/config.go index a046106e2ef..a183d2841d7 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/config.go +++ b/staging/src/k8s.io/apiserver/pkg/server/config.go @@ -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) diff --git a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go index c9695568777..8f8ead030c9 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go +++ b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go @@ -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{} diff --git a/staging/src/k8s.io/apiserver/pkg/server/mux/BUILD b/staging/src/k8s.io/apiserver/pkg/server/mux/BUILD index c7d53f924da..992a4b6934c 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/mux/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/server/mux/BUILD @@ -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", ], ) diff --git a/staging/src/k8s.io/apiserver/pkg/server/mux/pathrecorder.go b/staging/src/k8s.io/apiserver/pkg/server/mux/pathrecorder.go index 7a343b36972..ed79cfc83b1 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/mux/pathrecorder.go +++ b/staging/src/k8s.io/apiserver/pkg/server/mux/pathrecorder.go @@ -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 } diff --git a/staging/src/k8s.io/apiserver/pkg/server/mux/pathrecorder_test.go b/staging/src/k8s.io/apiserver/pkg/server/mux/pathrecorder_test.go index 9bf64fe787f..6d15b47e24c 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/mux/pathrecorder_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/mux/pathrecorder_test.go @@ -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) +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/routes/index.go b/staging/src/k8s.io/apiserver/pkg/server/routes/index.go index 2b7c1ea6c46..14075798867 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/routes/index.go +++ b/staging/src/k8s.io/apiserver/pkg/server/routes/index.go @@ -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) } diff --git a/staging/src/k8s.io/apiserver/pkg/server/routes/profiling.go b/staging/src/k8s.io/apiserver/pkg/server/routes/profiling.go index fce6b631f95..f09a9669571 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/routes/profiling.go +++ b/staging/src/k8s.io/apiserver/pkg/server/routes/profiling.go @@ -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) diff --git a/staging/src/k8s.io/apiserver/pkg/server/routes/swaggerui.go b/staging/src/k8s.io/apiserver/pkg/server/routes/swaggerui.go index 712210eff8c..637356f3e0c 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/routes/swaggerui.go +++ b/staging/src/k8s.io/apiserver/pkg/server/routes/swaggerui.go @@ -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)) } diff --git a/staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go b/staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go index 7378c5bb36d..76f52d9cbff 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go @@ -173,12 +173,11 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg apisHandler := &apisHandler{ codecs: Codecs, lister: s.lister, - delegate: s.GenericAPIServer.FallThroughHandler, serviceLister: s.serviceLister, endpointsLister: s.endpointsLister, } - s.GenericAPIServer.HandlerContainer.Handle("/apis", apisHandler) - s.GenericAPIServer.HandlerContainer.Handle("/apis/", apisHandler) + s.GenericAPIServer.FallThroughHandler.Handle("/apis", apisHandler) + s.GenericAPIServer.FallThroughHandler.UnlistedHandle("/apis/", apisHandler) apiserviceRegistrationController := NewAPIServiceRegistrationController(informerFactory.Apiregistration().InternalVersion().APIServices(), kubeInformers.Core().V1().Services(), s) diff --git a/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_apis.go b/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_apis.go index 37ab4b2dcf2..aecca63a2c4 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_apis.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_apis.go @@ -40,8 +40,6 @@ type apisHandler struct { serviceLister v1listers.ServiceLister endpointsLister v1listers.EndpointsLister - - delegate http.Handler } var discoveryGroup = metav1.APIGroup{ @@ -59,12 +57,6 @@ var discoveryGroup = metav1.APIGroup{ } func (r *apisHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { - // don't handle URLs that aren't /apis - if req.URL.Path != "/apis" && req.URL.Path != "/apis/" { - r.delegate.ServeHTTP(w, req) - return - } - discoveryGroupList := &metav1.APIGroupList{ // always add OUR api group to the list first. Since we'll never have a registered APIService for it // and since this is the crux of the API, having this first will give our names priority. It's good to be king. @@ -158,12 +150,6 @@ type apiGroupHandler struct { } func (r *apiGroupHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { - // don't handle URLs that aren't /apis/ - if req.URL.Path != "/apis/"+r.groupName && req.URL.Path != "/apis/"+r.groupName+"/" { - r.delegate.ServeHTTP(w, req) - return - } - apiServices, err := r.lister.List(labels.Everything()) if statusErr, ok := err.(*apierrors.StatusError); ok && err != nil { responsewriters.WriteRawJSON(int(statusErr.Status().Code), statusErr.Status(), w) diff --git a/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_apis_test.go b/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_apis_test.go index f5d6ffebedf..a58d786d4f6 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_apis_test.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_apis_test.go @@ -35,64 +35,6 @@ import ( listers "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/internalversion" ) -type delegationHTTPHandler struct { - called bool -} - -func (d *delegationHTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - d.called = true - w.WriteHeader(http.StatusOK) -} - -func TestAPIsDelegation(t *testing.T) { - indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) - delegate := &delegationHTTPHandler{} - handler := &apisHandler{ - codecs: Codecs, - lister: listers.NewAPIServiceLister(indexer), - delegate: delegate, - } - - server := httptest.NewServer(handler) - defer server.Close() - - pathToDelegation := map[string]bool{ - "/": true, - "/apis": false, - "/apis/": false, - "/apis/" + apiregistration.GroupName: true, - "/apis/" + apiregistration.GroupName + "/": true, - "/apis/" + apiregistration.GroupName + "/anything": true, - "/apis/" + apiregistration.GroupName + "/anything/again": true, - "/apis/something": true, - "/apis/something/nested": true, - "/apis/something/nested/deeper": true, - "/api": true, - "/api/v1": true, - "/version": true, - } - - for path, expectedDelegation := range pathToDelegation { - delegate.called = false - - resp, err := http.Get(server.URL + path) - if err != nil { - t.Errorf("%s: %v", path, err) - continue - } - if resp.StatusCode != http.StatusOK { - bytes, _ := httputil.DumpResponse(resp, true) - t.Log(string(bytes)) - t.Errorf("%s: %v", path, err) - continue - } - if e, a := expectedDelegation, delegate.called; e != a { - t.Errorf("%s: expected %v, got %v", path, e, a) - continue - } - } -} - func TestAPIs(t *testing.T) { tests := []struct { name string @@ -269,13 +211,11 @@ func TestAPIs(t *testing.T) { indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) serviceIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) endpointsIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) - delegate := &delegationHTTPHandler{} handler := &apisHandler{ codecs: Codecs, serviceLister: v1listers.NewServiceLister(serviceIndexer), endpointsLister: v1listers.NewEndpointsLister(endpointsIndexer), lister: listers.NewAPIServiceLister(indexer), - delegate: delegate, } for _, o := range tc.apiservices { indexer.Add(o) diff --git a/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_proxy.go b/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_proxy.go index 904f17147cd..5206a42122b 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_proxy.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_proxy.go @@ -73,6 +73,10 @@ func (r *proxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { } handlingInfo := value.(proxyHandlingInfo) if handlingInfo.local { + if r.localDelegate == nil { + http.Error(w, "", http.StatusNotFound) + return + } r.localDelegate.ServeHTTP(w, req) return }