脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|shell|

服务器之家 - 脚本之家 - Golang - Golang中HTTP路由设计的使用与实现

Golang中HTTP路由设计的使用与实现

2023-05-11 14:15未来谁可知 Golang

这篇文章主要介绍了Golang中HTTP路由设计的使用与实现,为什么要设计路由规则,因为路由规则是HTTP的请求按照一定的规则 ,匹配查找到对应的控制器并传递执行的逻辑,需要的朋友可以参考下

Golang之HTTP路由设计

为什么要设计路由规则,路由规则是HTTP的请求按照一定的规则 ,匹配查找到对应的控制器并传递执行的逻辑!

自己编写路由的话需要注意一下一共有几种路由!

  • 一种是支持原生的restful四种类型的访问方法!Get,Post,Delete,Put
  • 需要支持自定义的路径,也就是静态路由
  • 批量通用前缀,也就是下面我们将讲到的group
  • 动态路由匹配!

也就是像这样我们在route.go去注册

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func registerRouter(core *framework.Core) {
    print(111)
    // 设置控制器
    core.Get("/foo", FooController)
    core.Get("/user/login", UserLoginController)
    subjectApi := core.Group("/subject")
    {
    // restful路由,根据请求类型区分了开,:id为动态路由
        subjectApi.Get("/list/all", SubjectListController)
        subjectApi.Post("/add", SubjectListController)
        subjectApi.Delete("/:id", SubjectListController)
        subjectApi.Put("/:id", SubjectListController)
        subjectApi.Get("/:id", SubjectListController)
    }
}

动手编写自己的路由

在上一节中我们编写了自己的请求处理器,对应在里面加入我们的路由规则就好了!

framework/core.go

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package framework
import (
    "net/http"
    "strings"
)
const (
    GET    = "GET"
    PUT    = "PUT"
    DELETE = "DELETE"
    POST   = "POST"
)
//map[string]map[string]ControllerHandler 前面存请求类型后面是路径对应执行方法
type Core struct {
    router map[string]map[string]ControllerHandler
}
func (c Core) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
    ctx:= NewContext(request, writer)
    router:=c.FindRouteByRequest(request)
    if router==nil{
        ctx.Json(404,"router not found ")
        return
    }
    if err:=router(ctx);err!=nil{
        ctx.Json(500,"server Interval")
        return
    }
    //http.DefaultServeMux.ServeHTTP(writer, request)
}
func NewCore() *Core {
    getRouter := map[string]ControllerHandler{}
    postRouter := map[string]ControllerHandler{}
    putRouter := map[string]ControllerHandler{}
    deleteRouter := map[string]ControllerHandler{}
    core := &Core{
        router: make(map[string]map[string]ControllerHandler, 0),
    }
  // 初始化好四种类型的路由map
    core.router[GET] = getRouter
    core.router[POST] = postRouter
    core.router[PUT] = putRouter
    core.router[DELETE] = deleteRouter
    return core
}
// 注册Get方法
func (c *Core) Get(pattern string, handler ControllerHandler) {
    url := strings.ToUpper(pattern)
    c.router[GET][url] = handler
}
// 注册Post方法
func (c *Core) Post(pattern string, handler ControllerHandler) {
    url := strings.ToUpper(pattern) // 大小写不敏感
    c.router[POST][url] = handler
}
// 注册Put方法
func (c *Core) Put(pattern string, handler ControllerHandler) {
    url := strings.ToUpper(pattern)
    c.router[PUT][url] = handler
}
// 注册Delete方法
func (c *Core) Delete(pattern string, handler ControllerHandler) {
    url := strings.ToUpper(pattern)
    c.router[DELETE][url] = handler
}
// 寻找http+静态路由
func (c *Core) FindRouteByRequest(request *http.Request) ControllerHandler {
    uri := request.URL.Path    //请求处理器映射地址
    method := request.Method   // 请求类型
    upperMethod := strings.ToUpper(method)
    upperURI := strings.ToUpper(uri)
  // 找到类型下的具体地址的映射地址的方法,这里还没有实现动态什么的就固定有1个路径key,但是先别急,后面我们再来动手改造
    if data, ok := c.router[upperMethod]; ok {
        if handler, ok1 := data[upperURI]; ok1 {
            return handler
        }
    }
    return nil
}

framework/group.go

给我们的注册路由,加上分组,用group包装,这样对应我们在使用group时就会对应到不同的请求类型的方法了!并且在这一层给所有的注册地址统一加上group前缀地址!

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package framework
//IGroup 代表前缀分组
type IGroup interface {
    Get(string, ControllerHandler)
    Post(string, ControllerHandler)
    Delete(string, ControllerHandler)
    Put(string, ControllerHandler)
}
//
type Group struct {
    core   *Core //
    perfix string // 自身前缀
}
func (g Group) Get(s string, handler ControllerHandler) {
    url := g.perfix + s
    g.core.Get(url, handler)
}
func (g Group) Post(s string, handler ControllerHandler) {
    url := g.perfix + s
    g.core.Post(url, handler)
}
func (g Group) Delete(s string, handler ControllerHandler) {
    url := g.perfix + s
    g.core.Delete(url, handler)
}
func (g Group) Put(s string, handler ControllerHandler) {
    url := g.perfix + s
    g.core.Put(url, handler)
}
func NewGroup(core *Core, perfix string) *Group {
    return &Group{core: core, perfix: perfix}
}
func (c *Core)Group(prefix string)IGroup{
    return NewGroup(c,prefix)
}

如何实现动态路由

首先先定义好我们的动态路由数据结构

?
1
2
3
4
5
6
7
8
9
10
11
// 实现动态路由匹配树
type Tree struct {
    root *node // 根结点
}
// 代表节点
type node struct {
    isLast  bool              // 代表这个节点是否可以成为最终的路由规则。 该节点是否能成为一
    segment string            // url 中的字符串,代表这个节点表示的路由中某个段的字符串
    handler ControllerHandler // 代表这个节点中包含的控制器,用于最终加载调用
    childes []*node           // 代表这个节点下的子节点
}

我们要做的就是在每次注册的时候去将对应的路径的东西将之前的map[string]map[string]ControllerHandler替换为新改造的这个Tree!

从node的结构来看我们应该判断我们的segment去添加我们的childes的node在最后的节点的时候赋值一下处理方法

?
1
2
3
4
5
//matchNode 方法的参数是一个 URI,返回值是指向 node 的指针,它的实现思路是使用函数递归
// 判断是否动态路由
func isWildSegment(segment string) bool {
    return strings.HasPrefix(segment, ":")
}

下面是我们需要的一些功能函数,递归匹配路由和找到下一层的子节点

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//过滤下一层满足 segment 规则的子节点
func (n *node) filterChildNodes(segment string) []*node {
   if len(n.childes) == 0 {
      return nil
   }
   // 如果是动态路由则子节点直接满足条件
   if isWildSegment(segment) {
      return n.childes
   }
   // 不是的话就从子节点里面找2
   nodes := make([]*node, 0, len(n.childes))
   for _, node := range n.childes {
      // 判断所有子节点里面是否有动态路由或者唯一匹配的路由
      if isWildSegment(node.segment) || node.segment == segment {
         nodes = append(nodes, node)
      }
   }
   return nodes
}
// 匹配路由
func (n *node) matchNode(url string) *node {
   // 正序拆分路由第一个/
   segments := strings.SplitN(url, "/", 2)
   segment := segments[0] // 第一个路由节点
   //判断如果不是动态路由,那么都统一大写
   if !isWildSegment(segment) {
      segment = strings.ToUpper(segment)
   }
   // 找到下一层路由节点
   nodes := n.filterChildNodes(segment)
   // 错误返回
   if nodes == nil || len(nodes) <= 0 {
      return nil
   }
   //如果只有一个子节点了,是最后的话就返回最后的一个路由节点
   if len(segments) == 1 {
      for _, node := range nodes {
         if node.isLast {
            return node
         }
      }
      return nil
   }
   // 否则持续循环去判断各个节点集合中的递归下一层
   for _, v := range nodes {
      toMatch := v.matchNode(segments[1])
      if toMatch != nil {
         return toMatch
      }
      return nil
   }
   return nil
}

下面是增加路由,以及提供给外部用的,找到对应执行逻辑的控制器方法!

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 增加路由
func (tree *Tree) AddRoute(url string, handler ControllerHandler) error {
    n := tree.root
    // 确认路由是否已存在
    if n.matchNode(url) != nil {
        return errors.New(fmt.Sprintf("add router %v error", url))
    }
    segments := strings.Split(url, "/")
    // 对每个segment
    for index, segment := range segments {
        // 不是动态路由的静态节点 需要转变大写
        if !isWildSegment(segment) {
            segment = strings.ToUpper(segment)
        }
        isLast := index == len(segments)-1 // 判断是否为最后一个节点
        var objNode *node
        childNodes := n.filterChildNodes(segment)
        if len(childNodes) > 0 {
            // 如果有segment相同的子节点,则选择这个子节点
            for _, node := range childNodes {
                if node.segment == segment {
                    objNode = node
                    break
                }
            }
        }
        // 如果没有找到相同的子节点,那么就自己构造一个添加进tree里面
        if objNode == nil {
            objNode = &node{
                isLast:  isLast,
                segment: segment,
                handler: nil,
                childes: make([]*node, 0),
            }
            if isLast {
                objNode.handler = handler
            }
            n.childes = append(n.childes, objNode)
        }
        n = objNode
    }
    return nil
}
// 寻找对应的映射控制器处理方法
func (tree *Tree) FindHandler(url string) ControllerHandler {
    // 直接复用
    matchNode := tree.root.matchNode(url)
    if matchNode == nil {
        return nil
    }
    return matchNode.handler
}

改造一下core.go

将实现了动态路由的Tree替换进来

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package framework
import (
    "log"
    "net/http"
    "strings"
)
const (
    GET    = "GET"
    PUT    = "PUT"
    DELETE = "DELETE"
    POST   = "POST"
)
type Core struct {
    router map[string]*Tree
}
func (c Core) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
    ctx := NewContext(request, writer)
    router := c.FindRouteByRequest(request)
    if router == nil {
        ctx.Json(404, "router not found ")
        return
    }
    if err := router(ctx); err != nil {
        ctx.Json(500, "server Interval")
        return
    }
    //http.DefaultServeMux.ServeHTTP(writer, request)
}
func NewCore() *Core {
    getRouter := NewTree()
    postRouter := NewTree()
    putRouter := NewTree()
    deleteRouter := NewTree()
    core := &Core{
        router: make(map[string]*Tree, 0),
    }
    core.router[GET] = getRouter
    core.router[POST] = postRouter
    core.router[PUT] = putRouter
    core.router[DELETE] = deleteRouter
    return core
}
// 注册Get方法
func (c *Core) Get(pattern string, handler ControllerHandler) {
    url := strings.ToUpper(pattern)
    if err := c.router[GET].AddRoute(url, handler); err != nil {
        log.Fatal("add router error:", err)
    }
}
// 注册Post方法
func (c *Core) Post(pattern string, handler ControllerHandler) {
    url := strings.ToUpper(pattern) // 大小写不敏感
    if err := c.router[POST].AddRoute(url, handler); err != nil {
        log.Fatal("add router error:", err)
    }
}
func (c *Core) Put(pattern string, handler ControllerHandler) {
    url := strings.ToUpper(pattern)
    if err := c.router[PUT].AddRoute(url, handler); err != nil {
        log.Fatal("add router error:", err)
    }
}
func (c *Core) Delete(pattern string, handler ControllerHandler) {
    url := strings.ToUpper(pattern)
    if err := c.router[DELETE].AddRoute(url, handler); err != nil {
        log.Fatal("add router error:", err)
    }
}
// 寻找http+静态路由
func (c *Core) FindRouteByRequest(request *http.Request) ControllerHandler {
    uri := request.URL.Path
    method := request.Method
    upperMethod := strings.ToUpper(method)
    // upperURI := strings.ToUpper(uri)  内部路由会去判断非动态会转大写
    if data, ok := c.router[upperMethod]; ok {
        return data.FindHandler(uri)
    }
    return nil
}

验证

编写两个Controller

?
1
2
3
4
5
6
7
8
func UserLoginController(ctx *framework.Context) error {
    ctx.Json(200, "ok,UserLoginController")
    return nil
}
func SubjectListController(ctx *framework.Context) error {
    ctx.Json(200, "ok,SubjectListController")
    return nil
}

启动运行

到此这篇关于Golang中HTTP路由设计的使用与实现的文章就介绍到这了,更多相关Golang HTTP路由设计内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/jiohfgj/article/details/125682170

延伸 · 阅读

精彩推荐
  • GolangGo中time.RFC3339 时间格式化的实现

    Go中time.RFC3339 时间格式化的实现

    这篇文章主要介绍了Go中time.RFC3339 时间格式化的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们...

    新亮笔记6222021-03-27
  • Golang浅谈Go语言中字符串和数组

    浅谈Go语言中字符串和数组

    这篇文章主要简单介绍了Go语言中字符串和数组的使用方法和申明方式,需要的朋友可以参考下 ...

    hebedich10322020-04-12
  • Golanggolang中使用sync.Map的方法

    golang中使用sync.Map的方法

    这篇文章主要介绍了golang中使用sync.Map的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下 ...

    IT技术小小鸟9512020-08-21
  • GolangGoland 生成可执行文件的操作

    Goland 生成可执行文件的操作

    这篇文章主要介绍了Goland 生成可执行文件的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    夕雨71411872021-02-23
  • GolangGo语言按字节截取字符串的方法

    Go语言按字节截取字符串的方法

    这篇文章主要介绍了Go语言按字节截取字符串的方法,涉及Go语言操作字符串的技巧,非常具有实用价值,需要的朋友可以参考下 ...

    不吃皮蛋13792020-04-15
  • Golanggolang 如何删除二进制文件中的源码路径信息

    golang 如何删除二进制文件中的源码路径信息

    这篇文章主要介绍了golang 如何删除二进制文件中的源码路径信息,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    ishouyong9202021-06-10
  • GolangGo语言sync包与锁实现限制线程对变量的访问

    Go语言sync包与锁实现限制线程对变量的访问

    本文主要介绍了Go语言sync包与锁实现限制线程对变量的访问,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的...

    C语言中文网11642023-04-14
  • Golanggo-zero源码阅读之布隆过滤器实现代码

    go-zero源码阅读之布隆过滤器实现代码

    布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难,这...

    飞飞羽毛球3922023-03-20