在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ GO/ 13.2 自定義路由器設(shè)計
7 文本處理
3 Web基礎(chǔ)
14 擴展Web框架
10.4 小結(jié)
2.2 Go基礎(chǔ)
2.8 總結(jié)
6.1 session和cookie
5.5 使用beedb庫進行ORM開發(fā)
8.3 REST
13.6 小結(jié)
5.4 使用PostgreSQL數(shù)據(jù)庫
14.6 pprof支持
14.1 靜態(tài)文件支持
11.2 使用GDB調(diào)試
7.7 小結(jié)
1 GO環(huán)境配置
14.5 多語言支持
7.1 XML處理
1.5 總結(jié)
13 如何設(shè)計一個Web框架
14.3 表單及驗證支持
12 部署與維護
10 國際化和本地化
1.1 Go 安裝
6.2 Go如何使用session
5.6 NOSQL數(shù)據(jù)庫操作
6.5 小結(jié)
9.4 避免SQL注入
12.1 應(yīng)用日志
4.2 驗證表單的輸入
10.1 設(shè)置默認地區(qū)
1.3 Go 命令
9.6 加密和解密數(shù)據(jù)
4.1 處理表單的輸入
4.4 防止多次遞交表單
11.3 Go怎么寫測試用例
8 Web服務(wù)
12.3 應(yīng)用部署
5.7 小結(jié)
12.5 小結(jié)
11 錯誤處理,調(diào)試和測試
9.2 確保輸入過濾
14.2 Session支持
6.4 預(yù)防session劫持
12.4 備份和恢復(fù)
8.1 Socket編程
13.1 項目規(guī)劃
13.4 日志和配置設(shè)計
7.6 字符串處理
13.2 自定義路由器設(shè)計
6.3 session存儲
3.4 Go的http包詳解
8.2 WebSocket
10.3 國際化站點
7.5 文件操作
7.4 模板處理
9.1 預(yù)防CSRF攻擊
13.3 controller設(shè)計
2.6 interface
14.4 用戶認證
2.3 流程和函數(shù)
附錄A 參考資料
11.1 錯誤處理
9.5 存儲密碼
9.3 避免XSS攻擊
12.2 網(wǎng)站錯誤處理
6 session和數(shù)據(jù)存儲
2.4 struct類型
3.3 Go如何使得Web工作
2.5 面向?qū)ο?/span>
3.1 Web工作方式
1.2 GOPATH與工作空間
2.1 你好,Go
9.7 小結(jié)
13.5 實現(xiàn)博客的增刪改
7.2 JSON處理
10.2 本地化資源
7.3 正則處理
2 Go語言基礎(chǔ)
5.1 database/sql接口
4.5 處理文件上傳
8.5 小結(jié)
4.3 預(yù)防跨站腳本
5.3 使用SQLite數(shù)據(jù)庫
14.7 小結(jié)
3.2 Go搭建一個Web服務(wù)器
2.7 并發(fā)
5 訪問數(shù)據(jù)庫
4 表單
3.5 小結(jié)
1.4 Go開發(fā)工具
11.4 小結(jié)
9 安全與加密
5.2 使用MySQL數(shù)據(jù)庫
4.6 小結(jié)
8.4 RPC

13.2 自定義路由器設(shè)計

HTTP路由

HTTP路由組件負責(zé)將HTTP請求交到對應(yīng)的函數(shù)處理(或者是一個struct的方法),如前面小節(jié)所描述的結(jié)構(gòu)圖,路由在框架中相當于一個事件處理器,而這個事件包括:

  • 用戶請求的路徑(path)(例如:/user/123,/article/123),當然還有查詢串信息(例如?id=11)
  • HTTP的請求方法(method)(GET、POST、PUT、DELETE、PATCH等)

路由器就是根據(jù)用戶請求的事件信息轉(zhuǎn)發(fā)到相應(yīng)的處理函數(shù)(控制層)。

默認的路由實現(xiàn)

在3.4小節(jié)有過介紹Go的http包的詳解,里面介紹了Go的http包如何設(shè)計和實現(xiàn)路由,這里繼續(xù)以一個例子來說明:

func fooHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
}

http.HandleFunc("/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))

上面的例子調(diào)用了http默認的DefaultServeMux來添加路由,需要提供兩個參數(shù),第一個參數(shù)是希望用戶訪問此資源的URL路徑(保存在r.URL.Path),第二參數(shù)是即將要執(zhí)行的函數(shù),以提供用戶訪問的資源。路由的思路主要集中在兩點:

  • 添加路由信息
  • 根據(jù)用戶請求轉(zhuǎn)發(fā)到要執(zhí)行的函數(shù)

Go默認的路由添加是通過函數(shù)http.Handlehttp.HandleFunc等來添加,底層都是調(diào)用了DefaultServeMux.Handle(pattern string, handler Handler),這個函數(shù)會把路由信息存儲在一個map信息中map[string]muxEntry,這就解決了上面說的第一點。

Go監(jiān)聽端口,然后接收到tcp連接會扔給Handler來處理,上面的例子默認nil即為http.DefaultServeMux,通過DefaultServeMux.ServeHTTP函數(shù)來進行調(diào)度,遍歷之前存儲的map路由信息,和用戶訪問的URL進行匹配,以查詢對應(yīng)注冊的處理函數(shù),這樣就實現(xiàn)了上面所說的第二點。

for k, v := range mux.m {
    if !pathMatch(k, path) {
        continue
    }
    if h == nil || len(k) > n {
        n = len(k)
        h = v.h
    }
}

beego框架路由實現(xiàn)

目前幾乎所有的Web應(yīng)用路由實現(xiàn)都是基于http默認的路由器,但是Go自帶的路由器有幾個限制:

  • 不支持參數(shù)設(shè)定,例如/user/:uid 這種泛類型匹配
  • 無法很好的支持REST模式,無法限制訪問的方法,例如上面的例子中,用戶訪問/foo,可以用GET、POST、DELETE、HEAD等方式訪問
  • 一般網(wǎng)站的路由規(guī)則太多了,編寫繁瑣。我前面自己開發(fā)了一個API應(yīng)用,路由規(guī)則有三十幾條,這種路由多了之后其實可以進一步簡化,通過struct的方法進行一種簡化

beego框架的路由器基于上面的幾點限制考慮設(shè)計了一種REST方式的路由實現(xiàn),路由設(shè)計也是基于上面Go默認設(shè)計的兩點來考慮:存儲路由和轉(zhuǎn)發(fā)路由

存儲路由

針對前面所說的限制點,我們首先要解決參數(shù)支持就需要用到正則,第二和第三點我們通過一種變通的方法來解決,REST的方法對應(yīng)到struct的方法中去,然后路由到struct而不是函數(shù),這樣在轉(zhuǎn)發(fā)路由的時候就可以根據(jù)method來執(zhí)行不同的方法。

根據(jù)上面的思路,我們設(shè)計了兩個數(shù)據(jù)類型controllerInfo(保存路徑和對應(yīng)的struct,這里是一個reflect.Type類型)和ControllerRegistor(routers是一個slice用來保存用戶添加的路由信息,以及beego框架的應(yīng)用信息)

type controllerInfo struct {
    regex          *regexp.Regexp
    params         map[int]string
    controllerType reflect.Type
}

type ControllerRegistor struct {
    routers     []*controllerInfo
    Application *App
}

ControllerRegistor對外的接口函數(shù)有

func (p *ControllerRegistor) Add(pattern string, c ControllerInterface)

詳細的實現(xiàn)如下所示:

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)

}

靜態(tài)路由實現(xiàn)

上面我們實現(xiàn)的動態(tài)路由的實現(xiàn),Go的http包默認支持靜態(tài)文件處理FileServer,由于我們實現(xiàn)了自定義的路由器,那么靜態(tài)文件也需要自己設(shè)定,beego的靜態(tài)文件夾路徑保存在全局變量StaticDir中,StaticDir是一個map類型,實現(xiàn)如下:

func (app *App) SetStaticPath(url string, path string) *App {
    StaticDir[url] = path
    return app
}

應(yīng)用中設(shè)置靜態(tài)路徑可以使用如下方式實現(xiàn):

beego.SetStaticPath("/img","/static/img")

轉(zhuǎn)發(fā)路由

轉(zhuǎn)發(fā)路由是基于ControllerRegistor里的路由信息來進行轉(zhuǎn)發(fā)的,詳細的實現(xiàn)如下代碼所示:

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

使用入門

基于這樣的路由設(shè)計之后就可以解決前面所說的三個限制點,使用的方式如下所示:

基本的使用注冊路由:

beego.BeeApp.RegisterController("/", &controllers.MainController{})

參數(shù)注冊:

beego.BeeApp.RegisterController("/:param", &controllers.UserController{})

正則匹配:

beego.BeeApp.RegisterController("/users/:uid([0-9]+)", &controllers.UserController{})