学习Golang的HTTP中间件机制
2023-04-18 14:50:43 时间
因为 Golang 内置的 net/http 天生就支持 HTTP 中间件机制,所以即便不用 gin 之类的 Web 框架,我们也可以写出扩展性很好的 Web 应用。
假如你不了解 Golang 的 HTTP 中间件机制的话,那么可以把它看成是一个洋葱:
通过洋葱看中间件
每一个中间件都是一层洋葱皮,其中每一个中间件都可以改变请求和响应,我们可以很自然的把不同的逻辑放到不同的洋葱皮里,更代码更符合单一职责原则:
package main
import (
"net/http"
)
func foo(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("foo("))
next(w, r)
w.Write([]byte(")"))
}
}
func bar(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("bar("))
next(w, r)
w.Write([]byte(")"))
}
}
func test(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("test"))
}
func main() {
http.Handle("/", foo(bar(test)))
http.ListenAndServe(":8080", nil)
}
运行结果显示如下,它形象的说明了中间件的执行过程:
foo(bar(test))
联想一下洋葱的结构,基本就能明白 Golang 的 HTTP 中间件机制了,不过不爽的是不易维护,假如中间件很多的话,视觉上会呈现出复杂的嵌套,比如:
middleware(
middleware(
middleware(
middleware(
middleware(
handler
)
)
)
)
)
我可不想维护这样的代码,下面看看如何简化编码方式:
package main
import "net/http"
func foo(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("foo("))
next.ServeHTTP(w, r)
w.Write([]byte(")"))
})
}
func bar(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("bar("))
next.ServeHTTP(w, r)
w.Write([]byte(")"))
})
}
func test(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("test"))
}
type pipeline struct {
middlewares []middleware
}
type middleware func(http.Handler) http.Handler
func newPipeline(ms ...middleware) pipeline {
return pipeline{ms}
}
func (p pipeline) pipe(ms ...middleware) pipeline {
return pipeline{append(p.middlewares, ms...)}
}
func (p pipeline) process(h http.Handler) http.Handler {
for i := range p.middlewares {
h = p.middlewares[len(p.middlewares)-1-i](h)
}
return h
}
func main() {
http.Handle("/", newPipeline().pipe(foo, bar).process(http.HandlerFunc(test)))
http.ListenAndServe(":8080", nil)
}
需要说明的是,开始的代码使用的是 http.HandlerFunc 的形式,后面的代码参考大家的意见改成了更通用的 http.Handler 接口的形式,下面对比一下调用方式:
- 修改前:foo(bar(test))
- 修改后:newPipeline().pipe(foo, bar).process(test)
虽然表面上看代码更长了,但是通过使用 pipeline,我们把原本嵌套的结构改成了链式的结构,这不仅提高了代码的可维护性,而且也提高了代码的可复用性,设想一下,你有很多路由,它们有很多公共的中间件,利用 Pipeline,很简单就可以完成复用,类似的开源项目有很多,比如:「Alice – Painless middleware chaining for Go」,我就不多说了。
相关文章
- EasyCVR对接华为iVS订阅摄像机和用户变更请求接口介绍
- 精选 | 腾讯云CDN内容加速场景有哪些?
- 模块化网络防止基于模型的多任务强化学习中的灾难性干扰
- 用搜索和注意力学习稳健的调度方法
- 用于多变量时间序列异常检测的学习图神经网络
- 助力政企自动化自然生长,华为WeAutomate RPA是怎么做到的?
- 使用腾讯轻量云搭建Fiora聊天室
- TSRC安全测试规范
- 云计算“功守道”
- 助力成本优化,腾讯全场景在离线混部系统Caelus正式开源
- Flink 利器:开源平台 StreamX 简介
- 腾讯云实践 | 一图揭秘腾讯碳中和?解决方案
- 深度学习中的轻量级网络架构总结与代码实现
- 信息系统项目管理师(高项复习笔记三)
- Adobe国际认证让科技赋能时尚
- c++该怎么学习(面试吃土记)
- 面试官问发布订阅模式是在问什么?
- 面试官:请实现一个通用函数把 callback 转成 promise
- 空中悬停、翻滚转身、成功着陆,我用强化学习「回收」了SpaceX的火箭
- 中山大学林倞解读视觉语义理解新趋势:从表达学习到知识及因果融合