• Golang 中间件简介 MiddleWare


    https://mp.weixin.qq.com/s/-nRWwy8SjW1TlqCglL0CAQ

    引言

    web开发的背景下,“中间件”通常意思是“包装原始应用并添加一些额外的功能的应用的一部分”。这个概念似乎总是不被人理解,但是我认为中间件非常棒。

    首先,一个好的中间件有一个责任就是可插拔并且自足。这就意味着你可以在接口级别嵌入你的中间件他就能直接运行。它不会影响你编码方式,不是框架,仅仅是你请求处理里面的一层而已。完全没必要重写你的代码,如果你想使用中间件的一个功能,你就帮他插入到那里,如果不想使用了,就可以直接移除。

    纵观Go语言,中间件是非常普遍的,即使在标准库中。虽然开始的时候不会那么明显,在标准库net/http中的函数StripText或者TimeoutHandler就是我们要定义和的中间件的样子,处理请求和相应的时候他们包装你的handler,并处理一些额外的步骤。

    一开始,我们认为编写中间件似乎很容易,但是我们实际编写的时候也会遇到各种各样的坑。让我们来看看一些例子。

    1、读取请求

    在我们的示例中,所有的中间件都将接受http。处理程序作为一个参数,并返回一个http.Handler。这使得人们很容易就能把中间产品串起来。我们所有的中间产品的基本模式是这样的:

    1
    2
    3
    4
    5
    6
    func X(h http.Handler) http.Handler {
     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     // Something here...
     h.ServeHTTP(w, r)
     })
    }

    我们想要将所有的请求重定向到一个斜杠——比方说/message/,到它们的非斜杠等效,比如/message。我们可以这样写:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    func TrailingSlashRedirect(h http.Handler) http.Handler {
     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     if r.URL.Path != "/" && r.URL.Path[len(r.URL.Path)-1] == '/' {
     http.Redirect(w, r, r.URL.Path[:len(r.URL.Path)-1], http.StatusMovedPermanently)
     return
     }
     h.ServeHTTP(w, r)
     })
    }

    有没有很简单。

    2、修改请求

    比方说,我们想要向请求添加一个标题,或者修改它。http.Handler文档中指明:

    除了读取主体之外,处理程序不应该修改所提供的请求。

    Go标准库复制http.Request。请求对象在将其传递到响应链之前,我们也应该这样做。假设我们想在每个请求上设置Request-Id头,以便内部跟踪。创建*Request的浅副本,并在代理之前修改标题。

    1
    2
    3
    4
    5
    6
    7
    8
    func RequestID(h http.Handler) http.Handler {
     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     r2 := new(http.Request)
     *r2 = *r
     r2.Header.Set("X-Request-Id", uuid.NewV4().String())
     h.ServeHTTP(w, r2)
     })
    }

    3、编写响应头

    如果你想设置响应头,你可以只写它们,然后代理请求。

    1
    2
    3
    4
    5
    6
    func Server(h http.Handler, servername string) http.Handler {
     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     w.Header().Set("Server", servername)
     h.ServeHTTP(w, r)
     })
    }

    上面的问题是,如果内部处理器也设置了服务器头,那么你的头将被覆盖。如果不想公开内部软件的服务器头,或者如果想在发送响应给客户端之前去掉头部,这可能会带来问题。

    要做到这一点,我们必须自己实现ResponseWriter接口。大多数时候,我们只会代理到底层的ResponseWriter,但是如果用户试图写一个响应,我们就会潜入并添加我们的标题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    type serverWriter struct {
     w http.ResponseWriter
     name string
     wroteHeaders bool
    }
     
    func (s *serverWriter) Header() http.Header {
     return s.w.Header()
    }
     
    func (s *serverWriter) WriteHeader(code int) http.Header {
     if s.wroteHeader == false {
     s.w.Header().Set("Server", s.name)
     s.wroteHeader = true
     }
     s.w.WriteHeader(code)
    }
     
    func (s *serverWriter) Write(b []byte) (int, error) {
     if s.wroteHeader == false {
     // We hit this case if user never calls WriteHeader (default 200)
     s.w.Header().Set("Server", s.name)
     s.wroteHeader = true
     } return s.w.Write(b)
    }

    要在我们的中间件中使用它,我们会写:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    func Server(h http.Handler, servername string) http.Handler {
     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     sw := &serverWriter{
     w: w,
     name: servername,
     }
     h.ServeHTTP(sw, r)
     })
    }

    问题

    如果用户从不调用Write或WriteHeader呢?例如,有一个200状态并且是空body,或者对选项请求的响应——我们的拦截函数都不会运行。因此,我们应该在ServeHTTP调用之后再添加校验。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    func Server(h http.Handler, servername string) http.Handler {
     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     sw := &serverWriter{
     w: w,
     name: servername,
     }
     h.ServeHTTP(sw, r)
     if sw.wroteHeaders == false {
     s.w.Header().Set("Server", s.name)
     s.wroteHeader = true
     }
     })
    }

    其他ResponseWriter接口

    ResponseWriter接口只需要有三种方法。但在实践中,它也可以对其他接口作出响应,例如http.Pusher。你的中间件可能会意外地禁用HTTP/2支持,这是不好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // Push implements the http.Pusher interface.
    func (s *serverWriter) Push(target string, opts *http.PushOptions) error {
     if pusher, ok := s.w.(http.Pusher); ok {
     return pusher.Push(target, opts)
     }
     return http.ErrNotSupported
    }
     
    // Flush implements the http.Flusher interface.
    func (s *serverWriter) Flush() {
     f, ok := s.w.(http.Flusher)
     if ok {
     f.Flush()
     }
    }

    总结

    通过以上的学习,不知道大家对Go编写中间件有没有一个完整的认识。大家也可以尝试着用Go去编写一个中间件。

    好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

     

    https://slarker.me/go-middleware/

    今天学习了一个之前虽然听过,但是从来没搞懂的概念 — 中间件。这个概念在服务端开发中经常会用到,大的公司也会有专门的团队来做中间件开发,用来提高应用层的开发效率。在 Web 开发中,中间件也就是在请求流程中新增了一层,添加一些额外的功能,比如验证 Token,记录日志等等。一个优秀的中间件能做到可插拔,自我约束,无须重写代码。

    在 Go 语言中,实现一个中间件也非常简单,其原理和 Python 中的装饰器非常类似。比如我们想实现一个中间件,用来统计每个接口调用的次数:

    package middleware
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    )
    
    func RequestMiddleWare() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		path := c.Request.URL.Path
    		fmt.Println(string(path))
    		c.Next()
    	}
    }

    RequestMiddleWare 函数的返回值是一个函数,在函数实现中,可以根据 *gin.Context 拿到请求信息,做一些想要的操作,完成之后可以 c.Next() 执行下一个步骤。整个过程就是在原来的请求处理中新增了一层,非常方便。

    除此之外,如果希望在请求结束后还可以做一些操作,可以把相关逻辑放到 c.Next() 之后,当请求结束之后,还会返回来再把中间件中剩余的逻辑执行完毕。也就相当于:

    before middleware
        c.Next()
    after middleware
     https://www.jianshu.com/p/877492d3bcc8
     

    当构建 web 应用程序,可能对所有的请求会共享一些功能。例如我们每一个请求都会写入日志。

    • 打印 http 请求或返回的日志
    • 压缩 http 请求的返回值
    • 将 http 请求头保持一致
    • 实现安全以及权限验证

    在 go 语言 net/http 标准库中提供了中间件类似的函数 StripPrefix 和 TimeoutHandler。
    如何自己编写一个中间件呢?答案是

    • 这个函数接收一个 http.Handler 作为参数,目的是可使用其他中间件或应用处理器,调用其他中间件或应用的方法就是调用其 HttpServe 的方法。
    • 并且将 http.Handler 作为返回值,这样其他中间件可以使用 http.Handler 作为输入的参数,达到链式调用。
    func middlewareHandler(next http.Handler) http.Handler {
     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     // 在执行应用处理器前编译中间件的逻辑
     next.ServeHTTP(w, r)
     // 在执行应用处理器后将会执行的编译中间件的逻辑
     })
    }

    从上面示例我们可以看出 go 语言是支持高阶函数,通过向下传递 http.Handler 来实现中间件。

    package main
    
    import (
        "fmt"
        "log"
        "net/http"
        "time"
    )
    
    func logginHandler(next http.Handler) http.Handler{
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
            start := time.Now()
            log.Printf("Started %s %s",r.Method, r.URL.Path)
            next.ServeHTTP(w,r)
            log.Printf("Completed %s in %v", r.URL.Path, time.Since(start))
        })
    }
    
    func index(w http.ResponseWriter,r *http.Request ){
        log.Println("Executing index handler")
        fmt.Fprintf(w,"welcome!")
    }
    
    func about(w http.ResponseWriter, r *http.Request){
        log.Println("Executing about handler")
        fmt.Fprintf(w,"Go Middleware")
    }
    
    func iconHandler(w http.ResponseWriter, r *http.Request){
    
    }
    
    func main()  {
    
        http.HandleFunc("/favicon.ico", iconHandler)
    
        indexHandler := http.HandlerFunc(index)
        aboutHandler := http.HandlerFunc(about)
    
        http.Handle("/",logginHandler(indexHandler))
        http.Handle("/about",logginHandler(aboutHandler))
    
        server :=  &http.Server{
            Addr:":8080",
        }
    
        log.Println("Listening...")
        server.ListenAndServe()
    }
    • 上面的代码关键是 logginHandler 这个日志处理器,作为中间件会拦截请求,进行处理也就是输出日志,通过next.ServeHTTP(w,r)然后将请求向下进行传递
    • 这里创建两个简单路由处理器indexabout。都是向客户端输出不同的文本。
    • http.Handle("/",logginHandler(indexHandler)) 通过 logginHandler 函数进行包裹,从容我们日志中间件可以拦截到发起的请求进行一些业务逻辑,业务逻辑可以位于next.ServeHTTP(w,r)之前或之后。通过下面的输出我们可以清晰可见代码执行的顺序。
    Listening...
     Started GET /
     Executing index handler
     Completed / in 41.129µs
     Started GET /
     Executing index handler
     Completed / in 50.475µs
     Started GET /about
     Executing about handler
     Completed /about in 49.483µs
    

    许多书都提供代码示例,个人建议是不要 copy 过来运行看效果,自己手 coding 就可以发现一些问题,同时可以思考为什么要这样编写代码。

    通过上面示例我们了解如何写一个简单中间件,这个个人看来和 nodejs 实现中间件没有什么差别。实际开发中我们往往会有多个中间件来执行业务,那么这些中间件执行顺序也是我们值得考虑的问题。

    package main
    
    import(
        "fmt"
        "log"
        "net/http"
    )
    
    func middlewareFirst(next http.Handler) http.Handler{
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
            log.Println("MiddlewareFirst - before Handler")
            next.ServeHTTP(w,r)
            log.Println("MiddlewareFirst - after Handler")
        })
    }
    
    func middlewareSecond(next http.Handler) http.Handler{
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
            log.Println("MiddlewareSecond - before Handler")
            if r.URL.Path == "/message"{
                if r.URL.Query().Get("password") == "123"{
                    log.Println("Authorized to system...")
                    next.ServeHTTP(w,r)
                }else{
                    log.Println("Failed to authorize to the system")
                    return
                }
            }else{
                next.ServeHTTP(w,r)
            }
            log.Println("MiddlewareScond - after Handler")      
        })
    }
    
    func index(w http.ResponseWriter, r *http.Request){
        log.Println("Executed index Handler")
        fmt.Fprintf(w,"welcome")
    }
    
    func message(w http.ResponseWriter, r *http.Request){
        log.Println("Executed message Handler")
        fmt.Fprintf(w,"message...")
    }
    
    func iconHandler(w http.ResponseWriter, r *http.Request){
    
    }
    
    func main() {
        http.HandleFunc("/favicon",iconHandler)
        indexHandler := http.HandlerFunc(index)
        messageHandler := http.HandlerFunc(message)
    
        http.Handle("/",middlewareFirst(middlewareSecond(indexHandler)))
        http.Handle("/message",middlewareFirst(middlewareSecond(messageHandler)))
    
        server  := &http.Server{
            Addr:":8080",
        }
    
        log.Println("Listen...")
    
        server.ListenAndServe()
    
    }

    在上面代码中其实也没有什么特别之处,就是我们创建了两个middlewareFirstmiddlewareSecond两个中间件并且有一定先后顺序,然后在 middlewareSecond中写了一个对访问权限校验的逻辑。大家可以尝试地去运行一下。

     

    https://segmentfault.com/a/1190000018819804

    中间件是一种计算机软件,可为操作系统提供的软件应用程序提供服务,以便于各个软件之间的沟通,特别是系统软件和应用软件。广泛用于web应用和面向服务的体系结构等。

    纵观GO语言,中间件应用比较普遍,主要应用:

    • 记录对服务器发送的请求(request)
    • 处理服务器响应(response )
    • 请求和处理之间做一个权限认证工作
    • 远程调用
    • 安全
    • 等等

    中间件处理程序是简单的http.Handler,它包装另一个http.Handler做请求的一些预处理和/或后处理。它被称为“中间件”,因为它位于Go Web服务器和实际处理程序之间的中间位置。

    下面是一些中间件例子

    记录日志中间件

    package main
    
    import (
       "fmt"
       "log"
       "net/http"
    )
    
    func logging(f http.HandlerFunc) http.HandlerFunc {
       return func(w http.ResponseWriter, r *http.Request) {
          log.Println(r.URL.Path)
          f(w, r)
       }
    }
    func foo(w http.ResponseWriter, r *http.Request) {
       fmt.Fprintln(w, "foo")
    }
    
    func bar(w http.ResponseWriter, r *http.Request) {
       fmt.Fprintln(w, "bar")
    }
    
    func main() {
       http.HandleFunc("/foo", logging(foo))
       http.HandleFunc("/bar", logging(bar))
       http.ListenAndServe(":8080", nil)
    }

    访问 http://localhost:8080/foo

    返回结果

    foo

    将上面示例修改下,也可以实现相同的功能

    package main
    
    import (
       "fmt"
       "log"
       "net/http"
    )
    
    func foo(w http.ResponseWriter, r *http.Request) {
       fmt.Fprintln(w, "foo")
    }
    func bar(w http.ResponseWriter, r *http.Request) {
       fmt.Fprintln(w, "bar")
    }
    
    func loggingMiddleware(next http.Handler) http.Handler {
       return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
          log.Println(r.URL.Path)
          next.ServeHTTP(w, r)
       })
    }
    
    func main() {
    
       http.Handle("/foo", loggingMiddleware(http.HandlerFunc(foo)))
       http.Handle("/bar", loggingMiddleware(http.HandlerFunc(bar)))
       http.ListenAndServe(":8080", nil)
    }

    访问 http://localhost:8080/foo

    返回结果

    foo

    多中间件例子

    package main
    
    import (
       "fmt"
       "log"
       "net/http"
       "time"
    )
    
    type Middleware func(http.HandlerFunc) http.HandlerFunc
    
    // Logging logs all requests with its path and the time it took to process
    func Logging() Middleware {
    
       // Create a new Middleware
       return func(f http.HandlerFunc) http.HandlerFunc {
    
          // Define the http.HandlerFunc
          return func(w http.ResponseWriter, r *http.Request) {
    
             // Do middleware things
             start := time.Now()
             defer func() { log.Println(r.URL.Path, time.Since(start)) }()
    
             // Call the next middleware/handler in chain
             f(w, r)
          }
       }
    }
    
    // Method ensures that url can only be requested with a specific method, else returns a 400 Bad Request
    func Method(m string) Middleware {
    
       // Create a new Middleware
       return func(f http.HandlerFunc) http.HandlerFunc {
    
          // Define the http.HandlerFunc
          return func(w http.ResponseWriter, r *http.Request) {
    
             // Do middleware things
             if r.Method != m {
                http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
                return
             }
    
             // Call the next middleware/handler in chain
             f(w, r)
          }
       }
    }
    
    // Chain applies middlewares to a http.HandlerFunc
    func Chain(f http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc {
       for _, m := range middlewares {
          f = m(f)
       }
       return f
    }
    
    func Hello(w http.ResponseWriter, r *http.Request) {
       fmt.Fprintln(w, "hello world")
    }
    
    func main() {
       http.HandleFunc("/", Chain(Hello, Method("GET"), Logging()))
       http.ListenAndServe(":8080", nil)
    }

    中间件本身只是将其http.HandlerFunc作为其参数之一,包装它并返回一个新http.HandlerFunc的服务器来调用。在这里,我们定义了一种新类型Middleware,最终可以更容易地将多个中间件链接在一起。

    当然我们也可以改成如下形式

    package main
    
    import (
       "fmt"
       "log"
       "net/http"
       "time"
    )
    
    type Middleware func(http.Handler) http.Handler
    
    func Hello(w http.ResponseWriter, r *http.Request) {
       fmt.Fprintln(w, "hello world")
    }
    
    func Chain(f http.Handler, mmap ...Middleware) http.Handler {
       for _, m := range mmap {
          f = m(f)
       }
       return f
    }
    func Method(m string) Middleware {
       return func(f http.Handler) http.Handler {
          return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
             log.Println(r.URL.Path)
             if r.Method != m {
                http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
                return
             }
             f.ServeHTTP(w, r)
          })
       }
    
    }
    func Logging() Middleware {
       return func(f http.Handler) http.Handler {
          return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
             //log.Println(r.URL.Path)
             // Do middleware things
             start := time.Now()
             defer func() { log.Println(r.URL.Path, time.Since(start)) }()
             f.ServeHTTP(w, r)
          })
       }
    }
    
    func main() {
       http.Handle("/", Chain(http.HandlerFunc(Hello), Method("GET"), Logging()))
       http.ListenAndServe(":8080", nil)
    }

    在gin框架下实现中间件
    r := gin.Default() 创建带有默认中间件的路由,默认是包含logger和recovery中间件的
    r :=gin.new()      创建带有没有中间件的路由

    示例

    package main
    
    import (
       "github.com/gin-gonic/gin"
       "log"
       "time"
    )
    
    func Logger() gin.HandlerFunc {
       return func(c *gin.Context) {
          t := time.Now()
          // Set example variable
          c.Set("example", "12345")
          // before request
          c.Next()
          // after request
          latency := time.Since(t)
          log.Print(latency) //时间  0s
          // access the status we are sending
          status := c.Writer.Status()
          log.Println(status) //状态 200
       }
    }
    func main() {
       r := gin.New()
       r.Use(Logger())
    
       r.GET("/test", func(c *gin.Context) {
          example := c.MustGet("example").(string)
    
          // it would print: "12345"
          log.Println(example)
       })
    
       // Listen and serve on 0.0.0.0:8080
       r.Run(":8080")
    }
    以上示例也可改为
    package main
    
    import (
       "github.com/gin-gonic/gin"
       "log"
       "time"
    )
    
    func Logger() gin.HandlerFunc {
       return func(c *gin.Context) {
          t := time.Now()
          // Set example variable
          c.Set("example", "12345")
          // before request
          c.Next()
          // after request
          latency := time.Since(t)
          log.Print(latency) //时间  0s
          // access the status we are sending
          status := c.Writer.Status()
          log.Println(status) //状态 200
       }
    }
    
    func main() {
       r := gin.New()
       r.GET("/test", Logger(), func(c *gin.Context) {
          example := c.MustGet("example").(string)
          // it would print: "12345"
          log.Println(example)
       })
       // Listen and serve on 0.0.0.0:8080
       r.Run(":8080")
    }

    即不用r.use添加中间件,直接将Logger() 写到r.GET 方法的参数里("/test"之后)。

    更多gin中间件示例可参考 https://github.com/gin-gonic/gin

    在 Gin 中,接入中间件也非常简单:

    router := gin.Default()
    router.Use(middleware.RequestMiddleWare())

    上面这段代码是在所有的 API 中接入,如果仅仅想在某些 API 上接入,可以使用 Gin 的路由分组:

    group := router.Group("/api/v1", middleware.RequestMiddleWare())
  • 相关阅读:
    Mac搭建PHP多站点虚拟主机环境
    IE ajax缓存问题
    CSS hack总结
    div高度自适应
    asp.net EasyUI DataGrid 实现增删改查
    如何从 Windows Phone 8 模拟器连接到本地 Web 服务
    创建DomainService,添加终结点
    sqlserver 查询数量查看,并快速建立索引。
    在VS2013中强制IIS Express应用程序池使用经典模式
    理解HTTP幂等性
  • 原文地址:https://www.cnblogs.com/maji233/p/11237349.html
Copyright © 2020-2023  润新知