// BuildTemplate will build all template files in a directory. // it makes beego can render any template file in view directory. func BuildTemplate(dir string, files ...string) error { if _, err := os.Stat(dir); err != nil { if os.IsNotExist(err) { return nil } return errors.New("dir open err") } self := &templateFile{ root: dir, files: make(map[string][]string), } err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { return self.visit(path, f, err) }) if err != nil { fmt.Printf("filepath.Walk() returned %v\n", err) return err } for _, v := range self.files { for _, file := range v { if len(files) == 0 || utils.InSlice(file, files) { templatesLock.Lock() t, err := getTemplate(self.root, file, v...) if err != nil { Trace("parse template err:", file, err) } else { beeTemplates[file] = t } templatesLock.Unlock() } } } return nil }
// AddAutoPrefix Add auto router to ControllerRegister with prefix. // example beego.AddAutoPrefix("/admin",&MainContorlller{}), // MainController has method List and Page. // visit the url /admin/main/list to execute List function // /admin/main/page to execute Page function. func (p *ControllerRegister) AddAutoPrefix(prefix string, c ControllerInterface) { reflectVal := reflect.ValueOf(c) rt := reflectVal.Type() ct := reflect.Indirect(reflectVal).Type() controllerName := strings.TrimSuffix(ct.Name(), "Controller") for i := 0; i < rt.NumMethod(); i++ { if !utils.InSlice(rt.Method(i).Name, exceptMethod) { route := &controllerInfo{} route.routerType = routerTypeBeego route.methods = map[string]string{"*": rt.Method(i).Name} route.controllerType = ct pattern := path.Join(prefix, strings.ToLower(controllerName), strings.ToLower(rt.Method(i).Name), "*") patternInit := path.Join(prefix, controllerName, rt.Method(i).Name, "*") patternFix := path.Join(prefix, strings.ToLower(controllerName), strings.ToLower(rt.Method(i).Name)) patternFixInit := path.Join(prefix, controllerName, rt.Method(i).Name) route.pattern = pattern for _, m := range HTTPMETHOD { p.addToRouter(m, pattern, route) p.addToRouter(m, patternInit, route) p.addToRouter(m, patternFix, route) p.addToRouter(m, patternFixInit, route) } } } }
// ErrorController registers ControllerInterface to each http err code string. // usage: // beego.ErrorHandler(&controllers.ErrorController{}) func ErrorController(c ControllerInterface) *App { reflectVal := reflect.ValueOf(c) rt := reflectVal.Type() ct := reflect.Indirect(reflectVal).Type() for i := 0; i < rt.NumMethod(); i++ { if !utils.InSlice(rt.Method(i).Name, exceptMethod) && strings.HasPrefix(rt.Method(i).Name, "Error") { errinfo := &errorInfo{} errinfo.errorType = errorTypeController errinfo.controllerType = ct errinfo.method = rt.Method(i).Name errname := strings.TrimPrefix(rt.Method(i).Name, "Error") ErrorMaps[errname] = errinfo } } return BeeApp }
// Add auto router to ControllerRegistor with prefix. // example beego.AddAutoPrefix("/admin",&MainContorlller{}), // MainController has method List and Page. // visit the url /admin/main/list to execute List function // /admin/main/page to execute Page function. func (p *ControllerRegistor) AddAutoPrefix(prefix string, c ControllerInterface) { p.enableAuto = true reflectVal := reflect.ValueOf(c) rt := reflectVal.Type() ct := reflect.Indirect(reflectVal).Type() firstParam := strings.Trim(prefix, "/") + "/" + strings.ToLower(strings.TrimSuffix(ct.Name(), "Controller")) if _, ok := p.autoRouter[firstParam]; ok { return } else { p.autoRouter[firstParam] = make(map[string]reflect.Type) } for i := 0; i < rt.NumMethod(); i++ { if !utils.InSlice(rt.Method(i).Name, exceptMethod) { p.autoRouter[firstParam][rt.Method(i).Name] = ct } } }
// ErrorController registers ControllerInterface to each http err code string. // usage: // beego.ErrorController(&controllers.ErrorController{}) func ErrorController(c ControllerInterface) *App { reflectVal := reflect.ValueOf(c) rt := reflectVal.Type() ct := reflect.Indirect(reflectVal).Type() for i := 0; i < rt.NumMethod(); i++ { methodName := rt.Method(i).Name if !utils.InSlice(methodName, exceptMethod) && strings.HasPrefix(methodName, "Error") { errName := strings.TrimPrefix(methodName, "Error") ErrorMaps[errName] = &errorInfo{ errorType: errorTypeController, controllerType: ct, method: methodName, } } } return BeeApp }
// BuildTemplate will build all template files in a directory. // it makes beego can render any template file in view directory. func BuildTemplate(dir string, files ...string) error { if _, err := os.Stat(dir); err != nil { if os.IsNotExist(err) { return nil } return errors.New("dir open err") } self := &templateFile{ root: dir, files: make(map[string][]string), } err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { return self.visit(path, f, err) }) if err != nil { fmt.Printf("filepath.Walk() returned %v\n", err) return err } buildAllFiles := len(files) == 0 for _, v := range self.files { for _, file := range v { if buildAllFiles || utils.InSlice(file, files) { templatesLock.Lock() ext := filepath.Ext(file) var t *template.Template if len(ext) == 0 { t, err = getTemplate(self.root, file, v...) } else if fn, ok := beeTemplateEngines[ext[1:]]; ok { t, err = fn(self.root, file, beegoTplFuncMap) } else { t, err = getTemplate(self.root, file, v...) } if err != nil { logs.Trace("parse template err:", file, err) } else { beeTemplates[file] = t } templatesLock.Unlock() } } } return nil }
// add http method router // usage: // AddMethod("get","/api/:id", func(ctx *context.Context){ // ctx.Output.Body("hello world") // }) func (p *ControllerRegistor) AddMethod(method, pattern string, f FilterFunc) { if method != "*" && !utils.InSlice(strings.ToLower(method), HTTPMETHOD) { panic("not support http method: " + method) } route := &controllerInfo{} route.routerType = routerTypeRESTFul route.runfunction = f methods := make(map[string]string) if method == "*" { for _, val := range HTTPMETHOD { methods[val] = val } } else { methods[method] = method } route.methods = methods paramnums, params, parts := p.splitRoute(pattern) if paramnums == 0 { //now create the Route route.pattern = pattern p.fixrouters = append(p.fixrouters, route) } else { //recreate the url pattern, with parameters replaced //by regular expressions. then compile the regex pattern = strings.Join(parts, "/") regex, regexErr := regexp.Compile(pattern) if regexErr != nil { panic(regexErr) } //now create the Route route.regex = regex route.params = params route.pattern = pattern p.routers = append(p.routers, route) } }
// Add controller handler and pattern rules to ControllerRegistor. // usage: // default methods is the same name as method // Add("/user",&UserController{}) // Add("/api/list",&RestController{},"*:ListFood") // Add("/api/create",&RestController{},"post:CreateFood") // Add("/api/update",&RestController{},"put:UpdateFood") // Add("/api/delete",&RestController{},"delete:DeleteFood") // Add("/api",&RestController{},"get,post:ApiFunc") // Add("/simple",&SimpleController{},"get:GetFunc;post:PostFunc") func (p *ControllerRegistor) Add(pattern string, c ControllerInterface, mappingMethods ...string) { parts := strings.Split(pattern, "/") j := 0 params := make(map[int]string) for i, part := range parts { if strings.HasPrefix(part, ":") { expr := "(.*)" //a user may choose to override the defult expression // similar to expressjs: ‘/user/:id([0-9]+)’ if index := strings.Index(part, "("); index != -1 { expr = part[index:] part = part[:index] //match /user/:id:int ([0-9]+) //match /post/:username:string ([\w]+) } else if lindex := strings.LastIndex(part, ":"); lindex != 0 { switch part[lindex:] { case ":int": expr = "([0-9]+)" part = part[:lindex] case ":string": expr = `([\w]+)` part = part[:lindex] } } params[j] = part parts[i] = expr j++ } if strings.HasPrefix(part, "*") { expr := "(.*)" if part == "*.*" { params[j] = ":path" parts[i] = "([^.]+).([^.]+)" j++ params[j] = ":ext" j++ } else { params[j] = ":splat" parts[i] = expr j++ } } //url like someprefix:id(xxx).html if strings.Contains(part, ":") && strings.Contains(part, "(") && strings.Contains(part, ")") { var out []rune var start bool var startexp bool var param []rune var expt []rune for _, v := range part { if start { if v != '(' { param = append(param, v) continue } } if startexp { if v != ')' { expt = append(expt, v) continue } } if v == ':' { param = make([]rune, 0) param = append(param, ':') start = true } else if v == '(' { startexp = true start = false params[j] = string(param) j++ expt = make([]rune, 0) expt = append(expt, '(') } else if v == ')' { startexp = false expt = append(expt, ')') out = append(out, expt...) } else { out = append(out, v) } } parts[i] = string(out) } } reflectVal := reflect.ValueOf(c) t := reflect.Indirect(reflectVal).Type() methods := make(map[string]string) if len(mappingMethods) > 0 { semi := strings.Split(mappingMethods[0], ";") for _, v := range semi { colon := strings.Split(v, ":") if len(colon) != 2 { panic("method mapping format is invalid") } comma := strings.Split(colon[0], ",") for _, m := range comma { if m == "*" || utils.InSlice(strings.ToLower(m), HTTPMETHOD) { if val := reflectVal.MethodByName(colon[1]); val.IsValid() { methods[strings.ToLower(m)] = colon[1] } else { panic(colon[1] + " method doesn't exist in the controller " + t.Name()) } } else { panic(v + " is an invalid method mapping. Method doesn't exist " + m) } } } } if j == 0 { //now create the Route route := &controllerInfo{} route.pattern = pattern route.controllerType = t route.methods = methods if len(methods) > 0 { route.hasMethod = true } p.fixrouters = append(p.fixrouters, route) } else { // add regexp routers //recreate the url pattern, with parameters replaced //by regular expressions. then compile the regex pattern = strings.Join(parts, "/") regex, regexErr := regexp.Compile(pattern) if regexErr != nil { //TODO add error handling here to avoid panic panic(regexErr) return } //now create the Route route := &controllerInfo{} route.regex = regex route.params = params route.pattern = pattern route.methods = methods if len(methods) > 0 { route.hasMethod = true } route.controllerType = t p.routers = append(p.routers, route) } }
// Implement http.Handler interface. func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { if err == USERSTOPRUN { return } if _, ok := err.(middleware.HTTPException); ok { // catch intented errors, only for HTTP 4XX and 5XX } else { if RunMode == "dev" { if !RecoverPanic { panic(err) } else { if ErrorsShow { if handler, ok := middleware.ErrorMaps[fmt.Sprint(err)]; ok { handler(rw, r) return } } var stack string Critical("the request url is ", r.URL.Path) Critical("Handler crashed with error", err) for i := 1; ; i++ { _, file, line, ok := runtime.Caller(i) if !ok { break } Critical(file, line) stack = stack + fmt.Sprintln(file, line) } middleware.ShowErr(err, rw, r, stack) } } else { if !RecoverPanic { panic(err) } else { // in production model show all infomation if ErrorsShow { handler := p.getErrorHandler(fmt.Sprint(err)) handler(rw, r) return } else { Critical("the request url is ", r.URL.Path) Critical("Handler crashed with error", err) for i := 1; ; i++ { _, file, line, ok := runtime.Caller(i) if !ok { break } Critical(file, line) } } } } } } }() starttime := time.Now() requestPath := r.URL.Path var runrouter reflect.Type var findrouter bool var runMethod string params := make(map[string]string) w := &responseWriter{writer: rw} w.Header().Set("Server", BeegoServerName) // init context context := &beecontext.Context{ ResponseWriter: w, Request: r, Input: beecontext.NewInput(r), Output: beecontext.NewOutput(), } context.Output.Context = context context.Output.EnableGzip = EnableGzip if context.Input.IsWebsocket() { context.ResponseWriter = rw } // defined filter function do_filter := func(pos int) (started bool) { if p.enableFilter { if l, ok := p.filters[pos]; ok { for _, filterR := range l { if ok, p := filterR.ValidRouter(r.URL.Path); ok { context.Input.Params = p filterR.filterFunc(context) if w.started { return true } } } } } return false } // session init if SessionOn { context.Input.CruSession = GlobalSessions.SessionStart(w, r) defer func() { context.Input.CruSession.SessionRelease(w) }() } if !utils.InSlice(strings.ToLower(r.Method), HTTPMETHOD) { http.Error(w, "Method Not Allowed", 405) goto Admin } if do_filter(BeforeRouter) { goto Admin } //static file server for prefix, staticDir := range StaticDir { if r.URL.Path == "/favicon.ico" { file := staticDir + r.URL.Path http.ServeFile(w, r, file) w.started = true goto Admin } if strings.HasPrefix(r.URL.Path, prefix) { file := staticDir + r.URL.Path[len(prefix):] finfo, err := os.Stat(file) if err != nil { if RunMode == "dev" { Warn(err) } http.NotFound(w, r) goto Admin } //if the request is dir and DirectoryIndex is false then if finfo.IsDir() && !DirectoryIndex { middleware.Exception("403", rw, r, "403 Forbidden") goto Admin } //This block obtained from (https://github.com/smithfox/beego) - it should probably get merged into astaxie/beego after a pull request isStaticFileToCompress := false if StaticExtensionsToGzip != nil && len(StaticExtensionsToGzip) > 0 { for _, statExtension := range StaticExtensionsToGzip { if strings.HasSuffix(strings.ToLower(file), strings.ToLower(statExtension)) { isStaticFileToCompress = true break } } } if isStaticFileToCompress { if EnableGzip { w.contentEncoding = GetAcceptEncodingZip(r) } memzipfile, err := OpenMemZipFile(file, w.contentEncoding) if err != nil { return } w.InitHeadContent(finfo.Size()) http.ServeContent(w, r, file, finfo.ModTime(), memzipfile) } else { http.ServeFile(w, r, file) } w.started = true goto Admin } } if do_filter(AfterStatic) { goto Admin } if CopyRequestBody { context.Input.Body() } //first find path from the fixrouters to Improve Performance for _, route := range p.fixrouters { n := len(requestPath) if requestPath == route.pattern { runMethod = p.getRunMethod(r.Method, context, route) if runMethod != "" { runrouter = route.controllerType findrouter = true break } } // pattern /admin url /admin 200 /admin/ 200 // pattern /admin/ url /admin 301 /admin/ 200 if requestPath[n-1] != '/' && requestPath+"/" == route.pattern { http.Redirect(w, r, requestPath+"/", 301) goto Admin } if requestPath[n-1] == '/' && route.pattern+"/" == requestPath { runMethod = p.getRunMethod(r.Method, context, route) if runMethod != "" { runrouter = route.controllerType findrouter = true break } } } //find regex's router if !findrouter { //find a matching Route for _, route := range p.routers { //check if Route pattern matches url if !route.regex.MatchString(requestPath) { continue } //get submatches (params) matches := route.regex.FindStringSubmatch(requestPath) //double check that the Route matches the URL pattern. if len(matches[0]) != len(requestPath) { continue } if len(route.params) > 0 { //add url parameters to the query param map values := r.URL.Query() for i, match := range matches[1:] { values.Add(route.params[i], match) params[route.params[i]] = match } //reassemble query params and add to RawQuery r.URL.RawQuery = url.Values(values).Encode() } runMethod = p.getRunMethod(r.Method, context, route) if runMethod != "" { runrouter = route.controllerType context.Input.Params = params findrouter = true break } } } if !findrouter && p.enableAuto { // deal with url with diffirent ext // /controller/simple // /controller/simple.html // /controller/simple.json // /controller/simple.rss lastindex := strings.LastIndex(requestPath, "/") lastsub := requestPath[lastindex+1:] if subindex := strings.LastIndex(lastsub, "."); subindex != -1 { context.Input.Params[":ext"] = lastsub[subindex+1:] r.URL.Query().Add(":ext", lastsub[subindex+1:]) r.URL.RawQuery = r.URL.Query().Encode() requestPath = requestPath[:len(requestPath)-len(lastsub[subindex:])] } for cName, methodmap := range p.autoRouter { // if prev already find the router break if findrouter { break } if strings.ToLower(requestPath) == "/"+cName { http.Redirect(w, r, requestPath+"/", 301) goto Admin } // if there's no action, set the default action to index if strings.ToLower(requestPath) == "/"+cName+"/" { requestPath = requestPath + "index" } // if the request path start with controllerName if strings.HasPrefix(strings.ToLower(requestPath), "/"+cName+"/") { for mName, controllerType := range methodmap { if strings.ToLower(requestPath) == "/"+cName+"/"+strings.ToLower(mName) || (strings.HasPrefix(strings.ToLower(requestPath), "/"+cName+"/"+strings.ToLower(mName)) && requestPath[len("/"+cName+"/"+strings.ToLower(mName)):len("/"+cName+"/"+strings.ToLower(mName))+1] == "/") { runrouter = controllerType runMethod = mName findrouter = true //parse params otherurl := requestPath[len("/"+cName+"/"+strings.ToLower(mName)):] if len(otherurl) > 1 { plist := strings.Split(otherurl, "/") for k, v := range plist[1:] { context.Input.Params[strconv.Itoa(k)] = v } } break } } } } } //if no matches to url, throw a not found exception if !findrouter { middleware.Exception("404", rw, r, "") goto Admin } if findrouter { if r.Method == "POST" { r.ParseMultipartForm(MaxMemory) } //execute middleware filters if do_filter(BeforeExec) { goto Admin } //Invoke the request handler vc := reflect.New(runrouter) execController, ok := vc.Interface().(ControllerInterface) if !ok { panic("controller is not ControllerInterface") } //call the controller init function execController.Init(context, runrouter.Name(), runMethod, vc.Interface()) //if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf if EnableXSRF { execController.XsrfToken() if r.Method == "POST" || r.Method == "DELETE" || r.Method == "PUT" || (r.Method == "POST" && (r.Form.Get("_method") == "delete" || r.Form.Get("_method") == "put")) { execController.CheckXsrfCookie() } } //call prepare function execController.Prepare() if !w.started { //exec main logic switch runMethod { case "Get": execController.Get() case "Post": execController.Post() case "Delete": execController.Delete() case "Put": execController.Put() case "Head": execController.Head() case "Patch": execController.Patch() case "Options": execController.Options() default: in := make([]reflect.Value, 0) method := vc.MethodByName(runMethod) method.Call(in) } //render template if !w.started && !context.Input.IsWebsocket() { if AutoRender { if err := execController.Render(); err != nil { panic(err) } } } } // finish all runrouter. release resource execController.Finish() //execute middleware filters if do_filter(AfterExec) { goto Admin } } Admin: do_filter(FinishRouter) //admin module record QPS if EnableAdmin { timeend := time.Since(starttime) if FilterMonitorFunc(r.Method, requestPath, timeend) { if runrouter != nil { go toolbox.StatisticsMap.AddStatistics(r.Method, requestPath, runrouter.Name(), timeend) } else { go toolbox.StatisticsMap.AddStatistics(r.Method, requestPath, "", timeend) } } } }
// UrlFor does another controller handler in this request function. // it can access any controller method. func (p *ControllerRegistor) UrlFor(endpoint string, values ...string) string { paths := strings.Split(endpoint, ".") if len(paths) <= 1 { Warn("urlfor endpoint must like path.controller.method") return "" } if len(values)%2 != 0 { Warn("urlfor params must key-value pair") return "" } urlv := url.Values{} if len(values) > 0 { key := "" for k, v := range values { if k%2 == 0 { key = v } else { urlv.Set(key, v) } } } controllName := strings.Join(paths[:len(paths)-1], ".") methodName := paths[len(paths)-1] for _, route := range p.fixrouters { if route.controllerType.Name() == controllName { var finded bool if utils.InSlice(strings.ToLower(methodName), HTTPMETHOD) { if route.hasMethod { if m, ok := route.methods[strings.ToLower(methodName)]; ok && m != methodName { finded = false } else if m, ok = route.methods["*"]; ok && m != methodName { finded = false } else { finded = true } } else { finded = true } } else if route.hasMethod { for _, md := range route.methods { if md == methodName { finded = true } } } if !finded { continue } if len(values) > 0 { return route.pattern + "?" + urlv.Encode() } return route.pattern } } for _, route := range p.routers { if route.controllerType.Name() == controllName { var finded bool if utils.InSlice(strings.ToLower(methodName), HTTPMETHOD) { if route.hasMethod { if m, ok := route.methods[strings.ToLower(methodName)]; ok && m != methodName { finded = false } else if m, ok = route.methods["*"]; ok && m != methodName { finded = false } else { finded = true } } else { finded = true } } else if route.hasMethod { for _, md := range route.methods { if md == methodName { finded = true } } } if !finded { continue } var returnurl string var i int var startreg bool for _, v := range route.regex.String() { if v == '(' { startreg = true continue } else if v == ')' { startreg = false returnurl = returnurl + urlv.Get(route.params[i]) i++ } else if !startreg { returnurl = string(append([]rune(returnurl), v)) } } if route.regex.MatchString(returnurl) { return returnurl } } } if p.enableAuto { for cName, methodList := range p.autoRouter { if strings.ToLower(strings.TrimSuffix(paths[len(paths)-2], "Controller")) == cName { if _, ok := methodList[methodName]; ok { if len(values) > 0 { return "/" + strings.TrimSuffix(paths[len(paths)-2], "Controller") + "/" + methodName + "?" + urlv.Encode() } else { return "/" + strings.TrimSuffix(paths[len(paths)-2], "Controller") + "/" + methodName } } } } } return "" }
// Add controller handler and pattern rules to ControllerRegistor. // usage: // default methods is the same name as method // Add("/user",&UserController{}) // Add("/api/list",&RestController{},"*:ListFood") // Add("/api/create",&RestController{},"post:CreateFood") // Add("/api/update",&RestController{},"put:UpdateFood") // Add("/api/delete",&RestController{},"delete:DeleteFood") // Add("/api",&RestController{},"get,post:ApiFunc") // Add("/simple",&SimpleController{},"get:GetFunc;post:PostFunc") func (p *ControllerRegistor) Add(pattern string, c ControllerInterface, mappingMethods ...string) { j, params, parts := p.splitRoute(pattern) reflectVal := reflect.ValueOf(c) t := reflect.Indirect(reflectVal).Type() methods := make(map[string]string) if len(mappingMethods) > 0 { semi := strings.Split(mappingMethods[0], ";") for _, v := range semi { colon := strings.Split(v, ":") if len(colon) != 2 { panic("method mapping format is invalid") } comma := strings.Split(colon[0], ",") for _, m := range comma { if m == "*" || utils.InSlice(strings.ToLower(m), HTTPMETHOD) { if val := reflectVal.MethodByName(colon[1]); val.IsValid() { methods[strings.ToLower(m)] = colon[1] } else { panic(colon[1] + " method doesn't exist in the controller " + t.Name()) } } else { panic(v + " is an invalid method mapping. Method doesn't exist " + m) } } } } if j == 0 { //now create the Route route := &controllerInfo{} route.pattern = pattern route.controllerType = t route.methods = methods route.routerType = routerTypeBeego if len(methods) > 0 { route.hasMethod = true } p.fixrouters = append(p.fixrouters, route) } else { // add regexp routers //recreate the url pattern, with parameters replaced //by regular expressions. then compile the regex pattern = strings.Join(parts, "/") regex, regexErr := regexp.Compile(pattern) if regexErr != nil { //TODO add error handling here to avoid panic panic(regexErr) } //now create the Route route := &controllerInfo{} route.regex = regex route.params = params route.pattern = pattern route.methods = methods route.routerType = routerTypeBeego if len(methods) > 0 { route.hasMethod = true } route.controllerType = t p.routers = append(p.routers, route) } }
func (t *Tree) addtree(segments []string, tree *Tree, wildcards []string, reg string) { if len(segments) == 0 { panic("prefix should has path") } seg := segments[0] iswild, params, regexpStr := splitSegment(seg) // if it's ? meaning can igone this, so add one more rule for it if len(params) > 0 && params[0] == ":" { params = params[1:] if len(segments[1:]) > 0 { t.addtree(segments[1:], tree, append(wildcards, params...), reg) } else { filterTreeWithPrefix(tree, wildcards, reg) } } //Rule: /login/*/access match /login/2009/11/access //if already has *, and when loop the access, should as a regexpStr if !iswild && utils.InSlice(":splat", wildcards) { iswild = true regexpStr = seg } //Rule: /user/:id/* if seg == "*" && len(wildcards) > 0 && reg == "" { regexpStr = "(.+)" } if len(segments) == 1 { if iswild { if regexpStr != "" { if reg == "" { rr := "" for _, w := range wildcards { if w == ":splat" { rr = rr + "(.+)/" } else { rr = rr + "([^/]+)/" } } regexpStr = rr + regexpStr } else { regexpStr = "/" + regexpStr } } else if reg != "" { if seg == "*.*" { regexpStr = "([^.]+).(.+)" } else { for _, w := range params { if w == "." || w == ":" { continue } regexpStr = "([^/]+)/" + regexpStr } } } reg = strings.Trim(reg+"/"+regexpStr, "/") filterTreeWithPrefix(tree, append(wildcards, params...), reg) t.wildcard = tree } else { reg = strings.Trim(reg+"/"+regexpStr, "/") filterTreeWithPrefix(tree, append(wildcards, params...), reg) tree.prefix = seg t.fixrouters = append(t.fixrouters, tree) } return } if iswild { if t.wildcard == nil { t.wildcard = NewTree() } if regexpStr != "" { if reg == "" { rr := "" for _, w := range wildcards { if w == ":splat" { rr = rr + "(.+)/" } else { rr = rr + "([^/]+)/" } } regexpStr = rr + regexpStr } else { regexpStr = "/" + regexpStr } } else if reg != "" { if seg == "*.*" { regexpStr = "([^.]+).(.+)" params = params[1:] } else { for range params { regexpStr = "([^/]+)/" + regexpStr } } } else { if seg == "*.*" { params = params[1:] } } reg = strings.TrimRight(strings.TrimRight(reg, "/")+"/"+regexpStr, "/") t.wildcard.addtree(segments[1:], tree, append(wildcards, params...), reg) } else { subTree := NewTree() subTree.prefix = seg t.fixrouters = append(t.fixrouters, subTree) subTree.addtree(segments[1:], tree, append(wildcards, params...), reg) } }
// "/" // "admin" -> func (t *Tree) addseg(segments []string, route interface{}, wildcards []string, reg string) { if len(segments) == 0 { if reg != "" { t.leaves = append(t.leaves, &leafInfo{runObject: route, wildcards: wildcards, regexps: regexp.MustCompile("^" + reg + "$")}) } else { t.leaves = append(t.leaves, &leafInfo{runObject: route, wildcards: wildcards}) } } else { seg := segments[0] iswild, params, regexpStr := splitSegment(seg) // if it's ? meaning can igone this, so add one more rule for it if len(params) > 0 && params[0] == ":" { t.addseg(segments[1:], route, wildcards, reg) params = params[1:] } //Rule: /login/*/access match /login/2009/11/access //if already has *, and when loop the access, should as a regexpStr if !iswild && utils.InSlice(":splat", wildcards) { iswild = true regexpStr = seg } //Rule: /user/:id/* if seg == "*" && len(wildcards) > 0 && reg == "" { regexpStr = "(.+)" } if iswild { if t.wildcard == nil { t.wildcard = NewTree() } if regexpStr != "" { if reg == "" { rr := "" for _, w := range wildcards { if w == ":splat" { rr = rr + "(.+)/" } else { rr = rr + "([^/]+)/" } } regexpStr = rr + regexpStr } else { regexpStr = "/" + regexpStr } } else if reg != "" { if seg == "*.*" { regexpStr = "/([^.]+).(.+)" params = params[1:] } else { for range params { regexpStr = "/([^/]+)" + regexpStr } } } else { if seg == "*.*" { params = params[1:] } } t.wildcard.addseg(segments[1:], route, append(wildcards, params...), reg+regexpStr) } else { var subTree *Tree for _, sub := range t.fixrouters { if sub.prefix == seg { subTree = sub break } } if subTree == nil { subTree = NewTree() subTree.prefix = seg t.fixrouters = append(t.fixrouters, subTree) } subTree.addseg(segments[1:], route, wildcards, reg) } } }
func (leaf *leafInfo) match(wildcardValues []string) (ok bool, params map[string]string) { if leaf.regexps == nil { // has error if len(wildcardValues) == 0 && len(leaf.wildcards) > 0 { if utils.InSlice(":", leaf.wildcards) { params = make(map[string]string) j := 0 for _, v := range leaf.wildcards { if v == ":" { continue } params[v] = "" j += 1 } return true, params } return false, nil } else if len(wildcardValues) == 0 { // static path return true, nil } // match * if len(leaf.wildcards) == 1 && leaf.wildcards[0] == ":splat" { params = make(map[string]string) params[":splat"] = path.Join(wildcardValues...) return true, params } // match *.* if len(leaf.wildcards) == 3 && leaf.wildcards[0] == "." { params = make(map[string]string) lastone := wildcardValues[len(wildcardValues)-1] strs := strings.SplitN(lastone, ".", 2) if len(strs) == 2 { params[":ext"] = strs[1] } else { params[":ext"] = "" } params[":path"] = path.Join(wildcardValues[:len(wildcardValues)-1]...) + "/" + strs[0] return true, params } // match :id params = make(map[string]string) j := 0 for _, v := range leaf.wildcards { if v == ":" { continue } if v == "." { lastone := wildcardValues[len(wildcardValues)-1] strs := strings.SplitN(lastone, ".", 2) if len(strs) == 2 { params[":ext"] = strs[1] } else { params[":ext"] = "" } if len(wildcardValues[j:]) == 1 { params[":path"] = strs[0] } else { params[":path"] = path.Join(wildcardValues[j:]...) + "/" + strs[0] } return true, params } if len(wildcardValues) <= j { return false, nil } params[v] = wildcardValues[j] j += 1 } if len(params) != len(wildcardValues) { return false, nil } return true, params } if !leaf.regexps.MatchString(path.Join(wildcardValues...)) { return false, nil } params = make(map[string]string) matches := leaf.regexps.FindStringSubmatch(path.Join(wildcardValues...)) for i, match := range matches[1:] { params[leaf.wildcards[i]] = match } return true, params }
// "/" // "admin" -> func (t *Tree) addseg(segments []string, route interface{}, wildcards []string, reg string) { if len(segments) == 0 { if reg != "" { filterCards := []string{} for _, v := range wildcards { if v == ":" || v == "." { continue } filterCards = append(filterCards, v) } t.leaves = append(t.leaves, &leafInfo{runObject: route, wildcards: filterCards, regexps: regexp.MustCompile("^" + reg + "$")}) } else { t.leaves = append(t.leaves, &leafInfo{runObject: route, wildcards: wildcards}) } } else { seg := segments[0] iswild, params, regexpStr := splitSegment(seg) //for the router /login/*/access match /login/2009/11/access if !iswild && utils.InSlice(":splat", wildcards) { iswild = true regexpStr = seg } if seg == "*" && len(wildcards) > 0 && reg == "" { iswild = true regexpStr = "(.+)" } if iswild { if t.wildcard == nil { t.wildcard = NewTree() } if regexpStr != "" { if reg == "" { rr := "" for _, w := range wildcards { if w == "." || w == ":" { continue } if w == ":splat" { rr = rr + "(.+)/" } else { rr = rr + "([^/]+)/" } } regexpStr = rr + regexpStr } else { regexpStr = "/" + regexpStr } } else if reg != "" { if seg == "*.*" { regexpStr = "/([^.]+).(.+)" } else { for _, w := range params { if w == "." || w == ":" { continue } regexpStr = "/([^/]+)" + regexpStr } } } t.wildcard.addseg(segments[1:], route, append(wildcards, params...), reg+regexpStr) } else { subTree, ok := t.fixrouters[seg] if !ok { subTree = NewTree() t.fixrouters[seg] = subTree } subTree.addseg(segments[1:], route, wildcards, reg) } } }
// AutoRoute func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { if _, ok := err.(middleware.HTTPException); ok { // catch intented errors, only for HTTP 4XX and 5XX } else { if RunMode == "dev" { if !RecoverPanic { panic(err) } else { var stack string Critical("Handler crashed with error", err) for i := 1; ; i++ { _, file, line, ok := runtime.Caller(i) if !ok { break } Critical(file, line) stack = stack + fmt.Sprintln(file, line) } middleware.ShowErr(err, rw, r, stack) } } else { if ErrorsShow { handler := p.getErrorHandler(fmt.Sprint(err)) handler(rw, r) } else { if !RecoverPanic { panic(err) } else { Critical("Handler crashed with error", err) for i := 1; ; i++ { _, file, line, ok := runtime.Caller(i) if !ok { break } Critical(file, line) } } } } } } }() starttime := time.Now() requestPath := r.URL.Path var runrouter *controllerInfo var findrouter bool params := make(map[string]string) w := &responseWriter{writer: rw} w.Header().Set("Server", BeegoServerName) context := &beecontext.Context{ ResponseWriter: w, Request: r, Input: beecontext.NewInput(r), Output: beecontext.NewOutput(w), } context.Output.Context = context context.Output.EnableGzip = EnableGzip do_filter := func(pos int) (started bool) { if p.enableFilter { if l, ok := p.filters[pos]; ok { for _, filterR := range l { if ok, p := filterR.ValidRouter(r.URL.Path); ok { context.Input.Params = p filterR.filterFunc(context) if w.started { return true } } } } } return false } if context.Input.IsWebsocket() { context.ResponseWriter = rw context.Output = beecontext.NewOutput(rw) } if !utils.InSlice(strings.ToLower(r.Method), HTTPMETHOD) { http.Error(w, "Method Not Allowed", 405) goto Admin } if do_filter(BeforeRouter) { goto Admin } //static file server for prefix, staticDir := range StaticDir { if r.URL.Path == "/favicon.ico" { file := staticDir + r.URL.Path http.ServeFile(w, r, file) w.started = true goto Admin } if strings.HasPrefix(r.URL.Path, prefix) { file := staticDir + r.URL.Path[len(prefix):] finfo, err := os.Stat(file) if err != nil { if RunMode == "dev" { Warn(err) } http.NotFound(w, r) goto Admin } //if the request is dir and DirectoryIndex is false then if finfo.IsDir() && !DirectoryIndex { middleware.Exception("403", rw, r, "403 Forbidden") goto Admin } //This block obtained from (https://github.com/smithfox/beego) - it should probably get merged into astaxie/beego after a pull request isStaticFileToCompress := false if StaticExtensionsToGzip != nil && len(StaticExtensionsToGzip) > 0 { for _, statExtension := range StaticExtensionsToGzip { if strings.HasSuffix(strings.ToLower(file), strings.ToLower(statExtension)) { isStaticFileToCompress = true break } } } if isStaticFileToCompress { if EnableGzip { w.contentEncoding = GetAcceptEncodingZip(r) } memzipfile, err := OpenMemZipFile(file, w.contentEncoding) if err != nil { return } w.InitHeadContent(finfo.Size()) if strings.HasSuffix(file, ".mustache") { w.Header().Set("Content-Type", "text/html; charset=utf-8") //FIXME: hardcode } http.ServeContent(w, r, file, finfo.ModTime(), memzipfile) } else { http.ServeFile(w, r, file) } w.started = true goto Admin } } // session init after static file if SessionOn { context.Input.CruSession = GlobalSessions.SessionStart(w, r) } if do_filter(AfterStatic) { goto Admin } if CopyRequestBody { context.Input.Body() } //first find path from the fixrouters to Improve Performance for _, route := range p.fixrouters { n := len(requestPath) if requestPath == route.pattern { runrouter = route findrouter = true break } // pattern /admin url /admin 200 /admin/ 404 // pattern /admin/ url /admin 301 /admin/ 200 if requestPath[n-1] != '/' && len(route.pattern) == n+1 && route.pattern[n] == '/' && route.pattern[:n] == requestPath { http.Redirect(w, r, requestPath+"/", 301) goto Admin } } //find regex's router if !findrouter { //find a matching Route for _, route := range p.routers { //check if Route pattern matches url if !route.regex.MatchString(requestPath) { continue } //get submatches (params) matches := route.regex.FindStringSubmatch(requestPath) //double check that the Route matches the URL pattern. if len(matches[0]) != len(requestPath) { continue } if len(route.params) > 0 { //add url parameters to the query param map values := r.URL.Query() for i, match := range matches[1:] { values.Add(route.params[i], match) params[route.params[i]] = match } //reassemble query params and add to RawQuery r.URL.RawQuery = url.Values(values).Encode() } runrouter = route findrouter = true break } } context.Input.Params = params if runrouter != nil { if r.Method == "POST" { r.ParseMultipartForm(MaxMemory) } //execute middleware filters if do_filter(BeforeExec) { goto Admin } //Invoke the request handler vc := reflect.New(runrouter.controllerType) //call the controller init function method := vc.MethodByName("Init") in := make([]reflect.Value, 3) in[0] = reflect.ValueOf(context) in[1] = reflect.ValueOf(runrouter.controllerType.Name()) in[2] = reflect.ValueOf(vc.Interface()) method.Call(in) //if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf if EnableXSRF { in = make([]reflect.Value, 0) method = vc.MethodByName("XsrfToken") method.Call(in) if r.Method == "POST" || r.Method == "DELETE" || r.Method == "PUT" || (r.Method == "POST" && (r.Form.Get("_method") == "delete" || r.Form.Get("_method") == "put")) { method = vc.MethodByName("CheckXsrfCookie") method.Call(in) } } //call prepare function in = make([]reflect.Value, 0) method = vc.MethodByName("Prepare") method.Call(in) //if response has written,yes don't run next if !w.started { if r.Method == "GET" { if runrouter.hasMethod { if m, ok := runrouter.methods["get"]; ok { method = vc.MethodByName(m) } else if m, ok = runrouter.methods["*"]; ok { method = vc.MethodByName(m) } else { method = vc.MethodByName("Get") } } else { method = vc.MethodByName("Get") } method.Call(in) } else if r.Method == "HEAD" { if runrouter.hasMethod { if m, ok := runrouter.methods["head"]; ok { method = vc.MethodByName(m) } else if m, ok = runrouter.methods["*"]; ok { method = vc.MethodByName(m) } else { method = vc.MethodByName("Head") } } else { method = vc.MethodByName("Head") } method.Call(in) } else if r.Method == "DELETE" || (r.Method == "POST" && r.Form.Get("_method") == "delete") { if runrouter.hasMethod { if m, ok := runrouter.methods["delete"]; ok { method = vc.MethodByName(m) } else if m, ok = runrouter.methods["*"]; ok { method = vc.MethodByName(m) } else { method = vc.MethodByName("Delete") } } else { method = vc.MethodByName("Delete") } method.Call(in) } else if r.Method == "PUT" || (r.Method == "POST" && r.Form.Get("_method") == "put") { if runrouter.hasMethod { if m, ok := runrouter.methods["put"]; ok { method = vc.MethodByName(m) } else if m, ok = runrouter.methods["*"]; ok { method = vc.MethodByName(m) } else { method = vc.MethodByName("Put") } } else { method = vc.MethodByName("Put") } method.Call(in) } else if r.Method == "POST" { if runrouter.hasMethod { if m, ok := runrouter.methods["post"]; ok { method = vc.MethodByName(m) } else if m, ok = runrouter.methods["*"]; ok { method = vc.MethodByName(m) } else { method = vc.MethodByName("Post") } } else { method = vc.MethodByName("Post") } method.Call(in) } else if r.Method == "PATCH" { if runrouter.hasMethod { if m, ok := runrouter.methods["patch"]; ok { method = vc.MethodByName(m) } else if m, ok = runrouter.methods["*"]; ok { method = vc.MethodByName(m) } else { method = vc.MethodByName("Patch") } } else { method = vc.MethodByName("Patch") } method.Call(in) } else if r.Method == "OPTIONS" { if runrouter.hasMethod { if m, ok := runrouter.methods["options"]; ok { method = vc.MethodByName(m) } else if m, ok = runrouter.methods["*"]; ok { method = vc.MethodByName(m) } else { method = vc.MethodByName("Options") } } else { method = vc.MethodByName("Options") } method.Call(in) } gotofunc := vc.Elem().FieldByName("gotofunc").String() if gotofunc != "" { method = vc.MethodByName(gotofunc) if method.IsValid() { method.Call(in) } else { panic("gotofunc is exists:" + gotofunc) } } if !w.started && !context.Input.IsWebsocket() { if AutoRender { method = vc.MethodByName("Render") callMethodWithError(method, in) } } } method = vc.MethodByName("Finish") method.Call(in) //execute middleware filters if do_filter(AfterExec) { goto Admin } method = vc.MethodByName("Destructor") method.Call(in) } //start autorouter if p.enableAuto { if !findrouter { lastindex := strings.LastIndex(requestPath, "/") lastsub := requestPath[lastindex+1:] if subindex := strings.LastIndex(lastsub, "."); subindex != -1 { context.Input.Params[":ext"] = lastsub[subindex+1:] r.URL.Query().Add(":ext", lastsub[subindex+1:]) r.URL.RawQuery = r.URL.Query().Encode() requestPath = requestPath[:len(requestPath)-len(lastsub[subindex:])] } for cName, methodmap := range p.autoRouter { if strings.ToLower(requestPath) == "/"+cName { http.Redirect(w, r, requestPath+"/", 301) goto Admin } if strings.ToLower(requestPath) == "/"+cName+"/" { requestPath = requestPath + "index" } if strings.HasPrefix(strings.ToLower(requestPath), "/"+cName+"/") { for mName, controllerType := range methodmap { if strings.ToLower(requestPath) == "/"+cName+"/"+strings.ToLower(mName) || (strings.HasPrefix(strings.ToLower(requestPath), "/"+cName+"/"+strings.ToLower(mName)) && requestPath[len("/"+cName+"/"+strings.ToLower(mName)):len("/"+cName+"/"+strings.ToLower(mName))+1] == "/") { if r.Method == "POST" { r.ParseMultipartForm(MaxMemory) } // set find findrouter = true //execute middleware filters if do_filter(BeforeExec) { goto Admin } //parse params otherurl := requestPath[len("/"+cName+"/"+strings.ToLower(mName)):] if len(otherurl) > 1 { plist := strings.Split(otherurl, "/") for k, v := range plist[1:] { params[strconv.Itoa(k)] = v } } //Invoke the request handler vc := reflect.New(controllerType) //call the controller init function init := vc.MethodByName("Init") in := make([]reflect.Value, 3) in[0] = reflect.ValueOf(context) in[1] = reflect.ValueOf(controllerType.Name()) in[2] = reflect.ValueOf(vc.Interface()) init.Call(in) //call prepare function in = make([]reflect.Value, 0) method := vc.MethodByName("Prepare") method.Call(in) method = vc.MethodByName(mName) method.Call(in) //if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf if EnableXSRF { method = vc.MethodByName("XsrfToken") method.Call(in) if r.Method == "POST" || r.Method == "DELETE" || r.Method == "PUT" || (r.Method == "POST" && (r.Form.Get("_method") == "delete" || r.Form.Get("_method") == "put")) { method = vc.MethodByName("CheckXsrfCookie") method.Call(in) } } if !w.started && !context.Input.IsWebsocket() { if AutoRender { method = vc.MethodByName("Render") callMethodWithError(method, in) } } method = vc.MethodByName("Finish") method.Call(in) //execute middleware filters if do_filter(AfterExec) { goto Admin } method = vc.MethodByName("Destructor") method.Call(in) goto Admin } } } } } } //if no matches to url, throw a not found exception if !findrouter { middleware.Exception("404", rw, r, "") } Admin: do_filter(FinishRouter) //admin module record QPS if EnableAdmin { timeend := time.Since(starttime) if FilterMonitorFunc(r.Method, requestPath, timeend) { if runrouter != nil { go toolbox.StatisticsMap.AddStatistics(r.Method, requestPath, runrouter.controllerType.Name(), timeend) } else { go toolbox.StatisticsMap.AddStatistics(r.Method, requestPath, "", timeend) } } } }