123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 |
- 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
- }
- }
- }
|