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  }