From f95e5cb0571ea9f6e6efda41c22e05b739b7e86b Mon Sep 17 00:00:00 2001 From: Quoc-Viet Nguyen Date: Thu, 20 Oct 2016 14:21:18 +1000 Subject: [PATCH] Switch to gorilla/mux for server router Closes #1. --- README.md | 22 ++++++++++------ THIRDPARTY.md | 12 ++++----- auth/basic_test.go | 2 +- core/server.go | 8 +++--- example/restful/restful.go | 5 ++-- server/router/router.go | 53 ++++++++++++++------------------------ views/resource.go | 15 +---------- 7 files changed, 48 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index f95210c..1e5d541 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ Melon is a partial port of [Dropwizard](http://dropwizard.io/) in Go. Besides of builtin Go packages, it utilizes a number of [libraries](https://github.com/goburrow/melon/blob/master/THIRDPARTY.md) in order to build a server stack quickly, including: -* [goji](https://github.com/zenazn/goji): a robust web framework. +* [mux](https://github.com/gorilla/mux): a popular HTTP multiplexer. * [gol](https://github.com/goburrow/gol): a simple hierarchical logging API. * [metrics](https://github.com/codahale/metrics): a minimalist instrumentation library. -* [validator](https://github.com/goburrow/validator): extensible value validations. +* [validator](https://github.com/goburrow/validator): an extensible value validator. Features supported: @@ -29,6 +29,11 @@ Features supported: ## Examples See [example](https://github.com/goburrow/melon/tree/master/example) +- [Hello World](example/helloworld/helloword.go) +- [Restful](example/restful/restful.go) +- [HTML Template](example/template/template.go) +- [Basic Authentication](example/basicauth/basicauth.go) + ``` INFO [2015-02-04T12:00:01.289+10:00] melon/server: starting ______ @@ -41,14 +46,15 @@ INFO [2015-02-04T12:00:01.289+10:00] melon/server: starting \/_____/ INFO [2015-02-04T12:00:01.289+10:00] melon/assets: registering AssetsBundle for path /static/ -DEBUG [2015-02-04T12:00:01.289+10:00] melon/server: resources = [*rest.XMLProvider,*main.usersResource,*main.userResource] +DEBUG [2015-02-04T12:00:01.289+10:00] melon/server: resources = [*views.JSONProvider,*views.XMLProvider,*views.Resource,*views.Resource,*views.Resource,*views.Resource,*views.Resource] INFO [2015-02-04T12:00:01.289+10:00] melon/server: endpoints = - GET /users (*main.usersResource) - POST /users (*main.usersResource) - GET /user/:name (*main.userResource) - POST /user/:name (*main.userResource) - DELETE /user/:name (*main.userResource) + GET /static/* (http.HandlerFunc) + GET /users (*views.httpHandler) + POST /users (*views.httpHandler) + GET /user/{name} (*views.httpHandler) + PUT /user/{name} (*views.httpHandler) + DELETE /user/{name} (*views.httpHandler) INFO [2015-02-04T12:00:01.290+10:00] melon/admin: tasks = diff --git a/THIRDPARTY.md b/THIRDPARTY.md index c4048c2..7a54c54 100644 --- a/THIRDPARTY.md +++ b/THIRDPARTY.md @@ -1,10 +1,8 @@ -# Adopt - https://github.com/codahale/metrics -- https://github.com/goburrow/gol -- https://github.com/zenazn/goji - -# Trial - https://github.com/ghodss/yaml -- https://github.com/goburrow/validator -- https://github.com/goburrow/health - https://github.com/goburrow/dynamic +- https://github.com/goburrow/gol +- https://github.com/goburrow/health +- https://github.com/goburrow/validator +- https://github.com/gorilla/mux +- https://github.com/zanazn/goji/graceful diff --git a/auth/basic_test.go b/auth/basic_test.go index 8c27839..14079a3 100644 --- a/auth/basic_test.go +++ b/auth/basic_test.go @@ -26,7 +26,7 @@ func TestBasicAuthenticator(t *testing.T) { rt := router.New() rt.AddFilter(f) - rt.Handle("GET", "/", handler) + rt.Handle("GET", "/", http.HandlerFunc(handler)) srv := httptest.NewServer(rt) defer srv.Close() diff --git a/core/server.go b/core/server.go index df3b87f..f92ffda 100644 --- a/core/server.go +++ b/core/server.go @@ -3,6 +3,7 @@ package core import ( "bytes" "fmt" + "net/http" ) // Server is a managed HTTP server handling incoming connections to both application and admin. @@ -14,9 +15,8 @@ type Server interface { // Router allows users to register a http.Handler. type Router interface { - // Handle registers the handler for the given pattern. - // An implementation of ServerHandler must at least support http.Handler. - Handle(method, pattern string, handler interface{}) + // Handle registers the HTTP handler for the given pattern. + Handle(method, pattern string, handler http.Handler) // PathPrefix returns prefix path of this handler. PathPrefix() string // Endpoints returns registered HTTP endpoints. @@ -80,7 +80,7 @@ func (env *ServerEnvironment) logResources() { var buf bytes.Buffer for i, component := range env.components { if i > 0 { - fmt.Fprintf(&buf, ",") + buf.WriteByte(',') } fmt.Fprintf(&buf, "%T", component) } diff --git a/example/restful/restful.go b/example/restful/restful.go index e1d85c3..e268a40 100644 --- a/example/restful/restful.go +++ b/example/restful/restful.go @@ -8,6 +8,7 @@ import ( "github.com/goburrow/melon" "github.com/goburrow/melon/core" "github.com/goburrow/melon/debug" + "github.com/goburrow/melon/server/router" "github.com/goburrow/melon/views" ) @@ -48,7 +49,7 @@ func (s *resource) createUser(w http.ResponseWriter, r *http.Request) { } func (s *resource) getUser(w http.ResponseWriter, r *http.Request) { - params := views.Params(r) + params := router.PathParams(r) s.mu.RLock() user, ok := s.users[params["name"]] @@ -87,7 +88,7 @@ func run(conf interface{}, env *core.Environment) error { env.Server.Register( views.NewResource("POST", "/user", http.HandlerFunc(res.createUser), views.WithTimerMetric("UsersCreate")), - views.NewResource("GET", "/user/:name", http.HandlerFunc(res.getUser)), + views.NewResource("GET", "/user/{name}", http.HandlerFunc(res.getUser)), views.NewResource("GET", "/user", views.HandlerFunc(res.listUsers)), ) return nil diff --git a/server/router/router.go b/server/router/router.go index f1378ee..7abf52c 100644 --- a/server/router/router.go +++ b/server/router/router.go @@ -10,14 +10,14 @@ import ( "strings" "github.com/goburrow/melon/server/filter" - "github.com/zenazn/goji/web" + "github.com/gorilla/mux" ) // Router handles HTTP requests. // It implements core.Router type Router struct { // serverMux is the HTTP request router. - serveMux *web.Mux + serveMux *mux.Router // filterChain is the builder for HTTP filters. filterChain *filter.Chain @@ -27,12 +27,12 @@ type Router struct { // New creates a new Router. func New(options ...Option) *Router { - mux := web.New() + serveMux := mux.NewRouter() chain := filter.NewChain() - chain.Add(mux) + chain.Add(serveMux) r := &Router{ - serveMux: mux, + serveMux: serveMux, filterChain: chain, } for _, opt := range options { @@ -42,35 +42,17 @@ func New(options ...Option) *Router { } // Handle registers the handler for the given pattern. -func (h *Router) Handle(method, pattern string, handler interface{}) { - var f func(web.PatternType, web.HandlerType) - - switch method { - case "GET": - f = h.serveMux.Get - case "HEAD": - f = h.serveMux.Head - case "POST": - f = h.serveMux.Post - case "PUT": - f = h.serveMux.Put - case "DELETE": - f = h.serveMux.Delete - case "TRACE": - f = h.serveMux.Trace - case "OPTIONS": - f = h.serveMux.Options - case "CONNECT": - f = h.serveMux.Connect - case "PATCH": - f = h.serveMux.Patch - case "*": - f = h.serveMux.Handle - default: - panic("server: unsupported method " + method) +func (h *Router) Handle(method, pattern string, handler http.Handler) { + r := h.serveMux.NewRoute() + r.Handler(handler) + if method != "" && method != "*" { + r.Methods(method) + } + if strings.HasSuffix(pattern, "*") { + r.PathPrefix(pattern[:len(pattern)-1]) + } else { + r.Path(pattern) } - f(pattern, handler) - // log endpoint endpoint := fmt.Sprintf("%-7s %s%s (%T)", method, h.pathPrefix, pattern, handler) h.endpoints = append(h.endpoints, endpoint) @@ -122,3 +104,8 @@ func WithPathPrefix(prefix string) Option { r.pathPrefix = prefix } } + +// PathParams returns path parameters from the path of the request. +func PathParams(r *http.Request) map[string]string { + return mux.Vars(r) +} diff --git a/views/resource.go b/views/resource.go index 011f2aa..212d6b1 100644 --- a/views/resource.go +++ b/views/resource.go @@ -9,7 +9,6 @@ import ( "github.com/codahale/metrics" "github.com/goburrow/melon/core" - "github.com/zenazn/goji/web" ) // Resource is a view resource. @@ -159,7 +158,7 @@ type httpHandler struct { } // TODO: migrate to github.com/goji/goji when it supports Go 1.7. -func (h *httpHandler) ServeHTTPC(c web.C, w http.ResponseWriter, r *http.Request) { +func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if h.metricRequests != "" { h.metricRequests.Add() } @@ -173,7 +172,6 @@ func (h *httpHandler) ServeHTTPC(c web.C, w http.ResponseWriter, r *http.Request handler: h, readers: requestReaders, writers: responseWriters, - params: c.URLParams, contentType: contentType, } ctx := newContext(r.Context(), handlerCtx) @@ -240,7 +238,6 @@ type handlerContext struct { handler *httpHandler readers []requestReader writers []responseWriter - params map[string]string // contentType is expected response content type contentType string @@ -335,16 +332,6 @@ func Error(w http.ResponseWriter, r *http.Request, err error) { ctx.handler.errorMapper.MapError(w, r, err) } -// Params returns path parameters from request. -func Params(r *http.Request) map[string]string { - ctx := fromContext(r.Context()) - if ctx == nil { - logger.Errorf("no handler in request context: %v", r.Context()) - return nil - } - return ctx.params -} - // Entity reads and validates entity v from request r. func Entity(r *http.Request, v interface{}) error { ctx := fromContext(r.Context())