Golang中HTTP路由設(shè)計(jì)的使用與實(shí)現(xiàn)
Golang之HTTP路由設(shè)計(jì)
為什么要設(shè)計(jì)路由規(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)
// 找到類型下的具體地址的映射地址的方法,這里還沒有實(shí)現(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)
}如何實(shí)現(xiàn)動態(tài)路由
首先先定義好我們的動態(tài)路由數(shù)據(jù)結(jié)構(gòu)
// 實(shí)現(xiàn)動態(tài)路由匹配樹
type Tree struct {
root *node // 根結(jié)點(diǎn)
}
// 代表節(jié)點(diǎn)
type node struct {
isLast bool // 代表這個節(jié)點(diǎn)是否可以成為最終的路由規(guī)則。 該節(jié)點(diǎn)是否能成為一
segment string // url 中的字符串,代表這個節(jié)點(diǎn)表示的路由中某個段的字符串
handler ControllerHandler // 代表這個節(jié)點(diǎn)中包含的控制器,用于最終加載調(diào)用
childes []*node // 代表這個節(jié)點(diǎn)下的子節(jié)點(diǎn)
}我們要做的就是在每次注冊的時候去將對應(yīng)的路徑的東西將之前的map[string]map[string]ControllerHandler替換為新改造的這個Tree!
從node的結(jié)構(gòu)來看我們應(yīng)該判斷我們的segment去添加我們的childes的node在最后的節(jié)點(diǎn)的時候賦值一下處理方法
//matchNode 方法的參數(shù)是一個 URI,返回值是指向 node 的指針,它的實(shí)現(xiàn)思路是使用函數(shù)遞歸
// 判斷是否動態(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
}
// 如果是動態(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)里面是否有動態(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é)點(diǎn)
//判斷如果不是動態(tài)路由,那么都統(tǒng)一大寫
if !isWildSegment(segment) {
segment = strings.ToUpper(segment)
}
// 找到下一層路由節(jié)點(diǎn)
nodes := n.filterChildNodes(segment)
// 錯誤返回
if nodes == nil || len(nodes) <= 0 {
return nil
}
//如果只有一個子節(jié)點(diǎn)了,是最后的話就返回最后的一個路由節(jié)點(diǎn)
if len(segments) == 1 {
for _, node := range nodes {
if node.isLast {
return node
}
}
return nil
}
// 否則持續(xù)循環(huán)去判斷各個節(jié)點(diǎn)集合中的遞歸下一層
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é)點(diǎn) 需要轉(zhuǎn)變大寫
if !isWildSegment(segment) {
segment = strings.ToUpper(segment)
}
isLast := index == len(segments)-1 // 判斷是否為最后一個節(jié)點(diǎn)
var objNode *node
childNodes := n.filterChildNodes(segment)
if len(childNodes) > 0 {
// 如果有segment相同的子節(jié)點(diǎn),則選擇這個子節(jié)點(diǎn)
for _, node := range childNodes {
if node.segment == segment {
objNode = node
break
}
}
}
// 如果沒有找到相同的子節(jié)點(diǎn),那么就自己構(gòu)造一個添加進(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
}
// 尋找對應(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)了動態(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
}
// 注冊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
}驗(yàn)證
編寫兩個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
}啟動運(yùn)行
到此這篇關(guān)于Golang中HTTP路由設(shè)計(jì)的使用與實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Golang HTTP路由設(shè)計(jì)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺析Go中函數(shù)的健壯性,panic異常處理和defer機(jī)制
這篇文章主要為大家詳細(xì)介紹了Go中函數(shù)的健壯性,panic異常處理和defer機(jī)制的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-10-10
Go整合captcha實(shí)現(xiàn)驗(yàn)證碼功能
最近在使用Go語言搞一個用戶登錄&注冊的功能,我們油然會產(chǎn)生一種增加驗(yàn)證碼的想法。后來在GitHub上找到了這個名叫captcha的插件,于是就利用文檔進(jìn)行了初步的學(xué)習(xí),并融入到自己的項(xiàng)目中,整個過程下來感覺這個插件的設(shè)計(jì)非常巧妙2023-03-03
golang實(shí)現(xiàn)簡單的tcp數(shù)據(jù)傳輸
這篇文章主要為大家介紹了golang實(shí)現(xiàn)簡單的tcp數(shù)據(jù)傳輸,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
Golang 實(shí)現(xiàn) Redis系列(六)如何實(shí)現(xiàn) pipeline 模式的 redis 客戶端
pipeline 模式的 redis 客戶端需要有兩個后臺協(xié)程負(fù)責(zé) tcp 通信,調(diào)用方通過 channel 向后臺協(xié)程發(fā)送指令,并阻塞等待直到收到響應(yīng),本文是使用 golang 實(shí)現(xiàn) redis 系列的第六篇, 將介紹如何實(shí)現(xiàn)一個 Pipeline 模式的 Redis 客戶端。2021-07-07

