HTTP HTTP request routing components corresponding function handed process( or a struct method ), as described in the previous section structure, in the frame corresponds to a routing event handler, and the event comprises:
- User requests a path(path)( e.g.:/user/123,/article/123), of course, the query string information(e.g., ? Id = 11)
- HTTP request method(method)(GET, POST, PUT, DELETE, PATCH, etc. )
The router is based on the user's request is forwarded to the respective event information processing function( control layer ).
Have been introduced in section 3.4 Go's http package Detailed, which introduced the Go's http package how to design and implement routing, here continue to be an example to illustrate:
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 above example calls the http default DefaultServeMux to add a route, you need to provide two parameters, the first parameter is the resource you want users to access the URL path( stored in r.URL.Path), the second argument is about to be executed function to provide the user access to resources. Routing focused mainly on two ideas:
- Add routing information
- According to the user request is forwarded to the function to be performed
Go add default route is through a function http.Handle
and http.HandleFunc
, etc. to add, the bottom is called DefaultServeMux.Handle(pattern string, handler Handler)
, this function will set the routing information is stored in a map information in map [string] muxEntry
, which would address the above said first point.
Go listening port, and then receives the tcp connection thrown Handler to process, the above example is the default nil http.DefaultServeMux
, DefaultServeMux.ServeHTTP
by the scheduling function, the previously stored map traverse route information, and the user URL accessed matching to check the corresponding registered handler, so to achieve the above mentioned second point.
for k, v := range mux.m {
if !pathMatch(k, path) {
continue
}
if h == nil || len(k) > n {
n = len(k)
h = v.h
}
}
Almost all Web applications are based routing to achieve http default router, but the router comes Go has several limitations:
- Does not support parameter setting, such as/user/: uid This pan type matching
- Not very good support for REST mode, you can not restrict access methods, such as the above example, user access/foo, you can use GET, POST, DELETE, HEAD, etc. Access
- General site routing rules too much, write cumbersome. I'm in front of an API to develop their own applications, routing rules have thirty several, in fact, this route after more than can be further simplified by a simplified method of the struct
beego framework routers based on the above few limitations to consider the design of a REST approach to achieve routing, routing design is based on two points above the default design Go to consider: store -and-forward routing 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)
}
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 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)
}
}
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{})
- Directory
- Previous: Poject Planning
- The nextsection: controller design