github.com/erda-project/erda-infra@v1.0.9/providers/httpserver/router.go (about) 1 // Copyright (c) 2021 Terminus, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package httpserver 16 17 import ( 18 "fmt" 19 "net/http" 20 "net/url" 21 "path" 22 "path/filepath" 23 "reflect" 24 "sort" 25 "strings" 26 "sync" 27 28 "github.com/labstack/echo" 29 "github.com/recallsong/go-utils/net/httpx/filesystem" 30 31 "github.com/erda-project/erda-infra/providers/httpserver/server" 32 ) 33 34 type ( 35 // Router . 36 Router interface { 37 GET(path string, handler interface{}, options ...interface{}) 38 POST(path string, handler interface{}, options ...interface{}) 39 DELETE(path string, handler interface{}, options ...interface{}) 40 PUT(path string, handler interface{}, options ...interface{}) 41 PATCH(path string, handler interface{}, options ...interface{}) 42 HEAD(path string, handler interface{}, options ...interface{}) 43 CONNECT(path string, handler interface{}, options ...interface{}) 44 OPTIONS(path string, handler interface{}, options ...interface{}) 45 TRACE(path string, handler interface{}, options ...interface{}) 46 47 Any(path string, handler interface{}, options ...interface{}) 48 Static(prefix, root string, options ...interface{}) 49 File(path, filepath string, options ...interface{}) 50 51 Add(method, path string, handler interface{}, options ...interface{}) error 52 } 53 // RouterManager . 54 RouterManager interface { 55 NewRouter(opts ...interface{}) RouterTx 56 Reloadable() bool 57 Started() <-chan struct{} 58 } 59 // RouterTx . 60 RouterTx interface { 61 Router 62 Commit() error 63 Rollback() 64 Reloadable() bool 65 } 66 ) 67 68 type ( 69 routeKey struct { 70 method string 71 path string 72 } 73 route struct { 74 method string 75 path string 76 group string 77 hide bool 78 desc string 79 80 handler server.HandlerFunc 81 } 82 router struct { 83 lock *sync.Mutex 84 done bool 85 err error 86 updateRoutes func(map[routeKey]*route) 87 reportError func(err error) 88 tx server.RouterTx 89 pathFormater *pathFormater 90 routes map[routeKey]*route 91 group string 92 interceptors []server.MiddlewareFunc 93 } 94 ) 95 96 func (r *router) Add(method, path string, handler interface{}, options ...interface{}) error { 97 pathFormater := r.getPathFormater(options) 98 var pathParser server.MiddlewareFunc 99 if pathFormater.parser != nil { 100 pathParser = pathFormater.parser(path) 101 } 102 path = pathFormater.format(path) 103 method = strings.ToUpper(method) 104 105 key := routeKey{method: method, path: removeParamName(path)} 106 if rt, ok := r.routes[key]; ok { 107 if rt.group != r.group { 108 r.err = fmt.Errorf("httpserver routes [%s %s] conflict between groups (%s, %s)", 109 rt.method, key.path, rt.group, r.group) 110 } else { 111 r.err = fmt.Errorf("httpserver routes [%s %s] conflict in group %s", 112 rt.method, key.path, rt.group) 113 } 114 if r.lock == nil { 115 r.reportError(r.err) 116 } 117 return r.err 118 } 119 route := &route{ 120 method: method, 121 path: path, 122 group: r.group, 123 } 124 for _, opt := range options { 125 processRouteOptions(route, opt) 126 } 127 r.routes[key] = route 128 129 if handler != nil { 130 interceptors := getInterceptors(options) 131 route.handler = r.add(method, path, handler, interceptors, pathParser) 132 } 133 return nil 134 } 135 136 func removeParamName(path string) string { 137 sb := &strings.Builder{} 138 chars := []rune(path) 139 for i, n := 0, len(chars); i < n; i++ { 140 c := chars[i] 141 if c != ':' { 142 sb.WriteRune(c) 143 continue 144 } 145 sb.WriteRune('*') 146 i++ 147 for ; i < n && chars[i] != '/'; i++ { 148 } 149 if i < n { 150 sb.WriteRune(chars[i]) 151 } 152 } 153 return sb.String() 154 } 155 156 type routeOption func(r *route) 157 158 func processRouteOptions(r *route, opt interface{}) { 159 if fn, ok := opt.(routeOption); ok { 160 fn(r) 161 } 162 } 163 164 // WithDescription for Route, description for this route 165 func WithDescription(desc string) interface{} { 166 return routeOption(func(r *route) { 167 r.desc = desc 168 }) 169 } 170 171 // WithHide for Route, not print this route 172 func WithHide(hide bool) interface{} { 173 return routeOption(func(r *route) { 174 r.hide = hide 175 }) 176 } 177 178 // WithInterceptor for Router 179 func WithInterceptor(fn func(handler func(ctx Context) error) func(ctx Context) error) interface{} { 180 return Interceptor(fn) 181 } 182 183 func (r *router) GET(path string, handler interface{}, options ...interface{}) { 184 r.Add(http.MethodGet, path, handler, options...) 185 } 186 187 func (r *router) POST(path string, handler interface{}, options ...interface{}) { 188 r.Add(http.MethodPost, path, handler, options...) 189 } 190 191 func (r *router) DELETE(path string, handler interface{}, options ...interface{}) { 192 r.Add(http.MethodDelete, path, handler, options...) 193 } 194 195 func (r *router) PUT(path string, handler interface{}, options ...interface{}) { 196 r.Add(http.MethodPut, path, handler, options...) 197 } 198 199 func (r *router) PATCH(path string, handler interface{}, options ...interface{}) { 200 r.Add(http.MethodPatch, path, handler, options...) 201 } 202 203 func (r *router) HEAD(path string, handler interface{}, options ...interface{}) { 204 r.Add(http.MethodHead, path, handler, options...) 205 } 206 207 func (r *router) CONNECT(path string, handler interface{}, options ...interface{}) { 208 r.Add(http.MethodConnect, path, handler, options...) 209 } 210 211 func (r *router) OPTIONS(path string, handler interface{}, options ...interface{}) { 212 r.Add(http.MethodOptions, path, handler, options...) 213 } 214 215 func (r *router) TRACE(path string, handler interface{}, options ...interface{}) { 216 r.Add(http.MethodTrace, path, handler, options...) 217 } 218 219 var allMethods = []string{ 220 http.MethodConnect, 221 http.MethodDelete, 222 http.MethodGet, 223 http.MethodHead, 224 http.MethodOptions, 225 http.MethodPatch, 226 http.MethodPost, 227 http.MethodPut, 228 http.MethodTrace, 229 } 230 231 func init() { sort.Strings(allMethods) } 232 233 func (r *router) Any(path string, handler interface{}, options ...interface{}) { 234 for _, method := range allMethods { 235 r.Add(method, path, handler, options...) 236 } 237 } 238 239 // WithFileSystem for Static And File 240 func WithFileSystem(fs http.FileSystem) interface{} { 241 return fs 242 } 243 244 type filesystemPath string 245 246 // WithFileSystemPath for Static And File 247 func WithFileSystemPath(root string) interface{} { 248 return filesystemPath(root) 249 } 250 251 func (r *router) Static(prefix, root string, options ...interface{}) { 252 var fs http.FileSystem 253 for _, opt := range options { 254 if files, ok := opt.(http.FileSystem); ok { 255 fs = files 256 } else if path, ok := opt.(filesystemPath); ok { 257 root = filepath.Join(string(path), root) 258 } 259 } 260 if root == "" { 261 root = "." 262 } 263 if fs == nil { 264 r.addPrefix(prefix, root, func(c echo.Context) error { 265 p, err := url.PathUnescape(c.Param("*")) 266 if err != nil { 267 return err 268 } 269 name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security 270 return c.File(name) 271 }, options...) 272 } else { 273 fs := filesystem.New(fs).SetRoot(root).SetRoute(prefix) 274 handler := fs.Handler 275 r.addPrefix(prefix, root, func(c server.Context) error { 276 handler.ServeHTTP(c.Response(), c.Request()) 277 return nil 278 }, options...) 279 } 280 } 281 282 func (r *router) addPrefix(prefix, root string, h func(c echo.Context) error, options ...interface{}) { 283 r.GET(prefix, h, options...) 284 if prefix == "/" { 285 r.GET(prefix+"*", h, options...) 286 } else { 287 r.GET(prefix+"/*", h, options...) 288 } 289 } 290 291 func (r *router) File(path, file string, options ...interface{}) { 292 var fs http.FileSystem 293 for _, opt := range options { 294 if files, ok := opt.(http.FileSystem); ok { 295 fs = files 296 } else if path, ok := opt.(filesystemPath); ok { 297 file = filepath.Join(string(path), file) 298 } 299 } 300 if fs == nil { 301 r.GET(path, func(c echo.Context) error { 302 return c.File(file) 303 }, options...) 304 } else { 305 fs := filesystem.New(fs).SetRoot(file).SetRoute(path) 306 handler := fs.Handler 307 r.GET(path, func(c server.Context) error { 308 handler.ServeHTTP(c.Response(), c.Request()) 309 return nil 310 }, options...) 311 } 312 } 313 314 // Commit . 315 func (r *router) Commit() error { 316 if r.lock != nil { 317 if r.done { 318 return fmt.Errorf("routes commited") 319 } 320 r.done = true 321 if r.err != nil { 322 r.reportError(r.err) 323 r.lock.Unlock() 324 return r.err 325 } 326 r.tx.Commit() 327 r.updateRoutes(r.routes) 328 r.lock.Unlock() 329 } 330 return r.err 331 } 332 333 // Rollback . 334 func (r *router) Rollback() { 335 if r.lock != nil { 336 r.lock.Unlock() 337 } 338 } 339 340 // Reloadable . 341 func (r *router) Reloadable() bool { return r.lock != nil } 342 343 type routesSorter []*route 344 345 func (s routesSorter) Len() int { return len(s) } 346 func (s routesSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 347 func (s routesSorter) Less(i, j int) bool { 348 if s[i].group == s[j].group { 349 if s[i].path == s[j].path { 350 return s[i].method < s[j].method 351 } 352 return s[i].path < s[j].path 353 } 354 return s[i].group < s[j].group 355 } 356 357 func listRoutes(routeMap map[routeKey]*route) []*route { 358 routes := make([]*route, 0, len(routeMap)) 359 for _, route := range routeMap { 360 routes = append(routes, route) 361 } 362 sort.Sort(routesSorter(routes)) 363 return routes 364 } 365 366 func (p *provider) printRoutes(routes map[routeKey]*route) { 367 list := listRoutes(routes) 368 var group, path string 369 var methods []string 370 printRoute := func(group, path string, methods []string) { 371 sort.Strings(methods) 372 if reflect.DeepEqual(methods, allMethods) { 373 p.Log.Infof("%s --> [%s] %-7s %s", p.Cfg.Addr, group, "*", path) 374 } else { 375 for _, method := range methods { 376 p.Log.Infof("%s --> [%s] %-7s %s", p.Cfg.Addr, group, method, path) 377 } 378 } 379 } 380 for _, route := range list { 381 if route.hide { 382 continue 383 } 384 if methods == nil { 385 group, path = route.group, route.path 386 methods = []string{route.method} 387 continue 388 } else if path == route.path && group == route.group { 389 methods = append(methods, route.method) 390 continue 391 } 392 printRoute(group, path, methods) 393 group, path = route.group, route.path 394 methods = []string{route.method} 395 } 396 if len(methods) > 0 { 397 printRoute(group, path, methods) 398 } 399 } 400 401 type routerManager struct { 402 group string 403 reset bool 404 opts []interface{} 405 p *provider 406 } 407 408 func (rm *routerManager) NewRouter(opts ...interface{}) RouterTx { 409 args := make([]interface{}, len(rm.opts)+len(opts)) 410 copy(args, rm.opts) 411 copy(args[len(rm.opts):], opts) 412 return rm.p.newRouterTx(rm.reset, rm.group, args...) 413 } 414 415 func (rm *routerManager) Reloadable() bool { return rm.p.Cfg.Reloadable } 416 417 func (rm *routerManager) Started() <-chan struct{} { 418 return rm.p.startedChan 419 } 420 421 type autoCommitRouter struct { 422 tx RouterManager 423 routes []route 424 } 425 426 var _ Router = (*autoCommitRouter)(nil) 427 428 func (r *autoCommitRouter) add(method, path string, handler interface{}, options ...interface{}) { 429 tx := r.tx.NewRouter() 430 tx.Add(method, path, handler, options...) 431 r.commit(tx) 432 } 433 func (r *autoCommitRouter) commit(tx RouterTx) { 434 err := tx.Commit() 435 if err != nil { 436 panic(err) 437 } 438 } 439 func (r *autoCommitRouter) GET(path string, handler interface{}, options ...interface{}) { 440 r.add(http.MethodGet, path, handler, options...) 441 } 442 func (r *autoCommitRouter) POST(path string, handler interface{}, options ...interface{}) { 443 r.add(http.MethodPost, path, handler, options...) 444 } 445 func (r *autoCommitRouter) DELETE(path string, handler interface{}, options ...interface{}) { 446 r.add(http.MethodDelete, path, handler, options...) 447 } 448 func (r *autoCommitRouter) PUT(path string, handler interface{}, options ...interface{}) { 449 r.add(http.MethodPut, path, handler, options...) 450 } 451 func (r *autoCommitRouter) PATCH(path string, handler interface{}, options ...interface{}) { 452 r.add(http.MethodPatch, path, handler, options...) 453 } 454 func (r *autoCommitRouter) HEAD(path string, handler interface{}, options ...interface{}) { 455 r.add(http.MethodHead, path, handler, options...) 456 } 457 func (r *autoCommitRouter) CONNECT(path string, handler interface{}, options ...interface{}) { 458 r.add(http.MethodConnect, path, handler, options...) 459 } 460 func (r *autoCommitRouter) OPTIONS(path string, handler interface{}, options ...interface{}) { 461 r.add(http.MethodOptions, path, handler, options...) 462 } 463 func (r *autoCommitRouter) TRACE(path string, handler interface{}, options ...interface{}) { 464 r.add(http.MethodTrace, path, handler, options...) 465 } 466 func (r *autoCommitRouter) Any(path string, handler interface{}, options ...interface{}) { 467 tx := r.tx.NewRouter() 468 tx.Any(path, handler, options...) 469 r.commit(tx) 470 } 471 func (r *autoCommitRouter) Static(prefix, root string, options ...interface{}) { 472 tx := r.tx.NewRouter() 473 tx.Static(prefix, root, options...) 474 r.commit(tx) 475 } 476 func (r *autoCommitRouter) File(path, filepath string, options ...interface{}) { 477 tx := r.tx.NewRouter() 478 tx.File(path, filepath, options...) 479 r.commit(tx) 480 } 481 func (r *autoCommitRouter) Add(method, path string, handler interface{}, options ...interface{}) error { 482 tx := r.tx.NewRouter() 483 tx.GET(path, handler, options...) 484 return tx.Commit() 485 }