routergroup.go (9280B)
1 // Copyright 2014 Manu Martinez-Almeida. All rights reserved. 2 // Use of this source code is governed by a MIT style 3 // license that can be found in the LICENSE file. 4 5 package gin 6 7 import ( 8 "net/http" 9 "path" 10 "regexp" 11 "strings" 12 ) 13 14 var ( 15 // regEnLetter matches english letters for http method name 16 regEnLetter = regexp.MustCompile("^[A-Z]+$") 17 18 // anyMethods for RouterGroup Any method 19 anyMethods = []string{ 20 http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, 21 http.MethodHead, http.MethodOptions, http.MethodDelete, http.MethodConnect, 22 http.MethodTrace, 23 } 24 ) 25 26 // IRouter defines all router handle interface includes single and group router. 27 type IRouter interface { 28 IRoutes 29 Group(string, ...HandlerFunc) *RouterGroup 30 } 31 32 // IRoutes defines all router handle interface. 33 type IRoutes interface { 34 Use(...HandlerFunc) IRoutes 35 36 Handle(string, string, ...HandlerFunc) IRoutes 37 Any(string, ...HandlerFunc) IRoutes 38 GET(string, ...HandlerFunc) IRoutes 39 POST(string, ...HandlerFunc) IRoutes 40 DELETE(string, ...HandlerFunc) IRoutes 41 PATCH(string, ...HandlerFunc) IRoutes 42 PUT(string, ...HandlerFunc) IRoutes 43 OPTIONS(string, ...HandlerFunc) IRoutes 44 HEAD(string, ...HandlerFunc) IRoutes 45 Match([]string, string, ...HandlerFunc) IRoutes 46 47 StaticFile(string, string) IRoutes 48 StaticFileFS(string, string, http.FileSystem) IRoutes 49 Static(string, string) IRoutes 50 StaticFS(string, http.FileSystem) IRoutes 51 } 52 53 // RouterGroup is used internally to configure router, a RouterGroup is associated with 54 // a prefix and an array of handlers (middleware). 55 type RouterGroup struct { 56 Handlers HandlersChain 57 basePath string 58 engine *Engine 59 root bool 60 } 61 62 var _ IRouter = (*RouterGroup)(nil) 63 64 // Use adds middleware to the group, see example code in GitHub. 65 func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { 66 group.Handlers = append(group.Handlers, middleware...) 67 return group.returnObj() 68 } 69 70 // Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix. 71 // For example, all the routes that use a common middleware for authorization could be grouped. 72 func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup { 73 return &RouterGroup{ 74 Handlers: group.combineHandlers(handlers), 75 basePath: group.calculateAbsolutePath(relativePath), 76 engine: group.engine, 77 } 78 } 79 80 // BasePath returns the base path of router group. 81 // For example, if v := router.Group("/rest/n/v1/api"), v.BasePath() is "/rest/n/v1/api". 82 func (group *RouterGroup) BasePath() string { 83 return group.basePath 84 } 85 86 func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { 87 absolutePath := group.calculateAbsolutePath(relativePath) 88 handlers = group.combineHandlers(handlers) 89 group.engine.addRoute(httpMethod, absolutePath, handlers) 90 return group.returnObj() 91 } 92 93 // Handle registers a new request handle and middleware with the given path and method. 94 // The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes. 95 // See the example code in GitHub. 96 // 97 // For GET, POST, PUT, PATCH and DELETE requests the respective shortcut 98 // functions can be used. 99 // 100 // This function is intended for bulk loading and to allow the usage of less 101 // frequently used, non-standardized or custom methods (e.g. for internal 102 // communication with a proxy). 103 func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes { 104 if matched := regEnLetter.MatchString(httpMethod); !matched { 105 panic("http method " + httpMethod + " is not valid") 106 } 107 return group.handle(httpMethod, relativePath, handlers) 108 } 109 110 // POST is a shortcut for router.Handle("POST", path, handlers). 111 func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes { 112 return group.handle(http.MethodPost, relativePath, handlers) 113 } 114 115 // GET is a shortcut for router.Handle("GET", path, handlers). 116 func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { 117 return group.handle(http.MethodGet, relativePath, handlers) 118 } 119 120 // DELETE is a shortcut for router.Handle("DELETE", path, handlers). 121 func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes { 122 return group.handle(http.MethodDelete, relativePath, handlers) 123 } 124 125 // PATCH is a shortcut for router.Handle("PATCH", path, handlers). 126 func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes { 127 return group.handle(http.MethodPatch, relativePath, handlers) 128 } 129 130 // PUT is a shortcut for router.Handle("PUT", path, handlers). 131 func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes { 132 return group.handle(http.MethodPut, relativePath, handlers) 133 } 134 135 // OPTIONS is a shortcut for router.Handle("OPTIONS", path, handlers). 136 func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes { 137 return group.handle(http.MethodOptions, relativePath, handlers) 138 } 139 140 // HEAD is a shortcut for router.Handle("HEAD", path, handlers). 141 func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes { 142 return group.handle(http.MethodHead, relativePath, handlers) 143 } 144 145 // Any registers a route that matches all the HTTP methods. 146 // GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE. 147 func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes { 148 for _, method := range anyMethods { 149 group.handle(method, relativePath, handlers) 150 } 151 152 return group.returnObj() 153 } 154 155 // Match registers a route that matches the specified methods that you declared. 156 func (group *RouterGroup) Match(methods []string, relativePath string, handlers ...HandlerFunc) IRoutes { 157 for _, method := range methods { 158 group.handle(method, relativePath, handlers) 159 } 160 161 return group.returnObj() 162 } 163 164 // StaticFile registers a single route in order to serve a single file of the local filesystem. 165 // router.StaticFile("favicon.ico", "./resources/favicon.ico") 166 func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes { 167 return group.staticFileHandler(relativePath, func(c *Context) { 168 c.File(filepath) 169 }) 170 } 171 172 // StaticFileFS works just like `StaticFile` but a custom `http.FileSystem` can be used instead.. 173 // router.StaticFileFS("favicon.ico", "./resources/favicon.ico", Dir{".", false}) 174 // Gin by default uses: gin.Dir() 175 func (group *RouterGroup) StaticFileFS(relativePath, filepath string, fs http.FileSystem) IRoutes { 176 return group.staticFileHandler(relativePath, func(c *Context) { 177 c.FileFromFS(filepath, fs) 178 }) 179 } 180 181 func (group *RouterGroup) staticFileHandler(relativePath string, handler HandlerFunc) IRoutes { 182 if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") { 183 panic("URL parameters can not be used when serving a static file") 184 } 185 group.GET(relativePath, handler) 186 group.HEAD(relativePath, handler) 187 return group.returnObj() 188 } 189 190 // Static serves files from the given file system root. 191 // Internally a http.FileServer is used, therefore http.NotFound is used instead 192 // of the Router's NotFound handler. 193 // To use the operating system's file system implementation, 194 // use : 195 // 196 // router.Static("/static", "/var/www") 197 func (group *RouterGroup) Static(relativePath, root string) IRoutes { 198 return group.StaticFS(relativePath, Dir(root, false)) 199 } 200 201 // StaticFS works just like `Static()` but a custom `http.FileSystem` can be used instead. 202 // Gin by default uses: gin.Dir() 203 func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes { 204 if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") { 205 panic("URL parameters can not be used when serving a static folder") 206 } 207 handler := group.createStaticHandler(relativePath, fs) 208 urlPattern := path.Join(relativePath, "/*filepath") 209 210 // Register GET and HEAD handlers 211 group.GET(urlPattern, handler) 212 group.HEAD(urlPattern, handler) 213 return group.returnObj() 214 } 215 216 func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc { 217 absolutePath := group.calculateAbsolutePath(relativePath) 218 fileServer := http.StripPrefix(absolutePath, http.FileServer(fs)) 219 220 return func(c *Context) { 221 if _, noListing := fs.(*onlyFilesFS); noListing { 222 c.Writer.WriteHeader(http.StatusNotFound) 223 } 224 225 file := c.Param("filepath") 226 // Check if file exists and/or if we have permission to access it 227 f, err := fs.Open(file) 228 if err != nil { 229 c.Writer.WriteHeader(http.StatusNotFound) 230 c.handlers = group.engine.noRoute 231 // Reset index 232 c.index = -1 233 return 234 } 235 f.Close() 236 237 fileServer.ServeHTTP(c.Writer, c.Request) 238 } 239 } 240 241 func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { 242 finalSize := len(group.Handlers) + len(handlers) 243 assert1(finalSize < int(abortIndex), "too many handlers") 244 mergedHandlers := make(HandlersChain, finalSize) 245 copy(mergedHandlers, group.Handlers) 246 copy(mergedHandlers[len(group.Handlers):], handlers) 247 return mergedHandlers 248 } 249 250 func (group *RouterGroup) calculateAbsolutePath(relativePath string) string { 251 return joinPaths(group.basePath, relativePath) 252 } 253 254 func (group *RouterGroup) returnObj() IRoutes { 255 if group.root { 256 return group.engine 257 } 258 return group 259 }