github.com/cloudwego/hertz@v0.9.3/pkg/route/routergroup.go (about) 1 /* 2 * Copyright 2022 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * The MIT License (MIT) 16 * 17 * Copyright (c) 2014 Manuel MartÃnez-Almeida 18 * 19 * Permission is hereby granted, free of charge, to any person obtaining a copy 20 * of this software and associated documentation files (the "Software"), to deal 21 * in the Software without restriction, including without limitation the rights 22 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 * copies of the Software, and to permit persons to whom the Software is 24 * furnished to do so, subject to the following conditions: 25 * 26 * The above copyright notice and this permission notice shall be included in 27 * all copies or substantial portions of the Software. 28 * 29 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 * THE SOFTWARE. 36 * 37 * This file may have been modified by CloudWeGo authors. All CloudWeGo 38 * Modifications are Copyright 2022 CloudWeGo Authors 39 */ 40 41 package route 42 43 import ( 44 "context" 45 "path" 46 "regexp" 47 "strings" 48 49 "github.com/cloudwego/hertz/pkg/app" 50 "github.com/cloudwego/hertz/pkg/protocol/consts" 51 rConsts "github.com/cloudwego/hertz/pkg/route/consts" 52 ) 53 54 // IRouter defines all router handle interface includes single and group router. 55 type IRouter interface { 56 IRoutes 57 Group(string, ...app.HandlerFunc) *RouterGroup 58 } 59 60 // IRoutes defines all router handle interface. 61 type IRoutes interface { 62 Use(...app.HandlerFunc) IRoutes 63 Handle(string, string, ...app.HandlerFunc) IRoutes 64 Any(string, ...app.HandlerFunc) IRoutes 65 GET(string, ...app.HandlerFunc) IRoutes 66 POST(string, ...app.HandlerFunc) IRoutes 67 DELETE(string, ...app.HandlerFunc) IRoutes 68 PATCH(string, ...app.HandlerFunc) IRoutes 69 PUT(string, ...app.HandlerFunc) IRoutes 70 OPTIONS(string, ...app.HandlerFunc) IRoutes 71 HEAD(string, ...app.HandlerFunc) IRoutes 72 StaticFile(string, string) IRoutes 73 Static(string, string) IRoutes 74 StaticFS(string, *app.FS) IRoutes 75 } 76 77 // RouterGroup is used internally to configure router, a RouterGroup is associated with 78 // a prefix and an array of handlers (middleware). 79 type RouterGroup struct { 80 Handlers app.HandlersChain 81 basePath string 82 engine *Engine 83 root bool 84 } 85 86 var _ IRouter = (*RouterGroup)(nil) 87 88 // Use adds middleware to the group, see example code in GitHub. 89 func (group *RouterGroup) Use(middleware ...app.HandlerFunc) IRoutes { 90 group.Handlers = append(group.Handlers, middleware...) 91 return group.returnObj() 92 } 93 94 // Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix. 95 // For example, all the routes that use a common middleware for authorization could be grouped. 96 func (group *RouterGroup) Group(relativePath string, handlers ...app.HandlerFunc) *RouterGroup { 97 return &RouterGroup{ 98 Handlers: group.combineHandlers(handlers), 99 basePath: group.calculateAbsolutePath(relativePath), 100 engine: group.engine, 101 } 102 } 103 104 // BasePath returns the base path of router group. 105 // For example, if v := router.Group("/rest/n/v1/api"), v.BasePath() is "/rest/n/v1/api". 106 func (group *RouterGroup) BasePath() string { 107 return group.basePath 108 } 109 110 func (group *RouterGroup) handle(httpMethod, relativePath string, handlers app.HandlersChain) IRoutes { 111 absolutePath := group.calculateAbsolutePath(relativePath) 112 handlers = group.combineHandlers(handlers) 113 group.engine.addRoute(httpMethod, absolutePath, handlers) 114 return group.returnObj() 115 } 116 117 var upperLetterReg = regexp.MustCompile("^[A-Z]+$") 118 119 // Handle registers a new request handle and middleware with the given path and method. 120 // The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes. 121 // See the example code in GitHub. 122 // 123 // For GET, POST, PUT, PATCH and DELETE requests the respective shortcut 124 // functions can be used. 125 // 126 // This function is intended for bulk loading and to allow the usage of less 127 // frequently used, non-standardized or custom methods (e.g. for internal 128 // communication with a proxy). 129 func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...app.HandlerFunc) IRoutes { 130 if matches := upperLetterReg.MatchString(httpMethod); !matches { 131 panic("http method " + httpMethod + " is not valid") 132 } 133 return group.handle(httpMethod, relativePath, handlers) 134 } 135 136 // POST is a shortcut for router.Handle("POST", path, handle). 137 func (group *RouterGroup) POST(relativePath string, handlers ...app.HandlerFunc) IRoutes { 138 return group.handle(consts.MethodPost, relativePath, handlers) 139 } 140 141 // GET is a shortcut for router.Handle("GET", path, handle). 142 func (group *RouterGroup) GET(relativePath string, handlers ...app.HandlerFunc) IRoutes { 143 return group.handle(consts.MethodGet, relativePath, handlers) 144 } 145 146 // DELETE is a shortcut for router.Handle("DELETE", path, handle). 147 func (group *RouterGroup) DELETE(relativePath string, handlers ...app.HandlerFunc) IRoutes { 148 return group.handle(consts.MethodDelete, relativePath, handlers) 149 } 150 151 // PATCH is a shortcut for router.Handle("PATCH", path, handle). 152 func (group *RouterGroup) PATCH(relativePath string, handlers ...app.HandlerFunc) IRoutes { 153 return group.handle(consts.MethodPatch, relativePath, handlers) 154 } 155 156 // PUT is a shortcut for router.Handle("PUT", path, handle). 157 func (group *RouterGroup) PUT(relativePath string, handlers ...app.HandlerFunc) IRoutes { 158 return group.handle(consts.MethodPut, relativePath, handlers) 159 } 160 161 // OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle). 162 func (group *RouterGroup) OPTIONS(relativePath string, handlers ...app.HandlerFunc) IRoutes { 163 return group.handle(consts.MethodOptions, relativePath, handlers) 164 } 165 166 // HEAD is a shortcut for router.Handle("HEAD", path, handle). 167 func (group *RouterGroup) HEAD(relativePath string, handlers ...app.HandlerFunc) IRoutes { 168 return group.handle(consts.MethodHead, relativePath, handlers) 169 } 170 171 // Any registers a route that matches all the HTTP methods. 172 // GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE. 173 func (group *RouterGroup) Any(relativePath string, handlers ...app.HandlerFunc) IRoutes { 174 group.handle(consts.MethodGet, relativePath, handlers) 175 group.handle(consts.MethodPost, relativePath, handlers) 176 group.handle(consts.MethodPut, relativePath, handlers) 177 group.handle(consts.MethodPatch, relativePath, handlers) 178 group.handle(consts.MethodHead, relativePath, handlers) 179 group.handle(consts.MethodOptions, relativePath, handlers) 180 group.handle(consts.MethodDelete, relativePath, handlers) 181 group.handle(consts.MethodConnect, relativePath, handlers) 182 group.handle(consts.MethodTrace, relativePath, handlers) 183 return group.returnObj() 184 } 185 186 // StaticFile registers a single route in order to Serve a single file of the local filesystem. 187 // router.StaticFile("favicon.ico", "./resources/favicon.ico") 188 func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes { 189 if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") { 190 panic("URL parameters can not be used when serving a static file") 191 } 192 handler := func(c context.Context, ctx *app.RequestContext) { 193 ctx.File(filepath) 194 } 195 group.GET(relativePath, handler) 196 group.HEAD(relativePath, handler) 197 return group.returnObj() 198 } 199 200 // Static serves files from the given file system root. 201 // To use the operating system's file system implementation, 202 // use : 203 // 204 // router.Static("/static", "/var/www") 205 func (group *RouterGroup) Static(relativePath, root string) IRoutes { 206 return group.StaticFS(relativePath, &app.FS{Root: root}) 207 } 208 209 // StaticFS works just like `Static()` but a custom `FS` can be used instead. 210 func (group *RouterGroup) StaticFS(relativePath string, fs *app.FS) IRoutes { 211 if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") { 212 panic("URL parameters can not be used when serving a static folder") 213 } 214 handler := fs.NewRequestHandler() 215 urlPattern := path.Join(relativePath, "/*filepath") 216 217 // Register GET and HEAD handlers 218 group.GET(urlPattern, handler) 219 group.HEAD(urlPattern, handler) 220 return group.returnObj() 221 } 222 223 func (group *RouterGroup) combineHandlers(handlers app.HandlersChain) app.HandlersChain { 224 finalSize := len(group.Handlers) + len(handlers) 225 if finalSize >= int(rConsts.AbortIndex) { 226 panic("too many handlers") 227 } 228 mergedHandlers := make(app.HandlersChain, finalSize) 229 copy(mergedHandlers, group.Handlers) 230 copy(mergedHandlers[len(group.Handlers):], handlers) 231 return mergedHandlers 232 } 233 234 func (group *RouterGroup) calculateAbsolutePath(relativePath string) string { 235 return joinPaths(group.basePath, relativePath) 236 } 237 238 func (group *RouterGroup) returnObj() IRoutes { 239 if group.root { 240 return group.engine 241 } 242 return group 243 } 244 245 // GETEX adds a handlerName param. When handler is decorated or handler is an anonymous function, 246 // Hertz cannot get handler name directly. In this case, pass handlerName explicitly. 247 func (group *RouterGroup) GETEX(relativePath string, handler app.HandlerFunc, handlerName string) IRoutes { 248 app.SetHandlerName(handler, handlerName) 249 return group.GET(relativePath, handler) 250 } 251 252 // POSTEX adds a handlerName param. When handler is decorated or handler is an anonymous function, 253 // Hertz cannot get handler name directly. In this case, pass handlerName explicitly. 254 func (group *RouterGroup) POSTEX(relativePath string, handler app.HandlerFunc, handlerName string) IRoutes { 255 app.SetHandlerName(handler, handlerName) 256 return group.POST(relativePath, handler) 257 } 258 259 // PUTEX adds a handlerName param. When handler is decorated or handler is an anonymous function, 260 // Hertz cannot get handler name directly. In this case, pass handlerName explicitly. 261 func (group *RouterGroup) PUTEX(relativePath string, handler app.HandlerFunc, handlerName string) IRoutes { 262 app.SetHandlerName(handler, handlerName) 263 return group.PUT(relativePath, handler) 264 } 265 266 // DELETEEX adds a handlerName param. When handler is decorated or handler is an anonymous function, 267 // Hertz cannot get handler name directly. In this case, pass handlerName explicitly. 268 func (group *RouterGroup) DELETEEX(relativePath string, handler app.HandlerFunc, handlerName string) IRoutes { 269 app.SetHandlerName(handler, handlerName) 270 return group.DELETE(relativePath, handler) 271 } 272 273 // HEADEX adds a handlerName param. When handler is decorated or handler is an anonymous function, 274 // Hertz cannot get handler name directly. In this case, pass handlerName explicitly. 275 func (group *RouterGroup) HEADEX(relativePath string, handler app.HandlerFunc, handlerName string) IRoutes { 276 app.SetHandlerName(handler, handlerName) 277 return group.HEAD(relativePath, handler) 278 } 279 280 // AnyEX adds a handlerName param. When handler is decorated or handler is an anonymous function, 281 // Hertz cannot get handler name directly. In this case, pass handlerName explicitly. 282 func (group *RouterGroup) AnyEX(relativePath string, handler app.HandlerFunc, handlerName string) IRoutes { 283 app.SetHandlerName(handler, handlerName) 284 return group.Any(relativePath, handler) 285 } 286 287 // HandleEX adds a handlerName param. When handler is decorated or handler is an anonymous function, 288 // Hertz cannot get handler name directly. In this case, pass handlerName explicitly. 289 func (group *RouterGroup) HandleEX(httpMethod, relativePath string, handler app.HandlerFunc, handlerName string) IRoutes { 290 app.SetHandlerName(handler, handlerName) 291 return group.Handle(httpMethod, relativePath, handler) 292 } 293 294 func joinPaths(absolutePath, relativePath string) string { 295 if relativePath == "" { 296 return absolutePath 297 } 298 299 finalPath := path.Join(absolutePath, relativePath) 300 appendSlash := lastChar(relativePath) == '/' && lastChar(finalPath) != '/' 301 if appendSlash { 302 return finalPath + "/" 303 } 304 return finalPath 305 } 306 307 func lastChar(str string) uint8 { 308 if str == "" { 309 panic("The length of the string can't be 0") 310 } 311 return str[len(str)-1] 312 }