# 13.2 Customizing routers ## HTTP routing The HTTP routing component is responsible for mapping HTTP requests to a corresponding function or `struct` method. The router takes two key pieces of information from incoming requests: -The user requested path (for example, `/user/123,/article/123`), and any query strings or parameters that come with it (for example, `?id=11`) -The HTTP request method (GET, POST, PUT, and DELETE, PATCH, etc.) The router then forwards the request to the handler function (controller layer) that has been registered with that particular HTTP method and path. ## Default routing implementation In section 3.4, we introduced Go's `http` package in detail, which included how to design and implement routing. Here, we take another look at an example that illustrates the routing process: func fooHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) } http.Handle("/foo", fooHandler) http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) }) log.Fatal(http.ListenAndServe(":8080", nil)) The example above calls `http`'s default mux called `DefaultServeMux`, implicitly specified by the `nil` parameter in the call to `http.ListenAndServe`. The `http.Handle` function takes two parameters: the first parameter is the resource you want users to access, specified by its URL path (which is stored in `r.URL.Path`) and the second argument binds a handler function with this path. The Router has two main jobs: - To add and store routing information - To forward requests to a handler function for processing By default, Go routes are handled with `http.Handle` and `http.HandleFunc` types, registered by default through the underlying `DefaultServeMux.Handle(pattern string, handler Handler)` function. This function maps resource paths to handlers and stores them in a `map[string]muxEntry` map. This is the first job that we mentioned above. When the application is running, the Go server listens to a port. When it receives a tcp connection, it uses a `Handler` to process it. As aforementioned, since the `Handler` in the example above is `nil`, the default router `http.DefaultServeMux` is used. Using the map of previously stored routes, `DefaultServeMux.ServeHTTP` will dispatch the request to the first handler with a matching path. This is the router's second job. for k, v := range mux.m { if !pathMatch(k, path) { continue } if h == nil || len(k) > n { n = len(k) h = v.h } } ## Routing with Beego At present, most Go web applications base their routing on `http`'s default router, however this has several limitations: -Does not support dynamic routes with parameters, such as `the/user/:UID` -Does not have good support for REST. The access methods cannot be restricted; for instance in the above example, when users access `/foo`, they can use the GET, POST, DELETE, and HEAD HTTP methods, among others. - In large apps, routing rules can become repetitive and cumbersome. Personally, I've developed simple web APIs composed of nearly thirty routing rules when in fact, these rules could have been further simplified using method structs. The Beego framework's router is designed to overcome these limitations, taking the REST paradigm into consideration and simplifying the storing and forwarding of routes and requests. ### Storing a routing For the previously mentioned restriction point, we must first solve the arguments supporting the need to use regular, second and third point we passed a viable alternative to solve, REST method corresponds to a struct method to go, and then routed to the struct instead of a function, so that when the forward routing method can be performed according to different methods. Based on the above ideas, we designed two data types controllerInfo( save path and the corresponding struct, here is a reflect.Type type ) and ControllerRegistor(routers are used to save user to add a slice of routing information, and the application of the framework beego information ) type controllerInfo struct { regex *regexp.Regexp params map[int]string controllerType reflect.Type } type ControllerRegistor struct { routers []*controllerInfo Application *App } ControllerRegistor external interface function has func(p *ControllerRegistor) Add(pattern string, c ControllerInterface) Detailed implementation is as follows: func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) { 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] } params[j] = part parts[i] = expr j++ } } //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 t := reflect.Indirect(reflect.ValueOf(c)).Type() route := &controllerInfo{} route.regex = regex route.params = params route.controllerType = t p.routers = append(p.routers, route) } ### Static routing Above we achieve the realization of dynamic routing, Go the http package supported by default static file handler FileServer, because we have implemented a custom router, then the static files also need to set their own, beego static folder path stored in the global variable StaticDir, StaticDir is a map type to achieve the following: func (app *App) SetStaticPath(url string, path string) *App { StaticDir[url] = path return app } Applications can use the static route is set to achieve the following manner: beego.SetStaticPath("/img", "/static/img") ### Forwarding route Forwarding route in the routing is based ControllerRegistor forwarding information, detailed achieve the following code shows: // AutoRoute func (p *ControllerRegistor) ServeHTTP(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { if !RecoverPanic { // go back to panic panic(err) } else { Critical("Handler crashed with error", err) for i := 1; ; i += 1 { _, file, line, ok := runtime.Caller(i) if !ok { break } Critical(file, line) } } } }() var started bool for prefix, staticDir := range StaticDir { if strings.HasPrefix(r.URL.Path, prefix) { file := staticDir + r.URL.Path[len(prefix):] http.ServeFile(w, r, file) started = true return } } requestPath := r.URL.Path //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 } params := make(map[string]string) 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() + "&" + r.URL.RawQuery //r.URL.RawQuery = url.Values(values).Encode() } //Invoke the request handler vc := reflect.New(route.controllerType) init := vc.MethodByName("Init") in := make([]reflect.Value, 2) ct := &Context{ResponseWriter: w, Request: r, Params: params} in[0] = reflect.ValueOf(ct) in[1] = reflect.ValueOf(route.controllerType.Name()) init.Call(in) in = make([]reflect.Value, 0) method := vc.MethodByName("Prepare") method.Call(in) if r.Method == "GET" { method = vc.MethodByName("Get") method.Call(in) } else if r.Method == "POST" { method = vc.MethodByName("Post") method.Call(in) } else if r.Method == "HEAD" { method = vc.MethodByName("Head") method.Call(in) } else if r.Method == "DELETE" { method = vc.MethodByName("Delete") method.Call(in) } else if r.Method == "PUT" { method = vc.MethodByName("Put") method.Call(in) } else if r.Method == "PATCH" { method = vc.MethodByName("Patch") method.Call(in) } else if r.Method == "OPTIONS" { method = vc.MethodByName("Options") method.Call(in) } if AutoRender { method = vc.MethodByName("Render") method.Call(in) } method = vc.MethodByName("Finish") method.Call(in) started = true break } //if no matches to url, throw a not found exception if started == false { http.NotFound(w, r) } } ### Getting started After the design is based on the routing can solve the previously mentioned three restriction point, using a method as follows: The basic use of a registered route: beego.BeeApp.RegisterController("/", &controllers.MainController{}) Parameter registration: beego.BeeApp.RegisterController("/:param", &controllers.UserController{}) Are then matched: beego.BeeApp.RegisterController("/users/:uid([0-9]+)", &controllers.UserController{}) ## Links - [Directory](preface.md) - Previous section: [Project program](13.1.md) - Next section: [Design controllers](13.3.md)