Golang中HTTP路由設計的使用與實現(xiàn)
Golang之HTTP路由設計
為什么要設計路由規(guī)則,路由規(guī)則是HTTP的請求按照一定的規(guī)則 ,匹配查找到對應的控制器并傳遞執(zhí)行的邏輯!
自己編寫路由的話需要注意一下一共有幾種路由!
- 一種是支持原生的restful四種類型的訪問方法!
Get,Post,Delete,Put - 需要支持自定義的路徑,也就是靜態(tài)路由
- 批量通用前綴,也就是下面我們將講到的
group - 動態(tài)路由匹配!
也就是像這樣我們在route.go去注冊
func registerRouter(core *framework.Core) {
print(111)
// 設置控制器
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é)中我們編寫了自己的請求處理器,對應在里面加入我們的路由規(guī)則就好了!
framework/core.go
package framework
import (
"net/http"
"strings"
)
const (
GET = "GET"
PUT = "PUT"
DELETE = "DELETE"
POST = "POST"
)
//map[string]map[string]ControllerHandler 前面存請求類型后面是路徑對應執(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包裝,這樣對應我們在使用group時就會對應到不同的請求類型的方法了!并且在這一層給所有的注冊地址統(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ù)結構
// 實現(xiàn)動態(tài)路由匹配樹
type Tree struct {
root *node // 根結點
}
// 代表節(jié)點
type node struct {
isLast bool // 代表這個節(jié)點是否可以成為最終的路由規(guī)則。 該節(jié)點是否能成為一
segment string // url 中的字符串,代表這個節(jié)點表示的路由中某個段的字符串
handler ControllerHandler // 代表這個節(jié)點中包含的控制器,用于最終加載調用
childes []*node // 代表這個節(jié)點下的子節(jié)點
}我們要做的就是在每次注冊的時候去將對應的路徑的東西將之前的map[string]map[string]ControllerHandler替換為新改造的這個Tree!
從node的結構來看我們應該判斷我們的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
}下面是增加路由,以及提供給外部用的,找到對應執(zhí)行邏輯的控制器方法!
// 增加路由
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 {
// 不是動態(tài)路由的靜態(tài)節(jié)點 需要轉變大寫
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é)點,那么就自己構造一個添加進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
}改造一下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)會轉大寫
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路由設計的使用與實現(xiàn)的文章就介紹到這了,更多相關Golang HTTP路由設計內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Redis?BloomFilter布隆過濾器原理與實現(xiàn)
你在開發(fā)或者面試過程中,有沒有遇到過?海量數(shù)據(jù)需要查重,緩存穿透怎么避免等等這樣的問題呢?下面這個東西超棒,好好了解下,面試過關斬將,凸顯你的不一樣2022-10-10
Go語言網(wǎng)站使用異步編程和Goroutine提高Web的性能
作為一門現(xiàn)代化編程語言,Go語言提供了強大的異步編程能力,使得程序員可以以更高效的方式處理并發(fā)任務,在Go語言中,使用Goroutine在單個進程中實現(xiàn)多任務并行處理,以及如何使用協(xié)程池來進一步提高Web服務器的處理能力,2024-01-01
Golang使用Gin框架實現(xiàn)路由分類處理請求流程詳解
Gin是一個golang的微框架,封裝比較優(yōu)雅,具有快速靈活,容錯方便等特點,這篇文章主要介紹了Golang使用Gin框架實現(xiàn)路由分類處理請求,感興趣的同學可以參考下文2023-05-05

