Dive Into beego/router 系列一

上一次分析了默认的http server以及router的工作方式。 本文开始解析beego的router过程。

具体参见:

从今天开始分析国人创建的一个非常有名的web框架——beego

从github主页的hello world开始,

package main

import "github.com/astaxie/beego"

func main(){
    beego.Run()
}

Build and run

go build hello.go
./hello

打开浏览器,访问http://localhost:8080,得到了

beego_router_1.jpg

从之前对于默认的http router可以推测到beego对于查找不到的路由handler也会进行默认的处理,与go默认的不同是,

默认的只是返回一个404错误和一个字符串,”404 page not found”。

// NotFound replies to the request with an HTTP 404 not found error.
func NotFound(w ResponseWriter, r *Request) { Error(w, "404 page not found", StatusNotFound) }

// NotFoundHandler returns a simple request handler
// that replies to each request with a ``404 page not found'' reply.
func NotFoundHandler() Handler { return HandlerFunc(NotFound) }

而beego会返回一个HTML页面。

具体的是如何实现的,我们后面分析代码的时候会追踪到。

首先分析beego.Run(),看看beego.Run()在做什么?

猜测: beego.Run()在底层至少包括下面两步: 1. 创建了默认的router,其中包括一个NotFoundHandler来处理404错误;从上一次分析http router可以得知, router肯定会满足http中的ServeHTTP interface,这样才能满足对应的Handler规范。 2. 启动HTTP service,监听socket地址,当接收到请求的时候,调用对应的handler进行处理;

下面分析源码:

// Run beego application.
// beego.Run() default run on HttpPort
// beego.Run("localhost")
// beego.Run(":8089")
// beego.Run("127.0.0.1:8089")
func Run(params ...string) {

	initBeforeHTTPRun()
    // ...
	BeeApp.Run()
}

主要包括两部分:

  1. initBeforeHTTPRun()

  2. BeeApp.Run()

先看第1部分,在运行http server之前做了一些行为的处理。深入进去看看都是做了什么?

func initBeforeHTTPRun() {
	//init hooks
	AddAPPStartHook(
		registerMime,
		registerDefaultErrorHandler,
		registerSession,
		registerTemplate,
		registerAdmin,
		registerGzip,
	)

	for _, hk := range hooks {
		if err := hk(); err != nil {
			panic(err)
		}
	}
}

做了一大堆的hook,这些hook是什么呢?从后面for循环中可以看出是一个接口规范,在启动前会注册一大堆的参数或者配置, 其中有一个registerDefaultErrorHandler,应该就是我们要找的404页面的Handler处理函数。

我们以这个处理函数的hook为例,看看都在http server运行前都在做什么?

// register default error http handlers, 404,401,403,500 and 503.
func registerDefaultErrorHandler() error {
	m := map[string]func(http.ResponseWriter, *http.Request){
		"401": unauthorized,
		"402": paymentRequired,
		"403": forbidden,
		"404": notFound,
		"405": methodNotAllowed,
		"500": internalServerError,
		"501": notImplemented,
		"502": badGateway,
		"503": serviceUnavailable,
		"504": gatewayTimeout,
		"417": invalidxsrf,
		"422": missingxsrf,
	}
	for e, h := range m {
		if _, ok := ErrorMaps[e]; !ok {
			ErrorHandler(e, h)
		}
	}
	return nil
}

为4xx、5xx做了预先定义了一大堆的http HandleFunc,我们还是看我们的404错误的处理函数:notFound,

// show 404 not found error.
func notFound(rw http.ResponseWriter, r *http.Request) {
	responseError(rw, r,
		404,
		"<br>The page you have requested has flown the coop."+
			"<br>Perhaps you are here because:"+
			"<br><br><ul>"+
			"<br>The page has moved"+
			"<br>The page no longer exists"+
			"<br>You were looking for your puppy and got lost"+
			"<br>You like 404 pages"+
			"</ul>",
	)
}

func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errContent string) {
	t, _ := template.New("beegoerrortemp").Parse(errtpl)
	data := M{
		"Title":        http.StatusText(errCode),
		"BeegoVersion": VERSION,
		"Content":      template.HTML(errContent),
	}
	t.Execute(rw, data)
}

首先按照HandleFunc interface的规范,定义了notFound的处理函数,这个函数使用template库渲染了html页面。

我们最开始打开的404页面就是由该处理函数进行渲染得到的。

registerDefaultErrorHandler中其他的错误处理函数也是类似,使用http template渲染了不同的html页面。

下面这段for循环代码,是刚才那一堆默认的error handler注册到ErrorMaps上面。如果用户定义了自己的错误处理函数,那么使用用户自己定义的,否则使用beego自带的。

	for e, h := range m {
		if _, ok := ErrorMaps[e]; !ok { // 如果用户没有定义,则使用beego自带的错误处理函数
			ErrorHandler(e, h)
		}
	}

ErrorMaps是什么呢?

// ErrorMaps holds map of http handlers for each error string.
// there is 10 kinds default error(40x and 50x)
var ErrorMaps = make(map[string]*errorInfo, 10)

// ErrorHandler registers http.HandlerFunc to each http err code string.
// usage:
// 	beego.ErrorHandler("404",NotFound)
//	beego.ErrorHandler("500",InternalServerError)
func ErrorHandler(code string, h http.HandlerFunc) *App {
	ErrorMaps[code] = &errorInfo{
		errorType: errorTypeHandler,
		handler:   h,
		method:    code,
	}
	return BeeApp
}

ErrorMaps是一个默认的查找错误的处理函数的表,这也是router mux的一部分,只是它专门用来查找错误的HandleFunc。

ErrorHandler函数在注册完后,返回一个BeeApp,其类型为*App,看起来BeeApp就是咱们要找的默认的http server以及handler集合了。

var (
	// BeeApp is an application instance
	BeeApp *App
)

// App defines beego application with a new PatternServeMux.
type App struct {
	Handlers *ControllerRegister
	Server   *http.Server
}

再分析其他的Hook都是干什么?大部分暂时跳过,我们主要是要找到运行的原理,暂时不纠结细节处理。

  • registerMime:注册一大堆的文件与文件在HTTP Header上Content-Type对应关系,比如".json"注册为"application/json"

  • registerDefaultErrorHandler:注册错误的处理函数

  • registerSession:跳过。

  • registerTemplate:跳过。

  • registerAdmin:跳过。

  • registerGzip:跳过。

接下来看看初始化完后,http service是如何启动并且找到对应的handler的?

重点看BeeApp,App struct中包括两个,

  1. Handlers *ControllerRegister

  2. Server *http.Server

BeeApp.Run()启动后,

// MiddleWare function for http.Handler
type MiddleWare func(http.Handler) http.Handler

// Run beego application.
func (app *App) Run(mws ...MiddleWare) {
	addr := BConfig.Listen.HTTPAddr

	if BConfig.Listen.HTTPPort != 0 {
		addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPAddr, BConfig.Listen.HTTPPort)
	}

	var (
		err        error
		l          net.Listener
		endRunning = make(chan bool, 1)
	)
    // ...
	if BConfig.Listen.EnableHTTP {
		go func() {
			app.Server.Addr = addr
			logs.Info("http server Running on http://%s", app.Server.Addr)
			if BConfig.Listen.ListenTCP4 {
				ln, err := net.Listen("tcp4", app.Server.Addr)
				// ...
				if err = app.Server.Serve(ln); err != nil {
					// ...
					endRunning <- true
					return
				}
			} // ...
		}()
	}
	<-endRunning
}

暂时跳过MiddleWare相关,后面专门分析MiddleWare相关知识点。

我们查看普通的http模式,进入到if BConfig.Listen.EnableHTTP分支,并且我们假设仅仅监听tcp ipv4,而不监听ipv6。

在这里面起了一个goroutine运行,然后在整个函数的最后使用endRunning的channel等待goroutine的工作完毕。

goroutine中做了什么事情?

  1. 使用net.Listen做了一个tcp v4的监听;

  2. 使用app中的Server对这个连接进行处理(Serve);Serve会从监听的listen接收到进来的请求,然后创建一个新的goroutine去执行。在goroutine中,服务器端会读取request的信息,并且找到对应的srv.Handler去处理请求。

TODO: 不清楚为什么在Run()里面需要起一个goroutine来接收/处理请求。 我的感觉是不需要用goroutine,也不需要endRunning channel来阻塞。在Serve(ln)中,已经包括一个for的死循环了,程序不会结束退出。

beego router handler

前一章分析了beego app是如何启动的,并且分析出来当我们使用默认的beego app启动以后,在没有添加任何的404 handler的情况下,beego是如何为我们添加一个默认的404错误处理页面。

这一章我们分析当我们添加对应的Handler时,http request进来的时候,是如何引导到我们对应的Handler中。

参考beego的文档:https://beego.me/docs/mvc/controller/router.md

我们创建一些路由,

package main

import (
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/context"
)

func main() {
	beego.Get("/", func(ctx *context.Context) {
		ctx.Output.Body([]byte("hello world"))
	})

	beego.Post("/alice", func(ctx *context.Context) {
		ctx.Output.Body([]byte("bob"))
	})

	beego.Any("/foo", func(ctx *context.Context) {
		ctx.Output.Body([]byte("bar"))
	})

	beego.Run()
}

猜测一下, 在之前默认的http router学习过程中,我们可以看到由于在启动ListenAndServe时,第二个参数为nil, 也即是Server中的Handler为nil,所以导致在最后查找可用的Handler的时候,用的是系统自带的Default Mux, 所以我们如果不想用系统默认的mux,那么就需要把Server中的Handler配置上对应的对象。 我们接下来分析看beego是如何做的。

我们先把Handler interface的定义放出来,

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

如果我们想要替换默认的mux的话,那么我们就需要定义一个东西,并且实现了ServeHTTP(ResponseWriter, *Request)方法。

App的定义是下列,

var (
	// BeeApp is an application instance
	BeeApp *App
)

func init() {
	// create beego application
	BeeApp = NewApp()
}

// App defines beego application with a new PatternServeMux.
type App struct {
	Handlers *ControllerRegister
	Server   *http.Server
}

// NewApp returns a new beego application.
func NewApp() *App {
	cr := NewControllerRegister()
	app := &App{Handlers: cr, Server: &http.Server{}}
	return app
}

我们可以看到App的定义,里面有个Handlers成员,可以猜到Handlers应该和Server *http.Server中的Handler可能有关系。

首先看Handlers,在NewApp()中,调用了NewControllerRegister()出来的。

Run(mws ...MiddleWare)中进行了赋值,app.Server.Handler = app.Handlers,把App中的*ControllerRegister赋值给了Server.Handler。

从这里也可以猜到*ControllerRegister类型实现了Handler的接口,即实现了ServeHTTP(ResponseWriter, *Request)函数。

追踪到NewControllerRegister()中,

分析NewControllerRegister()方法

// ControllerRegister containers registered router rules, controller handlers and filters.
type ControllerRegister struct {
	routers      map[string]*Tree
	enablePolicy bool
	policies     map[string]*Tree
	enableFilter bool
	filters      [FinishRouter + 1][]*FilterRouter
	pool         sync.Pool
}


// NewControllerRegister returns a new ControllerRegister.
func NewControllerRegister() *ControllerRegister {
	return &ControllerRegister{
		routers:  make(map[string]*Tree),
		policies: make(map[string]*Tree),
		pool: sync.Pool{
			New: func() interface{} {
				return beecontext.NewContext()
			},
		},
	}
}

我们先考虑普通的情况,再考虑通配符(例如:id)的情况。

NewControllerRegister()就是创建了ControllerRegister,包含了注册的路由规则、http处理函数、以及filters(这个是做什么的?后面分析,暂时跳过)。

其中如何搜索router handler是使用Tree来实现,这里与go http中默认的mux使用map搜索不同。

分析注册一个handler

	beego.Get("/", func(ctx *context.Context) {
		ctx.Output.Body([]byte("hello world"))
	})

当我们把一个handler注册给一个路径的时候,都做了什么操作?

需要注意的是这里的context是beego/context,与go中的不同。

// Get used to register router for Get method
// usage:
//    beego.Get("/", func(ctx *context.Context){
//          ctx.Output.Body("hello world")
//    })
func Get(rootpath string, f FilterFunc) *App {
	BeeApp.Handlers.Get(rootpath, f)
	return BeeApp
}

beego中自定义了http handler func类型,为FilterFunc,定义如下,

// FilterFunc defines a filter function which is invoked before the controller handler is executed.
type FilterFunc func(*context.Context)

我们调用beego.Get方法,也即是调用了BeeApp.Handlers.Get,

// Get add get method
// usage:
//    Get("/", func(ctx *context.Context){
//          ctx.Output.Body("hello world")
//    })
func (p *ControllerRegister) Get(pattern string, f FilterFunc) {
	p.AddMethod("get", pattern, f)
}

// AddMethod add http method router
// usage:
//    AddMethod("get","/api/:id", func(ctx *context.Context){
//          ctx.Output.Body("hello world")
//    })
func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) {
	method = strings.ToUpper(method)
	if method != "*" && !HTTPMETHOD[method] {
		panic("not support http method: " + method)
	}
	route := &ControllerInfo{}
	route.pattern = pattern
	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
	for k := range methods {
		if k == "*" {
			for m := range HTTPMETHOD {
				p.addToRouter(m, pattern, route)
			}
		} else {
			p.addToRouter(k, pattern, route)
		}
	}
}

当我们使用Get()方法,其实就是定义了处理”get method”行为的处理。

如果我们调用Any()方法,就是定义了处理所有method行为,包括:get、post、put、delete等等。

在AddMethod的方法中,做了下列操作,

  1. 新建一个route,类型为ControllerInfo,初始化该值

  2. 注册新建的route,p.addToRouter(k, pattern, route),在当前行为下,其实就是:p.addToRouter("get", "/", route)

  3. addToRouter中可以看到对于每一种method,都是各自的一个Tree。这个跟http default mux有区别,在default mux中,所有的方法都同时引导到同一个handler func中,在那里面对每一种method进行区分。

func (p *ControllerRegister) addToRouter(method, pattern string, r *ControllerInfo) {
	if !BConfig.RouterCaseSensitive {
		pattern = strings.ToLower(pattern)
	}
	if t, ok := p.routers[method]; ok {
		t.AddRouter(pattern, r)
	} else {
		t := NewTree()
		t.AddRouter(pattern, r)
		p.routers[method] = t
	}
}

// AddRouter call addseg function
func (t *Tree) AddRouter(pattern string, runObject interface{}) {
	t.addseg(splitPath(pattern), runObject, nil, "")
}

// "/" -> []
// "/admin" -> ["admin"]
// "/admin/" -> ["admin"]
// "/admin/users" -> ["admin", "users"]
func splitPath(key string) []string {
	key = strings.Trim(key, "/ ")
	if key == "" {
		return []string{}
	}
	return strings.Split(key, "/")
}

在splitPath中可以看到,在注册路径的时候,按照/进行了路径分割,然后保存到了一个[]stringslice中,注册的形式比较特殊,跟http default mux直接把pattern放进去不同,default mux搜索时做了最长路径匹配的搜索,所以在搜索的过程中做了很多处理用来实现最长路径匹配,这也是导致default mux搜索慢的原因,不知道beego的这种注册方式是不是在优化这个搜索过程,后面分析到搜索的时候,详细看看。

// "/"
// "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)
		}
	}
}

上面这段代码是如何注册router的详细过程。

  1. 从input分析

    1. segments: 为路径按照/ split分割后的字符串数组
    2. route: 为route,里面包含了http handle func
    3. wildcards: 通配符,nil。如果我们的节点是通配符的话,那么我们会保留我们所有的通配符定义,比如[“id” “name”] for the wildcard “:id” and “:name”。
    4. reg: 为空字符串,表示我们不是一个正则表达式
  2. 函数里面按照segments的不同,进行了不同的处理。在第一个注册函数中,我们只是对路径/进行了注册,所以在这里的segments的长度也就是0,并且我们的没有使用正则表达式。

我们在该函数里面仅仅是调用了一行语句:

   			t.leaves = append(t.leaves, &leafInfo{runObject: route, wildcards: wildcards})

我们在Tree的叶子结点上增加了一个叶子结点的信息:route,也就是把我们的http handle func的所有信息都传递进去,wildcards为nil,没有通配符。

当该http处理函数注册完成后,启动http server时,我们下面分析查找的过程。

beego router 搜索过程

调用BeeApp.Run(),其实在底层就是调用了http Serve(l),这其中的过程参考http router的解析,在这里就不再重复。

最后会调用到http/server.go中的下列方法,

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := sh.srv.Handler
    if handler == nil {
        handler = DefaultServeMux
    }
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }
    handler.ServeHTTP(rw, req)
}

在http router中解析过该函数,当时由于sh.srv.Handler为nil,所以使用的是默认的DefaultServeMux。

但是在当前beego中,Handler不再为nil,在之前分析过,当前的Handler为ControllerRegister类型,所以其实当运行的过程中,接收到一个请求后,会进入到ControllerRegister.ServeHTTP方法。

// Implement http.Handler interface.
func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	startTime := time.Now()
	var (
		runRouter    reflect.Type
		findRouter   bool
		runMethod    string
		methodParams []*param.MethodParam
		routerInfo   *ControllerInfo
		isRunnable   bool
	)
	context := p.pool.Get().(*beecontext.Context)
	context.Reset(rw, r)

	defer p.pool.Put(context)
	if BConfig.RecoverFunc != nil {
		defer BConfig.RecoverFunc(context)
	}

	context.Output.EnableGzip = BConfig.EnableGzip

	if BConfig.RunMode == DEV {
		context.Output.Header("Server", BConfig.ServerName)
	}

	var urlPath = r.URL.Path

	if !BConfig.RouterCaseSensitive {
		urlPath = strings.ToLower(urlPath)
	}

	// filter wrong http method
	if !HTTPMETHOD[r.Method] {
		http.Error(rw, "Method Not Allowed", 405)
		goto Admin
	}

	// filter for static file
	if len(p.filters[BeforeStatic]) > 0 && p.execFilter(context, urlPath, BeforeStatic) {
		goto Admin
	}

	serverStaticRouter(context)

	if context.ResponseWriter.Started {
		findRouter = true
		goto Admin
	}

	if r.Method != http.MethodGet && r.Method != http.MethodHead {
		// ...
	}

	// session init
	if BConfig.WebConfig.Session.SessionOn {
		// ...
	}
	if len(p.filters[BeforeRouter]) > 0 && p.execFilter(context, urlPath, BeforeRouter) {
		goto Admin
	}
	// User can define RunController and RunMethod in filter
	if context.Input.RunController != nil && context.Input.RunMethod != "" {
		findRouter = true
		runMethod = context.Input.RunMethod
		runRouter = context.Input.RunController
	} else {
		routerInfo, findRouter = p.FindRouter(context)
	}

	//if no matches to url, throw a not found exception
	if !findRouter {
		exception("404", context)
		goto Admin
	}
	if splat := context.Input.Param(":splat"); splat != "" {
		for k, v := range strings.Split(splat, "/") {
			context.Input.SetParam(strconv.Itoa(k), v)
		}
	}

	//execute middleware filters
	if len(p.filters[BeforeExec]) > 0 && p.execFilter(context, urlPath, BeforeExec) {
		goto Admin
	}

	//check policies
	if p.execPolicy(context, urlPath) {
		goto Admin
	}

	if routerInfo != nil {
		//store router pattern into context
		context.Input.SetData("RouterPattern", routerInfo.pattern)
		if routerInfo.routerType == routerTypeRESTFul {
			if _, ok := routerInfo.methods[r.Method]; ok {
				isRunnable = true
				routerInfo.runFunction(context)
			} else {
				exception("405", context)
				goto Admin
			}
		} else if routerInfo.routerType == routerTypeHandler {
			isRunnable = true
			routerInfo.handler.ServeHTTP(rw, r)
		} else {
			runRouter = routerInfo.controllerType
			methodParams = routerInfo.methodParams
			method := r.Method
			if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodPost {
				method = http.MethodPut
			}
			if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodDelete {
				method = http.MethodDelete
			}
			if m, ok := routerInfo.methods[method]; ok {
				runMethod = m
			} else if m, ok = routerInfo.methods["*"]; ok {
				runMethod = m
			} else {
				runMethod = method
			}
		}
	}

	// also defined runRouter & runMethod from filter
	if !isRunnable {
		// ...
	}

	//execute middleware filters
	if len(p.filters[AfterExec]) > 0 && p.execFilter(context, urlPath, AfterExec) {
		goto Admin
	}

	if len(p.filters[FinishRouter]) > 0 && p.execFilter(context, urlPath, FinishRouter) {
		goto Admin
	}

Admin:
	//admin module record QPS

	statusCode := context.ResponseWriter.Status
	if statusCode == 0 {
		statusCode = 200
	}

	logAccess(context, &startTime, statusCode)

	timeDur := time.Since(startTime)
	context.ResponseWriter.Elapsed = timeDur
	if BConfig.Listen.EnableAdmin {
		// ... 如果启动admin,跳过
	}

	if BConfig.RunMode == DEV && !BConfig.Log.AccessLogs {
		// ... 如果是开发模式,跳过
	}
	// Call WriteHeader if status code has been set changed
	if context.Output.Status != 0 {
		context.ResponseWriter.WriteHeader(context.Output.Status)
	}
}

// Reset init Context, BeegoInput and BeegoOutput
func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) {
    ctx.Request = r
    if ctx.ResponseWriter == nil {
        ctx.ResponseWriter = &Response{}
    }
    ctx.ResponseWriter.reset(rw)
    ctx.Input.Reset(ctx)
    ctx.Output.Reset(ctx)
    ctx._xsrfToken = ""
}
  1. context(beecontext.Context)使用了sync.Pool做cache优化。每次在调用Reset时,把请求的http.ResponseWriterhttp.Request传给context。
  2. 定义了RecoverFunc用来恢复用。这个还比较感兴趣,记下TODO以后分析。
  3. method的请求方法如果beego不支持,则会写405,Method Not Allowed错误,不过大部分http method都支持。
  4. 搜索对应的router,调用的是routerInfo, findRouter = p.FindRouter(context),后面详细分析。
  5. 我们当前的请求为:curl -X GET http://localhost:8080,我们是可以找到对应的router。所以我们进入if routerInfo != nil分支中分析。我们在注册的时候,使用的是route.routerType = routerTypeRESTFul,所以我们我们会得到两个部分:
    1. isRunnable = true
    2. routerInfo.runFunction(context):这里就是调用到了我们定义的HandleFunc了,我们只是简单的进行了简单的输出ctx.Output.Body([]byte("hello world"))
  6. 由于我们当前的isRunnable是true,所以if分支里面的一大坨代码暂时跳过。后面的代码也暂时跳过。分析之前省略的如何搜索到对应的router。

如何搜索找到对应的router

// FindRouter Find Router info for URL
func (p *ControllerRegister) FindRouter(context *beecontext.Context) (routerInfo *Controller
Info, isFind bool) {
    var urlPath = context.Input.URL()
    if !BConfig.RouterCaseSensitive {
        urlPath = strings.ToLower(urlPath)
    }
    httpMethod := context.Input.Method()
    if t, ok := p.routers[httpMethod]; ok {
        runObject := t.Match(urlPath, context)
        if r, ok := runObject.(*ControllerInfo); ok {
            return r, true
        }
    }
    return
}

由于我们把http request的信息都放到了beego.context中的Input了,所以我们从context中找到请求的所有信息。这里我们需要根据request的url和method进行router的搜索匹配。