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://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())