Golang中HTTP路由設(shè)計(jì)的使用與實(shí)現(xiàn)
Golang之HTTP路由設(shè)計(jì)
為什么要設(shè)計(jì)路由規(guī)則,路由規(guī)則是HTTP的請(qǐng)求按照一定的規(guī)則 ,匹配查找到對(duì)應(yīng)的控制器并傳遞執(zhí)行的邏輯!
自己編寫路由的話需要注意一下一共有幾種路由!
- 一種是支持原生的restful四種類型的訪問方法!
Get
,Post
,Delete
,Put
- 需要支持自定義的路徑,也就是靜態(tài)路由
- 批量通用前綴,也就是下面我們將講到的
group
- 動(dòng)態(tài)路由匹配!
也就是像這樣我們?cè)?code>route.go去注冊(cè)
func registerRouter(core *framework.Core) { print(111) // 設(shè)置控制器 core.Get("/foo", FooController) core.Get("/user/login", UserLoginController) subjectApi := core.Group("/subject") { // restful路由,根據(jù)請(qǐng)求類型區(qū)分了開,:id為動(dòng)態(tài)路由 subjectApi.Get("/list/all", SubjectListController) subjectApi.Post("/add", SubjectListController) subjectApi.Delete("/:id", SubjectListController) subjectApi.Put("/:id", SubjectListController) subjectApi.Get("/:id", SubjectListController) } }
動(dòng)手編寫自己的路由
在上一節(jié)中我們編寫了自己的請(qǐng)求處理器,對(duì)應(yīng)在里面加入我們的路由規(guī)則就好了!
framework/core.go
package framework import ( "net/http" "strings" ) const ( GET = "GET" PUT = "PUT" DELETE = "DELETE" POST = "POST" ) //map[string]map[string]ControllerHandler 前面存請(qǐng)求類型后面是路徑對(duì)應(yīng)執(zhí)行方法 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 } // 注冊(cè)Get方法 func (c *Core) Get(pattern string, handler ControllerHandler) { url := strings.ToUpper(pattern) c.router[GET][url] = handler } // 注冊(cè)Post方法 func (c *Core) Post(pattern string, handler ControllerHandler) { url := strings.ToUpper(pattern) // 大小寫不敏感 c.router[POST][url] = handler } // 注冊(cè)Put方法 func (c *Core) Put(pattern string, handler ControllerHandler) { url := strings.ToUpper(pattern) c.router[PUT][url] = handler } // 注冊(cè)Delete方法 func (c *Core) Delete(pattern string, handler ControllerHandler) { url := strings.ToUpper(pattern) c.router[DELETE][url] = handler } // 尋找http+靜態(tài)路由 func (c *Core) FindRouteByRequest(request *http.Request) ControllerHandler { uri := request.URL.Path //請(qǐng)求處理器映射地址 method := request.Method // 請(qǐng)求類型 upperMethod := strings.ToUpper(method) upperURI := strings.ToUpper(uri) // 找到類型下的具體地址的映射地址的方法,這里還沒有實(shí)現(xiàn)動(dòng)態(tài)什么的就固定有1個(gè)路徑key,但是先別急,后面我們?cè)賮韯?dòng)手改造 if data, ok := c.router[upperMethod]; ok { if handler, ok1 := data[upperURI]; ok1 { return handler } } return nil }
framework/group.go
給我們的注冊(cè)路由,加上分組,用group包裝,這樣對(duì)應(yīng)我們?cè)谑褂胓roup時(shí)就會(huì)對(duì)應(yīng)到不同的請(qǐng)求類型的方法了!并且在這一層給所有的注冊(cè)地址統(tǒng)一加上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) }
如何實(shí)現(xiàn)動(dòng)態(tài)路由
首先先定義好我們的動(dòng)態(tài)路由數(shù)據(jù)結(jié)構(gòu)
// 實(shí)現(xiàn)動(dòng)態(tài)路由匹配樹 type Tree struct { root *node // 根結(jié)點(diǎn) } // 代表節(jié)點(diǎn) type node struct { isLast bool // 代表這個(gè)節(jié)點(diǎn)是否可以成為最終的路由規(guī)則。 該節(jié)點(diǎn)是否能成為一 segment string // url 中的字符串,代表這個(gè)節(jié)點(diǎn)表示的路由中某個(gè)段的字符串 handler ControllerHandler // 代表這個(gè)節(jié)點(diǎn)中包含的控制器,用于最終加載調(diào)用 childes []*node // 代表這個(gè)節(jié)點(diǎn)下的子節(jié)點(diǎn) }
我們要做的就是在每次注冊(cè)的時(shí)候去將對(duì)應(yīng)的路徑的東西將之前的map[string]map[string]ControllerHandler
替換為新改造的這個(gè)Tree!
從node的結(jié)構(gòu)來看我們應(yīng)該判斷我們的segment
去添加我們的childes的node在最后的節(jié)點(diǎn)的時(shí)候賦值一下處理方法
//matchNode 方法的參數(shù)是一個(gè) URI,返回值是指向 node 的指針,它的實(shí)現(xiàn)思路是使用函數(shù)遞歸 // 判斷是否動(dòng)態(tài)路由 func isWildSegment(segment string) bool { return strings.HasPrefix(segment, ":") }
下面是我們需要的一些功能函數(shù),遞歸匹配路由和找到下一層的子節(jié)點(diǎn)
//過濾下一層滿足 segment 規(guī)則的子節(jié)點(diǎn) func (n *node) filterChildNodes(segment string) []*node { if len(n.childes) == 0 { return nil } // 如果是動(dòng)態(tài)路由則子節(jié)點(diǎn)直接滿足條件 if isWildSegment(segment) { return n.childes } // 不是的話就從子節(jié)點(diǎn)里面找2 nodes := make([]*node, 0, len(n.childes)) for _, node := range n.childes { // 判斷所有子節(jié)點(diǎn)里面是否有動(dòng)態(tài)路由或者唯一匹配的路由 if isWildSegment(node.segment) || node.segment == segment { nodes = append(nodes, node) } } return nodes } // 匹配路由 func (n *node) matchNode(url string) *node { // 正序拆分路由第一個(gè)/ segments := strings.SplitN(url, "/", 2) segment := segments[0] // 第一個(gè)路由節(jié)點(diǎn) //判斷如果不是動(dòng)態(tài)路由,那么都統(tǒng)一大寫 if !isWildSegment(segment) { segment = strings.ToUpper(segment) } // 找到下一層路由節(jié)點(diǎn) nodes := n.filterChildNodes(segment) // 錯(cuò)誤返回 if nodes == nil || len(nodes) <= 0 { return nil } //如果只有一個(gè)子節(jié)點(diǎn)了,是最后的話就返回最后的一個(gè)路由節(jié)點(diǎn) if len(segments) == 1 { for _, node := range nodes { if node.isLast { return node } } return nil } // 否則持續(xù)循環(huán)去判斷各個(gè)節(jié)點(diǎn)集合中的遞歸下一層 for _, v := range nodes { toMatch := v.matchNode(segments[1]) if toMatch != nil { return toMatch } return nil } return nil }
下面是增加路由,以及提供給外部用的,找到對(duì)應(yīng)執(zhí)行邏輯的控制器方法!
// 增加路由 func (tree *Tree) AddRoute(url string, handler ControllerHandler) error { n := tree.root // 確認(rèn)路由是否已存在 if n.matchNode(url) != nil { return errors.New(fmt.Sprintf("add router %v error", url)) } segments := strings.Split(url, "/") // 對(duì)每個(gè)segment for index, segment := range segments { // 不是動(dòng)態(tài)路由的靜態(tài)節(jié)點(diǎn) 需要轉(zhuǎn)變大寫 if !isWildSegment(segment) { segment = strings.ToUpper(segment) } isLast := index == len(segments)-1 // 判斷是否為最后一個(gè)節(jié)點(diǎn) var objNode *node childNodes := n.filterChildNodes(segment) if len(childNodes) > 0 { // 如果有segment相同的子節(jié)點(diǎn),則選擇這個(gè)子節(jié)點(diǎn) for _, node := range childNodes { if node.segment == segment { objNode = node break } } } // 如果沒有找到相同的子節(jié)點(diǎn),那么就自己構(gòu)造一個(gè)添加進(jìn)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 } // 尋找對(duì)應(yīng)的映射控制器處理方法 func (tree *Tree) FindHandler(url string) ControllerHandler { // 直接復(fù)用 matchNode := tree.root.matchNode(url) if matchNode == nil { return nil } return matchNode.handler }
改造一下core.go
將實(shí)現(xiàn)了動(dòng)態(tài)路由的Tree替換進(jìn)來
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 } // 注冊(cè)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) } } // 注冊(cè)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+靜態(tài)路由 func (c *Core) FindRouteByRequest(request *http.Request) ControllerHandler { uri := request.URL.Path method := request.Method upperMethod := strings.ToUpper(method) // upperURI := strings.ToUpper(uri) 內(nèi)部路由會(huì)去判斷非動(dòng)態(tài)會(huì)轉(zhuǎn)大寫 if data, ok := c.router[upperMethod]; ok { return data.FindHandler(uri) } return nil }
驗(yàn)證
編寫兩個(gè)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 }
啟動(dòng)運(yùn)行
到此這篇關(guān)于Golang中HTTP路由設(shè)計(jì)的使用與實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Golang HTTP路由設(shè)計(jì)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
GO語(yǔ)言(golang)基礎(chǔ)知識(shí)
這篇文章主要介紹了GO語(yǔ)言(golang)基礎(chǔ)知識(shí),需要的朋友可以參考下2015-01-01淺析Go中函數(shù)的健壯性,panic異常處理和defer機(jī)制
這篇文章主要為大家詳細(xì)介紹了Go中函數(shù)的健壯性,panic異常處理和defer機(jī)制的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-10-10Go整合captcha實(shí)現(xiàn)驗(yàn)證碼功能
最近在使用Go語(yǔ)言搞一個(gè)用戶登錄&注冊(cè)的功能,我們油然會(huì)產(chǎn)生一種增加驗(yàn)證碼的想法。后來在GitHub上找到了這個(gè)名叫captcha的插件,于是就利用文檔進(jìn)行了初步的學(xué)習(xí),并融入到自己的項(xiàng)目中,整個(gè)過程下來感覺這個(gè)插件的設(shè)計(jì)非常巧妙2023-03-03golang實(shí)現(xiàn)簡(jiǎn)單的tcp數(shù)據(jù)傳輸
這篇文章主要為大家介紹了golang實(shí)現(xiàn)簡(jiǎn)單的tcp數(shù)據(jù)傳輸,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Golang 實(shí)現(xiàn) Redis系列(六)如何實(shí)現(xiàn) pipeline 模式的 redis 客戶端
pipeline 模式的 redis 客戶端需要有兩個(gè)后臺(tái)協(xié)程負(fù)責(zé) tcp 通信,調(diào)用方通過 channel 向后臺(tái)協(xié)程發(fā)送指令,并阻塞等待直到收到響應(yīng),本文是使用 golang 實(shí)現(xiàn) redis 系列的第六篇, 將介紹如何實(shí)現(xiàn)一個(gè) Pipeline 模式的 Redis 客戶端。2021-07-07