github.com/travisturner/buffalo@v0.11.1/router.go (about) 1 package buffalo 2 3 import ( 4 "fmt" 5 "net/http" 6 "os" 7 "path" 8 "reflect" 9 "sort" 10 "strings" 11 12 "github.com/markbates/inflect" 13 "github.com/pkg/errors" 14 ) 15 16 // GET maps an HTTP "GET" request to the path and the specified handler. 17 func (a *App) GET(p string, h Handler) *RouteInfo { 18 return a.addRoute("GET", p, h) 19 } 20 21 // POST maps an HTTP "POST" request to the path and the specified handler. 22 func (a *App) POST(p string, h Handler) *RouteInfo { 23 return a.addRoute("POST", p, h) 24 } 25 26 // PUT maps an HTTP "PUT" request to the path and the specified handler. 27 func (a *App) PUT(p string, h Handler) *RouteInfo { 28 return a.addRoute("PUT", p, h) 29 } 30 31 // DELETE maps an HTTP "DELETE" request to the path and the specified handler. 32 func (a *App) DELETE(p string, h Handler) *RouteInfo { 33 return a.addRoute("DELETE", p, h) 34 } 35 36 // HEAD maps an HTTP "HEAD" request to the path and the specified handler. 37 func (a *App) HEAD(p string, h Handler) *RouteInfo { 38 return a.addRoute("HEAD", p, h) 39 } 40 41 // OPTIONS maps an HTTP "OPTIONS" request to the path and the specified handler. 42 func (a *App) OPTIONS(p string, h Handler) *RouteInfo { 43 return a.addRoute("OPTIONS", p, h) 44 } 45 46 // PATCH maps an HTTP "PATCH" request to the path and the specified handler. 47 func (a *App) PATCH(p string, h Handler) *RouteInfo { 48 return a.addRoute("PATCH", p, h) 49 } 50 51 // Redirect from one URL to another URL. Only works for "GET" requests. 52 func (a *App) Redirect(status int, from, to string) *RouteInfo { 53 return a.GET(from, func(c Context) error { 54 return c.Redirect(status, to) 55 }) 56 } 57 58 // Mount mounts a http.Handler (or Buffalo app) and passes through all requests to it. 59 // 60 // func muxer() http.Handler { 61 // f := func(res http.ResponseWriter, req *http.Request) { 62 // fmt.Fprintf(res, "%s - %s", req.Method, req.URL.String()) 63 // } 64 // mux := mux.NewRouter() 65 // mux.HandleFunc("/foo", f).Methods("GET") 66 // mux.HandleFunc("/bar", f).Methods("POST") 67 // mux.HandleFunc("/baz/baz", f).Methods("DELETE") 68 // return mux 69 // } 70 // 71 // a.Mount("/admin", muxer()) 72 // 73 // $ curl -X DELETE http://localhost:3000/admin/baz/baz 74 func (a *App) Mount(p string, h http.Handler) { 75 prefix := path.Join(a.Prefix, p) 76 path := path.Join(p, "{path:.+}") 77 a.ANY(path, WrapHandler(http.StripPrefix(prefix, h))) 78 } 79 80 // ServeFiles maps an path to a directory on disk to serve static files. 81 // Useful for JavaScript, images, CSS, etc... 82 /* 83 a.ServeFiles("/assets", http.Dir("path/to/assets")) 84 */ 85 func (a *App) ServeFiles(p string, root http.FileSystem) { 86 path := path.Join(a.Prefix, p) 87 a.router.PathPrefix(path).Handler(http.StripPrefix(path, a.fileServer(root))) 88 } 89 90 func (a *App) fileServer(fs http.FileSystem) http.Handler { 91 fsh := http.FileServer(fs) 92 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 93 _, err := fs.Open(path.Clean(r.URL.Path)) 94 if os.IsNotExist(err) { 95 eh := a.ErrorHandlers.Get(404) 96 eh(404, errors.Errorf("could not find %s", r.URL.Path), a.newContext(RouteInfo{}, w, r)) 97 return 98 } 99 fsh.ServeHTTP(w, r) 100 }) 101 } 102 103 // Resource maps an implementation of the Resource interface 104 // to the appropriate RESTful mappings. Resource returns the *App 105 // associated with this group of mappings so you can set middleware, etc... 106 // on that group, just as if you had used the a.Group functionality. 107 /* 108 a.Resource("/users", &UsersResource{}) 109 110 // Is equal to this: 111 112 ur := &UsersResource{} 113 g := a.Group("/users") 114 g.GET("/", ur.List) // GET /users => ur.List 115 g.GET("/new", ur.New) // GET /users/new => ur.New 116 g.GET("/{user_id}", ur.Show) // GET /users/{user_id} => ur.Show 117 g.GET("/{user_id}/edit", ur.Edit) // GET /users/{user_id}/edit => ur.Edit 118 g.POST("/", ur.Create) // POST /users => ur.Create 119 g.PUT("/{user_id}", ur.Update) PUT /users/{user_id} => ur.Update 120 g.DELETE("/{user_id}", ur.Destroy) DELETE /users/{user_id} => ur.Destroy 121 */ 122 func (a *App) Resource(p string, r Resource) *App { 123 g := a.Group(p) 124 p = "/" 125 126 rv := reflect.ValueOf(r) 127 if rv.Kind() == reflect.Ptr { 128 rv = rv.Elem() 129 } 130 131 rt := rv.Type() 132 rname := fmt.Sprintf("%s.%s", rt.PkgPath(), rt.Name()) + ".%s" 133 134 name := strings.Replace(rt.Name(), "Resource", "", 1) 135 paramName := inflect.Singularize(inflect.Underscore(name)) 136 137 spath := path.Join(p, fmt.Sprintf("{%s_id}", paramName)) 138 setFuncKey(r.List, fmt.Sprintf(rname, "List")) 139 g.GET(p, r.List) 140 setFuncKey(r.New, fmt.Sprintf(rname, "New")) 141 g.GET(path.Join(p, "new"), r.New) 142 setFuncKey(r.Show, fmt.Sprintf(rname, "Show")) 143 g.GET(path.Join(spath), r.Show) 144 setFuncKey(r.Edit, fmt.Sprintf(rname, "Edit")) 145 g.GET(path.Join(spath, "edit"), r.Edit) 146 setFuncKey(r.Create, fmt.Sprintf(rname, "Create")) 147 g.POST(p, r.Create) 148 setFuncKey(r.Update, fmt.Sprintf(rname, "Update")) 149 g.PUT(path.Join(spath), r.Update) 150 setFuncKey(r.Destroy, fmt.Sprintf(rname, "Destroy")) 151 g.DELETE(path.Join(spath), r.Destroy) 152 g.Prefix = path.Join(g.Prefix, spath) 153 return g 154 } 155 156 // ANY accepts a request across any HTTP method for the specified path 157 // and routes it to the specified Handler. 158 func (a *App) ANY(p string, h Handler) { 159 a.GET(p, h) 160 a.POST(p, h) 161 a.PUT(p, h) 162 a.PATCH(p, h) 163 a.HEAD(p, h) 164 a.OPTIONS(p, h) 165 a.DELETE(p, h) 166 } 167 168 // Group creates a new `*App` that inherits from it's parent `*App`. 169 // This is useful for creating groups of end-points that need to share 170 // common functionality, like middleware. 171 /* 172 g := a.Group("/api/v1") 173 g.Use(AuthorizeAPIMiddleware) 174 g.GET("/users, APIUsersHandler) 175 g.GET("/users/:user_id, APIUserShowHandler) 176 */ 177 func (a *App) Group(groupPath string) *App { 178 g := New(a.Options) 179 g.Prefix = path.Join(a.Prefix, groupPath) 180 g.Name = g.Prefix 181 182 g.router = a.router 183 g.Middleware = a.Middleware.clone() 184 g.ErrorHandlers = a.ErrorHandlers 185 g.root = a 186 if a.root != nil { 187 g.root = a.root 188 } 189 a.children = append(a.children, g) 190 return g 191 } 192 193 func (a *App) addRoute(method string, url string, h Handler) *RouteInfo { 194 a.moot.Lock() 195 defer a.moot.Unlock() 196 197 url = path.Join(a.Prefix, url) 198 name := a.buildRouteName(url) 199 200 hs := funcKey(h) 201 r := &RouteInfo{ 202 Method: method, 203 Path: url, 204 HandlerName: hs, 205 Handler: h, 206 App: a, 207 Aliases: []string{}, 208 } 209 210 r.MuxRoute = a.router.Handle(url, r).Methods(method) 211 r.Name(name) 212 213 routes := a.Routes() 214 routes = append(routes, r) 215 sort.Sort(routes) 216 217 if a.root != nil { 218 a.root.routes = routes 219 } else { 220 a.routes = routes 221 } 222 223 return r 224 } 225 226 //buildRouteName builds a route based on the path passed. 227 func (a *App) buildRouteName(p string) string { 228 if p == "/" || p == "" { 229 return "root" 230 } 231 232 resultParts := []string{} 233 parts := strings.Split(p, "/") 234 235 for index, part := range parts { 236 237 if strings.Contains(part, "{") || part == "" { 238 continue 239 } 240 241 shouldSingularize := (len(parts) > index+1) && strings.Contains(parts[index+1], "{") 242 if shouldSingularize { 243 part = inflect.Singularize(part) 244 } 245 246 if parts[index] == "new" || parts[index] == "edit" { 247 resultParts = append([]string{part}, resultParts...) 248 continue 249 } 250 251 if index > 0 && strings.Contains(parts[index-1], "}") { 252 resultParts = append(resultParts, part) 253 continue 254 } 255 256 resultParts = append(resultParts, part) 257 } 258 259 if len(resultParts) == 0 { 260 return "unnamed" 261 } 262 263 underscore := strings.TrimSpace(strings.Join(resultParts, "_")) 264 return inflect.CamelizeDownFirst(underscore) 265 }