先簡單回顧一下在上一篇的文章中,上一篇我主要是結合了數組和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]*HandlerObject
。restful
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下包含了兩個屬性,Request
和responseWriter
,但這裏兩個屬性是我本身創建,裏面封裝了go團隊提供的Request
和responseWriter
,這樣子才方便擴展咱們想要的功能。性能
type Context struct { Req Request Rw responseWriter //對應restful的參數值 Params []string }
源碼路徑:https://gitee.com/1995zzf/go-oneday
路漫漫其修遠兮,客官點個讚唄url