什么是中间件

shensi · 2022-05-07 20:54:57 · 2120 次点击 · 预计阅读时间不到 1 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2022-05-07 20:54:57 的文章,其中的信息可能已经有所发展或是发生改变。

什么是中间件?

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

个人理解的“web的中间件” 就是 ”在原始请求上增加一些和原始请求数据无关的操作,可能在请求前也可能在请求后;比如:增加日志、验证 Token等“。

因为大部分中间件跟业务逻辑没什么关系,因此要求中间件需要具备:可插拔特性。不会影响业务逻辑,哪里要用插到哪里的。

golang中间件原理

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

图片出处:https://www.cnblogs.com/maji233/p/11237349.html

mux 介绍:(https://www.jianshu.com/p/8ade70e51210)

中间件本身只是将其http.HandlerFunc作为其参数之一,包装它并返回一个新http.HandlerFunc的服务器来调用。

举个栗子便于理解:

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

golang 如何自定义中间件

编写中间件的逻辑

  • 自定义的中间件函数接收一个 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)
 // 在执行应用处理器后将会执行的编译中间件的逻辑
 })
}

通过向下传递 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)将请求向下进行传递
  • 创建两个简单路由处理器index和about。都是向客户端输出不同的文本。
  • 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

更加优雅的中间件写法:

先看一个美观的栗子:

r = NewRouter()
r.Use(logger)
r.Use(timeout)
r.Use(ratelimit)
r.Add("/",helloHandler)

通过多步设置,我们拥有了和上一节差不多的执行函数链。胜在直观易懂,如果我们要增加或者删除中间件,只要简单地增加删除对应的Use()调用就可以了,非常方便。

从框架的角度来讲,怎么实现这样的功能呢?也不复杂:

type middleware func(http.Handler) http.Handler

type Router struct {
    middlewareChain []middleware
    mux             map[string]http.Handler
}

func NewRouter() *Router {
    return &Router{}
}

func (r *Router) Use(m middleware) {
    r.middlewareChain = append(r.middlewareChain, m)
}

func (r *Router) Add(route string, h http.Handler) {
    var mergedHandler = h
    for i := len(r.middlewareChain) - 1; i >= 0; i-- {
        mergedHandler = r.middlewareChain[i](mergedHandler)
    }

    r.mux[route] = mergedHandler
}

哪些事情适合在中间件做?

以go的chi 框架参考:

compress.go
    =>对http的响应体进行压缩处理
heartbeat.go
    =>设置一个特殊的路由,例如/ping; /healthcheck,用来给负载均衡一类的前置服务进行探活
logger.go
    =>打印请求处理处理日志,例如请求处理时间,请求路由
profiler.go
    =>挂载pprof需要的路由,如`/pprof`、`/pprof/trace`到系统中
realip.go
    =>从请求头中读取X-Forwarded-For和X-Real-IP,将http.Request中的RemoteAddr修改为得到的RealIP
requestid.go
    =>为本次请求生成单独的requestid,可一路透传,用来生成分布式调用链路,也可用于在日志中串连单次请求的所有逻辑
timeout.go
    =>用context.Timeout设置超时时间,并将其通过http.Request一路透传下去
throttler.go
    =>通过定长大小的channel存储token,并通过这些token对接口进行限流

参考资料

参考资料

参考资料


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2120 次点击  ∙  1 赞  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传