From a74fac7d7018744d5717f1841c47afc56d000d72 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Wed, 6 Aug 2014 13:06:42 -0400 Subject: [PATCH] Centralize path magic in apiserver into the New method Make OperationHandler and WatchHandler properly encapsulate their concerns and make them not depend on the global path --- pkg/apiserver/apiserver.go | 67 ++++++++++++++++++++++++-------------- pkg/apiserver/operation.go | 36 +++++++------------- pkg/apiserver/watch.go | 19 ++++------- 3 files changed, 59 insertions(+), 63 deletions(-) diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index a882d18571a..ba8be538eb0 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -21,6 +21,7 @@ import ( "fmt" "io/ioutil" "net/http" + "path" "runtime/debug" "strings" "time" @@ -48,12 +49,11 @@ type Codec interface { // // TODO: consider migrating this to go-restful which is a more full-featured version of the same thing. type APIServer struct { - prefix string storage map[string]RESTStorage codec Codec ops *Operations - mux *http.ServeMux asyncOpWait time.Duration + handler http.Handler } // New creates a new APIServer object. 'storage' contains a map of handlers. 'codec' @@ -65,31 +65,43 @@ type APIServer struct { // TODO: add multitype codec serialization func New(storage map[string]RESTStorage, codec Codec, prefix string) *APIServer { s := &APIServer{ - prefix: strings.TrimRight(prefix, "/"), storage: storage, codec: codec, ops: NewOperations(), - mux: http.NewServeMux(), // Delay just long enough to handle most simple write operations asyncOpWait: time.Millisecond * 25, } - // Primary API methods - s.mux.HandleFunc(s.prefix+"/", s.handleREST) - s.mux.HandleFunc(s.watchPrefix()+"/", s.handleWatch) + mux := http.NewServeMux() + + prefix = strings.TrimRight(prefix, "/") + + // Primary API handlers + restPrefix := prefix + "/" + mux.Handle(restPrefix, http.StripPrefix(restPrefix, http.HandlerFunc(s.handleREST))) + + // Watch API handlers + watchPrefix := path.Join(prefix, "watch") + "/" + mux.Handle(watchPrefix, http.StripPrefix(watchPrefix, &WatchHandler{storage})) // Support services for the apiserver - s.mux.Handle("/logs/", http.StripPrefix("/logs/", http.FileServer(http.Dir("/var/log/")))) - healthz.InstallHandler(s.mux) - s.mux.HandleFunc("/version", handleVersion) - s.mux.HandleFunc("/", handleIndex) + logsPrefix := "/logs/" + mux.Handle(logsPrefix, http.StripPrefix(logsPrefix, http.FileServer(http.Dir("/var/log/")))) + healthz.InstallHandler(mux) + mux.HandleFunc("/version", handleVersion) + mux.HandleFunc("/", handleIndex) // Handle both operations and operations/* with the same handler - s.mux.HandleFunc(s.operationPrefix(), s.handleOperation) - s.mux.HandleFunc(s.operationPrefix()+"/", s.handleOperation) + handler := &OperationHandler{s.ops, s.codec} + operationPrefix := path.Join(prefix, "operations") + mux.Handle(operationPrefix, http.StripPrefix(operationPrefix, handler)) + operationsPrefix := operationPrefix + "/" + mux.Handle(operationsPrefix, http.StripPrefix(operationsPrefix, handler)) // Proxy minion requests - s.mux.Handle("/proxy/minion/", http.StripPrefix("/proxy/minion", http.HandlerFunc(handleProxyMinion))) + mux.Handle("/proxy/minion/", http.StripPrefix("/proxy/minion", http.HandlerFunc(handleProxyMinion))) + + s.handler = mux return s } @@ -112,29 +124,25 @@ func (s *APIServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { ), ).Log() - // Dispatch via our mux. - s.mux.ServeHTTP(w, req) + // Dispatch to the internal handler + s.handler.ServeHTTP(w, req) } // handleREST handles requests to all our RESTStorage objects. func (s *APIServer) handleREST(w http.ResponseWriter, req *http.Request) { - if !strings.HasPrefix(req.URL.Path, s.prefix) { + parts := splitPath(req.URL.Path) + if len(parts) < 1 { notFound(w, req) return } - requestParts := strings.Split(req.URL.Path[len(s.prefix):], "/")[1:] - if len(requestParts) < 1 { - notFound(w, req) - return - } - storage := s.storage[requestParts[0]] + storage := s.storage[parts[0]] if storage == nil { - httplog.LogOf(w).Addf("'%v' has no storage object", requestParts[0]) + httplog.LogOf(w).Addf("'%v' has no storage object", parts[0]) notFound(w, req) return } - s.handleRESTStorage(requestParts, req, w, storage) + s.handleRESTStorage(parts, req, w, storage) } // handleRESTStorage is the main dispatcher for a storage object. It switches on the HTTP method, and then @@ -347,3 +355,12 @@ func readBody(req *http.Request) ([]byte, error) { defer req.Body.Close() return ioutil.ReadAll(req.Body) } + +// splitPath returns the segments for a URL path +func splitPath(path string) []string { + path = strings.Trim(path, "/") + if path == "" { + return []string{} + } + return strings.Split(path, "/") +} diff --git a/pkg/apiserver/operation.go b/pkg/apiserver/operation.go index ad0c25ecbba..2a102ae09c2 100644 --- a/pkg/apiserver/operation.go +++ b/pkg/apiserver/operation.go @@ -18,10 +18,8 @@ package apiserver import ( "net/http" - "path" "sort" "strconv" - "strings" "sync" "sync/atomic" "time" @@ -30,37 +28,25 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) -func (s *APIServer) operationPrefix() string { - return path.Join(s.prefix, "operations") +type OperationHandler struct { + ops *Operations + codec Codec } -func (s *APIServer) handleOperation(w http.ResponseWriter, req *http.Request) { - opPrefix := s.operationPrefix() - if !strings.HasPrefix(req.URL.Path, opPrefix) { - notFound(w, req) - return - } - trimmed := strings.TrimLeft(req.URL.Path[len(opPrefix):], "/") - parts := strings.Split(trimmed, "/") - if trimmed == "" { - parts = []string{} - } - if len(parts) > 1 { - notFound(w, req) - return - } - if req.Method != "GET" { +func (h *OperationHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + parts := splitPath(req.URL.Path) + if len(parts) > 1 || req.Method != "GET" { notFound(w, req) return } if len(parts) == 0 { // List outstanding operations. - list := s.ops.List() - writeJSON(http.StatusOK, s.codec, list, w) + list := h.ops.List() + writeJSON(http.StatusOK, h.codec, list, w) return } - op := s.ops.Get(parts[0]) + op := h.ops.Get(parts[0]) if op == nil { notFound(w, req) return @@ -68,9 +54,9 @@ func (s *APIServer) handleOperation(w http.ResponseWriter, req *http.Request) { obj, complete := op.StatusOrResult() if complete { - writeJSON(http.StatusOK, s.codec, obj, w) + writeJSON(http.StatusOK, h.codec, obj, w) } else { - writeJSON(http.StatusAccepted, s.codec, obj, w) + writeJSON(http.StatusAccepted, h.codec, obj, w) } } diff --git a/pkg/apiserver/watch.go b/pkg/apiserver/watch.go index b2f1cc1853b..5b8480cd168 100644 --- a/pkg/apiserver/watch.go +++ b/pkg/apiserver/watch.go @@ -19,8 +19,6 @@ package apiserver import ( "encoding/json" "net/http" - "path" - "strings" "code.google.com/p/go.net/websocket" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" @@ -28,22 +26,17 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" ) -func (s *APIServer) watchPrefix() string { - return path.Join(s.prefix, "watch") +type WatchHandler struct { + storage map[string]RESTStorage } // handleWatch processes a watch request -func (s *APIServer) handleWatch(w http.ResponseWriter, req *http.Request) { - prefix := s.watchPrefix() - if !strings.HasPrefix(req.URL.Path, prefix) { - notFound(w, req) - return - } - parts := strings.Split(req.URL.Path[len(prefix):], "/")[1:] - if req.Method != "GET" || len(parts) < 1 { +func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + parts := splitPath(req.URL.Path) + if len(parts) < 1 || req.Method != "GET" { notFound(w, req) } - storage := s.storage[parts[0]] + storage := h.storage[parts[0]] if storage == nil { notFound(w, req) }