github.com/segakazzz/buffalo@v0.16.22-0.20210119082501-1f52048d3feb/route_mappings.go (about) 1 package buffalo 2 3 import ( 4 "fmt" 5 "net/http" 6 "net/url" 7 "os" 8 "path" 9 "reflect" 10 "sort" 11 "strings" 12 13 "github.com/gobuffalo/envy" 14 "github.com/gobuffalo/flect/name" 15 "github.com/gorilla/handlers" 16 ) 17 18 const ( 19 // AssetsAgeVarName is the ENV variable used to specify max age when ServeFiles is used. 20 AssetsAgeVarName = "ASSETS_MAX_AGE" 21 ) 22 23 // GET maps an HTTP "GET" request to the path and the specified handler. 24 func (a *App) GET(p string, h Handler) *RouteInfo { 25 return a.addRoute("GET", p, h) 26 } 27 28 // POST maps an HTTP "POST" request to the path and the specified handler. 29 func (a *App) POST(p string, h Handler) *RouteInfo { 30 return a.addRoute("POST", p, h) 31 } 32 33 // PUT maps an HTTP "PUT" request to the path and the specified handler. 34 func (a *App) PUT(p string, h Handler) *RouteInfo { 35 return a.addRoute("PUT", p, h) 36 } 37 38 // DELETE maps an HTTP "DELETE" request to the path and the specified handler. 39 func (a *App) DELETE(p string, h Handler) *RouteInfo { 40 return a.addRoute("DELETE", p, h) 41 } 42 43 // HEAD maps an HTTP "HEAD" request to the path and the specified handler. 44 func (a *App) HEAD(p string, h Handler) *RouteInfo { 45 return a.addRoute("HEAD", p, h) 46 } 47 48 // OPTIONS maps an HTTP "OPTIONS" request to the path and the specified handler. 49 func (a *App) OPTIONS(p string, h Handler) *RouteInfo { 50 return a.addRoute("OPTIONS", p, h) 51 } 52 53 // PATCH maps an HTTP "PATCH" request to the path and the specified handler. 54 func (a *App) PATCH(p string, h Handler) *RouteInfo { 55 return a.addRoute("PATCH", p, h) 56 } 57 58 // Redirect from one URL to another URL. Only works for "GET" requests. 59 func (a *App) Redirect(status int, from, to string) *RouteInfo { 60 return a.GET(from, func(c Context) error { 61 return c.Redirect(status, to) 62 }) 63 } 64 65 // Mount mounts a http.Handler (or Buffalo app) and passes through all requests to it. 66 // 67 // func muxer() http.Handler { 68 // f := func(res http.ResponseWriter, req *http.Request) { 69 // fmt.Fprintf(res, "%s - %s", req.Method, req.URL.String()) 70 // } 71 // mux := mux.NewRouter() 72 // mux.HandleFunc("/foo", f).Methods("GET") 73 // mux.HandleFunc("/bar", f).Methods("POST") 74 // mux.HandleFunc("/baz/baz", f).Methods("DELETE") 75 // return mux 76 // } 77 // 78 // a.Mount("/admin", muxer()) 79 // 80 // $ curl -X DELETE http://localhost:3000/admin/baz/baz 81 func (a *App) Mount(p string, h http.Handler) { 82 prefix := path.Join(a.Prefix, p) 83 path := path.Join(p, "{path:.+}") 84 a.ANY(path, WrapHandler(http.StripPrefix(prefix, h))) 85 } 86 87 // ServeFiles maps an path to a directory on disk to serve static files. 88 // Useful for JavaScript, images, CSS, etc... 89 /* 90 a.ServeFiles("/assets", http.Dir("path/to/assets")) 91 */ 92 func (a *App) ServeFiles(p string, root http.FileSystem) { 93 path := path.Join(a.Prefix, p) 94 a.filepaths = append(a.filepaths, path) 95 96 h := stripAsset(path, a.fileServer(root), a) 97 a.router.PathPrefix(path).Handler(h) 98 } 99 100 func (a *App) fileServer(fs http.FileSystem) http.Handler { 101 fsh := http.FileServer(fs) 102 baseHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 103 f, err := fs.Open(path.Clean(r.URL.Path)) 104 if os.IsNotExist(err) { 105 eh := a.ErrorHandlers.Get(http.StatusNotFound) 106 eh(http.StatusNotFound, fmt.Errorf("could not find %s", r.URL.Path), a.newContext(RouteInfo{}, w, r)) 107 return 108 } 109 110 stat, _ := f.Stat() 111 maxAge := envy.Get(AssetsAgeVarName, "31536000") 112 w.Header().Add("ETag", fmt.Sprintf("%x", stat.ModTime().UnixNano())) 113 w.Header().Add("Cache-Control", fmt.Sprintf("max-age=%s", maxAge)) 114 fsh.ServeHTTP(w, r) 115 }) 116 117 if a.CompressFiles { 118 return handlers.CompressHandler(baseHandler) 119 } 120 121 return baseHandler 122 } 123 124 type newable interface { 125 New(Context) error 126 } 127 128 type editable interface { 129 Edit(Context) error 130 } 131 132 // Resource maps an implementation of the Resource interface 133 // to the appropriate RESTful mappings. Resource returns the *App 134 // associated with this group of mappings so you can set middleware, etc... 135 // on that group, just as if you had used the a.Group functionality. 136 /* 137 a.Resource("/users", &UsersResource{}) 138 139 // Is equal to this: 140 141 ur := &UsersResource{} 142 g := a.Group("/users") 143 g.GET("/", ur.List) // GET /users => ur.List 144 g.GET("/new", ur.New) // GET /users/new => ur.New 145 g.GET("/{user_id}", ur.Show) // GET /users/{user_id} => ur.Show 146 g.GET("/{user_id}/edit", ur.Edit) // GET /users/{user_id}/edit => ur.Edit 147 g.POST("/", ur.Create) // POST /users => ur.Create 148 g.PUT("/{user_id}", ur.Update) PUT /users/{user_id} => ur.Update 149 g.DELETE("/{user_id}", ur.Destroy) DELETE /users/{user_id} => ur.Destroy 150 */ 151 func (a *App) Resource(p string, r Resource) *App { 152 g := a.Group(p) 153 154 if mw, ok := r.(Middler); ok { 155 g.Use(mw.Use()...) 156 } 157 158 p = "/" 159 160 rv := reflect.ValueOf(r) 161 if rv.Kind() == reflect.Ptr { 162 rv = rv.Elem() 163 } 164 165 rt := rv.Type() 166 resourceName := rt.Name() 167 handlerName := fmt.Sprintf("%s.%s", rt.PkgPath(), resourceName) + ".%s" 168 169 n := strings.TrimSuffix(rt.Name(), "Resource") 170 paramName := name.New(n).ParamID().String() 171 172 type paramKeyable interface { 173 ParamKey() string 174 } 175 176 if pk, ok := r.(paramKeyable); ok { 177 paramName = pk.ParamKey() 178 } 179 180 spath := path.Join(p, "{"+paramName+"}") 181 182 setFuncKey(r.List, fmt.Sprintf(handlerName, "List")) 183 g.GET(p, r.List).ResourceName = resourceName 184 185 if n, ok := r.(newable); ok { 186 setFuncKey(n.New, fmt.Sprintf(handlerName, "New")) 187 g.GET(path.Join(p, "new"), n.New).ResourceName = resourceName 188 } 189 190 setFuncKey(r.Show, fmt.Sprintf(handlerName, "Show")) 191 g.GET(path.Join(spath), r.Show).ResourceName = resourceName 192 193 if n, ok := r.(editable); ok { 194 setFuncKey(n.Edit, fmt.Sprintf(handlerName, "Edit")) 195 g.GET(path.Join(spath, "edit"), n.Edit).ResourceName = resourceName 196 } 197 198 setFuncKey(r.Create, fmt.Sprintf(handlerName, "Create")) 199 g.POST(p, r.Create).ResourceName = resourceName 200 201 setFuncKey(r.Update, fmt.Sprintf(handlerName, "Update")) 202 g.PUT(path.Join(spath), r.Update).ResourceName = resourceName 203 204 setFuncKey(r.Destroy, fmt.Sprintf(handlerName, "Destroy")) 205 g.DELETE(path.Join(spath), r.Destroy).ResourceName = resourceName 206 207 g.Prefix = path.Join(g.Prefix, spath) 208 209 return g 210 } 211 212 // ANY accepts a request across any HTTP method for the specified path 213 // and routes it to the specified Handler. 214 func (a *App) ANY(p string, h Handler) { 215 a.GET(p, h) 216 a.POST(p, h) 217 a.PUT(p, h) 218 a.PATCH(p, h) 219 a.HEAD(p, h) 220 a.OPTIONS(p, h) 221 a.DELETE(p, h) 222 } 223 224 // Group creates a new `*App` that inherits from it's parent `*App`. 225 // This is useful for creating groups of end-points that need to share 226 // common functionality, like middleware. 227 /* 228 g := a.Group("/api/v1") 229 g.Use(AuthorizeAPIMiddleware) 230 g.GET("/users, APIUsersHandler) 231 g.GET("/users/:user_id, APIUserShowHandler) 232 */ 233 func (a *App) Group(groupPath string) *App { 234 g := New(a.Options) 235 g.Prefix = path.Join(a.Prefix, groupPath) 236 g.Name = g.Prefix 237 238 g.router = a.router 239 g.RouteNamer = a.RouteNamer 240 g.Middleware = a.Middleware.clone() 241 g.ErrorHandlers = a.ErrorHandlers 242 g.root = a 243 if a.root != nil { 244 g.root = a.root 245 } 246 a.children = append(a.children, g) 247 return g 248 } 249 250 // RouteHelpers returns a map of BuildPathHelper() for each route available in the app. 251 func (a *App) RouteHelpers() map[string]RouteHelperFunc { 252 rh := map[string]RouteHelperFunc{} 253 for _, route := range a.Routes() { 254 cRoute := route 255 rh[cRoute.PathName] = cRoute.BuildPathHelper() 256 } 257 return rh 258 } 259 260 func (a *App) addRoute(method string, url string, h Handler) *RouteInfo { 261 a.moot.Lock() 262 defer a.moot.Unlock() 263 264 url = path.Join(a.Prefix, url) 265 url = a.normalizePath(url) 266 name := a.RouteNamer.NameRoute(url) 267 268 hs := funcKey(h) 269 r := &RouteInfo{ 270 Method: method, 271 Path: url, 272 HandlerName: hs, 273 Handler: h, 274 App: a, 275 Aliases: []string{}, 276 } 277 278 r.MuxRoute = a.router.Handle(url, r).Methods(method) 279 r.Name(name) 280 281 routes := a.Routes() 282 routes = append(routes, r) 283 sort.Sort(routes) 284 285 if a.root != nil { 286 a.root.routes = routes 287 } else { 288 a.routes = routes 289 } 290 291 return r 292 } 293 294 func stripAsset(path string, h http.Handler, a *App) http.Handler { 295 if path == "" { 296 return h 297 } 298 299 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 300 up := r.URL.Path 301 up = strings.TrimPrefix(up, path) 302 up = strings.TrimSuffix(up, "/") 303 304 u, err := url.Parse(up) 305 if err != nil { 306 eh := a.ErrorHandlers.Get(http.StatusBadRequest) 307 eh(http.StatusBadRequest, err, a.newContext(RouteInfo{}, w, r)) 308 return 309 } 310 311 r.URL = u 312 h.ServeHTTP(w, r) 313 }) 314 }