Golang middleware Introduction MiddleWare

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

introduction

In the context of web development, "middleware" generally means "original packaging applications and add some part of the application of additional functionality." This concept does not always seem to be understood, but I think that the middleware is great.

First of all, a good pluggable middleware has a responsibility and is self-sufficient. This means that you can embed in your middleware interface level he can run directly. It does not affect your encoding, not the frame, you just request processing layer inside it. Absolutely no need to rewrite your code, if you want to use a middleware function, you can insert help him get there, if you do not want to use, you can directly remove.

Throughout the Go language, middleware is very common, even in the standard library. Although the beginning is not so obvious, in the standard library functions StripText net / http or TimeoutHandler of what we want to define the look and middleware, and the corresponding processing requests when they wrap your handler, and there are some additional steps .

At first, we think that the middleware writing seems easy, but we will encounter a variety of actually writing the pit Let's look at some examples.

1, a read request

In our example, all of the middleware will receive http. Handler as a parameter, and returns a http.Handler. This makes it very easy to string together the intermediate product. All of our products are among the basic model like this:

?
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)
  })
}

We want to redirect all requests to a slash - say / message /, equivalent to their non-slash, such as / message. We can write:

?
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)
  })
}

There is no very simple.

2, modification request

Let's say we want to add a title to the request, or modify it. http.Handler document indicated:

In addition to reading outside the main body, the handler should not be provided by modification requests.

Go standard library copy http.Request. Request object before passing it to the chain in response, we should do so. Suppose we want to set the Request-Id header on each request, to the inside track. Create a shallow copy * Request and modify the title before the agency.

?
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, in response to the first write

If you want to set response headers, you can just write them, then agent request.

?
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)
  })
}

The above question is, if the internal processor also set up a server head, your head will be overwritten. If you do not want to expose internal server software head, or if you want to get rid of the head before sending the response to the client, which could cause problems.

To do this, we must realize his ResponseWriter interface. Most of the time, we only agent to ResponseWriter the bottom, but if the user attempts to write a response, we'll dive into and add our title.

?
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)
}

To use it in our middleware, we will write:

?
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)
  })
}

problem

如果用户从不调用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://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())

Guess you like

Origin www.cnblogs.com/maji233/p/11237349.html