• 【Go入门学习】golang自定义路由控制实现(二)-流式注册接口以及支持RESTFUL


        先简单回顾一下在上一篇的文章中,上一篇我主要是结合了数组和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*通过正则表达式匹配则可以匹配到相对应的接口函数。
        第二点,如何实现流式注册接口。

    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

    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
    		}
    	}
    }
    
    

    注册接口的代码如下

    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
        路漫漫其修远兮,客官点个赞呗

  • 相关阅读:
    [zz]redhat6.0无法识别ntfs分区的解决方法
    使用ftp搭建yum源问题解决
    [zz]搭建centos6.0本地yum源(32位)
    JAVA传统线程技术
    JAVA判断字符串是否为数字
    java之异常
    随便记两笔Java中的反射
    【转】单例模式完全解析
    java.lang.Enum
    文件搜索
  • 原文地址:https://www.cnblogs.com/xxzhuang/p/9056358.html
Copyright © 2020-2023  润新知