首發於Golang中文網php
這東西是我最近開始寫的一個玩意兒...html
剛從PHP轉過來,對Go的特性還不是很瞭解,適用了一下gin,以爲雖然挺好的,可是一些語法沒有Laravel那麼方便git
因此想再造個輪子看看... en .... 就醬github
bingo是一個基於go語言的輕量級API框架,專一構建restfulAPIajax
GitHub地址:silsuer/bingo數據庫
最近我作了不少修改,gayhub上的跟這篇文章很不符,因此在這再把commit的連接貼出來點這裏這裏~json
go的net包極其好用,用它開發框架也是極其快速(竟然比寫php框架還要快...)數組
首先肯定main函數,在main函數中實例化一個結構體,而後調用其中的Run函數便可瀏覽器
動手操做:緩存
func main() {
// new 一個bingo對象,而後bingo.Run()便可
// 指定靜態目錄,默認指向index.html ,加載路由文件
// 加載env文件
bingo := new(core.Bingo)
bingo.Run(":12345")
}
複製代碼
接下來去寫bingo文件:
func (b *Bingo) Run(port string) {
// 傳入一個端口號,沒有返回值,根據端口號開啓http監聽
// 此處要進行資源初始化,加載全部路由、配置文件等等
// 實例化router類,這個類去獲取全部router目錄下的json文件,而後根據json中的配置,加載數據
// 實例化env文件和config文件夾下的全部數據,根據配置
// 根據路由列表,開始定義路由,而且根據端口號,開啓http服務器
http.ListenAndServe(port, bin)
// TODO 監聽平滑升級和重啓
}
複製代碼
Run
函數很是簡單,只有一行代碼,就是開啓一個Http服務器並監聽傳入的端口,
因爲咱們要本身控制各類路由,因此咱們不能用net包中自帶的http服務,網上有不少原理說的很清楚了
咱們須要本身實現 ServeHTTP
方法,以實現Mux這種路由器接口,因此再寫一個ServeHttp
方法
func (b *Bingo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
flag := false // 這個變量用來標記是否找到了動態路由
// 每個http請求都會走到這裏,而後在這裏,根據請求的URL,爲其分配所須要調用的方法
params := []reflect.Value{reflect.ValueOf(w), reflect.ValueOf(r)}
for _, v := range RoutesList {
// 檢測中間件,根據中間件首先開啓中間件,而後再註冊其餘路由
// 檢測路由,根據路由指向須要的數據
if r.URL.Path == v.path && r.Method == v.method {
flag = true // 尋找到了對應路由,無需使用靜態服務器
//TODO 調用一個公共中間件,在這個中間件中尋找路由以及調用中間件收尾等功能
// 檢測該路由中是否存在中間件,若是存在,順序調用
for _, m := range v.middleware {
if mid, ok := MiddlewareMap[m]; ok { // 判斷是否註冊了這個中間件
rmid := reflect.ValueOf(mid)
params = rmid.MethodByName("Handle").Call(params) // 執行中間件,返回values數組
// 判斷中間件執行結果,是否還要繼續往下走
str := rmid.Elem().FieldByName("ResString").String()
if str != "" {
status := rmid.Elem().FieldByName("Status").Int()
// 字符串不空,查看狀態碼,默認返回500錯誤
if status == 0 {
status = 500
}
w.WriteHeader(int(status))
fmt.Fprint(w,str)
return
}
}
}
// 檢測成功,開始調用方法
// 獲取一個控制器包下的結構體
if d, ok := ControllerMap[v.controller]; ok { // 存在 c爲結構體,調用c上掛載的方法
reflect.ValueOf(d).MethodByName(v.function).Call(params)
}
// 中止向後執行
return
}
}
// 若是路由列表中仍是沒有的話,去靜態服務器中尋找
if !flag {
// 去靜態目錄中尋找
http.ServeFile(w,r,GetPublicPath()+ r.URL.Path)
}
return
}
複製代碼
能夠看到,咱們使用從新定義了ServeHttp方法,在這個方法中,咱們根據瀏覽器訪問的不一樣URL,經過反射獲得不一樣的控制器或者中間件的結構體,而且
調用對應的方法,若是訪問的URL咱們沒有定義的話,會到靜態文件夾下去尋找,若是找到了,輸出靜態文件,不然輸出404頁面
(P.S. 由於咱們要實現的是一個無狀態的API快速開發框架,因此不須要進行模版渲染,全部數據均經過ajax傳輸到頁面中)
注意到在這個函數裏我使用了MiddlewareMap[m]
以及ControllerMap[m]
,這是中間件以及控制器的map,在程序初始化的時候就會存入內存中
具體定義以下:
// 這裏記錄全部的應該註冊的結構體
// 控制器map
var ControllerMap map[string]interface{}
// 中間件map
var MiddlewareMap map[string]interface{}
func init() {
ControllerMap = make(map[string]interface{})
MiddlewareMap = make(map[string]interface{})
// 給這兩個map賦初始值 每次添加完一條路由或中間件,都要在此處把路由或者中間件註冊到這裏
// 註冊中間件
MiddlewareMap["WebMiddleware"] =&middleware.WebMiddleware{}
// 註冊路由
ControllerMap["Controller"] = &controller.Controller{}
}
複製代碼
在此處咱們用到了app/controller以及middleware包下的結構體,當路由解析完成後會把請求的路徑和這裏的map對應起來,如今咱們看看router中解析路由代碼:
type route struct {
path string // 路徑
target string // 對應的控制器路徑 Controller@index 這樣的方法
method string // 訪問類型 是get post 或者其餘
alias string // 路由的別名
middleware []string // 中間件名稱
controller string // 控制器名稱
function string // 掛載到控制器上的方法名稱
}
type route_group struct {
root_path string // 路徑
root_target string // 對應的控制器路徑 Controller@index 這樣的方法
alias string // 路由的別名
middleware []string // 中間件名稱
routes []route // 包含的路由
}
var Routes []route // 單個的路由集合
var RoutesGroups []route_group // 路由組集合
var RoutesList []route // 所有路由列表
var R interface{}
func init() {
// 初始化方法,加載路由文件
// 獲取路由路徑,根據路由路徑獲取全部路由文件,而後讀取全部文件,賦值給當前成員變量
routes_path := GetRoutesPath()
dir_list, err := ioutil.ReadDir(routes_path)
Check(err)
// 根據dir list 遍歷全部文件 獲取全部json文件,拿到全部的路由 路由組
for _, v := range dir_list {
fmt.Println("正在加載路由文件........" + v.Name())
// 讀取文件內容,轉換成json,而且加入數組中
content, err := FileGetContents(routes_path + "/" + v.Name())
Check(err)
err = json.Unmarshal([]byte(content), &R)
Check(err)
// 開始解析R,將其分類放入全局變量中
parse(R)
}
}
複製代碼
在準備編譯的階段便會執行init函數,獲取到路由文件夾下的全部路由列表,咱們使用json格式來組織路由,解析出來的數據存入RoutesList列表中
下面是解析代碼
func parse(r interface{}) {
// 拿到了r 咱們要解析成實際的數據
m := r.(map[string]interface{})
//newRoute := route{}
for k, v := range m {
if k == "Routes" {
// 解析單個路由
parseRoutes(v)
}
if k == "RoutesGroups" {
// 解析路由組
parseRoutesGroups(v)
}
}
}
// 解析json文件中的單一路由的集合
func parseRoutes(r interface{}) {
m := r.([]interface{})
for _, v := range m {
// v 就是單個的路由了
simpleRoute := v.(map[string]interface{})
// 定義一個路由結構體
newRoute := route{}
for kk, vv := range simpleRoute {
switch kk {
case "Route":
newRoute.path = vv.(string)
break
case "Target":
newRoute.target = vv.(string)
break
case "Method":
newRoute.method = vv.(string)
break
case "Alias":
newRoute.alias = vv.(string)
break
case "Middleware":
//newRoute.middleware = vv.([])
var mdw []string
vvm := vv.([]interface{})
for _, vvv := range vvm {
mdw = append(mdw, vvv.(string))
}
newRoute.middleware = mdw
break
default:
break
}
}
// 把target拆分紅控制器和方法
cf := strings.Split(newRoute.target,"@")
if len(cf)==2 {
newRoute.controller = cf[0]
newRoute.function = cf[1]
}else{
fmt.Println("Target格式錯誤!"+newRoute.target)
return
}
// 把這個新的路由,放到單個路由切片中,也要放到路由列表中
Routes = append(Routes, newRoute)
RoutesList = append(RoutesList, newRoute)
}
}
func parseRoutesGroups(r interface{}) {
// 解析路由組
m := r.([]interface{})
for _, v := range m {
group := v.(map[string]interface{})
for kk, vv := range group {
// 新建一個路由組結構體
var newGroup route_group
switch kk {
case "RootRoute":
newGroup.root_path = vv.(string)
break
case "RootTarget":
newGroup.root_target = vv.(string)
break
case "Middleware":
var mdw []string
vvm := vv.([]interface{})
for _, vvv := range vvm {
mdw = append(mdw, vvv.(string))
}
newGroup.middleware = mdw
break
case "Routes":
// 因爲涉及到根路由之類的概念,因此不能使用上面的parseRoutes方法,須要再寫一個方法用來解析真實路由
rs := parseRootRoute(group)
newGroup.routes = rs
break
default:
break
}
// 把這個group放到路由組裏
RoutesGroups = append(RoutesGroups,newGroup)
}
}
}
// 解析根路由 傳入根路由路徑 目標跟路徑 而且傳入路由inteface列表,返回一個完整的路由集合
// 只傳入一個路由組,返回一個完整的路由集合
func parseRootRoute(group map[string]interface{}) []route {
// 獲取路由根路徑和目標根路徑,還有公共中間件
var tmpRoutes []route // 要返回的路由切片
var route_root_path string
var target_root_path string
var public_middleware []string
for k, v := range group {
if k == "RootRoute" {
route_root_path = v.(string)
}
if k == "RootTarget" {
target_root_path = v.(string)
}
if k=="Middleware" {
vvm := v.([]interface{})
for _, vvv := range vvm {
public_middleware = append(public_middleware, vvv.(string))
}
}
}
// 開始獲取路由
for k, s := range group {
if k == "Routes" {
m := s.([]interface{})
for _, v := range m {
// v 就是單個的路由了
simpleRoute := v.(map[string]interface{})
// 定義一個路由結構體
newRoute := route{}
for kk, vv := range simpleRoute {
switch kk {
case "Route":
newRoute.path = route_root_path+ vv.(string)
break
case "Target":
newRoute.target = target_root_path+ vv.(string)
break
case "Method":
newRoute.method = vv.(string)
break
case "Alias":
newRoute.alias = vv.(string)
break
case "Middleware":
vvm := vv.([]interface{})
for _, vvv := range vvm {
newRoute.middleware = append(public_middleware,vvv.(string))// 公共的和新加入的放在一塊兒就是總共的
}
break
default:
break
}
}
// 把target拆分紅控制器和方法
cf := strings.Split(newRoute.target,"@")
if len(cf)==2 {
newRoute.controller = cf[0]
newRoute.function = cf[1]
}else{
fmt.Println("Target格式錯誤!"+newRoute.target)
os.Exit(2)
}
// 把這個新的路由,放到路由列表中,而且返回放到路由集合中,做爲返回值返回
RoutesList = append(RoutesList, newRoute)
tmpRoutes = append(tmpRoutes,newRoute)
}
}
}
return tmpRoutes
}
複製代碼
經過解析json文件,得到路由列表,而後在上面的ServeHttp文件中便可與路由列表進行對比了。
到此,咱們實現了一個簡單的使用GO製做的Web框架
目前只能作到顯示靜態頁面以及進行API響應
接下來咱們要實現的是:
求star,歡迎PR~哈哈哈(silsuer/bingo)
這個東西好久以前寫的,如今框架作了不少更新,可是對於原生開發來講依舊有些參考價值吧,第一次玩掘金,看成第一篇發佈的內容吧,你們好,請多關照!