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下包含了两个属性,Request和responseWriter,但这里两个属性是我自己建立,里面封装了go团队提供的Request和responseWriter,这样子才方便扩展我们想要的功能。
type Context struct {
Req Request
Rw responseWriter
//对应restful的参数值
Params []string
}
源码路径:https://gitee.com/1995zzf/go-oneday
路漫漫其修远兮,客官点个赞呗
golang自定义路由控制实现(二)-流式注册接口以及支持RESTFUL的更多相关文章
- golang自定义路由控制实现(一)
由于本人之前一直是Java Coder,在Java web开发中其实大家都很依赖框架,所以当在学习Golang的时候,自己便想着在Go开发中脱离框架,自己动手造框架来练习.通过学习借鉴Java ...
- JDK8新特性(二) 流式编程Stream
流式编程是1.8中的新特性,基于常用的四种函数式接口以及Lambda表达式对集合类数据进行类似流水线一般的操作 流式编程分为大概三个步骤:获取流 → 操作流 → 返回操作结果 流的获取方式 这里先了解 ...
- WPF自定义路由事件(二)
WPF中的路由事件 as U know,和以前Windows消息事件区别不再多讲,这篇博文中,将首先回顾下WPF内置的路由事件的用法,然后在此基础上自定义一个路由事件. 1.WPF内置路由事件 WPF ...
- web项目自定义路由_实现静态资源URL控制
前言: IIS会默认把:图片.JS.HTML.CSS这些文件当成静态资源处理,为了减少服务器压力,默认这些静态资源是不走URL路由规则控制的. 作为小白及初学者,本人对这些了解甚少,补充基础知识吧: ...
- 实现 MyBatis 流式查询的方法
基本概念流式查询指的是查询成功后不是返回一个集合而是返回一个迭代器,应用每次从迭代器取一条查询结果.流式查询的好处是能够降低内存使用.如果没有流式查询,我们想要从数据库取 1000 万条记录而又没有足 ...
- MyBatis 如何实现流式查询
基本概念 流式查询指的是查询成功后不是返回一个集合而是返回一个迭代器,应用每次从迭代器取一条查询结果.流式查询的好处是能够降低内存使用. 如果没有流式查询,我们想要从数据库取 1000 万条记录而又没 ...
- MyBatis 流式查询
流式查询指的是查询成功后不是返回一个集合而是返回一个迭代器,应用每次从迭代器取一条查询结果.流式查询的好处是能够降低内存使用. 流式查询的过程当中,数据库连接是保持打开状态的,因此要注意的是:执行一个 ...
- 基于zuul实现自定义路由源码分析
ZuulFilter定义 通过继承ZuulFilter我们可以定义一个新的过滤器,如下 public class IpAddressFilter extends ZuulFilter { @Autow ...
- BootStrap入门教程 (一) :手脚架Scaffolding(全局样式(Global Style),格网系统(Grid System),流式格网(Fluid grid System),自定义(Customing),布局(Layouts))
2011年,twitter的“一小撮”工程师为了提高他们内部的分析和管理能力,用业余时间为他们的产品构建了一套易用.优雅.灵活.可扩展的前端工具集--BootStrap.Bootstrap由MARK ...
随机推荐
- iOS监听模式系列之键值编码KVC、键值监听KVO的简单介绍和应用
键值编码KVC 我们知道在C#中可以通过反射读写一个对象的属性,有时候这种方式特别方便,因为你可以利用字符串的方式去动态控制一个对象.其实由于ObjC的语言特性,你根部不必进行任何操作就可以进行属性的 ...
- ActiveMQ 入门
1.下载ActiveMQ 去官方网站下载:http://activemq.apache.org/ 2.运行ActiveMQ 解压缩apache-activemq-5.5.1-bin.zip,然后双击a ...
- Mina源码阅读笔记(一)-整体解读
今天的这一节,将从整体上对mina的源代码进行把握,网上已经有好多关于mina源码的阅读笔记,但好多都是列举了一下每个接口或者类的方法.我倒是想从mina源码的结构和功能上对这个框架进行剖析.源码的阅 ...
- JAVA线程与线程、进程与进程间通信
I.线程与线程间通信 一.基本概念以及线程与进程之间的区别联系: 关于进程和线程,首先从定义上理解就有所不同1.进程是什么?是具有一定独立功能的程序.它是系统进行资源分配和调度的一个独立单位,重点在系 ...
- 01_Linux学习_基础知识
学Linux就学命令行 === cd / 转到根目录,相对当前路径 cd dev 转到dev目录 whoami 查阅当前用户 pwd 查阅当前目录 ls 查阅当前目录下的目录和文件 === Linux ...
- Oracle技术面试问题
这也许是你一直期待的文章,在关注这部分技术问题的同时,请务必阅读有关面试中有关个人的问题和解答.这里的回答并不是十分全面,这些问题可以通过多个 角度来进行解释,也许你不必在面试过程中给出完全详尽的答案 ...
- zookeeper 分布式管理
分布式框架: Zookeeper与paxos算法 一. zookeeper是什么 官方说辞:Zookeeper 分布式服务框架是Apache Hadoop 的一个子项目,它主要是 ...
- Django之代码风格
1 代码风格 稍微关注一下下面这些代码标准风格指导规则将会对你大有益处,我们高度建议你通读词章,即便你此时可能正想跳过它. 1.1 让你的代码保持可读性的重要性 代码在读方面的重要性胜过写.一个代码块 ...
- CVE-2017-12149 JBOOS AS 6.X 反序列化漏洞利用
检测目录: 返回500 一般就是存在了. 下载工具: http://scan.javasec.cn/java/JavaDeserH2HC.zip 使用方法: javac -cp .:commons-c ...
- Scala编程入门---Map与Tuple
创建Map //创建一个不可变的Map val ages = Map("Leo" -> 30,"Jen" ->25,"Jack" ...