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

    先簡單回顧一下在上一篇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下包含了兩個屬性,RequestresponseWriter,但這裏兩個屬性是我本身創建,裏面封裝了go團隊提供的RequestresponseWriter,這樣子才方便擴展咱們想要的功能。restful

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

    源碼路徑:github.com/iamlufy/go-…     路漫漫其修遠兮,客官點個讚唄app

相關文章
相關標籤/搜索