先簡單回顧一下在上一篇golang自定義路由控制實現(一)的文章中,上一篇我主要是結合了數組和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*
,經過正則表達式匹配則能夠匹配到相對應的接口函數。 第二點,如何實現流式註冊接口。github
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
。個人設計以下,詳情看註釋。golang
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
。正則表達式
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狀況。數組
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
}
}
}
複製代碼
註冊接口的代碼以下bash
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
,這樣子才方便擴展咱們想要的功能。restful
type Context struct {
Req Request
Rw responseWriter
//對應restful的參數值
Params []string
}
複製代碼
源碼路徑:github.com/iamlufy/go-… 路漫漫其修遠兮,客官點個讚唄app