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

 更新时间:2023年05月10日 09:35:22   作者:未来谁可知  
这篇文章主要介绍了Golang中HTTP路由设计的使用与实现,为什么要设计路由规则,因为路由规则是HTTP的请求按照一定的规则 ,匹配查找到对应的控制器并传递执行的逻辑,需要的朋友可以参考下

Golang之HTTP路由设计

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

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

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

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

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

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前缀地址!

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

如何实现动态路由

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

// 实现动态路由匹配树
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在最后的节点的时候赋值一下处理方法

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

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

//过滤下一层满足 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
}

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

// 增加路由
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替换进来

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

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路由设计内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go语言错误处理异常捕获+异常抛出

    Go语言错误处理异常捕获+异常抛出

    这篇文章主要介绍了Go语言错误处理异常捕获和异常抛出,Go语言的作者认为java等语言的错误处理底层实现较为复杂,就实现了函数可以返回错误类型以及简单的异常捕获,虽然简单但是也非常精妙,大大的提高了运行效率,下文需要的朋友可以参考一下
    2022-02-02
  • GO语言(golang)基础知识

    GO语言(golang)基础知识

    这篇文章主要介绍了GO语言(golang)基础知识,需要的朋友可以参考下
    2015-01-01
  • Go语言单元测试基础从入门到放弃

    Go语言单元测试基础从入门到放弃

    这篇文章主要介绍了Go单元测试基础从入门到放弃为大家开启Go语言单元测试第一篇章,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • Go秒爬博客园100页新闻

    Go秒爬博客园100页新闻

    利用go语言的协程并发优势爬取网页速度相当之快,博客园100页新闻标题只需一秒即可全部爬取,跟着小编一起去看看如何实现的,希望大家可以从中受益
    2018-09-09
  • Goland的设置与配置全过程

    Goland的设置与配置全过程

    这篇文章主要介绍了Goland的设置与配置全过程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • 浅析Go中函数的健壮性,panic异常处理和defer机制

    浅析Go中函数的健壮性,panic异常处理和defer机制

    这篇文章主要为大家详细介绍了Go中函数的健壮性,panic异常处理和defer机制的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2023-10-10
  • Go整合captcha实现验证码功能

    Go整合captcha实现验证码功能

    最近在使用Go语言搞一个用户登录&注册的功能,我们油然会产生一种增加验证码的想法。后来在GitHub上找到了这个名叫captcha的插件,于是就利用文档进行了初步的学习,并融入到自己的项目中,整个过程下来感觉这个插件的设计非常巧妙
    2023-03-03
  • golang实现简单的tcp数据传输

    golang实现简单的tcp数据传输

    这篇文章主要为大家介绍了golang实现简单的tcp数据传输,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • go tar包归档文件处理操作全面指南

    go tar包归档文件处理操作全面指南

    这篇文章主要为大家介绍了使用go tar包归档文件处理操作全面指南,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • Golang 实现 Redis系列(六)如何实现 pipeline 模式的 redis 客户端

    Golang 实现 Redis系列(六)如何实现 pipeline 模式的 redis 客户端

    pipeline 模式的 redis 客户端需要有两个后台协程负责 tcp 通信,调用方通过 channel 向后台协程发送指令,并阻塞等待直到收到响应,本文是使用 golang 实现 redis 系列的第六篇, 将介绍如何实现一个 Pipeline 模式的 Redis 客户端。
    2021-07-07

最新评论