golang自定义路由控制实现(二)-流式注册接口以及支持RESTFUL

简介:     先简单回顾一下在上一篇的文章中,上一篇我主要是结合了数组和Map完成路由映射,数组的大小为8,下标为0的代表Get方法,以此类推,而数组的值则是Map,键为URL,值则是我们编写对应的接口。但是上篇的设计仍存在着不足,主要是无法很好的面向RESTFUL设计,同时,我希望还能够希望一个功能,类似于SpringMVC中,可以将@Controller作用于类上,代表着该类下所有接口的一个起始路径。

    先简单回顾一下在上一篇的文章中,上一篇我主要是结合了数组和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
    路漫漫其修远兮,客官点个赞呗

目录
相关文章
|
4月前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
368 4
|
9月前
|
存储 Go
Go语言之接口与多态 -《Go语言实战指南》
Go 语言中的接口是实现多态的核心机制,通过一组方法签名定义行为。任何类型只要实现接口的所有方法即视为实现该接口,无需显式声明。本文从接口定义、使用、底层机制、组合、动态行为到工厂模式全面解析其特性与应用,帮助理解 Go 的面向接口编程思想及注意事项(如 `nil` 陷阱)。
243 22
|
4月前
|
Java 编译器 Go
【Golang】(5)Go基础的进阶知识!带你认识迭代器与类型以及声明并使用接口与泛型!
好烦好烦好烦!你是否还在为弄不懂Go中的泛型和接口而烦恼?是否还在苦恼思考迭代器的运行方式和意义?本篇文章将带你了解Go的接口与泛型,还有迭代器的使用,附送类型断言的解释
241 3
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
8月前
|
开发框架 JSON 中间件
Go语言Web开发框架实践:路由、中间件、参数校验
Gin框架以其极简风格、强大路由管理、灵活中间件机制及参数绑定校验系统著称。本文详解其核心功能:1) 路由管理,支持分组与路径参数;2) 中间件机制,实现全局与局部控制;3) 参数绑定,涵盖多种来源;4) 结构体绑定与字段校验,确保数据合法性;5) 自定义校验器扩展功能;6) 统一错误处理提升用户体验。Gin以清晰模块化、流程可控及自动化校验等优势,成为开发者的优选工具。
|
9月前
|
存储 JSON Go
Go语言之空接口与类型断言
本文介绍了 Go 语言中空接口(`interface{}`)和类型断言的核心概念及其应用。空接口可存储任意类型数据,适用于通用函数、动态数据结构与 JSON 解析等场景;类型断言用于将接口变量还原为具体类型,推荐使用带 `ok` 的写法以避免程序崩溃。此外,文章通过示例讲解了 `type switch` 类型判断与 JSON 处理技巧,并总结了空接口的注意事项,强调滥用可能导致类型安全性降低。内容深入浅出,帮助开发者灵活运用这些特性。
262 15
|
9月前
|
Go
Go语言接口的定义与实现
Go 语言的接口提供了一种灵活的多态机制,支持隐式实现和抽象编程。本文介绍了接口的基本定义、实现方式、空接口的使用、类型断言以及接口组合等核心概念,并探讨了接口与 nil 的关系及应用场景。通过示例代码详细说明了如何利用接口提升代码的可扩展性和可测试性,总结了接口的关键特性及其在依赖注入、规范定义和多态调用中的重要作用。
361 14
|
10月前
|
JSON API Go
Golang工程组件:自定义HTTP规则的grpc-gateway选项
总的来说,grpc-gateway提供了一种简单有效的方式来为你的gRPC服务提供RESTful风格的API。通过自定义HTTP规则,你可以灵活地定义你的API的行为,以满足你的应用的需求。
250 27
|
存储 Rust Go
Go nil 空结构体 空接口有什么区别?
本文介绍了Go语言中的`nil`、空结构体和空接口的区别。`nil`是预定义的零值变量,适用于指针、管道等类型;空结构体大小为0,多个空结构体实例指向同一地址;空接口由`_type`和`data`字段组成,仅当两者均为`nil`时,空接口才为`nil`。
287 1
Go nil 空结构体 空接口有什么区别?
|
存储 Go
Go to Learn Go之接口
Go to Learn Go之接口
155 7

热门文章

最新文章

推荐镜像

更多