mirror of
https://github.com/chai2010/advanced-go-programming-book.git
synced 2025-05-24 04:22:22 +00:00
fix middleware
This commit is contained in:
parent
6f083a9fb9
commit
886a63bb79
@ -77,9 +77,9 @@ func main() {
|
|||||||
|
|
||||||
每一个handler里都有之前提到的记录运行时间的代码,每次增加新的路由我们也同样需要把这些看起来长得差不多的代码拷贝到我们需要的地方去。因为代码不太多,所以实施起来也没有遇到什么大问题。
|
每一个handler里都有之前提到的记录运行时间的代码,每次增加新的路由我们也同样需要把这些看起来长得差不多的代码拷贝到我们需要的地方去。因为代码不太多,所以实施起来也没有遇到什么大问题。
|
||||||
|
|
||||||
渐渐的我们的系统增加到了 30 个路由和 handler 函数,每次增加新的 handler,我们的第一件工作就是把之前写的所有和业务逻辑无关的周边代码先拷贝过来。
|
渐渐的我们的系统增加到了30个路由和`handler`函数,每次增加新的handler,我们的第一件工作就是把之前写的所有和业务逻辑无关的周边代码先拷贝过来。
|
||||||
|
|
||||||
接下来系统安稳地运行了一段时间,突然有一天,老板找到你,我们最近找人新开发了监控系统,为了系统运行可以更加可控,需要把每个接口运行的耗时数据主动上报到我们的监控系统里。给监控系统起个名字吧,叫 metrics。现在你需要修改代码并把耗时通过 http post 的方式发给 metrics 了。我们来修改一下 helloHandler:
|
接下来系统安稳地运行了一段时间,突然有一天,老板找到你,我们最近找人新开发了监控系统,为了系统运行可以更加可控,需要把每个接口运行的耗时数据主动上报到我们的监控系统里。给监控系统起个名字吧,叫metrics。现在你需要修改代码并把耗时通过HTTP Post的方式发给metrics 了。我们来修改一下helloHandler:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func helloHandler(wr http.ResponseWriter, r *http.Request) {
|
func helloHandler(wr http.ResponseWriter, r *http.Request) {
|
||||||
@ -98,7 +98,7 @@ func helloHandler(wr http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
我们来分析一下,一开始在哪里做错了呢?我们只是一步一步地满足需求,把我们需要的逻辑按照流程写下去呀?
|
我们来分析一下,一开始在哪里做错了呢?我们只是一步一步地满足需求,把我们需要的逻辑按照流程写下去呀?
|
||||||
|
|
||||||
实际上,我们犯的最大的错误是把业务代码和非业务代码揉在了一起。对于大多数的场景来讲,非业务的需求都是在 http 请求处理前做一些事情,或者/并且在响应完成之后做一些事情。我们有没有办法使用一些重构思路把这些公共的非业务功能代码剥离出去呢?回到刚开头的例子,我们需要给我们的 helloHandler 增加超时时间统计,我们可以使用一种叫 `function adapter` 的方法来对 helloHandler 进行包装:
|
实际上,我们犯的最大的错误是把业务代码和非业务代码揉在了一起。对于大多数的场景来讲,非业务的需求都是在http请求处理前做一些事情,并且在响应完成之后做一些事情。我们有没有办法使用一些重构思路把这些公共的非业务功能代码剥离出去呢?回到刚开头的例子,我们需要给我们的`helloHandler()`增加超时时间统计,我们可以使用一种叫`function adapter`的方法来对`helloHandler()`进行包装:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
这样就非常轻松地实现了业务与非业务之间的剥离,魔法就在于这个 timeMiddleware。可以从代码中看到,我们的 timeMiddleware 也是一个函数,其参数为 http.Handler,http.Handler 的定义在 net/http 包中:
|
这样就非常轻松地实现了业务与非业务之间的剥离,魔法就在于这个timeMiddleware。可以从代码中看到,我们的timeMiddleware也是一个函数,其参数为http.Handler,http.Handler的定义在`net/http`包中:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
@ -133,7 +133,7 @@ type Handler interface {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
任何方法实现了 ServeHTTP,即是一个合法的 http.Handler,读到这里你可能会有一些混乱,我们先来梳理一下 http 库的 Handler,HandlerFunc 和 ServeHTTP 的关系:
|
任何方法实现了`ServeHTTP`,即是一个合法的`http.Handler`,读到这里你可能会有一些混乱,我们先来梳理一下http库的`Handler`,`HandlerFunc`和`ServeHTTP`的关系:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
@ -153,13 +153,13 @@ func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
|
|||||||
func (ResponseWriter, *Request)
|
func (ResponseWriter, *Request)
|
||||||
```
|
```
|
||||||
|
|
||||||
那么这个 handler 和 http.HandlerFunc 就有了一致的函数签名,可以将该 handler 函数进行类型转换,转为 http.HandlerFunc。而 http.HandlerFunc 实现了 http.Handler 这个接口。在 http 库需要调用你的 handler 函数来处理 http 请求时,会调用 HandlerFunc 的 ServeHTTP 函数,可见一个请求的基本调用链是这样的:
|
那么这个handler和`http.HandlerFunc()`就有了一致的函数签名,可以将该handler函数进行类型转换,转为`http.HandlerFunc`。而`http.HandlerFunc`实现了`http.Handler`这个接口。在http库需要调用你的handler函数来处理http请求时,会调用`HandlerFunc`的`ServeHTTP`函数,可见一个请求的基本调用链是这样的:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
h = getHandler() => h.ServeHTTP(w, r) => h(w, r)
|
h = getHandler() => h.ServeHTTP(w, r) => h(w, r)
|
||||||
```
|
```
|
||||||
|
|
||||||
上面提到的把自定义 handler 转换为 http.HandlerFunc 这个过程是必须的,因为我们的 handler 没有直接实现 ServeHTTP 这个接口。上面的代码中我们看到的 HandleFunc(注意 HandlerFunc 和 HandleFunc 的区别)里也可以看到这个强制转换过程:
|
上面提到的把自定义handler转换为`http.HandlerFunc`这个过程是必须的,因为我们的handler没有直接实现`ServeHTTP`这个接口。上面的代码中我们看到的HandleFunc(注意HandlerFunc和HandleFunc的区别)里也可以看到这个强制转换过程:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
|
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
|
||||||
@ -207,7 +207,7 @@ customizedHandler = logger(timeout(ratelimit(helloHandler)))
|
|||||||
|
|
||||||
## 5.3.3 更优雅的 middleware 写法
|
## 5.3.3 更优雅的 middleware 写法
|
||||||
|
|
||||||
上一节中解决了业务功能代码和非业务功能代码的解耦,但也提到了,看起来并不美观,如果需要修改这些函数的顺序,或者增删 middleware 还是有点费劲,本节我们来进行一些“写法”上的优化。
|
上一节中解决了业务功能代码和非业务功能代码的解耦,但也提到了,看起来并不美观,如果需要修改这些函数的顺序,或者增删中间件还是有点费劲,本节我们来进行一些“写法”上的优化。
|
||||||
|
|
||||||
看一个例子:
|
看一个例子:
|
||||||
|
|
||||||
@ -276,11 +276,11 @@ throttler.go
|
|||||||
=> 通过定长大小的 channel 存储 token,并通过这些 token 对接口进行限流
|
=> 通过定长大小的 channel 存储 token,并通过这些 token 对接口进行限流
|
||||||
```
|
```
|
||||||
|
|
||||||
每一个 Web 框架都会有对应的 middleware 组件,如果你有兴趣,也可以向这些项目贡献有用的 middleware,只要合理一般项目的维护人也愿意合并你的 pull request。
|
每一个Web框架都会有对应的middleware组件,如果你有兴趣,也可以向这些项目贡献有用的middleware,只要合理一般项目的维护人也愿意合并你的Pull Request。
|
||||||
|
|
||||||
比如开源界很火的gin这个框架,就专门为用户贡献的middleware开了一个仓库:
|
比如开源界很火的gin这个框架,就专门为用户贡献的middleware开了一个仓库:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
如果读者去阅读 gin 的源码的话,可能会发现 gin 的 middleware 中处理的并不是 http.Handler,而是一个叫 gin.HandlerFunc 的函数类型,和本节中讲解的 http.Handler 签名并不一样。不过实际上 gin 的 handler 也只是针对其框架的一种封装,middleware 的原理与本节中的说明是一致的。
|
如果读者去阅读gin的源码的话,可能会发现gin的middleware中处理的并不是`http.Handler`,而是一个叫`gin.HandlerFunc`的函数类型,和本节中讲解的`http.Handler`签名并不一样。不过实际上gin的handler也只是针对其框架的一种封装,middleware的原理与本节中的说明是一致的。
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user