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