From 4397e13a8220bb0530a824acdcf4d955e25af007 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Fri, 17 Mar 2017 14:46:20 -0500 Subject: [PATCH] Use gorilla mux instead of big switch statement --- glide.lock | 12 ++- glide.yaml | 2 + handler.go | 227 +++++++++++++---------------------------------------- 3 files changed, 65 insertions(+), 176 deletions(-) diff --git a/glide.lock b/glide.lock index 001257bf1..422bc678e 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 7de62dbaf3cc1dc4959f4f6d8213102cb182b4dd7a87b3ac29260ad6bc1b0cef -updated: 2017-03-03T12:25:48.088390296-06:00 +hash: 743e8f978eb4ad8f80a2ab71b05caebbf50b6769b71aa457bc4f144fef8c6595 +updated: 2017-03-15T15:39:07.186226037-05:00 imports: - name: github.com/boltdb/bolt version: 4b1ebc1869ad66568b313d0dc410e2be72670dda @@ -27,6 +27,10 @@ imports: version: 888eb0692c857ec880338addf316bd662d5e630e subpackages: - proto +- name: github.com/gorilla/context + version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 +- name: github.com/gorilla/mux + version: 392c28fe23e1c45ddba891b0320b3b5df220beea - name: github.com/hashicorp/hcl version: 630949a3c5fa3c613328e1b8256052cbc2327c9b subpackages: @@ -48,6 +52,10 @@ imports: version: c37440a7cf42ac63b919c752ca73a85067e05992 - name: github.com/pelletier/go-toml version: 13d49d4606eb801b8f01ae542b4afc4c6ee3d84a +- name: github.com/rakyll/statik + version: a2b9c3533409cccb4bb188346bcfc789629a424d + subpackages: + - fs - name: github.com/satori/go.uuid version: 879c5887cd475cd7864858769793b2ceb0d44feb - name: github.com/spf13/afero diff --git a/glide.yaml b/glide.yaml index 1c4f35dae..d4615b0ba 100644 --- a/glide.yaml +++ b/glide.yaml @@ -29,3 +29,5 @@ import: version: ^1.1.0 - package: github.com/spf13/cobra - package: github.com/spf13/viper +- package: github.com/gorilla/mux + version: ^1.3.0 diff --git a/handler.go b/handler.go index 5b44c51d7..55eee3e04 100644 --- a/handler.go +++ b/handler.go @@ -11,13 +11,14 @@ import ( "io/ioutil" "log" "net/http" - "net/http/pprof" + _ "net/http/pprof" "os" "strconv" "strings" "time" "github.com/gogo/protobuf/proto" + "github.com/gorilla/mux" "github.com/pilosa/pilosa/internal" "github.com/pilosa/pilosa/pql" ) @@ -30,6 +31,8 @@ type Handler struct { Host string Cluster *Cluster + Router *mux.Router + // The execution engine for running queries. Executor interface { Execute(context context.Context, db string, query *pql.Query, slices []uint64, opt *ExecOptions) ([]interface{}, error) @@ -44,178 +47,56 @@ type Handler struct { // NewHandler returns a new instance of Handler with a default logger. func NewHandler() *Handler { - return &Handler{ + handler := &Handler{ LogOutput: os.Stderr, } + handler.Router = NewRouter(handler) + return handler +} + +func NewRouter(handler *Handler) *mux.Router { + router := mux.NewRouter() + router.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux).Methods("GET") + router.HandleFunc("/debug/vars", handler.handleExpvar).Methods("GET") + router.HandleFunc("/schema", handler.handleGetSchema).Methods("GET") + router.HandleFunc("/query", handler.handlePostQuery).Methods("POST") + router.HandleFunc("/import", handler.handlePostImport).Methods("POST") + router.HandleFunc("/export", handler.handleGetExport).Methods("GET") + router.HandleFunc("/slices/max", handler.handleGetSliceMax).Methods("GET") + router.HandleFunc("/db", handler.handlePostDB).Methods("POST") + router.HandleFunc("/db", handler.handleDeleteDB).Methods("DELETE") + router.HandleFunc("/db/time_quantum", handler.handlePatchDBTimeQuantum).Methods("PATCH") + router.HandleFunc("/db/attr/diff", handler.handlePostDBAttrDiff).Methods("POST") + router.HandleFunc("/frame", handler.handlePostFrame).Methods("POST") + router.HandleFunc("/frame", handler.handleDeleteFrame).Methods("DELETE") + router.HandleFunc("/frame/time_quantum", handler.handlePatchFrameTimeQuantum).Methods("PATCH") + router.HandleFunc("/frame/attr/diff", handler.handlePostFrameAttrDiff).Methods("POST") + router.HandleFunc("/fragment/nodes", handler.handleGetFragmentNodes).Methods("GET") + router.HandleFunc("/fragment/data", handler.handleGetFragmentData).Methods("GET") + router.HandleFunc("/fragment/data", handler.handlePostFragmentData).Methods("POST") + router.HandleFunc("/fragment/blocks", handler.handleGetFragmentBlocks).Methods("GET") + router.HandleFunc("/fragment/block/data", handler.handleGetFragmentBlockData).Methods("GET") + router.HandleFunc("/frame/restore", handler.handlePostFrameRestore).Methods("POST") + router.HandleFunc("/nodes", handler.handleGetNodes).Methods("GET") + router.HandleFunc("/version", handler.handleGetVersion).Methods("GET") + router.HandleFunc("/frame/views", handler.handleGetFrameViews).Methods("GET") + + // TODO: Apply MethodNotAllowed statuses to all endpoints. + // Ideally this would be automatic, as described in this (wontfix) ticket: + // https://github.com/gorilla/mux/issues/6 + // For now we just do it for the most commonly used handler, /query + router.HandleFunc("/query", handler.methodNotAllowedHandler).Methods("GET") + + return router +} + +func (h *Handler) methodNotAllowedHandler(w http.ResponseWriter, r *http.Request) { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) } // ServeHTTP handles an HTTP request. func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // Handle pprof requests separately. - if strings.HasPrefix(r.URL.Path, "/debug/pprof") { - switch r.URL.Path { - case "/debug/pprof/cmdline": - pprof.Cmdline(w, r) - case "/debug/pprof/profile": - pprof.Profile(w, r) - case "/debug/pprof/symbol": - pprof.Symbol(w, r) - case "/debug/pprof/trace": - pprof.Trace(w, r) - default: - pprof.Index(w, r) - } - return - } - - // Route API calls to appropriate handler functions. - t := time.Now() - switch r.URL.Path { - case "/schema": - switch r.Method { - case "GET": - h.handleGetSchema(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/query": - switch r.Method { - case "POST": - h.handlePostQuery(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/import": - switch r.Method { - case "POST": - h.handlePostImport(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/export": - switch r.Method { - case "GET": - h.handleGetExport(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/slices/max": - switch r.Method { - case "GET": - h.handleGetSliceMax(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/db": - switch r.Method { - case "POST": - h.handlePostDB(w, r) - case "DELETE": - h.handleDeleteDB(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/db/time_quantum": - switch r.Method { - case "PATCH": - h.handlePatchDBTimeQuantum(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/db/attr/diff": - switch r.Method { - case "POST": - h.handlePostDBAttrDiff(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/frame": - switch r.Method { - case "POST": - h.handlePostFrame(w, r) - case "DELETE": - h.handleDeleteFrame(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/frame/time_quantum": - switch r.Method { - case "PATCH": - h.handlePatchFrameTimeQuantum(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/frame/views": - switch r.Method { - case "GET": - h.handleGetFrameViews(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/frame/attr/diff": - switch r.Method { - case "POST": - h.handlePostFrameAttrDiff(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/fragment/nodes": - switch r.Method { - case "GET": - h.handleGetFragmentNodes(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/fragment/data": - switch r.Method { - case "GET": - h.handleGetFragmentData(w, r) - case "POST": - h.handlePostFragmentData(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/fragment/blocks": - switch r.Method { - case "GET": - h.handleGetFragmentBlocks(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/fragment/block/data": - switch r.Method { - case "GET": - h.handleGetFragmentBlockData(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/frame/restore": - switch r.Method { - case "POST": - h.handlePostFrameRestore(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/nodes": - switch r.Method { - case "GET": - h.handleGetNodes(w, r) - default: - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } - case "/version": - h.handleVersion(w, r) - - case "/debug/vars": - h.handleExpvar(w, r) - default: - http.NotFound(w, r) - } - - dif := time.Since(t).Seconds() - if dif > 90 { - h.logger().Printf("%s %s %.03fs", r.Method, r.URL.String(), dif) - } + h.Router.ServeHTTP(w, r) } // handleGetSchema handles GET /schema requests. @@ -291,26 +172,24 @@ func (h *Handler) handlePostQuery(w http.ResponseWriter, r *http.Request) { } } -func (h *Handler) handleGetSliceMax(w http.ResponseWriter, r *http.Request) error { +func (h *Handler) handleGetSliceMax(w http.ResponseWriter, r *http.Request) { var ms map[string]uint64 if inverse, _ := strconv.ParseBool(r.URL.Query().Get("inverse")); inverse { ms = h.Index.MaxInverseSlices() } else { ms = h.Index.MaxSlices() } - if strings.Contains(r.Header.Get("Accept"), "application/x-protobuf") { pb := &internal.MaxSlicesResponse{ MaxSlices: ms, } if buf, err := proto.Marshal(pb); err != nil { - return err + h.logger().Printf("protobuf marshal error: %s", err) } else if _, err := w.Write(buf); err != nil { - return err + h.logger().Printf("stream write error: %s", err) } - return nil } - return json.NewEncoder(w).Encode(sliceMaxResponse{ + json.NewEncoder(w).Encode(sliceMaxResponse{ MaxSlices: ms, }) } @@ -1275,7 +1154,7 @@ func (h *Handler) handleGetNodes(w http.ResponseWriter, r *http.Request) { } // handleGetVersion handles /version requests. -func (h *Handler) handleVersion(w http.ResponseWriter, r *http.Request) { +func (h *Handler) handleGetVersion(w http.ResponseWriter, r *http.Request) { if err := json.NewEncoder(w).Encode(struct { Version string `json:"version"` }{