什么是中间件

shensi · · 793 次点击 · · 开始浏览    

# 什么是中间件? 中间件是一种计算机软件,可为操作系统提供的软件应用程序提供服务,以便于各个软件之间的沟通,特别是系统软件和应用软件。广泛用于web应用和面向服务的体系结构等。 个人理解的“web的中间件” 就是 ”在原始请求上增加一些和原始请求数据无关的操作,可能在请求前也可能在请求后;比如:增加日志、验证 Token等“。 因为大部分中间件跟业务逻辑没什么关系,因此要求中间件需要具备:可插拔特性。不会影响业务逻辑,哪里要用插到哪里的。 # golang中间件原理 中间件处理程序是简单的http.Handler,它包装另一个http.Handler做请求的一些预处理和/或后处理。它被称为“中间件”,因为它位于Go Web服务器和实际处理程序之间的中间位置。 ![图片出处:https://www.cnblogs.com/maji233/p/11237349.html](https://static.golangjob.cn/220509/dfcab26d5325e11d602abf0ddac8bc62.png) mux 介绍:(https://www.jianshu.com/p/8ade70e51210) 中间件本身只是将其http.HandlerFunc作为其参数之一,包装它并返回一个新http.HandlerFunc的服务器来调用。 ### 举个栗子便于理解: ```go 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 作为输入的参数,达到链式调用。 例如: ``` go func middlewareHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 在执行应用处理器前编译中间件的逻辑 next.ServeHTTP(w, r) // 在执行应用处理器后将会执行的编译中间件的逻辑 }) } ``` 通过向下传递 http.Handler 实现了多个中间件的调用。 ```go 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 ``` # 更加优雅的中间件写法: 先看一个美观的栗子: ```go r = NewRouter() r.Use(logger) r.Use(timeout) r.Use(ratelimit) r.Add("/",helloHandler) ``` 通过多步设置,我们拥有了和上一节差不多的执行函数链。胜在直观易懂,如果我们要增加或者删除中间件,只要简单地增加删除对应的Use()调用就可以了,非常方便。 从框架的角度来讲,怎么实现这样的功能呢?也不复杂: ```go 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对接口进行限流 ``` [参考资料](https://www.jianshu.com/p/8ade70e51210) [参考资料](https://segmentfault.com/a/1190000018819804) [参考资料](https://www.jianshu.com/p/877492d3bcc8)

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

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

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