前言
也許beego框架在國內應該是衆多PHPer轉go的首選,因爲beego的MVC、ORM、完善的中文文檔讓PHPer們得心應手,毫無疑問我也是。這種感覺就像當年入門PHP時使用ThinkPHP一樣。
也許隨着你的認知的提升,你會討厭現在東西,比如某一天你可能慢慢的開始討厭beego,你會發現go語言裏包的真正意義,你開始反思MVC真的適合go嗎,或者你開始覺着ORM在靜態語言裏的雞肋,等等。我只想說:“也許你成長了~”。但是這些都不重要,每一個受歡迎的事物自然有我們值的學習的地方。今天這篇文章很簡單,像一篇筆記,記錄了我這幾天抽空讀beego源碼的記錄。
如何讀一個框架?
毫無疑問讀go的框架和PHP框架也是一樣的:
- 配置加載:如何加載配置文件的。
- 路由:分析框架如何通過URI執行對應業務的。
- ORM:ORM如何實現的。
這裏(1.)和(3.)無非就是加載個文件和sql解析器的實現,我就忽略了,重點就看看路由的實現。
安裝
簡單帶過:
// Step1: 安裝beego
go get github.com/astaxie/beego
// Step2: 安裝bee
go get github.com/beego/bee
// Step3: 用bee工具創建一個新的項目
bee new beego-code-read
代碼分析
go有自己實現的http包,大多go框架也是基於這個http包,所以看beego之前我們先補充或者複習下這個知識點。如下:
go如何啓動一個http server
package main
import (
// 導入net/http包
"net/http"
)
func main() {
// ------------------ 使用http包啓動一個http服務 方式一 ------------------
// *http.Request http請求內容實例的指針
// http.ResponseWriter 寫http響應內容的實例
http.HandleFunc("/v1/demo", func(w http.ResponseWriter, r *http.Request) {
// 寫入響應內容
w.Write([]byte("Hello TIGERB !\n"))
})
// 啓動一個http服務並監聽8888端口 這裏第二個參數可以指定handler
http.ListenAndServe(":8888", nil)
}
// 測試我們的服務
// --------------------
// 啓動:bee run
// 訪問: curl "http://127.0.0.1:8888/v1/demo"
// 響應結果:Hello TIGERB !
ListenAndServe是對http.Server的進一步封裝,除了上面的方式,還可以使用http.Server直接啓服務,這個需要設置Handler,這個Handler要實現Server.Handler這個接口。當請求來了會執行這個Handler的ServeHTTP
方法,如下:
package main
// 導入net/http包
import (
"net/http"
)
// DemoHandle server handle示例
type DemoHandle struct {
}
// ServeHTTP 匹配到路由後執行的方法
func (DemoHandle) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello TIGERB !\n"))
}
func main() {
// ------------------ 使用http包的Server啓動一個http服務 方式二 ------------------
// 初始化一個http.Server
server := &http.Server{}
// 初始化handler並賦值給server.Handler
server.Handler = DemoHandle{}
// 綁定地址
server.Addr = ":8888"
// 啓動一個http服務
server.ListenAndServe()
}
// 測試我們的服務
// --------------------
// 啓動:bee run
// 訪問: curl "http://127.0.0.1:8888/v1/demo"
// 響應結果:Hello TIGERB !
beego路由分析
接下里我們開始看beego的代碼。拿訪問"http://127.0.0.1:8080/"
來說,對於beego代碼來說有三個關鍵點,分別如下:
- 啓動:main.go ->
beego.Run()
- 註冊路由:routersrouter.go ->
beego.Router("/", &controllers.MainController{})
- 控制器:controllersdefault.go ->
Get()
下面來看3個關鍵點的詳細分析:
beego.Run()主要的工作
// github.com/astaxie/beego/beego.go
func Run(params ...string) {
// 啓動http服務之前的一些初始化 忽略 往下看
initBeforeHTTPRun()
// http服務的ip&port設置
if len(params) > 0 && params[0] != "" {
strs := strings.Split(params[0], ":")
if len(strs) > 0 && strs[0] != "" {
BConfig.Listen.HTTPAddr = strs[0]
}
if len(strs) > 1 && strs[1] != "" {
BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1])
}
}
// 又一個run 往下看
BeeApp.Run()
}
// github.com/astaxie/beego/app.go
func (app *App) Run(mws ...MiddleWare) {
// ... 省略
// 看了下這裏app.Server的類型就是*http.Server 也就是說用的原生http包 且是上面“go如何啓動一個http server”中的第二種方式
app.Server.Handler = app.Handlers
// ... 省略
if BConfig.Listen.EnableHTTP {
go func() {
app.Server.Addr = addr
logs.Info("http server Running on http://%s", app.Server.Addr)
// 默認配置false不強制tcp4
if BConfig.Listen.ListenTCP4 {
//...
// 忽略 默認false
} else {
// 關鍵點 ListenAndServe: app.Server的類型就是*http.Server 所以這裏就啓動了http服務
if err := app.Server.ListenAndServe(); err != nil {
logs.Critical("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
}
}()
}
// 阻塞到服務啓動
<-endRunning
}
// 看到這裏http已經啓動了 而且是註冊Handler的方式
接着去找這個Handler的ServeHTTP方法,通過上面的這段代碼app.Server.Handler = app.Handlers
,我們找到了下面的定義,Handler即是ControllerRegister
的值,所以每次親求來了就會去執行ControllerRegister.ServeHTTP(rw http.ResponseWriter, r *http.Request)
。
// src/github.com/astaxie/beego/app.go
func init() {
// 調用 創建beego框架實例的方法
BeeApp = NewApp()
}
// App結構體
type App struct {
// 關鍵的請求回調Handler
Handlers *ControllerRegister
// http包的服務
Server *http.Server
}
func NewApp() *App {
// 初始化http handler
cr := NewControllerRegister()
// 創建beego 實例
app := &App{Handlers: cr, Server: &http.Server{}}
return app
}
通過我們追beego.Run()
的代碼,目前我們得到的結論就是:
- 使用的http包啓動的服務
- 沒有使用
http.HandleFun()
的定義路由策略,而是註冊Handler的方式
所以beego就是通過beego.Router()
自己管理路由,如果http請求來了,回調ControllerRegister.ServeHTTP(rw http.ResponseWriter, r *http.Request)
方法,在ControllerRegister.ServeHTTP(rw http.ResponseWriter, r *http.Request)
方法中去匹配路由並執行對應的controller 也就是beegoControllerInterface
類型的控制器的方法,比如RESTFUL或者自定義啊等。
beego.Router() 如何註冊路由
首先路由文件是如何加載的,我們發現在main.go
文件裏導入了路由包:
package main
import (
// 導入routers包 只執行init方法
_ "beego-code-read/routers"
"github.com/astaxie/beego"
)
func main() {
beego.Run()
}
上面我們啓動了http服務,接着關鍵就是beego.Router()
如何註冊路由了。追了下代碼如下:
beego.Router()
-> BeeApp.Handlers.Add(rootpath, c, mappingMethods...)
-> ControllerRegister.addWithMethodParams(pattern, c, nil, mappingMethods...)
-> ControllerRegister.addToRouter(method, pattern string, r *ControllerInfo)
-> *Tree.AddRouter(pattern string, runObject interface{})
最後就是在*Tree.AddRouter()
完成了路由註冊,這裏的代碼邏輯暫時就先不看了,至此這個beego框架的流程就其本理順了,最後我們在回頭總結下整個流程如下圖:
備註:go導入包相當於入棧過程,先import後執行init