zoukankan      html  css  js  c++  java
  • 仿照Go web框架gin手写自己的web框架 【下】

    最后,就差几个核心功能了,路由分组,中间件,异常恢复。

    声明: 三部曲文章主要参考: https://geektutu.com/post/gee.html

    直接把这三个核心的点放到整体代码中,不好理解,所以我打算把这三个单独拆出来,单独分析。

    路由分组

    实现类似gin框架的这种效果,实例对象可以直击调用GET, 路由分组的对象也可以调用 GET方法,并且分组路由是有层级继承关系

    package main
    
    import (
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	r := gin.New()
    
    	r.GET("/", func(context *gin.Context) {
    		context.JSON(200, gin.H{
    			"index": "首页",
    		})
    	})
    
    	v1 := r.Group("/v1")
    	v1.GET("/api", func(context *gin.Context) {
    		context.JSON(200, gin.H{
    			"/v1/api": "分组路由",
    		})
    	})
    	_ = r.Run("127.0.0.1:7000")
    }
    

    简单实现一个核心路由分组的代码 本段代码GitHub地址

    package main
    
    import (
    	"fmt"
    	"testing"
    )
    
    // 路由分组
    // https://github.com/gin-gonic/gin/blob/master/routergroup.go#L41
    type RouterGroup struct {
    	prefix string		// 前缀
    	engine *Engine		// 内部的Engine始终保证为共享的一个
    	parent *RouterGroup	// 父路由
    }
    
    // 核心
    type Engine struct {
    	*RouterGroup  // 使Engine 也拥有RouterGroup的方法
    }
    
    // Get方法 这里没有实现响应处理函数
    func (group *RouterGroup) Get(pattern string) {
    	fmt.Printf("Get %s%s
    ", group.prefix, pattern)
    }
    
    // 创建路由分组
    func (group *RouterGroup) Group(prefix string) *RouterGroup {
    	return &RouterGroup{
    		prefix: group.prefix + prefix, // 前缀为上一个 路由分组前缀 加下一个
    		parent: group,                 // 当前路由设置为父路由
    	}
    }
    
    func New() *Engine {
    	engine := &Engine{}
    	// RouterGroup里面的 engine属性为 自身的engine  确保所有的engine 为一个
    	engine.RouterGroup = &RouterGroup{engine: engine}
    	return engine
    }
    
    func TestRouter(t *testing.T) {
    	// 创建实例
    	r := New()
    
    	r.Get("/index")
    
    	// 路由分组
    	api := r.Group("/api")
    	api.Get("/123")
    
    	// api子路由分组
    	v1 := api.Group("/v1")
    	v1.Get("/666")
    
    	// 输出
    	// Get /index
    	// Get /api/123
    	// Get /api/v1/666
    }
    

    上面gin框架就设置了 两个路由,为此我们可以实现路由分组最基础的功能来复刻一下。
    主要是 RouterGroupEngine 两个结构体,两者之间互相递归属性,可以实现多级路由分组。

    我简单用Python复刻一遍路由分组
    class RouterGroup(object):
        def __init__(self):
            self.prefix = ""   # 路由前缀
    
        def group(self, *, prefix):
            self.prefix = self.prefix + prefix
            return self
    
        def http_get(self, pattern: str):
            print(f"GET {self.prefix}{pattern}")
    
    
    class Engine(RouterGroup):
        def __init__(self):
            super(Engine, self).__init__()
            self.RouterGroup = RouterGroup
    
    
    e = Engine()
    e.http_get("/api")
    
    v1_router = e.group(prefix="/v1")
    v1_router.http_get("/666")
    
    v1_auth = v1_router.group(prefix="/auth")
    v1_auth.http_get("/admin")
    # GET /api
    # GET /v1/666
    # GET /v1/auth/admin
    

    中间件功能核心实现

    首先中间件功能呢,形象的比喻就好像拿东西穿洋葱一样,从一边进去,先穿过一层层的外壁,然后经过内芯,最后再穿过一层层外壁出来。

    伪代码就好像这样A和B表示两个中间件函数, c表示请求上下文信息。c.Next() 表示交出执行权,给下一个函数执行,执行完了之后再执行这个。

    func A(c *Context){
    	fmt.Println("A--1")
    	c.Next()
    	fmt.Println("A--2")  	      
    }
    
    func B(c *Context){
          	fmt.Println("B-------1")
    	c.Next()
    	fmt.Println("B-------2")
    }
    

    所以上面的函数执行输出路径,我们期望是这样的,和穿洋葱的例子结合起来就非常形象了。

    // A--1
    // B-------1
    // 请求处理函数
    // B-------2
    // A--2
    
    // 最终执行顺序就是: A1-> B1 -> 处理函数 -> B2 -> A1
    

    所以最终函数就如下所示

    package main
    
    import (
    	"fmt"
    	"testing"
    )
    
    type HandlerFunc func(c *Context)
    
    // HandlersChain defines a HandlerFunc array.
    type HandlersChain []HandlerFunc
    
    // 管理中间件的上下文
    type Context struct {
    	// middleware
    	handlers HandlersChain //存储顺序 [中间件1, 中间件2, 中间件3...., 请求处理函数]
    	index    int
    }
    
    func newContext() *Context {
    	return &Context{
    		// gin 里面初始化变量
    		// https://github.com/gin-gonic/gin/blob/master/context.go#L91
    		index: -1,
    	}
    }
    
    // 下一步调用
    func (c *Context) Next() {
    	// 如果把 Next() c.index++ 去掉,就会一直卡在第一个中间件上了,必然死循环。
    	// 每调用一次 Next(), c.index 必须得 +1,否则 c.index 就会一直是 0
            // 参考gin https://github.com/gin-gonic/gin/blob/master/context.go#L163
    	c.index++            // 0
    	s := len(c.handlers)  // 3
    	for ; c.index < s; c.index++ {
    		c.handlers[c.index](c)
    	}
    }
    
    // 直接中断响应
    func (c *Context) Fail(code int, err string) {
    	// 直接让计数下角标 == 中间件数组长度, 即上Next的 index < 3 不成立
    	c.index = len(c.handlers)
    	// 返回响应信息等 操作
    	fmt.Println(err)
    }
    
    
    // A 中间件
    func middlewareA(c *Context) {
    	fmt.Println("A--1")
    	//c.Fail(500, "中断操作")
    	c.Next()
    	fmt.Println("A--2")
    }
    
    // B 中间件
    func middlewareB(c *Context) {
    	fmt.Println("B-------1")
    	c.Next()
    	fmt.Println("B-------2")
    }
    
    // 请求处理到函数
    func handleFunc(c *Context) {
    	fmt.Println("请求处理函数")
    }
    
    func TestMiddleware(t *testing.T) {
    
    	n := newContext()
    
    	n.handlers = append(n.handlers, middlewareA)
    	n.handlers = append(n.handlers, middlewareB)
    	n.handlers = append(n.handlers, handleFunc)
    
    	// 启动
    	n.Next()
    }
    
    Python 复刻上面中间件执行顺序
    class Context(object):
    
        def __init__(self):
            self.handles = []
            self.index = -1
    
        def func_next(self):
            self.index += 1
            s = len(self.handles)
            while self.index < s:
                self.handles[self.index](self)
                self.index += 1
    
    
    def middleware_a(c: Context):
        print("A--1")
        c.func_next()
        print("A--2")
    
    
    def middleware_b(c: Context):
        print("B-----1")
        c.func_next()
        print("B-----2")
    
    
    def func_task(c: Context):
        print("请求处理的函数")
    
    
    ctx = Context()
    ctx.handles.append(middleware_a)
    ctx.handles.append(middleware_b)
    ctx.handles.append(func_task)
    
    ctx.func_next()
    # A--1
    # B-----1
    # 请求处理的函数
    # B-----2
    # A--2
    
    

    异常恢复机制

    异常恢复这个可以参考gin框架,是基于中间件 engine.Use(Logger(), Recovery())
    见代码 https://github.com/gin-gonic/gin/blob/master/gin.go#L162

    所以实现起来也不复杂,明白Go的异常恢复机制即可
    新建一个recovery.go文件

    //Go错误捕获机制参考
    // https://stackoverflow.com/questions/35212985/is-it-possible-get-information-about-caller-function-in-golang
    
    package gee
    
    import (
    	"fmt"
    	"log"
    	"net/http"
    	"runtime"
    	"strings"
    )
    
    // print stack trace for debug
    func trace(message string) string {
    	var pcs [32]uintptr
    	n := runtime.Callers(3, pcs[:]) // skip first 3 caller
    
    	var str strings.Builder
    	str.WriteString(message + "
    Traceback:")
    	for _, pc := range pcs[:n] {
    		fn := runtime.FuncForPC(pc)
    		file, line := fn.FileLine(pc)
    		str.WriteString(fmt.Sprintf("
    	%s:%d", file, line))
    	}
    	return str.String()
    }
    
    func Recovery() HandlerFunc {
    	return func(c *Context) {
    		defer func() {
    			if err := recover(); err != nil {
    				message := fmt.Sprintf("%s", err)
    				log.Printf("%s
    
    ", trace(message))
    				c.Fail(http.StatusInternalServerError, "Internal Server Error")
    			}
    		}()
    		c.Next()
    	}
    }
    

    只用像gin一样在中间件里面使用这个 异常恢复中间件函数即可。

    然后可以把路由分组,和 中间件这两大功能整理到 项目中。

    最终代码目录结构如下

    - gee/
          - context.go       // 上下文
          - gee.go            // gee核心函数
          - recovery.go     // 处理异常恢复中间件 并且trace错误
          - router.go        // 路由处理
          - trie.go            // 前缀树路由 (这一点实现比较复杂,可以参考 https://geektutu.com/post/gee-day3.html)
    

    最终代码过长,但是核心两个方法我都在上面精简出来了,然后就是中间件函数的顺序添加,用了Use函数封装, 添加一个Fail函数,中断中间件的运行。

    完整github地址

  • 相关阅读:
    JAVA基础知识总结:十五
    JAVA基础知识总结:十四
    JAVA基础知识总结:十三
    JAVA基础知识总结:十二
    Python图像处理库(1)
    python写的的简单的爬虫小程序
    python中使用pyqt做GUI小试牛刀
    Qt中使用cout, cin, cerr
    QT中ui更改后不能更新的解决方法
    QT中循环显示图片和简单的显示图片
  • 原文地址:https://www.cnblogs.com/CharmCode/p/14373471.html
Copyright © 2011-2022 走看看