美文网首页
编写中间件的思维方式 以golang为例 2023-06-20

编写中间件的思维方式 以golang为例 2023-06-20

作者: 9_SooHyun | 来源:发表于2023-06-19 11:51 被阅读0次

    如何编写中间件

    总览

    本文首先介绍中间件模型

    随后,本文将给出两种中间件的实现方式

    • 函数装饰器的中间件实现
    • 流水线式的函数调用实现的中间件

    中间件模型

    中间件是一个洋葱模型

    [图片上传失败...(image-ddce8c-1687233072081)]

    中间件,本质上是装饰器模式的具体实践

    每一个中间件都负责一种装饰职能,直到触及洋葱的核心

    函数装饰器的中间件实现 + 手动管理中间件的多层嵌套关系

    package main
    
    import (
        "fmt"
        "nettp"
    )
    
    // loggerMiddleware 函数装饰器实现的日志中间件
    func loggerMiddleware(f http.HandlerFunc) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
            fmt.Println("before")
            f(w, r)
            fmt.Println("after")
        }
    }
    
    // authMiddleware 函数装饰器实现的鉴权中间件
    func authMiddleware(f http.HandlerFunc) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
            if token := r.Header.Get("token"); token != "fake_token" {
                _, _ = w.Write([]byte("unauthorized\n"))
                return
            }
            f(w, r)
        }
    }
    
    // 洋葱的“核心”
    func handleHello(w http.ResponseWriter, r *http.Request) {
        fmt.Println("handle hello")
        _, _ = w.Write([]byte("Hello World!\n"))
    }
    
    func main() {
        // authMiddleware(loggerMiddleware(handleHello)) 手动管理中间件的多层嵌套关系
        http.HandleFunc("/hello", authMiddleware(loggerMiddleware(handleHello)))
        fmt.Println(http.ListenAndServe(":8888", nil))
    } 
    
    
    [root@VM-centos ~]# curl http://127.0.0.1:8888/hello
    unauthorized
    [root@VM-centos ~]# curl http://127.0.0.1:8888/hello -H "token:fake_token"
    Hello World!
    

    main里面authMiddleware(loggerMiddleware(handleHello))手动地管理中间件的多层嵌套关系,auth->log->hello

    如果我们还需要加入其他中间件拦截功能,可以通过这种方式进行无限包装,但是手写的话。。。

    因此,我们需要一个中间件链,可以自动帮我们给函数加上一系列装饰

    函数装饰器的中间件实现 + 自动管理中间件的多层嵌套关系

    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    type Middleware func(http.HandlerFunc) http.HandlerFunc
    
    // loggerMiddleware 函数装饰器实现的日志中间件
    func loggerMiddleware(f http.HandlerFunc) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
            fmt.Println("before")
            f(w, r)
            fmt.Println("after")
        }
    }
    
    // authMiddleware 函数装饰器实现的鉴权中间件
    func authMiddleware(f http.HandlerFunc) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
            if token := r.Header.Get("token"); token != "fake_token" {
                _, _ = w.Write([]byte("unauthorized\n"))
                return
            }
            f(w, r)
        }
    }
    
    // 洋葱的“核心”
    func handleHello(w http.ResponseWriter, r *http.Request) {
        fmt.Println("handle hello")
        _, _ = w.Write([]byte("Hello World!\n"))
    }
    
    // pipelineHandlers 聚合 handler 和 middleware
    func pipelineHandlers(h http.HandlerFunc, mw ...Middleware) http.HandlerFunc {
        for i := range mw {
            h = mw[len(mw)-1-i](h)
        }
    
        return h
    }
    
    func main() {
        // pipeline自动管理中间件的多层嵌套关系
        middlewareChain := []Middleware{authMiddleware, loggerMiddleware}
        http.HandleFunc("/hello", pipelineHandlers(handleHello, middlewareChain...))
        fmt.Println(http.ListenAndServe(":8888", nil))
    }
    

    [图片上传失败...(image-75cdd9-1687233072081)]

    【流水线式的函数调用】实现的中间件

    上面给出了通过闭包(函数装饰器)实现中间件的实践

    下面再给出【流水线式的函数调用】的中间件实现

    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    // 上面是闭包的实现
    
    // 【流水线式的函数调用】实现的中间件
    // 核心思路:
    // 1. 维护一个 function pipeline,依次执行 pipeline 上的 function
    // 2. 那么需要定义一个 Pipeline 结构体,保存整个流水线的【定义】和【运行时上下文信息】,如流水线本体、流水线入参
    // 3. pipeline 上的 function 需要相同的函数签名
    
    // Pipeline 保存整个流水线的【定义】和【运行时上下文信息】
    type Pipeline struct {
        PipelineWorkers []PipelineWorkerFunc // 流水线定义
        workerIndex     int                  // 流水线运行时worker的索引。always init with -1
    
        W http.ResponseWriter
        R *http.Request // 流水线入参数
    }
    
    func NewPipeline(w http.ResponseWriter, r *http.Request, pWorkers ...PipelineWorkerFunc) *Pipeline {
        return &Pipeline{
            PipelineWorkers: pWorkers,
            workerIndex:     -1,
            W:               w,
            R:               r,
        }
    }
    
    // 流水线上的每个“工人”,直接接受一个Pipeline实例作为入参
    type PipelineWorkerFunc func(p *Pipeline)
    
    // Run 启动,让整条Pipeline依次作业,除非 Pipeline isAborted
    func (p *Pipeline) Run() {
        for p.workerIndex < len(p.PipelineWorkers) {
            p.Next()
        }
    }
    
    func (p *Pipeline) Next() {
        // 流水线已停止,直接返回
        if p.IsStopped() {
            return
        }
    
        // 1. workerIndex 后移
        p.workerIndex++
        if p.workerIndex < len(p.PipelineWorkers) {
            // 2. worker干活
            worker := p.PipelineWorkers[p.workerIndex]
            worker(p)
        }
    }
    
    func (p *Pipeline) IsStopped() bool {
        return p.workerIndex == len(p.PipelineWorkers)
    }
    
    func (p *Pipeline) Stop() {
        p.workerIndex = len(p.PipelineWorkers)
    }
    
    // ----下面定义一系列PipelineWorkerFunc----
    func authMiddle(p *Pipeline) {
        fmt.Println("enter authMiddle")
        defer fmt.Println("quit authMiddle")
        if token := p.R.Header.Get("token"); token != "fake_token" {
            _, _ = p.W.Write([]byte("unauthorized\n"))
            p.Stop()
            return
        }
    
    }
    
    func loggerMiddle(p *Pipeline) {
        fmt.Println("enter loggerMiddle")
        defer fmt.Println("quit loggerMiddle")
        // p.Next()
    }
    
    func helloMiddle(p *Pipeline) {
        fmt.Println("enter helloMiddle")
        defer fmt.Println("quit helloMiddle")
        _, _ = p.W.Write([]byte("Hello World!\n"))
        return
    }
    
    // httpHandleEntry is a normal http.HandlerFunc
    func httpHandleEntry(w http.ResponseWriter, r *http.Request) {
        fmt.Println("---enter pipeline !")
        p := NewPipeline(w, r, authMiddle, loggerMiddle, helloMiddle)
        p.Run()
        fmt.Println("---quit pipeline !")
    }
    
    func main() {
        http.HandleFunc("/hello", httpHandleEntry)
        fmt.Println(http.ListenAndServe(":8888", nil))
    }
    
    

    实际上,gin的handler就是类似的思想

    思考与总结

    闭包装饰器 和 流水线函数 的两种实现,实际上是思维方式不同

    闭包法处理的对象是最终的handler,它从内到外,一层一层不断包装handler,就像我们平时包装东西一样

    而流水线法处理的对象是http request,从外到内,流水潺潺

    相关文章

      网友评论

          本文标题:编写中间件的思维方式 以golang为例 2023-06-20

          本文链接:https://www.haomeiwen.com/subject/ncreydtx.html