先简单回顾一下在上一篇的文章中,上一篇我主要是结合了数组和Map完成路由映射,数组的大小为8,下标为0的代表Get方法,以此类推,而数组的值则是Map,键为URL,值则是我们编写对应的接口。但是上篇的设计仍存在着不足,主要是无法很好的面向RESTFUL设计,同时,我希望还能够希望一个功能,类似于SpringMVC中,可以将@Controller作用于类上,代表着该类下所有接口的一个起始路径。因此,本篇文章主要是讲解如何实现以上提到的两个功能。即面向RESTFUL以及流式注册接口。下面先看效果代码。
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*,通过正则表达式匹配则可以匹配到相对应的接口函数。
第二点,如何实现流式注册接口。
这里的设计主要是考虑到RESTFUL的知识,即URL描述的是资源,而Http Method描述的才是动作,所以大多数情况下,按照RESTFUL的规范是会出现URL相同但是Http Method不同。因此,这里的设计比起上一篇中的设计要做进一步重构:先匹配路径,再匹配方法(上一篇的设计是先匹配方法,再匹配路径)
第一步我们自然想到要设计一个map,键是URL,但是值该如何设计,而值的主要目标是匹配方法,以及拥有其他属性能够进行额外的功能开发,即下面的HandlerObject。我的设计如下,详情看注释。
上面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, "http://www.likecs.com/")[1:] regexpArray := strings.Split(k.String(), "http://www.likecs.com/")[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 == "http://www.likecs.com/" { ho.path = ho.startPath + DeleteSlash(url) } else { if strings.HasSuffix(ho.startPath, "http://www.likecs.com/") { 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, "http://www.likecs.com/") { s = "http://www.likecs.com/" + s } return s } func DeleteSlash(s string) string { if strings.HasPrefix(s, "http://www.likecs.com/") { array := strings.SplitN(s, "http://www.likecs.com/", 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 }