github.com/astaxie/beego@v1.12.3/router.go (about) 1 // Copyright 2014 beego Author. All Rights Reserved. 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 beego 16 17 import ( 18 "errors" 19 "fmt" 20 "net/http" 21 "os" 22 "path" 23 "path/filepath" 24 "reflect" 25 "strconv" 26 "strings" 27 "sync" 28 "time" 29 30 beecontext "github.com/astaxie/beego/context" 31 "github.com/astaxie/beego/context/param" 32 "github.com/astaxie/beego/logs" 33 "github.com/astaxie/beego/toolbox" 34 "github.com/astaxie/beego/utils" 35 ) 36 37 // default filter execution points 38 const ( 39 BeforeStatic = iota 40 BeforeRouter 41 BeforeExec 42 AfterExec 43 FinishRouter 44 ) 45 46 const ( 47 routerTypeBeego = iota 48 routerTypeRESTFul 49 routerTypeHandler 50 ) 51 52 var ( 53 // HTTPMETHOD list the supported http methods. 54 HTTPMETHOD = map[string]bool{ 55 "GET": true, 56 "POST": true, 57 "PUT": true, 58 "DELETE": true, 59 "PATCH": true, 60 "OPTIONS": true, 61 "HEAD": true, 62 "TRACE": true, 63 "CONNECT": true, 64 "MKCOL": true, 65 "COPY": true, 66 "MOVE": true, 67 "PROPFIND": true, 68 "PROPPATCH": true, 69 "LOCK": true, 70 "UNLOCK": true, 71 } 72 // these beego.Controller's methods shouldn't reflect to AutoRouter 73 exceptMethod = []string{"Init", "Prepare", "Finish", "Render", "RenderString", 74 "RenderBytes", "Redirect", "Abort", "StopRun", "UrlFor", "ServeJSON", "ServeJSONP", 75 "ServeYAML", "ServeXML", "Input", "ParseForm", "GetString", "GetStrings", "GetInt", "GetBool", 76 "GetFloat", "GetFile", "SaveToFile", "StartSession", "SetSession", "GetSession", 77 "DelSession", "SessionRegenerateID", "DestroySession", "IsAjax", "GetSecureCookie", 78 "SetSecureCookie", "XsrfToken", "CheckXsrfCookie", "XsrfFormHtml", 79 "GetControllerAndAction", "ServeFormatted"} 80 81 urlPlaceholder = "{{placeholder}}" 82 // DefaultAccessLogFilter will skip the accesslog if return true 83 DefaultAccessLogFilter FilterHandler = &logFilter{} 84 ) 85 86 // FilterHandler is an interface for 87 type FilterHandler interface { 88 Filter(*beecontext.Context) bool 89 } 90 91 // default log filter static file will not show 92 type logFilter struct { 93 } 94 95 func (l *logFilter) Filter(ctx *beecontext.Context) bool { 96 requestPath := path.Clean(ctx.Request.URL.Path) 97 if requestPath == "/favicon.ico" || requestPath == "/robots.txt" { 98 return true 99 } 100 for prefix := range BConfig.WebConfig.StaticDir { 101 if strings.HasPrefix(requestPath, prefix) { 102 return true 103 } 104 } 105 return false 106 } 107 108 // ExceptMethodAppend to append a slice's value into "exceptMethod", for controller's methods shouldn't reflect to AutoRouter 109 func ExceptMethodAppend(action string) { 110 exceptMethod = append(exceptMethod, action) 111 } 112 113 // ControllerInfo holds information about the controller. 114 type ControllerInfo struct { 115 pattern string 116 controllerType reflect.Type 117 methods map[string]string 118 handler http.Handler 119 runFunction FilterFunc 120 routerType int 121 initialize func() ControllerInterface 122 methodParams []*param.MethodParam 123 } 124 125 func (c *ControllerInfo) GetPattern() string { 126 return c.pattern 127 } 128 129 // ControllerRegister containers registered router rules, controller handlers and filters. 130 type ControllerRegister struct { 131 routers map[string]*Tree 132 enablePolicy bool 133 policies map[string]*Tree 134 enableFilter bool 135 filters [FinishRouter + 1][]*FilterRouter 136 pool sync.Pool 137 } 138 139 // NewControllerRegister returns a new ControllerRegister. 140 func NewControllerRegister() *ControllerRegister { 141 return &ControllerRegister{ 142 routers: make(map[string]*Tree), 143 policies: make(map[string]*Tree), 144 pool: sync.Pool{ 145 New: func() interface{} { 146 return beecontext.NewContext() 147 }, 148 }, 149 } 150 } 151 152 // Add controller handler and pattern rules to ControllerRegister. 153 // usage: 154 // default methods is the same name as method 155 // Add("/user",&UserController{}) 156 // Add("/api/list",&RestController{},"*:ListFood") 157 // Add("/api/create",&RestController{},"post:CreateFood") 158 // Add("/api/update",&RestController{},"put:UpdateFood") 159 // Add("/api/delete",&RestController{},"delete:DeleteFood") 160 // Add("/api",&RestController{},"get,post:ApiFunc" 161 // Add("/simple",&SimpleController{},"get:GetFunc;post:PostFunc") 162 func (p *ControllerRegister) Add(pattern string, c ControllerInterface, mappingMethods ...string) { 163 p.addWithMethodParams(pattern, c, nil, mappingMethods...) 164 } 165 166 func (p *ControllerRegister) addWithMethodParams(pattern string, c ControllerInterface, methodParams []*param.MethodParam, mappingMethods ...string) { 167 reflectVal := reflect.ValueOf(c) 168 t := reflect.Indirect(reflectVal).Type() 169 methods := make(map[string]string) 170 if len(mappingMethods) > 0 { 171 semi := strings.Split(mappingMethods[0], ";") 172 for _, v := range semi { 173 colon := strings.Split(v, ":") 174 if len(colon) != 2 { 175 panic("method mapping format is invalid") 176 } 177 comma := strings.Split(colon[0], ",") 178 for _, m := range comma { 179 if m == "*" || HTTPMETHOD[strings.ToUpper(m)] { 180 if val := reflectVal.MethodByName(colon[1]); val.IsValid() { 181 methods[strings.ToUpper(m)] = colon[1] 182 } else { 183 panic("'" + colon[1] + "' method doesn't exist in the controller " + t.Name()) 184 } 185 } else { 186 panic(v + " is an invalid method mapping. Method doesn't exist " + m) 187 } 188 } 189 } 190 } 191 192 route := &ControllerInfo{} 193 route.pattern = pattern 194 route.methods = methods 195 route.routerType = routerTypeBeego 196 route.controllerType = t 197 route.initialize = func() ControllerInterface { 198 vc := reflect.New(route.controllerType) 199 execController, ok := vc.Interface().(ControllerInterface) 200 if !ok { 201 panic("controller is not ControllerInterface") 202 } 203 204 elemVal := reflect.ValueOf(c).Elem() 205 elemType := reflect.TypeOf(c).Elem() 206 execElem := reflect.ValueOf(execController).Elem() 207 208 numOfFields := elemVal.NumField() 209 for i := 0; i < numOfFields; i++ { 210 fieldType := elemType.Field(i) 211 elemField := execElem.FieldByName(fieldType.Name) 212 if elemField.CanSet() { 213 fieldVal := elemVal.Field(i) 214 elemField.Set(fieldVal) 215 } 216 } 217 218 return execController 219 } 220 221 route.methodParams = methodParams 222 if len(methods) == 0 { 223 for m := range HTTPMETHOD { 224 p.addToRouter(m, pattern, route) 225 } 226 } else { 227 for k := range methods { 228 if k == "*" { 229 for m := range HTTPMETHOD { 230 p.addToRouter(m, pattern, route) 231 } 232 } else { 233 p.addToRouter(k, pattern, route) 234 } 235 } 236 } 237 } 238 239 func (p *ControllerRegister) addToRouter(method, pattern string, r *ControllerInfo) { 240 if !BConfig.RouterCaseSensitive { 241 pattern = strings.ToLower(pattern) 242 } 243 if t, ok := p.routers[method]; ok { 244 t.AddRouter(pattern, r) 245 } else { 246 t := NewTree() 247 t.AddRouter(pattern, r) 248 p.routers[method] = t 249 } 250 } 251 252 // Include only when the Runmode is dev will generate router file in the router/auto.go from the controller 253 // Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{}) 254 func (p *ControllerRegister) Include(cList ...ControllerInterface) { 255 if BConfig.RunMode == DEV { 256 skip := make(map[string]bool, 10) 257 wgopath := utils.GetGOPATHs() 258 go111module := os.Getenv(`GO111MODULE`) 259 for _, c := range cList { 260 reflectVal := reflect.ValueOf(c) 261 t := reflect.Indirect(reflectVal).Type() 262 // for go modules 263 if go111module == `on` { 264 pkgpath := filepath.Join(WorkPath, "..", t.PkgPath()) 265 if utils.FileExists(pkgpath) { 266 if pkgpath != "" { 267 if _, ok := skip[pkgpath]; !ok { 268 skip[pkgpath] = true 269 parserPkg(pkgpath, t.PkgPath()) 270 } 271 } 272 } 273 } else { 274 if len(wgopath) == 0 { 275 panic("you are in dev mode. So please set gopath") 276 } 277 pkgpath := "" 278 for _, wg := range wgopath { 279 wg, _ = filepath.EvalSymlinks(filepath.Join(wg, "src", t.PkgPath())) 280 if utils.FileExists(wg) { 281 pkgpath = wg 282 break 283 } 284 } 285 if pkgpath != "" { 286 if _, ok := skip[pkgpath]; !ok { 287 skip[pkgpath] = true 288 parserPkg(pkgpath, t.PkgPath()) 289 } 290 } 291 } 292 } 293 } 294 for _, c := range cList { 295 reflectVal := reflect.ValueOf(c) 296 t := reflect.Indirect(reflectVal).Type() 297 key := t.PkgPath() + ":" + t.Name() 298 if comm, ok := GlobalControllerRouter[key]; ok { 299 for _, a := range comm { 300 for _, f := range a.Filters { 301 p.InsertFilter(f.Pattern, f.Pos, f.Filter, f.ReturnOnOutput, f.ResetParams) 302 } 303 304 p.addWithMethodParams(a.Router, c, a.MethodParams, strings.Join(a.AllowHTTPMethods, ",")+":"+a.Method) 305 } 306 } 307 } 308 } 309 310 // GetContext returns a context from pool, so usually you should remember to call Reset function to clean the context 311 // And don't forget to give back context to pool 312 // example: 313 // ctx := p.GetContext() 314 // ctx.Reset(w, q) 315 // defer p.GiveBackContext(ctx) 316 func (p *ControllerRegister) GetContext() *beecontext.Context { 317 return p.pool.Get().(*beecontext.Context) 318 } 319 320 // GiveBackContext put the ctx into pool so that it could be reuse 321 func (p *ControllerRegister) GiveBackContext(ctx *beecontext.Context) { 322 p.pool.Put(ctx) 323 } 324 325 // Get add get method 326 // usage: 327 // Get("/", func(ctx *context.Context){ 328 // ctx.Output.Body("hello world") 329 // }) 330 func (p *ControllerRegister) Get(pattern string, f FilterFunc) { 331 p.AddMethod("get", pattern, f) 332 } 333 334 // Post add post method 335 // usage: 336 // Post("/api", func(ctx *context.Context){ 337 // ctx.Output.Body("hello world") 338 // }) 339 func (p *ControllerRegister) Post(pattern string, f FilterFunc) { 340 p.AddMethod("post", pattern, f) 341 } 342 343 // Put add put method 344 // usage: 345 // Put("/api/:id", func(ctx *context.Context){ 346 // ctx.Output.Body("hello world") 347 // }) 348 func (p *ControllerRegister) Put(pattern string, f FilterFunc) { 349 p.AddMethod("put", pattern, f) 350 } 351 352 // Delete add delete method 353 // usage: 354 // Delete("/api/:id", func(ctx *context.Context){ 355 // ctx.Output.Body("hello world") 356 // }) 357 func (p *ControllerRegister) Delete(pattern string, f FilterFunc) { 358 p.AddMethod("delete", pattern, f) 359 } 360 361 // Head add head method 362 // usage: 363 // Head("/api/:id", func(ctx *context.Context){ 364 // ctx.Output.Body("hello world") 365 // }) 366 func (p *ControllerRegister) Head(pattern string, f FilterFunc) { 367 p.AddMethod("head", pattern, f) 368 } 369 370 // Patch add patch method 371 // usage: 372 // Patch("/api/:id", func(ctx *context.Context){ 373 // ctx.Output.Body("hello world") 374 // }) 375 func (p *ControllerRegister) Patch(pattern string, f FilterFunc) { 376 p.AddMethod("patch", pattern, f) 377 } 378 379 // Options add options method 380 // usage: 381 // Options("/api/:id", func(ctx *context.Context){ 382 // ctx.Output.Body("hello world") 383 // }) 384 func (p *ControllerRegister) Options(pattern string, f FilterFunc) { 385 p.AddMethod("options", pattern, f) 386 } 387 388 // Any add all method 389 // usage: 390 // Any("/api/:id", func(ctx *context.Context){ 391 // ctx.Output.Body("hello world") 392 // }) 393 func (p *ControllerRegister) Any(pattern string, f FilterFunc) { 394 p.AddMethod("*", pattern, f) 395 } 396 397 // AddMethod add http method router 398 // usage: 399 // AddMethod("get","/api/:id", func(ctx *context.Context){ 400 // ctx.Output.Body("hello world") 401 // }) 402 func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) { 403 method = strings.ToUpper(method) 404 if method != "*" && !HTTPMETHOD[method] { 405 panic("not support http method: " + method) 406 } 407 route := &ControllerInfo{} 408 route.pattern = pattern 409 route.routerType = routerTypeRESTFul 410 route.runFunction = f 411 methods := make(map[string]string) 412 if method == "*" { 413 for val := range HTTPMETHOD { 414 methods[val] = val 415 } 416 } else { 417 methods[method] = method 418 } 419 route.methods = methods 420 for k := range methods { 421 if k == "*" { 422 for m := range HTTPMETHOD { 423 p.addToRouter(m, pattern, route) 424 } 425 } else { 426 p.addToRouter(k, pattern, route) 427 } 428 } 429 } 430 431 // Handler add user defined Handler 432 func (p *ControllerRegister) Handler(pattern string, h http.Handler, options ...interface{}) { 433 route := &ControllerInfo{} 434 route.pattern = pattern 435 route.routerType = routerTypeHandler 436 route.handler = h 437 if len(options) > 0 { 438 if _, ok := options[0].(bool); ok { 439 pattern = path.Join(pattern, "?:all(.*)") 440 } 441 } 442 for m := range HTTPMETHOD { 443 p.addToRouter(m, pattern, route) 444 } 445 } 446 447 // AddAuto router to ControllerRegister. 448 // example beego.AddAuto(&MainContorlller{}), 449 // MainController has method List and Page. 450 // visit the url /main/list to execute List function 451 // /main/page to execute Page function. 452 func (p *ControllerRegister) AddAuto(c ControllerInterface) { 453 p.AddAutoPrefix("/", c) 454 } 455 456 // AddAutoPrefix Add auto router to ControllerRegister with prefix. 457 // example beego.AddAutoPrefix("/admin",&MainContorlller{}), 458 // MainController has method List and Page. 459 // visit the url /admin/main/list to execute List function 460 // /admin/main/page to execute Page function. 461 func (p *ControllerRegister) AddAutoPrefix(prefix string, c ControllerInterface) { 462 reflectVal := reflect.ValueOf(c) 463 rt := reflectVal.Type() 464 ct := reflect.Indirect(reflectVal).Type() 465 controllerName := strings.TrimSuffix(ct.Name(), "Controller") 466 for i := 0; i < rt.NumMethod(); i++ { 467 if !utils.InSlice(rt.Method(i).Name, exceptMethod) { 468 route := &ControllerInfo{} 469 route.routerType = routerTypeBeego 470 route.methods = map[string]string{"*": rt.Method(i).Name} 471 route.controllerType = ct 472 pattern := path.Join(prefix, strings.ToLower(controllerName), strings.ToLower(rt.Method(i).Name), "*") 473 patternInit := path.Join(prefix, controllerName, rt.Method(i).Name, "*") 474 patternFix := path.Join(prefix, strings.ToLower(controllerName), strings.ToLower(rt.Method(i).Name)) 475 patternFixInit := path.Join(prefix, controllerName, rt.Method(i).Name) 476 route.pattern = pattern 477 for m := range HTTPMETHOD { 478 p.addToRouter(m, pattern, route) 479 p.addToRouter(m, patternInit, route) 480 p.addToRouter(m, patternFix, route) 481 p.addToRouter(m, patternFixInit, route) 482 } 483 } 484 } 485 } 486 487 // InsertFilter Add a FilterFunc with pattern rule and action constant. 488 // params is for: 489 // 1. setting the returnOnOutput value (false allows multiple filters to execute) 490 // 2. determining whether or not params need to be reset. 491 func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) error { 492 mr := &FilterRouter{ 493 tree: NewTree(), 494 pattern: pattern, 495 filterFunc: filter, 496 returnOnOutput: true, 497 } 498 if !BConfig.RouterCaseSensitive { 499 mr.pattern = strings.ToLower(pattern) 500 } 501 502 paramsLen := len(params) 503 if paramsLen > 0 { 504 mr.returnOnOutput = params[0] 505 } 506 if paramsLen > 1 { 507 mr.resetParams = params[1] 508 } 509 mr.tree.AddRouter(pattern, true) 510 return p.insertFilterRouter(pos, mr) 511 } 512 513 // add Filter into 514 func (p *ControllerRegister) insertFilterRouter(pos int, mr *FilterRouter) (err error) { 515 if pos < BeforeStatic || pos > FinishRouter { 516 return errors.New("can not find your filter position") 517 } 518 p.enableFilter = true 519 p.filters[pos] = append(p.filters[pos], mr) 520 return nil 521 } 522 523 // URLFor does another controller handler in this request function. 524 // it can access any controller method. 525 func (p *ControllerRegister) URLFor(endpoint string, values ...interface{}) string { 526 paths := strings.Split(endpoint, ".") 527 if len(paths) <= 1 { 528 logs.Warn("urlfor endpoint must like path.controller.method") 529 return "" 530 } 531 if len(values)%2 != 0 { 532 logs.Warn("urlfor params must key-value pair") 533 return "" 534 } 535 params := make(map[string]string) 536 if len(values) > 0 { 537 key := "" 538 for k, v := range values { 539 if k%2 == 0 { 540 key = fmt.Sprint(v) 541 } else { 542 params[key] = fmt.Sprint(v) 543 } 544 } 545 } 546 controllerName := strings.Join(paths[:len(paths)-1], "/") 547 methodName := paths[len(paths)-1] 548 for m, t := range p.routers { 549 ok, url := p.getURL(t, "/", controllerName, methodName, params, m) 550 if ok { 551 return url 552 } 553 } 554 return "" 555 } 556 557 func (p *ControllerRegister) getURL(t *Tree, url, controllerName, methodName string, params map[string]string, httpMethod string) (bool, string) { 558 for _, subtree := range t.fixrouters { 559 u := path.Join(url, subtree.prefix) 560 ok, u := p.getURL(subtree, u, controllerName, methodName, params, httpMethod) 561 if ok { 562 return ok, u 563 } 564 } 565 if t.wildcard != nil { 566 u := path.Join(url, urlPlaceholder) 567 ok, u := p.getURL(t.wildcard, u, controllerName, methodName, params, httpMethod) 568 if ok { 569 return ok, u 570 } 571 } 572 for _, l := range t.leaves { 573 if c, ok := l.runObject.(*ControllerInfo); ok { 574 if c.routerType == routerTypeBeego && 575 strings.HasSuffix(path.Join(c.controllerType.PkgPath(), c.controllerType.Name()), controllerName) { 576 find := false 577 if HTTPMETHOD[strings.ToUpper(methodName)] { 578 if len(c.methods) == 0 { 579 find = true 580 } else if m, ok := c.methods[strings.ToUpper(methodName)]; ok && m == strings.ToUpper(methodName) { 581 find = true 582 } else if m, ok = c.methods["*"]; ok && m == methodName { 583 find = true 584 } 585 } 586 if !find { 587 for m, md := range c.methods { 588 if (m == "*" || m == httpMethod) && md == methodName { 589 find = true 590 } 591 } 592 } 593 if find { 594 if l.regexps == nil { 595 if len(l.wildcards) == 0 { 596 return true, strings.Replace(url, "/"+urlPlaceholder, "", 1) + toURL(params) 597 } 598 if len(l.wildcards) == 1 { 599 if v, ok := params[l.wildcards[0]]; ok { 600 delete(params, l.wildcards[0]) 601 return true, strings.Replace(url, urlPlaceholder, v, 1) + toURL(params) 602 } 603 return false, "" 604 } 605 if len(l.wildcards) == 3 && l.wildcards[0] == "." { 606 if p, ok := params[":path"]; ok { 607 if e, isok := params[":ext"]; isok { 608 delete(params, ":path") 609 delete(params, ":ext") 610 return true, strings.Replace(url, urlPlaceholder, p+"."+e, -1) + toURL(params) 611 } 612 } 613 } 614 canSkip := false 615 for _, v := range l.wildcards { 616 if v == ":" { 617 canSkip = true 618 continue 619 } 620 if u, ok := params[v]; ok { 621 delete(params, v) 622 url = strings.Replace(url, urlPlaceholder, u, 1) 623 } else { 624 if canSkip { 625 canSkip = false 626 continue 627 } 628 return false, "" 629 } 630 } 631 return true, url + toURL(params) 632 } 633 var i int 634 var startReg bool 635 regURL := "" 636 for _, v := range strings.Trim(l.regexps.String(), "^$") { 637 if v == '(' { 638 startReg = true 639 continue 640 } else if v == ')' { 641 startReg = false 642 if v, ok := params[l.wildcards[i]]; ok { 643 delete(params, l.wildcards[i]) 644 regURL = regURL + v 645 i++ 646 } else { 647 break 648 } 649 } else if !startReg { 650 regURL = string(append([]rune(regURL), v)) 651 } 652 } 653 if l.regexps.MatchString(regURL) { 654 ps := strings.Split(regURL, "/") 655 for _, p := range ps { 656 url = strings.Replace(url, urlPlaceholder, p, 1) 657 } 658 return true, url + toURL(params) 659 } 660 } 661 } 662 } 663 } 664 665 return false, "" 666 } 667 668 func (p *ControllerRegister) execFilter(context *beecontext.Context, urlPath string, pos int) (started bool) { 669 var preFilterParams map[string]string 670 for _, filterR := range p.filters[pos] { 671 if filterR.returnOnOutput && context.ResponseWriter.Started { 672 return true 673 } 674 if filterR.resetParams { 675 preFilterParams = context.Input.Params() 676 } 677 if ok := filterR.ValidRouter(urlPath, context); ok { 678 filterR.filterFunc(context) 679 if filterR.resetParams { 680 context.Input.ResetParams() 681 for k, v := range preFilterParams { 682 context.Input.SetParam(k, v) 683 } 684 } 685 } 686 if filterR.returnOnOutput && context.ResponseWriter.Started { 687 return true 688 } 689 } 690 return false 691 } 692 693 // Implement http.Handler interface. 694 func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) { 695 startTime := time.Now() 696 var ( 697 runRouter reflect.Type 698 findRouter bool 699 runMethod string 700 methodParams []*param.MethodParam 701 routerInfo *ControllerInfo 702 isRunnable bool 703 ) 704 context := p.GetContext() 705 706 context.Reset(rw, r) 707 708 defer p.GiveBackContext(context) 709 if BConfig.RecoverFunc != nil { 710 defer BConfig.RecoverFunc(context) 711 } 712 713 context.Output.EnableGzip = BConfig.EnableGzip 714 715 if BConfig.RunMode == DEV { 716 context.Output.Header("Server", BConfig.ServerName) 717 } 718 719 var urlPath = r.URL.Path 720 721 if !BConfig.RouterCaseSensitive { 722 urlPath = strings.ToLower(urlPath) 723 } 724 725 // filter wrong http method 726 if !HTTPMETHOD[r.Method] { 727 exception("405", context) 728 goto Admin 729 } 730 731 // filter for static file 732 if len(p.filters[BeforeStatic]) > 0 && p.execFilter(context, urlPath, BeforeStatic) { 733 goto Admin 734 } 735 736 serverStaticRouter(context) 737 738 if context.ResponseWriter.Started { 739 findRouter = true 740 goto Admin 741 } 742 743 if r.Method != http.MethodGet && r.Method != http.MethodHead { 744 if BConfig.CopyRequestBody && !context.Input.IsUpload() { 745 // connection will close if the incoming data are larger (RFC 7231, 6.5.11) 746 if r.ContentLength > BConfig.MaxMemory { 747 logs.Error(errors.New("payload too large")) 748 exception("413", context) 749 goto Admin 750 } 751 context.Input.CopyBody(BConfig.MaxMemory) 752 } 753 context.Input.ParseFormOrMulitForm(BConfig.MaxMemory) 754 } 755 756 // session init 757 if BConfig.WebConfig.Session.SessionOn { 758 var err error 759 context.Input.CruSession, err = GlobalSessions.SessionStart(rw, r) 760 if err != nil { 761 logs.Error(err) 762 exception("503", context) 763 goto Admin 764 } 765 defer func() { 766 if context.Input.CruSession != nil { 767 context.Input.CruSession.SessionRelease(rw) 768 } 769 }() 770 } 771 if len(p.filters[BeforeRouter]) > 0 && p.execFilter(context, urlPath, BeforeRouter) { 772 goto Admin 773 } 774 // User can define RunController and RunMethod in filter 775 if context.Input.RunController != nil && context.Input.RunMethod != "" { 776 findRouter = true 777 runMethod = context.Input.RunMethod 778 runRouter = context.Input.RunController 779 } else { 780 routerInfo, findRouter = p.FindRouter(context) 781 } 782 783 // if no matches to url, throw a not found exception 784 if !findRouter { 785 exception("404", context) 786 goto Admin 787 } 788 if splat := context.Input.Param(":splat"); splat != "" { 789 for k, v := range strings.Split(splat, "/") { 790 context.Input.SetParam(strconv.Itoa(k), v) 791 } 792 } 793 794 if routerInfo != nil { 795 // store router pattern into context 796 context.Input.SetData("RouterPattern", routerInfo.pattern) 797 } 798 799 // execute middleware filters 800 if len(p.filters[BeforeExec]) > 0 && p.execFilter(context, urlPath, BeforeExec) { 801 goto Admin 802 } 803 804 // check policies 805 if p.execPolicy(context, urlPath) { 806 goto Admin 807 } 808 809 if routerInfo != nil { 810 if routerInfo.routerType == routerTypeRESTFul { 811 if _, ok := routerInfo.methods[r.Method]; ok { 812 isRunnable = true 813 routerInfo.runFunction(context) 814 } else { 815 exception("405", context) 816 goto Admin 817 } 818 } else if routerInfo.routerType == routerTypeHandler { 819 isRunnable = true 820 routerInfo.handler.ServeHTTP(context.ResponseWriter, context.Request) 821 } else { 822 runRouter = routerInfo.controllerType 823 methodParams = routerInfo.methodParams 824 method := r.Method 825 if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodPut { 826 method = http.MethodPut 827 } 828 if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodDelete { 829 method = http.MethodDelete 830 } 831 if m, ok := routerInfo.methods[method]; ok { 832 runMethod = m 833 } else if m, ok = routerInfo.methods["*"]; ok { 834 runMethod = m 835 } else { 836 runMethod = method 837 } 838 } 839 } 840 841 // also defined runRouter & runMethod from filter 842 if !isRunnable { 843 // Invoke the request handler 844 var execController ControllerInterface 845 if routerInfo != nil && routerInfo.initialize != nil { 846 execController = routerInfo.initialize() 847 } else { 848 vc := reflect.New(runRouter) 849 var ok bool 850 execController, ok = vc.Interface().(ControllerInterface) 851 if !ok { 852 panic("controller is not ControllerInterface") 853 } 854 } 855 856 // call the controller init function 857 execController.Init(context, runRouter.Name(), runMethod, execController) 858 859 // call prepare function 860 execController.Prepare() 861 862 // if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf 863 if BConfig.WebConfig.EnableXSRF { 864 execController.XSRFToken() 865 if r.Method == http.MethodPost || r.Method == http.MethodDelete || r.Method == http.MethodPut || 866 (r.Method == http.MethodPost && (context.Input.Query("_method") == http.MethodDelete || context.Input.Query("_method") == http.MethodPut)) { 867 execController.CheckXSRFCookie() 868 } 869 } 870 871 execController.URLMapping() 872 873 if !context.ResponseWriter.Started { 874 // exec main logic 875 switch runMethod { 876 case http.MethodGet: 877 execController.Get() 878 case http.MethodPost: 879 execController.Post() 880 case http.MethodDelete: 881 execController.Delete() 882 case http.MethodPut: 883 execController.Put() 884 case http.MethodHead: 885 execController.Head() 886 case http.MethodPatch: 887 execController.Patch() 888 case http.MethodOptions: 889 execController.Options() 890 case http.MethodTrace: 891 execController.Trace() 892 default: 893 if !execController.HandlerFunc(runMethod) { 894 vc := reflect.ValueOf(execController) 895 method := vc.MethodByName(runMethod) 896 in := param.ConvertParams(methodParams, method.Type(), context) 897 out := method.Call(in) 898 899 // For backward compatibility we only handle response if we had incoming methodParams 900 if methodParams != nil { 901 p.handleParamResponse(context, execController, out) 902 } 903 } 904 } 905 906 // render template 907 if !context.ResponseWriter.Started && context.Output.Status == 0 { 908 if BConfig.WebConfig.AutoRender { 909 if err := execController.Render(); err != nil { 910 logs.Error(err) 911 } 912 } 913 } 914 } 915 916 // finish all runRouter. release resource 917 execController.Finish() 918 } 919 920 // execute middleware filters 921 if len(p.filters[AfterExec]) > 0 && p.execFilter(context, urlPath, AfterExec) { 922 goto Admin 923 } 924 925 if len(p.filters[FinishRouter]) > 0 && p.execFilter(context, urlPath, FinishRouter) { 926 goto Admin 927 } 928 929 Admin: 930 // admin module record QPS 931 932 statusCode := context.ResponseWriter.Status 933 if statusCode == 0 { 934 statusCode = 200 935 } 936 937 LogAccess(context, &startTime, statusCode) 938 939 timeDur := time.Since(startTime) 940 context.ResponseWriter.Elapsed = timeDur 941 if BConfig.Listen.EnableAdmin { 942 pattern := "" 943 if routerInfo != nil { 944 pattern = routerInfo.pattern 945 } 946 947 if FilterMonitorFunc(r.Method, r.URL.Path, timeDur, pattern, statusCode) { 948 routerName := "" 949 if runRouter != nil { 950 routerName = runRouter.Name() 951 } 952 go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, routerName, timeDur) 953 } 954 } 955 956 if BConfig.RunMode == DEV && !BConfig.Log.AccessLogs { 957 match := map[bool]string{true: "match", false: "nomatch"} 958 devInfo := fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", 959 context.Input.IP(), 960 logs.ColorByStatus(statusCode), statusCode, logs.ResetColor(), 961 timeDur.String(), 962 match[findRouter], 963 logs.ColorByMethod(r.Method), r.Method, logs.ResetColor(), 964 r.URL.Path) 965 if routerInfo != nil { 966 devInfo += fmt.Sprintf(" r:%s", routerInfo.pattern) 967 } 968 969 logs.Debug(devInfo) 970 } 971 // Call WriteHeader if status code has been set changed 972 if context.Output.Status != 0 { 973 context.ResponseWriter.WriteHeader(context.Output.Status) 974 } 975 } 976 977 func (p *ControllerRegister) handleParamResponse(context *beecontext.Context, execController ControllerInterface, results []reflect.Value) { 978 // looping in reverse order for the case when both error and value are returned and error sets the response status code 979 for i := len(results) - 1; i >= 0; i-- { 980 result := results[i] 981 if result.Kind() != reflect.Interface || !result.IsNil() { 982 resultValue := result.Interface() 983 context.RenderMethodResult(resultValue) 984 } 985 } 986 if !context.ResponseWriter.Started && len(results) > 0 && context.Output.Status == 0 { 987 context.Output.SetStatus(200) 988 } 989 } 990 991 // FindRouter Find Router info for URL 992 func (p *ControllerRegister) FindRouter(context *beecontext.Context) (routerInfo *ControllerInfo, isFind bool) { 993 var urlPath = context.Input.URL() 994 if !BConfig.RouterCaseSensitive { 995 urlPath = strings.ToLower(urlPath) 996 } 997 httpMethod := context.Input.Method() 998 if t, ok := p.routers[httpMethod]; ok { 999 runObject := t.Match(urlPath, context) 1000 if r, ok := runObject.(*ControllerInfo); ok { 1001 return r, true 1002 } 1003 } 1004 return 1005 } 1006 1007 func toURL(params map[string]string) string { 1008 if len(params) == 0 { 1009 return "" 1010 } 1011 u := "?" 1012 for k, v := range params { 1013 u += k + "=" + v + "&" 1014 } 1015 return strings.TrimRight(u, "&") 1016 } 1017 1018 // LogAccess logging info HTTP Access 1019 func LogAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) { 1020 // Skip logging if AccessLogs config is false 1021 if !BConfig.Log.AccessLogs { 1022 return 1023 } 1024 // Skip logging static requests unless EnableStaticLogs config is true 1025 if !BConfig.Log.EnableStaticLogs && DefaultAccessLogFilter.Filter(ctx) { 1026 return 1027 } 1028 var ( 1029 requestTime time.Time 1030 elapsedTime time.Duration 1031 r = ctx.Request 1032 ) 1033 if startTime != nil { 1034 requestTime = *startTime 1035 elapsedTime = time.Since(*startTime) 1036 } 1037 record := &logs.AccessLogRecord{ 1038 RemoteAddr: ctx.Input.IP(), 1039 RequestTime: requestTime, 1040 RequestMethod: r.Method, 1041 Request: fmt.Sprintf("%s %s %s", r.Method, r.RequestURI, r.Proto), 1042 ServerProtocol: r.Proto, 1043 Host: r.Host, 1044 Status: statusCode, 1045 ElapsedTime: elapsedTime, 1046 HTTPReferrer: r.Header.Get("Referer"), 1047 HTTPUserAgent: r.Header.Get("User-Agent"), 1048 RemoteUser: r.Header.Get("Remote-User"), 1049 BodyBytesSent: 0, // @todo this one is missing! 1050 } 1051 logs.AccessLog(record, BConfig.Log.AccessLogsFormat) 1052 }