【Go入門學習】golang自定義路由控制實現(二)-流式註冊接口以及支持RESTFUL

    先簡單回顧一下在上一篇的文章中,上一篇我主要是結合了數組和Map完成路由映射,數組的大小爲8,下標爲0的表明Get方法,以此類推,而數組的值則是Map,鍵爲URL,值則是咱們編寫對應的接口。可是上篇的設計仍存在着不足,主要是沒法很好的面向RESTFUL設計,同時,我但願還可以但願一個功能,相似於SpringMVC中,能夠將@Controller做用於類上,表明着該類下全部接口的一個起始路徑。所以,本篇文章主要是講解如何實現以上提到的兩個功能。即面向RESTFUL以及流式註冊接口。下面先看效果代碼。git

o := odserver.Default()
    o.Start("/main").
    Target("/test/").Get(HelloServer).Post(HelloServer).Delete(HelloServer).And().
        Target("/test2").Get(HelloServer2)
    o.Start("/{test}/main/").Target("/number/{number}").
        Get(HelloServer3).Post(HelloServer4)

    http.ListenAndServe(":8080",o)
func HelloServer3(c *odserver.Context) {

    fmt.Fprint(c.Rw, c.Params)
}

    首先第一點的是,咱們要如何將客戶端訪問的URL,準確的映射到含有佔位符的接口。原理其實也不難,這裏也主要簡化了一下:即利用正則表達式,將接口路徑中的參數轉換成\w*。以/{test}/main/number/{number}爲例子,轉換結果爲/\w*/main/number/\w*經過正則表達式匹配則能夠匹配到相對應的接口函數。
    第二點,如何實現流式註冊接口。正則表達式

o.Start("/main").
Target("/test/").Get(HelloServer).Post(HelloServer).Delete(HelloServer).And().
Target("/test2").Get(HelloServer2)

    這裏的設計主要是考慮到RESTFUL的知識,即URL描述的是資源,而Http Method描述的纔是動做,因此大多數狀況下,按照RESTFUL的規範是會出現URL相同可是Http Method不一樣。所以,這裏的設計比起上一篇中的設計要作進一步重構:先匹配路徑,再匹配方法(上一篇的設計是先匹配方法,再匹配路徑)
    第一步咱們天然想到要設計一個map,鍵是URL,可是值該如何設計,而值的主要目標是匹配方法,以及擁有其餘屬性可以進行額外的功能開發,即下面的HandlerObject。個人設計以下,詳情看註釋。數組

type FuncObject struct {
    params []string
//對應編寫的接口,IHandlerFunc只是個空接口
    f      IHandlerFunc
    exist  bool
    *httpConfig
}
//方法函數映射,0表明GET方法下的接口
type methodFuncs []FuncObject
/**
    關鍵struct,表明每一個實體的請求
 */
type HandlerObject struct {
    *Router
    //對應占位符的參數
    params    []string
    //對該請求的http配置
    *httpConfig
    //請求路徑 即start+target的路徑
    path        string
    startPath   string
//方法函數映射
    methodFuncs methodFuncs
}

    上面HandlerObject出現了對Router的引用,Router至關於路由控制中心,他持有map[string]*HandlerObjectrestful

func NewRouter() *Router {
    return &Router{
        handler:   make(map[string]*HandlerObject),
        regexpMap: make(map[*regexp.Regexp]*HandlerObject),
    }
}

type Router struct {
    handler
    regexpMap
}

    這裏有個問題,regexpMap做用是什麼,相信仔細看的讀者心裏應該有答案了,沒錯,這裏對應的是匹配正則路徑的Map。可是還有一個問題是,我怎麼知道當前請求的路徑,是精準匹配仍是模糊匹配。這裏就要利用到Go中的協程和通道了,設置一個無緩衝的通道,對精準匹配和模糊匹配分別開啓一條協程,哪一個協程先匹配到,則往通道中傳送對應的值,這樣就能保證到不管是精準匹配和模糊匹配,咱們最終都會且僅獲取到一個值。同時對通道設置超時處理,如若超時,則認爲是404狀況。app

func (r *Router) doUrlMapping(url string, method int) (*HandlerObject,bool) {
    ch := make(chan *HandlerObject)
    //精準匹配
    go func() {
        if ho, ok := r.handler[url]; ok {
            ch <- ho
        }
    }()
    //正則匹配
    go func() {
        for k, v := range r.regexpMap {
            if k.MatchString(url) {
                pathArray := strings.Split(url, "/")[1:]
                regexpArray := strings.Split(k.String(), "/")[1:]
                if len(pathArray) == len(regexpArray) {
                  //設置參數
                    paramsNum := 0
                    for i := 0; i < len(pathArray); i++ {
                        if matcher.IsPattern(regexpArray[i]) {
                            v.params[paramsNum] = pathArray[i]
                            paramsNum++
                        }
                    }
                    v.params = v.params[:paramsNum]
                }
                ch <- v
            }
        }
    }()
    select {
    case ho := <-ch:
        {
            return ho,true
        }
    case <-time.After(2e6):
        {
            return &HandlerObject{},false
        }
    }
}

註冊接口的代碼以下函數

func (r *Router) Start(url string) *HandlerObject {
    return NewHandlerObject(r, AddSlash(url))
}

func (ho *HandlerObject) And() *HandlerObject {
    if ho.Router == nil || ho.startPath == "" {
        panic("ho.Router is nil or startPath is unknown,maybe u should use Start()")
    }
    return NewHandlerObject(ho.Router, ho.startPath)
}

func (ho *HandlerObject) Target(url string) *HandlerObject {
    //設置完整的路徑
    if ho.startPath == "/" {
        ho.path = ho.startPath + DeleteSlash(url)
    } else {
        if strings.HasSuffix(ho.startPath, "/") {
            url = DeleteSlash(url)
        } else {
            url = AddSlash(url)
        }
        ho.path = ho.startPath + url
    }
    //嘗試將url轉換成正則表達式,若是沒有佔位符,則轉換不成功
    pattern, ok := matcher.ToPattern(ho.path)
    if ok {
        ho.path = pattern
        re, err := regexp.Compile(pattern)
        if err != nil {
            panic("error compile pattern:" + pattern)
        }
        ho.Router.regexpMap[re] = ho
    } else {
        ho.handler[ho.path] = ho
    }
    return ho
}
func AddSlash(s string) string {
    if !strings.HasPrefix(s, "/") {
        s = "/" + s
    }
    return s
}

func DeleteSlash(s string) string {
    if strings.HasPrefix(s, "/") {
        array := strings.SplitN(s, "/", 2)
        s = array[1]
    }
    return s
}

func (ho *HandlerObject) Get(f IHandlerFunc) *HandlerObject {
    if ho.methodFuncs[GET].exist {
        panic("GetFunc has existed")
    }

    ho.methodFuncs[GET] = NewFuncObject(f)
    return ho
}

    最後還有一個struct須要介紹,即Context,在Tomcat的設計中,是不直接使用Java提供的request和response,這裏也參考來對應的設計,Context下包含了兩個屬性,RequestresponseWriter,但這裏兩個屬性是我本身創建,裏面封裝了go團隊提供的RequestresponseWriter,這樣子才方便擴展咱們想要的功能。性能

type Context struct {
    Req    Request
    Rw     responseWriter
    //對應restful的參數值
    Params []string
}

    源碼路徑:https://gitee.com/1995zzf/go-oneday
    路漫漫其修遠兮,客官點個讚唄url

相關文章
相關標籤/搜索