package martini import ( "fmt" "net/http" "reflect" "regexp" "strconv" "sync" ) // Params is a map of name/value pairs for named routes. An instance of martini.Params is available to be injected into any route handler. type Params map[string]string // Router is Martini's de-facto routing interface. Supports HTTP verbs, stacked handlers, and dependency injection. type Router interface { Routes // Group adds a group where related routes can be added. Group(string, func(Router), ...Handler) // Get adds a route for a HTTP GET request to the specified matching pattern. Get(string, ...Handler) Route // Patch adds a route for a HTTP PATCH request to the specified matching pattern. Patch(string, ...Handler) Route // Post adds a route for a HTTP POST request to the specified matching pattern. Post(string, ...Handler) Route // Put adds a route for a HTTP PUT request to the specified matching pattern. Put(string, ...Handler) Route // Delete adds a route for a HTTP DELETE request to the specified matching pattern. Delete(string, ...Handler) Route // Options adds a route for a HTTP OPTIONS request to the specified matching pattern. Options(string, ...Handler) Route // Head adds a route for a HTTP HEAD request to the specified matching pattern. Head(string, ...Handler) Route // Any adds a route for any HTTP method request to the specified matching pattern. Any(string, ...Handler) Route // AddRoute adds a route for a given HTTP method request to the specified matching pattern. AddRoute(string, string, ...Handler) Route // NotFound sets the handlers that are called when a no route matches a request. Throws a basic 404 by default. NotFound(...Handler) // Handle is the entry point for routing. This is used as a martini.Handler Handle(http.ResponseWriter, *http.Request, Context) } type router struct { routes []*route notFounds []Handler groups []group routesLock sync.RWMutex } type group struct { pattern string handlers []Handler } // NewRouter creates a new Router instance. // If you aren't using ClassicMartini, then you can add Routes as a // service with: // // m := martini.New() // r := martini.NewRouter() // m.MapTo(r, (*martini.Routes)(nil)) // // If you are using ClassicMartini, then this is done for you. func NewRouter() Router { return &router{notFounds: []Handler{http.NotFound}, groups: make([]group, 0)} } func (r *router) Group(pattern string, fn func(Router), h ...Handler) { r.groups = append(r.groups, group{pattern, h}) fn(r) r.groups = r.groups[:len(r.groups)-1] } func (r *router) Get(pattern string, h ...Handler) Route { return r.addRoute("GET", pattern, h) } func (r *router) Patch(pattern string, h ...Handler) Route { return r.addRoute("PATCH", pattern, h) } func (r *router) Post(pattern string, h ...Handler) Route { return r.addRoute("POST", pattern, h) } func (r *router) Put(pattern string, h ...Handler) Route { return r.addRoute("PUT", pattern, h) } func (r *router) Delete(pattern string, h ...Handler) Route { return r.addRoute("DELETE", pattern, h) } func (r *router) Options(pattern string, h ...Handler) Route { return r.addRoute("OPTIONS", pattern, h) } func (r *router) Head(pattern string, h ...Handler) Route { return r.addRoute("HEAD", pattern, h) } func (r *router) Any(pattern string, h ...Handler) Route { return r.addRoute("*", pattern, h) } func (r *router) AddRoute(method, pattern string, h ...Handler) Route { return r.addRoute(method, pattern, h) } func (r *router) Handle(res http.ResponseWriter, req *http.Request, context Context) { bestMatch := NoMatch var bestVals map[string]string var bestRoute *route for _, route := range r.getRoutes() { match, vals := route.Match(req.Method, req.URL.Path) if match.BetterThan(bestMatch) { bestMatch = match bestVals = vals bestRoute = route if match == ExactMatch { break } } } if bestMatch != NoMatch { params := Params(bestVals) context.Map(params) bestRoute.Handle(context, res) return } // no routes exist, 404 c := &routeContext{context, 0, r.notFounds} context.MapTo(c, (*Context)(nil)) c.run() } func (r *router) NotFound(handler ...Handler) { r.notFounds = handler } func (r *router) addRoute(method string, pattern string, handlers []Handler) *route { if len(r.groups) > 0 { groupPattern := "" h := make([]Handler, 0) for _, g := range r.groups { groupPattern += g.pattern h = append(h, g.handlers...) } pattern = groupPattern + pattern h = append(h, handlers...) handlers = h } route := newRoute(method, pattern, handlers) route.Validate() r.appendRoute(route) return route } func (r *router) appendRoute(rt *route) { r.routesLock.Lock() defer r.routesLock.Unlock() r.routes = append(r.routes, rt) } func (r *router) getRoutes() []*route { r.routesLock.RLock() defer r.routesLock.RUnlock() return r.routes[:] } func (r *router) findRoute(name string) *route { for _, route := range r.getRoutes() { if route.name == name { return route } } return nil } // Route is an interface representing a Route in Martini's routing layer. type Route interface { // URLWith returns a rendering of the Route's url with the given string params. URLWith([]string) string // Name sets a name for the route. Name(string) // GetName returns the name of the route. GetName() string // Pattern returns the pattern of the route. Pattern() string // Method returns the method of the route. Method() string } type route struct { method string regex *regexp.Regexp handlers []Handler pattern string name string } var routeReg1 = regexp.MustCompile(`:[^/#?()\.\\]+`) var routeReg2 = regexp.MustCompile(`\*\*`) func newRoute(method string, pattern string, handlers []Handler) *route { route := route{method, nil, handlers, pattern, ""} pattern = routeReg1.ReplaceAllStringFunc(pattern, func(m string) string { return fmt.Sprintf(`(?P<%s>[^/#?]+)`, m[1:]) }) var index int pattern = routeReg2.ReplaceAllStringFunc(pattern, func(m string) string { index++ return fmt.Sprintf(`(?P<_%d>[^#?]*)`, index) }) pattern += `\/?` route.regex = regexp.MustCompile(pattern) return &route } type RouteMatch int const ( NoMatch RouteMatch = iota StarMatch OverloadMatch ExactMatch ) //Higher number = better match func (r RouteMatch) BetterThan(o RouteMatch) bool { return r > o } func (r route) MatchMethod(method string) RouteMatch { switch { case method == r.method: return ExactMatch case method == "HEAD" && r.method == "GET": return OverloadMatch case r.method == "*": return StarMatch default: return NoMatch } } func (r route) Match(method string, path string) (RouteMatch, map[string]string) { // add Any method matching support match := r.MatchMethod(method) if match == NoMatch { return match, nil } matches := r.regex.FindStringSubmatch(path) if len(matches) > 0 && matches[0] == path { params := make(map[string]string) for i, name := range r.regex.SubexpNames() { if len(name) > 0 { params[name] = matches[i] } } return match, params } return NoMatch, nil } func (r *route) Validate() { for _, handler := range r.handlers { validateHandler(handler) } } func (r *route) Handle(c Context, res http.ResponseWriter) { context := &routeContext{c, 0, r.handlers} c.MapTo(context, (*Context)(nil)) c.MapTo(r, (*Route)(nil)) context.run() } var urlReg = regexp.MustCompile(`:[^/#?()\.\\]+|\(\?P<[a-zA-Z0-9]+>.*\)`) // URLWith returns the url pattern replacing the parameters for its values func (r *route) URLWith(args []string) string { if len(args) > 0 { argCount := len(args) i := 0 url := urlReg.ReplaceAllStringFunc(r.pattern, func(m string) string { var val interface{} if i < argCount { val = args[i] } else { val = m } i += 1 return fmt.Sprintf(`%v`, val) }) return url } return r.pattern } func (r *route) Name(name string) { r.name = name } func (r *route) GetName() string { return r.name } func (r *route) Pattern() string { return r.pattern } func (r *route) Method() string { return r.method } // Routes is a helper service for Martini's routing layer. type Routes interface { // URLFor returns a rendered URL for the given route. Optional params can be passed to fulfill named parameters in the route. URLFor(name string, params ...interface{}) string // MethodsFor returns an array of methods available for the path MethodsFor(path string) []string // All returns an array with all the routes in the router. All() []Route } // URLFor returns the url for the given route name. func (r *router) URLFor(name string, params ...interface{}) string { route := r.findRoute(name) if route == nil { panic("route not found") } var args []string for _, param := range params { switch v := param.(type) { case int: args = append(args, strconv.FormatInt(int64(v), 10)) case string: args = append(args, v) default: if v != nil { panic("Arguments passed to URLFor must be integers or strings") } } } return route.URLWith(args) } func (r *router) All() []Route { routes := r.getRoutes() var ri = make([]Route, len(routes)) for i, route := range routes { ri[i] = Route(route) } return ri } func hasMethod(methods []string, method string) bool { for _, v := range methods { if v == method { return true } } return false } // MethodsFor returns all methods available for path func (r *router) MethodsFor(path string) []string { methods := []string{} for _, route := range r.getRoutes() { matches := route.regex.FindStringSubmatch(path) if len(matches) > 0 && matches[0] == path && !hasMethod(methods, route.method) { methods = append(methods, route.method) } } return methods } type routeContext struct { Context index int handlers []Handler } func (r *routeContext) Next() { r.index += 1 r.run() } func (r *routeContext) run() { for r.index < len(r.handlers) { handler := r.handlers[r.index] vals, err := r.Invoke(handler) if err != nil { panic(err) } r.index += 1 // if the handler returned something, write it to the http response if len(vals) > 0 { ev := r.Get(reflect.TypeOf(ReturnHandler(nil))) handleReturn := ev.Interface().(ReturnHandler) handleReturn(r, vals) } if r.Written() { return } } }