beego 0.9.0 中智能路由AutoRouter的使用方法及源碼解讀

瞭解beego的開發者確定知道,beego的路由設計來源於sinatra,原來是不支持自動路由的,每個路由都要本身配置的,如:php

type MainController struct {
    beego.Controller
}
func (this *MainController) Get() {
    this.Ctx.WriteString("hello world")
}

func main() {
    beego.Router("/", &MainController{})
    beego.Run()
}

beego.Controller 提供全部的restful方法,Get,Post,Delete等方法,經過重寫這些方法,已響應客戶端不一樣的請求方式。java

用過Node.js的同窗,確定以爲很熟悉,拿最經常使用的express框架來講,路由的定義方式也大抵是這樣的,如:git

app.get('/', index);

var index = function(req,res){
    res.send("Hello,express");
};

有的同窗確定以爲爲何像php,java,asp這種服務器語言就不須要這樣定義路由,天然會根據請求url,判斷腳本path,進項執行並返回。github

其實這些也是須要的,只不過這個工做交給了服務器軟件來解決,如Apache,Nginx,IIS,Tomcat等。由這些軟件提供Http服務,腳本程序自己更專一於服務的邏輯。golang

而如Node.js,Golang這種語言,是由自生提供Http服務,並監聽某一端口。因此經過查看服務器響應Header能夠看出,此類語言的server顯示爲express,beegoserver,而大部分網站返回頭的server爲Nginx,Apache等。固然,Golang,Node.js也能夠經過反向代理功能(如Nginx),使真正與客戶端打交道的變爲這些反向代理軟件,但注意的是,這並不表明Node.js等的Http服務和路由調度不工做了,他們依然接受來自反向代理軟件的Http請求,並做出響應。express

好了,扯了這麼多,那0.9.0版本的beego提供的智能路由到底是怎樣呢?服務器

先看一段,示例代碼:restful

package main

import (  
    "github.com/astaxie/beego"
    "myapp/beego/controllers"
)
func main() {
    beego.AutoRouter(&controllers.UserController{})
    beego.AutoRouter(&controllers.PageController{})
    //........
    beego.Run()
}

控制器代碼以下:cookie

package controllers
import (
    "github.com/astaxie/beego"
)

type UserController struct {
    beego.Controller
}

type PageController struct {
beego.Controller 
} 
func (this *UserController) Add() {
    this.Ctx.WriteString("/user/add")
}
func (this *PageController) About() {
this.Ctx.WriteString("/page/about") 
}

有了這個AutoRouter,便不須要像之前那樣逐一註冊了,訪問/user/add 調用UserController的Add方法,訪問/page/about調用PageController的About方法。app

這裏須要稍微提醒兩點:

1.控制器struct極其下func都必須以大寫字母開頭,由於在Golang裏默認大寫開頭的爲public,小寫開頭的爲private,私有的內容沒法被包外訪問。

2.在使用了AutoRouter以後,原來的Router方法依然是有效的,能夠繼續使用。

好了,AutoRouter的使用就先介紹這裏,0.9.0版本的beego仍是更新和添加了很多功能的,在這裏感謝Astaxie爲golang項目所作的努力。

beego具體全面的使用,你們若是感興趣的話,我之後能夠抽個時間作個完成的介紹。

接下來咱們來具體看看AutoRouter是怎麼工做的,源碼走起

beego.AutoRouter()
func AutoRouter(c ControllerInterface) *App {
    BeeApp.AutoRouter(c)
    return BeeApp
}
此處調用App的AutoRouter方法,以下:
func (app *App) AutoRouter(c ControllerInterface) *App {
    app.Handlers.AddAuto(c)
    return app
}
看下App的struct
type App struct {
    Handlers *ControllerRegistor
}
可見app.Handlers就是ControllerRegistor,來看看ControllerRegistor的AddAuto方法
func (p *ControllerRegistor) AddAuto(c ControllerInterface) {
    p.enableAuto = true
    reflectVal := reflect.ValueOf(c)
    rt := reflectVal.Type()
    ct := reflect.Indirect(reflectVal).Type()
    firstParam := 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++ {
        p.autoRouter[firstParam][rt.Method(i).Name] = ct
    }
}
這個能夠說就是智能路由的關鍵了,它充分利用率Golang的反射(reflect)機制。
看看這個方法都爲ControllerRegistor作了什麼呢?先來看看ControllerRegistor的struct
type ControllerRegistor struct {
    routers      []*controllerInfo
    fixrouters   []*controllerInfo
    enableFilter bool
    filters      []http.HandlerFunc
    enableAfter  bool
    afterFilters []http.HandlerFunc
    enableUser   bool
    userHandlers map[string]*userHandler
    enableAuto   bool
    autoRouter   map[string]map[string]reflect.Type //key:controller key:method value:reflect.type
}
AddAuto方法首先將ControllerRegistor的enableAuto設置爲true(具體做用稍後介紹)
而後對傳入的控制器作反射,rt := reflectVal.Type() 獲取傳入控制器的Type
firstParam := strings.ToLower(strings.TrimSuffix(ct.Name(), " Controller " )) 能夠看出自動路由定義時,命名必須爲XxxxController格式,不然是沒法解析映射到路由上的。
if _, ok := p.autoRouter[firstParam]; ok { return } else { p.autoRouter[firstParam] = make(map[ string ]reflect.Type) }
這裏若是autoRouter這個map裏已經有firstParam這個鍵的時候,就不在映射,因此後定義的同名router是沒法覆蓋前面已經定義了的。
這裏autoRouter是一個二維map,傳入一個UserController經過
for i := 0; i < rt.NumMethod(); i++ { p.autoRouter[firstParam][rt.Method(i).Name] = ct }
處理以後,autoRouter會新增autoRouter[「user」][「func1」],autoRouter[「user」][「func2」]….其中func1,func2即爲UserController的所有方法。
經過AddAuto方法咱們獲得了包含全部AutoRouter的一個map,即autoRouter,那麼怎麼樣將這個map註冊到路由裏呢?
繼續八源碼,在router.go文件中,爲ControllerRegistor定義了一個ServeHttp的方法,這個方法比較長,愚安摘取相關的代碼貼出來:
if p.enableAuto {
        if !findrouter {
            for cName, methodmap := range p.autoRouter {

                if strings.ToLower(requestPath) == "/"+cName {
                    http.Redirect(w, r, requestPath+"/", 301)
                    return
                }

                if strings.ToLower(requestPath) == "/"+cName+"/" {
                    requestPath = requestPath + "index"
                }
                if strings.HasPrefix(strings.ToLower(requestPath), "/"+cName+"/") {
                    for mName, controllerType := range methodmap {
                        if strings.HasPrefix(strings.ToLower(requestPath), "/"+cName+"/"+strings.ToLower(mName)) {
                            //execute middleware filters
                            if p.enableFilter {
                                for _, filter := range p.filters {
                                    filter(w, r)
                                    if w.started {
                                        return
                                    }
                                }
                            }
                            //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, 2)
                            ct := &Context{ResponseWriter: w, Request: r, Params: params, RequestBody: requestbody}

                            in[0] = reflect.ValueOf(ct)
                            in[1] = reflect.ValueOf(controllerType.Name())
                            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 {
                                if AutoRender {
                                    method = vc.MethodByName("Render")
                                    method.Call(in)
                                }
                            }
                            method = vc.MethodByName("Finish")
                            method.Call(in)
                            //execute middleware filters
                            if p.enableAfter {
                                for _, filter := range p.afterFilters {
                                    filter(w, r)
                                    if w.started {
                                        return
                                    }
                                }
                            }
                            method = vc.MethodByName("Destructor")
                            method.Call(in)
                            // set find
                            findrouter = true
                        }
                    }
                }
            }
        }
    }

這裏能夠看到最早有一個判斷if !findrouter 即若是沒有找到路由匹配,纔會進行智能路由匹配,因此Router的優先級是比AutoRouter要高的。

在這裏再次用到了reflect,這裏 ct := &Context{ResponseWriter: w, Request: r, Params: params, RequestBody: requestbody} 即獲取http請求上下文,經過method.Call(in),

將http請求參數傳給Controller內的相對應的方法。

不難看出,作了多步處理,有點相似PHP的鉤子(hooks),依次通過控制器init方法->Prepare->XsrfToken->Render->Finish->Destructor等處理。

在最後set findrouter 爲true,若是在這裏仍沒有匹配到router,接下來就404了

if !findrouter {
        if h, ok := ErrorMaps["404"]; ok {
            w.status = 404
            h(w, r)
        } else {
            http.NotFound(w, r)
        }
    }

因此beego的設計仍是比較嚴謹且有效率的,在這裏在此表明廣大Golang初學者感謝謝大。

額,第一次寫Golang的文章,感受力不從心,說了一堆廢話,忘園友們見諒!

相關文章
相關標籤/搜索