// Copyright 2018-2019 Martin Riedl // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package gohttprouter import ( "fmt" "net/http" "strings" ) // Router is the main struct for the whole routing logic. type Router struct { registry map[string]*route } // RouteHandler is the function construct called by this framework. type RouteHandler func(http.ResponseWriter, *http.Request, RoutingInfo) // RoutingInfo is returned for each web server method call and includes details abount the execution. type RoutingInfo struct { Parameters map[string]string } // Handler interface that must be implemented by custom structs. type Handler interface { ServeHTTP(http.ResponseWriter, *http.Request, RoutingInfo) } type defaultHandler struct { Func RouteHandler } func (handler defaultHandler) ServeHTTP(response http.ResponseWriter, request *http.Request, routingInfo RoutingInfo) { handler.Func(response, request, routingInfo) } // New creates a new router instance. func New() *Router { return &Router{ registry: make(map[string]*route), } } // Handle registers a new handler using method and path for execution. func (router *Router) Handle(method string, path string, handler Handler) error { return router.addRoute(method, path, handler) } // HandleFunc registers a new handler function using method and path for execution. func (router *Router) HandleFunc(method string, path string, handler RouteHandler) error { newHandler := defaultHandler{Func: handler} return router.addRoute(method, path, newHandler) } func (router *Router) addRoute(method string, path string, handler Handler) error { if err := router.validateRoute(path); err != nil { return err } // parse path and set new handler route, created, _ := router.findRoute(method, path, true) if !created { return fmt.Errorf("there is already a handler registered for the path %s", path) } route.handler = handler return nil } func (router *Router) findRoute(method string, path string, create bool) (route *route, created bool, parameter map[string]string) { methodUpper := strings.ToUpper(method) // add method, if not already existing methodRoute, ok := router.registry[methodUpper] if !ok && !create { return nil, false, nil } else if !ok && create { methodRoute = newRoute() router.registry[methodUpper] = methodRoute created = true } // parse path and set new handler route, pathCreated, parameter := methodRoute.parsePath(path, create) created = created || pathCreated return } func (router *Router) validateRoute(path string) error { // path must start with a slash if strings.Index(path, "/") != 0 { return fmt.Errorf("URL path must start with a slash (/)") } // path must not end with a slash if len(path) > 1 && path[len(path)-1] == '/' { return fmt.Errorf("URL path must not end with a slash (/)") } return nil } func (router *Router) ServeHTTP(response http.ResponseWriter, request *http.Request) { // check for exact matching route, _, parameter := router.findRoute(request.Method, request.URL.Path, false) if route != nil && route.handler != nil { route.handler.ServeHTTP(response, request, RoutingInfo{Parameters: parameter}) return } // nothing found http.NotFound(response, request) } // Must is a wrapper that panics if an error was returned. func Must(err error) { if err != nil { panic(err) } }