目录
- Golang之HTTP路由设计
- 动手编写自己的路由
- framework/core.go
- framework/group.go
- 如何实现动态路由
- 改造一下core.go
- 验证
Golang之HTTP路由设计
为什么要设计路由规则,路由规则是HTTP的请求按照一定的规则 ,匹配查找到对应的控制器并传递执行的逻辑!
自己编写路由的话需要注意一下一共有几种路由!
- 一种是支持原生的restful四种类型的访问方法!
Get
,Post
,Delete
,Put
- 需要支持自定义的路径,也就是静态路由
- 批量通用前缀,也就是下面我们将讲到的
group
- 动态路由匹配!
也就是像这样我们在route.go
去注册
func registerRouter(core *framework.Core) {
print(111)
// 设置控制器
core.Get("/foo", FooController)
core.Get("/user/login", UserLoginController)
subjectApi := core.Group("/subject")
{
// restful路由,根据请求类型区分了开,:id为动态路由
subjectApi.Get("/list/all", SubjectListController)
subjectApi.Post("/add", SubjectListController)
subjectApi.Delete("/:id", SubjectListController)
subjectApi.Put("/:id", SubjectListController)
subjectApi.Get("/:id", SubjectListController)
}
}
动手编写自己的路由
在上一节中我们编写了自己的请求处理器,对应在里面加入我们的路由规则就好了!
framework/core.go
package framework
import (
"net/http"
"strings"
)
const (
GET = "GET"
PUT = "PUT"
DELETE = "DELETE"
POST = "POST"
)
//map[string]map[string]ControllerHandler 前面存请求类型后面是路径对应执行方法
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+静态路由
func (c *Core) FindRouteByRequest(request *http.Request) ControllerHandler {
uri := request.URL.Path //请求处理器映射地址
method := request.Method // 请求类型
upperMethod := strings.ToUpper(method)
upperURI := strings.ToUpper(uri)
// 找到类型下的具体地址的映射地址的方法,这里还没有实现动态什么的就固定有1个路径key,但是先别急,后面我们再来动手改造
if data, ok := c.router[upperMethod]; ok {
if handler, ok1 := data[upperURI]; ok1 {
return handler
}
}
return nil
}
framework/group.go
给我们的注册路由,加上分组,用group包装,这样对应我们在使用group时就会对应到不同的请求类型的方法了!并且在这一层给所有的注册地址统一加上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)
}
如何实现动态路由
首先先定义好我们的动态路由数据结构
// 实现动态路由匹配树
type Tree struct {
root *node // 根结点
}
// 代表节点
type node struct {
isLast bool // 代表这个节点是否可以成为最终的路由规则。 该节点是否能成为一
segment string // url 中的字符串,代表这个节点表示的路由中某个段的字符串
handler ControllerHandler // 代表这个节点中包含的控制器,用于最终加载调用
childes []*node // 代表这个节点下的子节点
}
我们要做的就是在每次注册的时候去将对应的路径的东西将之前的map[string]map[string]ControllerHandler
替换为新改造的这个Tree!
从node的结构来看我们应该判断我们的segment
去添加我们的childes的node在最后的节点的时候赋值一下处理方法
//matchNode 方法的参数是一个 URI,返回值是指向 node 的指针,它的实现思路是使用函数递归
// 判断是否动态路由
func isWildSegment(segment string) bool {
return strings.HasPrefix(segment, ":")
}
下面是我们需要的一些功能函数,递归匹配路由和找到下一层的子节点
//过滤下一层满足 segment 规则的子节点
func (n *node) filterChildNodes(segment string) []*node {
if len(n.childes) == 0 {
return nil
}
// 如果是动态路由则子节点直接满足条件
if isWildSegment(segment) {
return n.childes
}
// 不是的话就从子节点里面找2
nodes := make([]*node, 0, len(n.childes))
for _, node := range n.childes {
// 判断所有子节点里面是否有动态路由或者唯一匹配的路由
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] // 第一个路由节点
//判断如果不是动态路由,那么都统一大写
if !isWildSegment(segment) {
segment = strings.ToUpper(segment)
}
// 找到下一层路由节点
nodes := n.filterChildNodes(segment)
// 错误返回
if nodes == nil || len(nodes) <= 0 {
return nil
}
//如果只有一个子节点了,是最后的话就返回最后的一个路由节点
if len(segments) == 1 {
for _, node := range nodes {
if node.isLast {
return node
}
}
return nil
}
// 否则持续循环去判断各个节点集合中的递归下一层
for _, v := range nodes {
toMatch := v.matchNode(segments[1])
if toMatch != nil {
return toMatch
}
return nil
}
return nil
}
下面是增加路由,以及提供给外部用的,找到对应执行逻辑的控制器方法!
// 增加路由
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 {
// 不是动态路由的静态节点 需要转变大写
if !isWildSegment(segment) {
segment = strings.ToUpper(segment)
}
isLast := index == len(segments)-1 // 判断是否为最后一个节点
var objNode *node
childNodes := n.filterChildNodes(segment)
if len(childNodes) > 0 {
// 如果有segment相同的子节点,则选择这个子节点
for _, node := range childNodes {
if node.segment == segment {
objNode = node
break
}
}
}
// 如果没有找到相同的子节点,那么就自己构造一个添加进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
将实现了动态路由的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+静态路由
func (c *Core) FindRouteByRequest(request *http.Request) ControllerHandler {
uri := request.URL.Path
method := request.Method
upperMethod := strings.ToUpper(method)
// upperURI := strings.ToUpper(uri) 内部路由会去判断非动态会转大写
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
}
启动运行