Go 微服務框架 | 路由實現

文章目錄

    • 不用框架實現web接口
    • 實現簡單的路由
    • 實現分組路由
    • 支持不同的請求方式
    • 支持同一個路徑的不同請求方式
    • 前綴樹
    • 應用前綴樹
    • 完善路由代碼

不用框架實現web接口

// blog main.go 文件
package mainimport ("fmt""log""net/http"
)func main() {fmt.Println("Hello World!")// 注冊 HTTP 路由 /hellohttp.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello Go!")})// 啟動 HTTP 服務器err := http.ListenAndServe("8111", nil)if err != nil {log.Fatal(err)}
}
  • http.HandleFunc 注冊一個路由 /hello 并綁定處理函數。
  • 當用戶訪問 http://localhost:8111/hello 時,執行匿名函數:
    • w http.ResponseWriter:用于向客戶端發送 HTTP 響應。
    • r *http.Request:包含客戶端發來的請求信息(如 URL、Headers 等)。
  • fmt.Fprintf(w, "Hello Go!")
    • 向 HTTP 響應寫入 "Hello Go!"(相當于 w.Write([]byte("Hello Go!")))。
    • 默認狀態碼是 200 OK
  • http.ListenAndServe(":8111", nil)
    • 啟動 HTTP 服務器,監聽 8111 端口(: 表示監聽所有網絡接口)。
    • nil 表示使用默認的 DefaultServeMux 路由器(即之前用 http.HandleFunc 注冊的路由)。
  • if err != nil { log.Fatal(err) }:如果服務器啟動失敗(如端口被占用),打印錯誤并終止程序。
  • fmt.Fprintf 是 Go 語言 fmt 包提供的一個格式化輸出函數,用于將格式化后的字符串寫入指定的 io.Writer 接口(如文件、HTTP 響應、標準輸出等)。它的作用類似于 fmt.Printf,但不是輸出到終端,而是寫入到任意實現了 io.Writer 的對象。
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
  • w io.Writer:目標寫入器(如 http.ResponseWriter、文件、緩沖區等)。
  • format string:格式化字符串(包含占位符,如 %s, %d, %v 等)。
  • a ...interface{}:可變參數,用于填充格式化字符串中的占位符。
  • 返回值:
    • n int:成功寫入的字節數。
    • err error:寫入過程中遇到的錯誤(如寫入失敗)。

實現簡單的路由

// zjgo ms.go 文件package zjgoimport ("log""net/http"
)type Engine struct {
}func New() *Engine {return &Engine{}
}func (e *Engine) Run() {err := http.ListenAndServe("8111", nil)if err != nil {log.Fatal(err)}
}
  • 經過封裝之后,原來的 main 函數可以簡潔為如下:
package mainimport ("fmt""github.com/ErizJ/ZJGo/zjgo"
)func main() {fmt.Println("Hello World!")// // 注冊 HTTP 路由 /hello// http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!")// })// // 啟動 HTTP 服務器// err := http.ListenAndServe("8111", nil)// if err != nil {// 	log.Fatal(err)// }engine := zjgo.New()engine.Run()
}
  • 注意這里服務啟動后會 404 Not Found,因為我們沒有實現對應的響應函數 Handler
package zjgoimport ("log""net/http"
)// 定義處理響應函數
type HandleFunc func(w http.ResponseWriter, r *http.Request)// 定義路由結構體
type router struct {handleFuncMap map[string]HandleFunc
}// 給路由結構體添加一個添加路由功能的函數
func (r *router) Add(name string, handleFunc HandleFunc) {r.handleFuncMap[name] = handleFunc
}// 定義一個引擎結構體
type Engine struct {router
}// 引擎結構體的初始化方法
func New() *Engine {return &Engine{router: router{handleFuncMap: make(map[string]HandleFunc),},}
}// 引擎的啟動方法
func (e *Engine) Run() {for key, value := range e.handleFuncMap {http.HandleFunc(key, value)}err := http.ListenAndServe("8111", nil)if err != nil {log.Fatal(err)}
}
  • 在 Go 語言中,當你將一個類型(如 router)嵌入到另一個結構體(如 Engine)中時,這被稱為類型嵌入(Embedded Type)。這是一種組合的方式,它允許 Engine 直接訪問 router 的字段和方法,而不需要顯式地通過一個字段名來訪問。
package mainimport ("fmt""net/http""github.com/ErizJ/ZJGo/zjgo"
)func main() {fmt.Println("Hello World!")// // 注冊 HTTP 路由 /hello// http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!")// })// // 啟動 HTTP 服務器// err := http.ListenAndServe("8111", nil)// if err != nil {// 	log.Fatal(err)// }engine := zjgo.New()engine.Add("/hello", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello Go!")})engine.Run()
}
  • 這樣我們就實現了一個簡單的路由功能,下面進行進一步完善。

實現分組路由

  • 大多數情況下我們希望寫的接口歸屬于某一個模塊,這樣便于管理以及維護,代碼也會更為清晰。
  • 例如:/user/getUser/user/createUser 都同屬于 user 模塊。
// zj.gopackage zjgoimport ("log""net/http"
)// 定義處理響應函數
type HandleFunc func(w http.ResponseWriter, r *http.Request)// 抽象出路由的概念
type routerGroup struct {name          string                // 組名handleFuncMap map[string]HandleFunc // 映射關系應該由每個路由組去維護
}// 定義路由結構體
type router struct {routerGroups []*routerGroup // 路由下面應該維護著不同的組
}// 添加路由組
func (r *router) Group(name string) *routerGroup {routerGroup := &routerGroup{name:          name,handleFuncMap: make(map[string]HandleFunc),}r.routerGroups = append(r.routerGroups, routerGroup)return routerGroup
}// 給路由結構體添加一個添加路由功能的函數
func (routerGroup *routerGroup) Add(name string, handleFunc HandleFunc) {routerGroup.handleFuncMap[name] = handleFunc
}// 定義一個引擎結構體
type Engine struct {router
}// 引擎結構體的初始化方法
func New() *Engine {return &Engine{router: router{},}
}// 引擎的啟動方法
func (e *Engine) Run() {for _, group := range e.routerGroups {for name, value := range group.handleFuncMap {http.HandleFunc("/"+group.name+name, value)}}err := http.ListenAndServe(":3986", nil)if err != nil {log.Fatal(err)}
}
// main.gopackage mainimport ("fmt""net/http""github.com/ErizJ/ZJGo/zjgo"
)func main() {fmt.Println("Hello World!")// // 注冊 HTTP 路由 /hello// http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!")// })// // 啟動 HTTP 服務器// err := http.ListenAndServe("8111", nil)// if err != nil {// 	log.Fatal(err)// }engine := zjgo.New()g1 := engine.Group("user")g1.Add("/hello", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello Go!——user/hello")})g1.Add("/info", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello Go!——user/info")})g2 := engine.Group("order")g2.Add("/hello", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello Go!——order/hello")})g2.Add("/info", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello Go!——order/info")})fmt.Println("Starting...")engine.Run()}

支持不同的請求方式

  • net / http下的路由,只要路徑匹配,就可以進入處理方法。
  • 但是在我們實際應用之中,比如我們使用 Restful 風格的接口在同一路徑下,會使用 GETPOSTDELETEPUT來代替增刪改查,所以我們要對不同的請求方式做相應的支持。
// zj.gopackage zjgoimport ("fmt""log""net/http"
)// 定義處理響應函數
type HandleFunc func(w http.ResponseWriter, r *http.Request)// 抽象出路由的概念
type routerGroup struct {name             string                // 組名handleFuncMap    map[string]HandleFunc // 映射關系應該由每個路由組去維護handlerMethodMap map[string][]string   // 記錄GET,POST等請求方式所記錄的路由,實現對同一路由不同請求方式的支持
}// 定義路由結構體
type router struct {routerGroups []*routerGroup // 路由下面應該維護著不同的組
}// 添加路由組
func (r *router) Group(name string) *routerGroup {routerGroup := &routerGroup{name:             name,handleFuncMap:    make(map[string]HandleFunc),handlerMethodMap: make(map[string][]string),}r.routerGroups = append(r.routerGroups, routerGroup)return routerGroup
}// 給路由結構體添加一個添加路由功能的函數
// func (routerGroup *routerGroup) Add(name string, handleFunc HandleFunc) {
// 	routerGroup.handleFuncMap[name] = handleFunc
// }// Any代表支持任意的請求方式
func (routerGroup *routerGroup) Any(name string, handleFunc HandleFunc) {routerGroup.handleFuncMap[name] = handleFuncrouterGroup.handlerMethodMap["ANY"] = append(routerGroup.handlerMethodMap["ANY"], name)
}// POST代表支持POST請求方式
func (routerGroup *routerGroup) Post(name string, handleFunc HandleFunc) {routerGroup.handleFuncMap[name] = handleFuncrouterGroup.handlerMethodMap[http.MethodPost] = append(routerGroup.handlerMethodMap[http.MethodPost], name)
}// GET代表支持GET請求方式
func (routerGroup *routerGroup) Get(name string, handleFunc HandleFunc) {routerGroup.handleFuncMap[name] = handleFuncrouterGroup.handlerMethodMap[http.MethodGet] = append(routerGroup.handlerMethodMap[http.MethodGet], name)
}// 只要實現 ServeHTTP 這個方法,就相當于實現了對應的 HTTP 處理器
// 結構體 Engine 實現了 ServeHTTP(w http.ResponseWriter, r *http.Request) 方法
// 所以它就自動實現了 http.Handler 接口,因此可以直接被用于 http.ListenAndServe
// Engine 實現了 ServeHTTP,它就是一個合法的 http.Handler,可以用 http.Handle 來綁定它到某個具體的路由路徑上!
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {// 在 Go 的 net/http 包中,r *http.Request 代表了客戶端發來的 HTTP 請求對象。// 可以通過 r.Method 來獲取這次請求使用的是什么方法(Method),例如:GET、POST、PUT、DELETE 等。if r.Method == http.MethodGet {fmt.Fprintf(w, "這是一個 GET 請求")} else if r.Method == http.MethodPost {fmt.Fprintf(w, "這是一個 POST 請求")} else {fmt.Fprintf(w, "這是一個其他類型的請求:%s", r.Method)}for _, group := range e.routerGroups {for name, methodHandle := range group.handleFuncMap {url := "/" + group.name + name//判斷一下當前的url是否等于請求的url,即路由匹配if r.RequestURI == url {// 先判斷當前請求路由是否在支持任意請求方式的 Any map里面if routers, exist := group.handlerMethodMap["ANY"]; exist {for _, routerName := range routers {// 確實支持 Any 請求方式if routerName == name {methodHandle(w, r)return}}}// 不支持 Any,去該請求所對應的請求方式對應的 Map 里面去找是否有對應的路由if routers, exist := group.handlerMethodMap[r.Method]; exist {for _, routerName := range routers {// 確實支持對應請求方式if routerName == name {methodHandle(w, r)return}}}// 沒找到對應的路由,說明該請求方式不允許w.WriteHeader(http.StatusMethodNotAllowed)fmt.Fprintf(w, "%s %s not allowed!!!\n", r.Method, r.RequestURI)return}}}w.WriteHeader(http.StatusNotFound)fmt.Fprintf(w, "%s %s not found!!!\n", r.Method, r.RequestURI)return
}// 定義一個引擎結構體
type Engine struct {router
}// 引擎結構體的初始化方法
func New() *Engine {return &Engine{router: router{},}
}// 引擎的啟動方法
func (e *Engine) Run() {// for _, group := range e.routerGroups {// 	for name, value := range group.handleFuncMap {// 		http.HandleFunc("/"+group.name+name, value)// 	}// }// 把 e 這個http處理器綁定到對應路由下http.Handle("/", e)err := http.ListenAndServe(":3986", nil)if err != nil {log.Fatal(err)}
}
// main.gopackage mainimport ("fmt""net/http""github.com/ErizJ/ZJGo/zjgo"
)func main() {fmt.Println("Hello World!")// // 注冊 HTTP 路由 /hello// http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!")// })// // 啟動 HTTP 服務器// err := http.ListenAndServe("8111", nil)// if err != nil {// 	log.Fatal(err)// }engine := zjgo.New()g1 := engine.Group("user")g1.Get("/hello", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, http.MethodGet+" Hello Go!——user/hello")})// 瀏覽器地址欄輸入的都是 GET 請求// 需要用 curl 或 Postman 來發一個真正的 POST 請求,才會命中 Post handlerg1.Post("/info", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, http.MethodPost+" Hello Go!——user/info")})// 只要路由匹配,就會執行對應的處理函數g1.Any("/any", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, " Hello Go!——user/any")})// g2 := engine.Group("order")// g2.Add("/hello", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!——order/hello")// })// g2.Add("/info", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!——order/info")// })fmt.Println("Starting...")engine.Run()
}
  • 但是目前還是存在一些問題的,目前不支持同一個路由進行 GETPOST,因為在 map 里面會被覆蓋。
  • routerGroup.handleFuncMap[name] = handleFunc,在 PostGet 方法下都有這段代碼,這就會造成方法的覆蓋。

支持同一個路徑的不同請求方式

  • 標準庫 net/http 本身只提供了最基礎的路由匹配機制,也就是通過:http.HandleFunc("/path", handler),它的匹配機制非常簡單:只根據請求路徑匹配,不區分請求方法(GET/POST)。
  • 如果在方法中這樣寫,是手動在 handler 里面區分請求方法,net/http 本身并不會替你區分。
http.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {if r.Method == http.MethodPost {// 處理 POST} else if r.Method == http.MethodGet {// 處理 GET} else {http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)}
})
  • 要限制不同請求方法(GETPOST 分別調用不同的 handler),就得框架(或者你自己)在 ServeHTTP 里手動實現。
  • 像 Gin、Echo、Fiber 等這些 Web 框架,都是在它們內部封裝了:
    • 請求方法和路徑的雙重匹配機制
    • 路由注冊表,支持多種請求方式綁定不同 handler
    • 匹配失敗時返回 405 Method Not Allowed404 Not Found
  • 考慮在每個路由組 routerGrouphandleFuncMap 上做文章,原先是 map[name]HandleFunc,現在可以加入 mapmap,也即 map[name]map[method]HandleFunc
// context.gopackage zjgoimport "net/http"// context 用于保存上下文的信息,用于傳遞信息
type Context struct {W http.ResponseWriterR *http.Request
}
// zj.gopackage zjgoimport ("fmt""log""net/http"
)const ANY = "ANY"// 定義處理響應函數
type HandleFunc func(ctx *Context)// 抽象出路由的概念
type routerGroup struct {name             string                           // 組名handleFuncMap    map[string]map[string]HandleFunc // 映射關系應該由每個路由組去維護handlerMethodMap map[string][]string              // 記錄GET,POST等請求方式所記錄的路由,實現對同一路由不同請求方式的支持
}// 定義路由結構體
type router struct {routerGroups []*routerGroup // 路由下面應該維護著不同的組
}// 添加路由組
func (r *router) Group(name string) *routerGroup {routerGroup := &routerGroup{name:             name,handleFuncMap:    make(map[string]map[string]HandleFunc),handlerMethodMap: make(map[string][]string),}r.routerGroups = append(r.routerGroups, routerGroup)return routerGroup
}// 給路由結構體添加一個添加路由功能的函數
// func (routerGroup *routerGroup) Add(name string, handleFunc HandleFunc) {
// 	routerGroup.handleFuncMap[name] = handleFunc
// }// 由于 ANY POST GET 都需要重復相同的邏輯代碼,所以做一個提取操作
func (routerGroup *routerGroup) handleRequest(name string, method string, handleFunc HandleFunc) {if _, exist := routerGroup.handleFuncMap[name]; !exist {routerGroup.handleFuncMap[name] = make(map[string]HandleFunc)}if _, exist := routerGroup.handleFuncMap[name][method]; !exist {routerGroup.handleFuncMap[name][method] = handleFuncrouterGroup.handlerMethodMap[method] = append(routerGroup.handlerMethodMap[method], name)} else {panic("Under the same route, duplication is not allowed!!!")}
}// Any代表支持任意的請求方式
func (routerGroup *routerGroup) Any(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, ANY, handleFunc)
}// POST代表支持POST請求方式
func (routerGroup *routerGroup) Post(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodPost, handleFunc)
}// GET代表支持GET請求方式
func (routerGroup *routerGroup) Get(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodGet, handleFunc)
}// 只要實現 ServeHTTP 這個方法,就相當于實現了對應的 HTTP 處理器
// 結構體 Engine 實現了 ServeHTTP(w http.ResponseWriter, r *http.Request) 方法
// 所以它就自動實現了 http.Handler 接口,因此可以直接被用于 http.ListenAndServe
// Engine 實現了 ServeHTTP,它就是一個合法的 http.Handler,可以用 http.Handle 來綁定它到某個具體的路由路徑上!
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {// 在 Go 的 net/http 包中,r *http.Request 代表了客戶端發來的 HTTP 請求對象。// 可以通過 r.Method 來獲取這次請求使用的是什么方法(Method),例如:GET、POST、PUT、DELETE 等。if r.Method == http.MethodGet {fmt.Fprintf(w, "這是一個 GET 請求!!!")} else if r.Method == http.MethodPost {fmt.Fprintf(w, "這是一個 POST 請求!!!")} else {fmt.Fprintf(w, "這是一個其他類型的請求:%s", r.Method)}for _, group := range e.routerGroups {for name, methodHandleMap := range group.handleFuncMap {url := "/" + group.name + name//判斷一下當前的url是否等于請求的url,即路由匹配if r.RequestURI == url {ctx := &Context{W: w, R: r}// 先判斷當前請求路由是否支持任意請求方式的Anyif handle, exist := methodHandleMap[ANY]; exist {handle(ctx)return}// 不支持 Any,去該請求所對應的請求方式對應的 Map 里面去找是否有對應的路由if handle, exist := methodHandleMap[r.Method]; exist {handle(ctx)return}// 沒找到對應的路由,說明該請求方式不允許w.WriteHeader(http.StatusMethodNotAllowed)fmt.Fprintf(w, "%s %s not allowed!!!\n", r.Method, r.RequestURI)return}}}w.WriteHeader(http.StatusNotFound)fmt.Fprintf(w, "%s %s not found!!!\n", r.Method, r.RequestURI)return
}// 定義一個引擎結構體
type Engine struct {router
}// 引擎結構體的初始化方法
func New() *Engine {return &Engine{router: router{},}
}// 引擎的啟動方法
func (e *Engine) Run() {// for _, group := range e.routerGroups {// 	for name, value := range group.handleFuncMap {// 		http.HandleFunc("/"+group.name+name, value)// 	}// }// 把 e 這個http處理器綁定到對應路由下http.Handle("/", e)err := http.ListenAndServe(":3986", nil)if err != nil {log.Fatal(err)}
}
// main.gopackage mainimport ("fmt""net/http""github.com/ErizJ/ZJGo/zjgo"
)func main() {fmt.Println("Hello World!")// // 注冊 HTTP 路由 /hello// http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!")// })// // 啟動 HTTP 服務器// err := http.ListenAndServe("8111", nil)// if err != nil {// 	log.Fatal(err)// }engine := zjgo.New()g1 := engine.Group("user")g1.Get("/hello", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/hello")})// 瀏覽器地址欄輸入的都是 GET 請求// 需要用 curl 或 Postman 來發一個真正的 POST 請求,才會命中 Post handlerg1.Post("/info", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, http.MethodPost+" Hello Go!——user/info——POST")})g1.Get("/info", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/info——GET")})// 只要路由匹配,就會執行對應的處理函數g1.Any("/any", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, " Hello Go!——user/any")})// g2 := engine.Group("order")// g2.Add("/hello", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!——order/hello")// })// g2.Add("/info", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!——order/info")// })fmt.Println("Starting...")engine.Run()}

前綴樹

  • 前面的實現,我們只是實現了靜態路由,不能實現更為復雜的需求,比如 /user/get/:id 這種才有參數的。
  • 帶有參數的路由路徑,成為動態路由。
  • 除了帶有參數的,一般情況下我們還希望有更多支持,比如希望支持通配符 **,比如 /static/**,可以匹配 /static/vue.js/static/css/index.css 這些。
// 簡單的前綴樹實現代碼思路type Trie struct {next    [26]*Trieend     bool
}func Constructor() Trie {myTrie := Trie{}return myTrie
}func (this *Trie) Insert(word string) {if this.Search(word) {return}node := thisfor _, ch := range word {if node.next[ch-'a'] == nil {node.next[ch-'a'] = &Trie{}}node = node.next[ch-'a']}node.end = true
}func (this *Trie) Search(word string) bool {node := this.search(word)return node != nil && (*node).end
}func (this *Trie) search(word string) *Trie {node := thisfor _, ch := range word {if node.next[ch-'a'] == nil {return nil}node = node.next[ch-'a']}return node
}func (this *Trie) StartsWith(prefix string) bool {node := this.search(prefix)return node != nil
}

在這里插入圖片描述

// tree.gopackage zjgoimport "strings"type TreeNode struct {name     string      // 用 '/' 分割路徑后的每個路由名稱children []*TreeNode // 子節點
}// Put path --> /user/get/:id
// PUT 一般用于“更新”
// :id 是一種路由參數(Path Parameter)占位符
// /user/get/123 會匹配 /user/get/:id,并提取出 id = 123
// /user/get/abc 會匹配 /user/get/:id,提取出 id = abc
func (t *TreeNode) Put(path string) {root := tstrs := strings.Split(path, "/")for index, name := range strs {// 忽略第一個斜杠前的那個空格if index == 0 {continue}isMatch := falsefor _, node := range t.children {if node.name == name {isMatch = truet = nodebreak}}if !isMatch {node := &TreeNode{name:     name,children: make([]*TreeNode, 0),}t.children = append(t.children, node)t = node}}t = root
}// Get path --> /user/get/1
// GET 一般用于“查”
func (t *TreeNode) Get(path string) *TreeNode {strs := strings.Split(path, "/")for index, name := range strs {// 忽略第一個斜杠前的那個空格if index == 0 {continue}isMatch := falsefor _, node := range t.children {if node.name == name || node.name == "*" || strings.Contains(node.name, ":") {isMatch = truet = nodeif index == len(strs)-1 {return node}break}}if !isMatch {for _, node := range t.children {// /user/**// /user/get/userInfo// /user/aa/bbif node.name == "**" {// only onereturn node}}}}return nil
}// test_tree.go
package zjgoimport ("fmt""testing"
)func TestTreeNode(t *testing.T) {root := &TreeNode{name: "/", children: make([]*TreeNode, 0)}root.Put("/user/get/:id")root.Put("/user/info/hello")root.Put("/user/create/aaa")root.Put("/order/get/aaa")node := root.Get("/user/get/1")fmt.Println(node)node = root.Get("/user/info/hello")fmt.Println(node)node = root.Get("/user/create/aaa")fmt.Println(node)node = root.Get("/order/get/aaa")fmt.Println(node)
}=== RUN   TestTreeNode
&{:id []}
&{hello []}
&{aaa []}
&{aaa []}
--- PASS: TestTreeNode (0.00s)
PASS
ok      github.com/ErizJ/ZJGo/zjgo      (cached)

應用前綴樹

// utils.gopackage zjgoimport "strings"// 分割字符串,只留下去掉了路由組名字后的字路由的路徑
func SubStringLast(name string, groupName string) string {if index := strings.Index(name, groupName); index < 0 {return ""} else {return name[index+len(groupName):]}
}
// tree.gopackage zjgoimport "strings"type TreeNode struct {name       string      // 用 '/' 分割路徑后的每個路由名稱children   []*TreeNode // 子節點routerName string      // 前綴樹到當前節點所走過的路徑isEnd      bool        // 對bug的修正,防止 /user/hello/xx 這樣的路由 /user/hello 訪問這個路徑也一樣能從前綴樹查找出來,并不會報404
}// Put path --> /user/get/:id
// PUT 一般用于“更新”
// :id 是一種路由參數(Path Parameter)占位符
// /user/get/123 會匹配 /user/get/:id,并提取出 id = 123
// /user/get/abc 會匹配 /user/get/:id,提取出 id = abc
func (t *TreeNode) Put(path string) {strs := strings.Split(path, "/")for index, name := range strs {// 忽略第一個斜杠前的那個空格if index == 0 {continue}isMatch := falsefor _, node := range t.children {if node.name == name {isMatch = truet = nodebreak}}if !isMatch {node := &TreeNode{name:     name,children: make([]*TreeNode, 0),isEnd:    index == len(strs)-1,}t.children = append(t.children, node)t = node}}t.isEnd = true
}// Get path --> /user/get/1
// GET 一般用于“查”
func (t *TreeNode) Get(path string) *TreeNode {strs := strings.Split(path, "/")routerName := ""for index, name := range strs {// 忽略第一個斜杠前的那個空格if index == 0 {continue}isMatch := falsefor _, node := range t.children {if node.name == name || node.name == "*" || strings.Contains(node.name, ":") {isMatch = truerouterName += "/" + node.namenode.routerName = routerNamet = nodeif index == len(strs)-1 {return node}break}}if !isMatch {for _, node := range t.children {// /user/**// /user/get/userInfo// /user/aa/bbif node.name == "**" {// only onerouterName += "/" + node.namenode.routerName = routerNamereturn node}}}}return nil
}
// zj.gopackage zjgoimport ("fmt""log""net/http"
)const ANY = "ANY"// 定義處理響應函數
type HandleFunc func(ctx *Context)// 抽象出路由的概念
type routerGroup struct {name             string                           // 組名handleFuncMap    map[string]map[string]HandleFunc // 映射關系應該由每個路由組去維護handlerMethodMap map[string][]string              // 記錄GET,POST等請求方式所記錄的路由,實現對同一路由不同請求方式的支持TreeNode         *TreeNode                        // 記錄該路由組下的路由前綴樹
}// 定義路由結構體
type router struct {routerGroups []*routerGroup // 路由下面應該維護著不同的組
}// 添加路由組
func (r *router) Group(name string) *routerGroup {routerGroup := &routerGroup{name:             name,handleFuncMap:    make(map[string]map[string]HandleFunc),handlerMethodMap: make(map[string][]string),TreeNode:         &TreeNode{name: "/", children: make([]*TreeNode, 0)},}r.routerGroups = append(r.routerGroups, routerGroup)return routerGroup
}// 給路由結構體添加一個添加路由功能的函數
// func (routerGroup *routerGroup) Add(name string, handleFunc HandleFunc) {
// 	routerGroup.handleFuncMap[name] = handleFunc
// }// 由于 ANY POST GET 都需要重復相同的邏輯代碼,所以做一個提取操作
func (routerGroup *routerGroup) handleRequest(name string, method string, handleFunc HandleFunc) {if _, exist := routerGroup.handleFuncMap[name]; !exist {routerGroup.handleFuncMap[name] = make(map[string]HandleFunc)}if _, exist := routerGroup.handleFuncMap[name][method]; !exist {routerGroup.handleFuncMap[name][method] = handleFuncrouterGroup.handlerMethodMap[method] = append(routerGroup.handlerMethodMap[method], name)} else {panic("Under the same route, duplication is not allowed!!!")}routerGroup.TreeNode.Put(name)
}// Any代表支持任意的請求方式
func (routerGroup *routerGroup) Any(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, ANY, handleFunc)
}// POST代表支持POST請求方式
func (routerGroup *routerGroup) Post(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodPost, handleFunc)
}// GET代表支持GET請求方式
func (routerGroup *routerGroup) Get(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodGet, handleFunc)
}// 只要實現 ServeHTTP 這個方法,就相當于實現了對應的 HTTP 處理器
// 結構體 Engine 實現了 ServeHTTP(w http.ResponseWriter, r *http.Request) 方法
// 所以它就自動實現了 http.Handler 接口,因此可以直接被用于 http.ListenAndServe
// Engine 實現了 ServeHTTP,它就是一個合法的 http.Handler,可以用 http.Handle 來綁定它到某個具體的路由路徑上!
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {// 在 Go 的 net/http 包中,r *http.Request 代表了客戶端發來的 HTTP 請求對象。// 可以通過 r.Method 來獲取這次請求使用的是什么方法(Method),例如:GET、POST、PUT、DELETE 等。if r.Method == http.MethodGet {fmt.Fprintf(w, "這是一個 GET 請求!!! ")} else if r.Method == http.MethodPost {fmt.Fprintf(w, "這是一個 POST 請求!!! ")} else {fmt.Fprintf(w, "這是一個其他類型的請求:%s!!! ", r.Method)}for _, group := range e.routerGroups {routerName := SubStringLast(r.RequestURI, "/"+group.name)if node := group.TreeNode.Get(routerName); node != nil && node.isEnd {// 路由匹配上了ctx := &Context{W: w, R: r}// 先判斷當前請求路由是否支持任意請求方式的Anyif handle, exist := group.handleFuncMap[node.routerName][ANY]; exist {handle(ctx)return}// 不支持 Any,去該請求所對應的請求方式對應的 Map 里面去找是否有對應的路由if handle, exist := group.handleFuncMap[node.routerName][r.Method]; exist {handle(ctx)return}// 沒找到對應的路由,說明該請求方式不允許w.WriteHeader(http.StatusMethodNotAllowed)fmt.Fprintf(w, "%s %s not allowed!!!\n", r.Method, r.RequestURI)return}// for name, methodHandleMap := range group.handleFuncMap {// 	url := "/" + group.name + name// 	//判斷一下當前的url是否等于請求的url,即路由匹配// 	if r.RequestURI == url {// 	}// }}w.WriteHeader(http.StatusNotFound)fmt.Fprintf(w, "%s %s not found!!!\n", r.Method, r.RequestURI)return
}// 定義一個引擎結構體
type Engine struct {router
}// 引擎結構體的初始化方法
func New() *Engine {return &Engine{router: router{},}
}// 引擎的啟動方法
func (e *Engine) Run() {// for _, group := range e.routerGroups {// 	for name, value := range group.handleFuncMap {// 		http.HandleFunc("/"+group.name+name, value)// 	}// }// 把 e 這個http處理器綁定到對應路由下http.Handle("/", e)err := http.ListenAndServe(":3986", nil)if err != nil {log.Fatal(err)}
}
// main.gopackage mainimport ("fmt""net/http""github.com/ErizJ/ZJGo/zjgo"
)func main() {fmt.Println("Hello World!")// // 注冊 HTTP 路由 /hello// http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!")// })// // 啟動 HTTP 服務器// err := http.ListenAndServe("8111", nil)// if err != nil {// 	log.Fatal(err)// }engine := zjgo.New()g1 := engine.Group("user")g1.Get("/hello", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/hello")})// 瀏覽器地址欄輸入的都是 GET 請求// 需要用 curl 或 Postman 來發一個真正的 POST 請求,才會命中 Post handlerg1.Post("/info", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, http.MethodPost+" Hello Go!——user/info——POST")})g1.Get("/info", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/info——GET")})g1.Get("/get/:id", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/get/:id——GET")})g1.Get("/isEnd/get", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/isEnd/get——GET")})// 只要路由匹配,就會執行對應的處理函數g1.Any("/any", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, " Hello Go!——user/any")})// g2 := engine.Group("order")// g2.Add("/hello", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!——order/hello")// })// g2.Add("/info", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!——order/info")// })fmt.Println("Starting...")engine.Run()}

完善路由代碼

// zj.gopackage zjgoimport ("fmt""log""net/http"
)const ANY = "ANY"// 定義處理響應函數
type HandleFunc func(ctx *Context)// 抽象出路由的概念
type routerGroup struct {name             string                           // 組名handleFuncMap    map[string]map[string]HandleFunc // 映射關系應該由每個路由組去維護handlerMethodMap map[string][]string              // 記錄GET,POST等請求方式所記錄的路由,實現對同一路由不同請求方式的支持TreeNode         *TreeNode                        // 記錄該路由組下的路由前綴樹
}// 定義路由結構體
type router struct {routerGroups []*routerGroup // 路由下面應該維護著不同的組
}// 添加路由組
func (r *router) Group(name string) *routerGroup {routerGroup := &routerGroup{name:             name,handleFuncMap:    make(map[string]map[string]HandleFunc),handlerMethodMap: make(map[string][]string),TreeNode:         &TreeNode{name: "/", children: make([]*TreeNode, 0)},}r.routerGroups = append(r.routerGroups, routerGroup)return routerGroup
}// 給路由結構體添加一個添加路由功能的函數
// func (routerGroup *routerGroup) Add(name string, handleFunc HandleFunc) {
// 	routerGroup.handleFuncMap[name] = handleFunc
// }// 由于 ANY POST GET 都需要重復相同的邏輯代碼,所以做一個提取操作
func (routerGroup *routerGroup) handleRequest(name string, method string, handleFunc HandleFunc) {if _, exist := routerGroup.handleFuncMap[name]; !exist {routerGroup.handleFuncMap[name] = make(map[string]HandleFunc)}if _, exist := routerGroup.handleFuncMap[name][method]; !exist {routerGroup.handleFuncMap[name][method] = handleFuncrouterGroup.handlerMethodMap[method] = append(routerGroup.handlerMethodMap[method], name)} else {panic("Under the same route, duplication is not allowed!!!")}routerGroup.TreeNode.Put(name)
}// Any代表支持任意的請求方式
func (routerGroup *routerGroup) Any(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, ANY, handleFunc)
}// POST代表支持POST請求方式
func (routerGroup *routerGroup) Post(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodPost, handleFunc)
}// GET代表支持GET請求方式
func (routerGroup *routerGroup) Get(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodGet, handleFunc)
}// DELETE代表支持DELETE請求方式
func (routerGroup *routerGroup) Delete(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodDelete, handleFunc)
}// PUT代表支持PUT請求方式
func (routerGroup *routerGroup) Put(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodPut, handleFunc)
}// PATCH代表支持PATCH請求方式
func (routerGroup *routerGroup) Patch(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodPatch, handleFunc)
}// 只要實現 ServeHTTP 這個方法,就相當于實現了對應的 HTTP 處理器
// 結構體 Engine 實現了 ServeHTTP(w http.ResponseWriter, r *http.Request) 方法
// 所以它就自動實現了 http.Handler 接口,因此可以直接被用于 http.ListenAndServe
// Engine 實現了 ServeHTTP,它就是一個合法的 http.Handler,可以用 http.Handle 來綁定它到某個具體的路由路徑上!
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {// 在 Go 的 net/http 包中,r *http.Request 代表了客戶端發來的 HTTP 請求對象。// 可以通過 r.Method 來獲取這次請求使用的是什么方法(Method),例如:GET、POST、PUT、DELETE 等。if r.Method == http.MethodGet {fmt.Fprintf(w, "這是一個 GET 請求!!! ")} else if r.Method == http.MethodPost {fmt.Fprintf(w, "這是一個 POST 請求!!! ")} else {fmt.Fprintf(w, "這是一個其他類型的請求:%s!!! ", r.Method)}for _, group := range e.routerGroups {routerName := SubStringLast(r.RequestURI, "/"+group.name)if node := group.TreeNode.Get(routerName); node != nil && node.isEnd {// 路由匹配上了ctx := &Context{W: w, R: r}// 先判斷當前請求路由是否支持任意請求方式的Anyif handle, exist := group.handleFuncMap[node.routerName][ANY]; exist {handle(ctx)return}// 不支持 Any,去該請求所對應的請求方式對應的 Map 里面去找是否有對應的路由if handle, exist := group.handleFuncMap[node.routerName][r.Method]; exist {handle(ctx)return}// 沒找到對應的路由,說明該請求方式不允許w.WriteHeader(http.StatusMethodNotAllowed)fmt.Fprintf(w, "%s %s not allowed!!!\n", r.Method, r.RequestURI)return}// for name, methodHandleMap := range group.handleFuncMap {// 	url := "/" + group.name + name// 	//判斷一下當前的url是否等于請求的url,即路由匹配// 	if r.RequestURI == url {// 	}// }}w.WriteHeader(http.StatusNotFound)fmt.Fprintf(w, "%s %s not found!!!\n", r.Method, r.RequestURI)return
}// 定義一個引擎結構體
type Engine struct {router
}// 引擎結構體的初始化方法
func New() *Engine {return &Engine{router: router{},}
}// 引擎的啟動方法
func (e *Engine) Run() {// for _, group := range e.routerGroups {// 	for name, value := range group.handleFuncMap {// 		http.HandleFunc("/"+group.name+name, value)// 	}// }// 把 e 這個http處理器綁定到對應路由下http.Handle("/", e)err := http.ListenAndServe(":3986", nil)if err != nil {log.Fatal(err)}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/76154.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/76154.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/76154.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

zabbix中通過模板實現自動發現對tcp端口批量監控

主要為了解決監控大量端口&#xff0c;避免繁瑣的重復操作監控項和觸發器 諸位~ 僅供參考哈 自動發現監控參考地址: https://blog.csdn.net/qq_37510195/article/details/130893655 模板 首先創建一個模板 自定義名稱和群組 創建自動發現規則 模板——自動發現——創建發現規則…

Mysql備忘記錄

1、簡介 Mysql操作經常忘記命令&#xff0c;本文將持續記錄Mysql一些常用操作。 2、常見問題 2.1、忘記密碼 # 1、首先停止Mysql服務 systemctl stop mysqld # windows 從任務管理器里面停 # 2、更改配置文件 my.cnf (windows是 ini文件) vim /etc/my.cnf 在[mysqld]下面添…

【藍橋杯】15屆JAVA研究生組F回文字符串

一、思路 1.這題去年考的時候想的是使用全排列進行嘗試&#xff0c;實際不用這么麻煩&#xff0c;只用找到第一個和最后一個非特殊字符串的位置&#xff0c;然后分別向內檢查是否對稱&#xff0c;向外檢查是否對稱直到左指針小于0(可以通過添加使其對稱) 2.至于如何找到第一個…

X 進制減法

題目鏈接&#xff1a; 思路&#xff1a; X進制數321怎么轉換為十進制數為65&#xff1f;如下圖&#xff1a; ①題目要求我們求 A - B 的最小值&#xff0c;對第 i 位&#xff0c;要求 A[i] - B[i] 的最小值&#xff0c;當進制越小的時候差值越小&#xff0c;但進制要比 max&…

java線程安全-單例模式-線程通信

首先看看單例模式的寫法 首先我們先來回顧一下餓漢式單例模式&#xff1a; class Singleton{private static Singleton singletonnew Singleton();private Singleton(){}public static Singleton getInstrance(){return singleton;} } public class Test{public static void …

大數據技術之SPARK

Spark Core 什么是 RDD 代碼中是一個抽象類&#xff0c;它代表一個彈性的、不可變、可分區、里面的元素可并行計算的集合 彈性 存儲的彈性&#xff1a;內存與磁盤的自動切換&#xff1b; 容錯的彈性&#xff1a;數據丟失可以自動恢復&#xff1b; 計算的彈性&#xff1a;…

Go 語言范圍 (Range)

Go 語言范圍 (Range) Go 語言是一種靜態強類型、編譯型、并發型編程語言&#xff0c;由 Google 開發。它的簡潔性和高效性使其成為眾多開發者的首選。在 Go 語言中&#xff0c;range 是一個非常有用的關鍵字&#xff0c;用于遍歷數組、切片、字符串以及通道&#xff08;channe…

VUE中數據綁定之OptionAPI

<template> <div> 姓名:<input v-model="userName" /> {{ userName }} <br /> 薪水:<input type="number" v-model="salary" /> <br /> <button v-on:click="submit">提交</button>…

react動態路由

框架的權限控制&#xff08;在config.ts中配置&#xff09; export default {access: {}, }; 權限配置文件&#xff08;access.ts&#xff09; 新建 src/access.ts &#xff0c;在該文件中 export default 一個函數&#xff0c;定義用戶擁有的權限 該文件需要返回一個 functi…

Android里面如何優化xml布局

在 Android 開發中&#xff0c;以下是系統化的優化方案&#xff0c;從基礎到高級分層解析&#xff1a; 一、基礎優化策略 1. 減少布局層級 問題&#xff1a;每增加一層布局&#xff0c;測量/布局時間增加 1-2ms 解決方案&#xff1a; <!-- 避免嵌套 --> <LinearLayo…

基于STM32、HAL庫的IP6525S快充協議芯片簡介及驅動程序設計

一、簡介: IP6525S是一款高性能的同步降壓DC-DC轉換器芯片,具有以下特點: 輸入電壓范圍:4.5V至32V 輸出電壓范圍:0.8V至30V 最大輸出電流:5A 效率高達95% 可編程開關頻率(100kHz-1MHz) 支持PWM和PFM模式 內置過流保護、過溫保護等功能 該芯片常用于工業控制、通信設備…

二分算法的入門筆記

二分查找 使用前提&#xff1a;有序。可理解為枚舉的一種技巧。時間復雜度&#xff1a; l o g ( n ) log(n) log(n) 基礎模版代碼 使用時根據情景進行相應的變化。注意跳出條件and分支處理方式and返回答案&#xff0c;三者之間的配合&#xff0c;不要進入死循環。可以在模擬…

輕量級Java跨包調用(完全解耦)

Java函數式命令模式 輕量級跨包調用解耦方案&#xff0c;讓跨包調用就像調用本地接口那樣簡單。適用與具有公共依賴的兩個jar包&#xff0c;但是又不想相互引入對方作為依賴。 函數式命令模式&#xff0c;很好地實現了跨包調用解耦的目標&#xff0c;并且通過泛型JSON動態轉換保…

離散數學問題集--問題5.9

問題 5.9 綜合了計算機組成原理、數字邏輯和離散數學中的關鍵概念&#xff0c;旨在幫助學生理解二進制算術運算的硬件實現、邏輯門與算術運算的關系&#xff0c;以及如何使用數學方法來驗證數字系統的正確性。它強調了從規范到實現再到驗證的完整過程。 思想 函數抽象&#xf…

OpenLayers:海量圖形渲染之矢量切片

最近由于在工作中涉及到了海量圖形渲染的問題&#xff0c;因此我開始研究相關的解決方案。在咨詢了許多朋友之后發現矢量切片似乎是行業內最常用的一種解決方案&#xff0c;于是我便開始研究它該如何使用。 一、什么是矢量切片 矢量切片按照我的理解就是用柵格切片的方式把矢…

神經網絡入門—自定義網絡

網絡模型 定義一個兩層網絡 import torch import torch.nn as nn import torch.optim as optim import torch.nn.functional as F# 定義神經網絡模型 class Net(nn.Module):def __init__(self, init_x0.0):super().__init__()self.fc1 nn.Linear(1, 10)self.fc2 nn.Linear(…

無人機裝調與測試

文章目錄 前言一、無人機基本常識/預備知識&#xff08;一&#xff09;無人機飛行原理無人機硬件組成/各組件作用1.飛控2.GPS3.接收機4.電流計5.電調6.電機7.電池8.螺旋槳9.UBEC&#xff08;穩壓模塊&#xff09; &#xff08;二&#xff09;飛控硬件簡介&#xff08;三&#x…

2024年-全國大學生數學建模競賽(CUMCM)試題速瀏、分類及淺析

2024年-全國大學生數學建模競賽(CUMCM)試題速瀏、分類及淺析 全國大學生數學建模競賽&#xff08;China Undergraduate Mathematical Contest in Modeling&#xff09;是國家教委高教司和中國工業與應用數學學會共同主辦的面向全國大學生的群眾性科技活動&#xff0c;目的在于激…

Linux入門指南:從零開始探索開源世界

&#x1f680; 前言 大家好&#xff01;今天我們來聊一聊Linux這個神奇的操作系統~ &#x1f916; 很多小伙伴可能覺得Linux是程序員專屬&#xff0c;其實它早已滲透到我們生活的各個角落&#xff01;本文將帶你了解Linux的誕生故事、發行版選擇攻略、應用領域&#xff0c;還有…

記錄vscode連接不上wsl子系統下ubuntu18.04問題解決方法

記錄vscode連接不上wsl子系統下ubuntu18.04問題解決方法 報錯內容嘗試第一次解決方法嘗試第二次解決方法注意事項參考連接 報錯內容 Unable to download server on client side: Error: Request downloadRequest failed unexpectedly without providing any details… Will tr…