Content

Golang中HTTP路由设计的使用与实现(golannia品牌)这样也行?

文章摘要

这篇文章介绍了在Golang中设计和实现HTTP路由的基本原理和实践。文章详细讲解了如何编写自己的路由框架,支持动态路由匹配,并通过分组和Tree结构实现灵活的路由管理。阅读了文章后,可以了解到以下核心内容: 1. **路由设计的重要性**:文章强调了动态路由在处理不同请求类型和路径上的优势,展示了如何通过路由规则实现分页、分类管理等功能。 2. **动态路由实现**:通过定义Tree结构和node节点,实现了动态路由的匹配逻辑。文章详细解释了如何递归匹配路由,处理动态路由中的路径分段。 3. **路由框架扩展**:文章展示了如何在Golang中扩展路由框架,注册不同的路由规则,并使用分组管理不同的请求类型。通过添加新的路由和控制器,可以灵活支持多种业务逻辑。 4. **错误处理与优化**:文章还提到了错误处理机制,如返回404或500状态码,以及如何优化路由匹配以提高效率。 5. **示例控制器**:通过示例的UserLoginController和SubjectListController,文章展示了如何将路由映射到具体的业务逻辑,支持用户登录和数据查询等功能。 总结来说,这篇文章为读者提供了Golang中动态路由设计与实现的详细指导,帮助理解如何通过自定义路由框架实现灵活的HTTP服务设计。


目录Golang之HTTP路由设计动手编写自己的路由framework/core.goframework/group.go如何实现动态路由改造一下core.go验证

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

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

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

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

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

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

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 }

给我们的注册路由,加上分组,用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 // 代表这个节点下的子节点 }

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

从node的结构来看我们应该判断我们的去添加我们的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 }

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

您可能感兴趣的文章:Golang?HTTP服务超时控制实现原理分析Golang搭建HTTP服务器Golang中基于HTTP协议的网络服务Golang实现简单http服务器的示例详解Golang?HTTP编程的源码解析详解Golang实现HTTP编程请求和响应Golang Http请求返回结果处理Golang中HTTP服务的分析与设计详解