[转]HTTP中间件机制实现与原理 – 从零开始写GO-API框架

 

转,原文: http://www.guoxiaolong.cn/blog/?id=8600

 

 

-----------------

 

中间件Middleware

所谓中间件,就是连接上下级不同功能的函数或者软件,通常进行一些包裹函数的行为,为被包裹函数提供添加一些功能或行为。前文的HandleFunc就能把签名为 func(w http.ResponseWriter, r *http.Reqeust)的函数包裹成handler。这个函数也算是中间件。

这里我们以HTTP请求的中间件为例子,提供一个log中间件,能够打印出每一个请求的log。

go的http中间件很简单,只要实现一个函数签名为func(http.Handler) http.Handler的函数即可。http.Handler是一个接口,接口方法我们熟悉的为serveHTTP。返回也是一个handler。因为go中的函数也可以当成变量传递或者或者返回,因此也可以在中间件函数中传递定义好的函数,只要这个函数是一个handler即可,即实现或者被handlerFunc包裹成为handler处理器。

func middlewareHandler(next http.Handler) http.Handler{
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
        // 执行handler之前的逻辑
        next.ServeHTTP(w, r)
        // 执行完毕handler后的逻辑
    })
}

这种方式在Elixir的Plug框架中很流行,思想偏向于函数式范式。熟悉python的朋友一定也想到了装饰器。闲话少说,来看看go是如何实现的吧:

func loggingHandler(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("Comleted %s in %v", r.URL.Path, time.Since(start))
    })
}

func main() {
    http.Handle("/", loggingHandler(http.HandlerFunc(index)))
    http.ListenAndServe(":8000", nil)
}

loggingHandler即是一个中间件函数,将请求的和完成的时间处理。可以看见请求或go的输出:

2016/12/04 21:18:13 Started GET /
2016/12/04 21:18:13 Comleted / in 13.365µs
2016/12/04 21:18:20 Started GET /
2016/12/04 21:18:20 Comleted / in 17.541µs

既然中间件是一种函数,并且签名都是一样,那么很容易就联想到函数一层包一层的中间件。再添加一个函数,然后修改main函数:

func hook(next http.Handler) http.Handler{
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println("before hook")
        next.ServeHTTP(w, r)
        log.Println("after hook")

    })
}

func main() {
    http.Handle("/", hook(loggingHandler(http.HandlerFunc(index))))
    http.ListenAndServe(":8000", nil)
}

在loggingHandler再包了一层hook,可以看到输出为:

2016/12/04 21:26:30 before hook
2016/12/04 21:26:30 Started GET /
2016/12/04 21:26:30 Comleted / in 14.016µs
2016/12/04 21:26:30 after hook

函数调用形成了一条链,可以是在这条链上做很多事情。当然go的写法上,比起elixir的|>的符号,优雅性略差。



作者:dongzd
链接:https://www.jianshu.com/p/9b081b179e71
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

---------------------

HTTP中间件机制实现与原理 – 从零开始写GO-API框架

 

大家好,很高兴您能阅读这篇文章。

最近在投稿公众号时发现从未做过自我介绍,首先请允许我介绍一下自己。

我叫张晓亮,就职于新浪微博,Golang的忠实粉丝,平时的爱好看看书、撸撸码,典型的程序员性格,最近喜欢上写博客,发现不仅能从中了解到很多细节,也能帮助到其他人。

引言

因golang内置的net/http天生就支持http 中间件 机制,所以即使不使用开源web框架,我们也可以写出扩展性很好的应用。

一个好的中间件有一个责任就是可插拔并且自足,这就意味着你可以在接口级别嵌入你的中间件他就能直接运行,且不会影响你的编码方式,这不是框架,仅仅是在请求处理里面的一层而已。

可以想象每一个中间件都是一层洋葱皮,其中每一个中间件都可以改变请求响应,我们可以很自然的把不同的逻辑放到不同的洋葱皮里,让代码更符合单一原则。

你可以使用中间件做什么?

 1、重置HTTP请求路由
2、统一安全限制、信息上报
3、Header操作、http请求认证
4、屏蔽爬虫
5、提供调试信息
6、请求日志记录

还有很多,可以自行发掘下  

 

 


 

 

中间件通常会是一小段代码,它可以接受一个请求,对其进行处理,每个中间件只处理一件事,完成后将其传递给下一层或最终处理。这样就做到了程序的 解耦 。

如果没有中间件我们必须在最终处理程序中来完成这个逻辑操作,这无疑会造成你现有业务逻辑的臃肿和代码复用率不高的问题.

http中间件使用案例

惯例我们来看使用中间件的案例:

测试代码

 func  Middleware Loger() koala.HandlerFunc {
return func(ctx *koala.Context) {

fmt.Printf("%+v", ctx.Req)
fmt.Println("middloger 中间件")
// fmt.Printf("%+V 进入到中间件了\n", ctx)
}
}

func main() {
    app := koala.New()
app.Use(middlewareLoger())
app.Add("GET", "/ profile /xiaoliang", func(ctx *koala.Context) {
ctx.Text("profile.xiaoliang")
})
}
  

web请求地址: 输出

 [KOALA-DEBUG][启动中]
Add GET /profile/xiaoliang 
Add GET /profile/xiaoliang1 
Add GET /member/:id 
[KOALA-DEBUG]监听端口[:8080]
[KOALA-DEBUG][服务启动成功]

此处是中间件输出信息
&{Method:GET URL:/profile/xiaoliang1 Proto:HTTP/1.1 ...... ctx:0xc00005c4c0}middlewareLoger 中间件

  

实现扩展HTTP中间件机制

以上讲解了中间件的相关基础知识,也看了使用案例,下面我们来实现一下中间件是如何嵌入到web框架中,并以此来实现各种解耦的功能。

先来看张图

 

 


 

 

可以看看,如果我们把路由函数xiaoliang看做汉堡里的肉饼,中间件函数看成面包,那么 middleware Loger包住了肉饼。

这里可以实现很多层的中间件,为了实现简单,我们这里就做一层。

撸代码

准备工作完成,我们开始撸代码吧。我比较喜欢这个环节_.

以下代码逻辑,是按照实现先后顺序展示

代码位置:

 type Middleware interface {}

// 定义 koala引擎结构
type Koala struct {
...代码折叠

// 中间件
middleware []HandlerFunc

}

// 注册中间件
func(k *Koala) Use(m ...Middleware) {
for i := range m {
if m[i] != nil {
    // 注册中间件放入切片数组
k.middleware = append(k.middleware, warpMiddleware(m[i]))
}
}
}
  

处理不同中间件类型,在这里我们就很容易扩展HTTP中间件机制了

目前仅实现request,后续会陆续扩展

 func warpMiddleware(m Middleware) HandlerFunc {
    // 断言当前函数类型
switch m:= m.(type) {
case HandlerFunc:
return m
case func (*Context):
return m
default:
fmt.Printf("%+V", m)
panic("没找到相关中间件")
}
}
  

next.ServHTTP中间件用户态实现

 // 实现net/http 需要的servehttp服务
func (k *Koala) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

    ...代码折叠

// 执行相关操作
ctx.Next()

...代码折叠
}
  

代码位置:

 // 首先处理中间件,然后处理路由句柄
func (c *Context) Next() {
// http中间件处理
c.middleware()

// 路由映射处理
c.koala. router ().HandlerRouter(c)
}


// 执行HTTP中间件
func (c *Context) middleware() {
for m := range c.koala.middleware{
    // c http上下文传递给注册的中间件函数,这时候已经拿到了用户侧web请求数据,可以进行响应的逻辑操作
c.koala.middleware[m](c)
}
}

  

晓亮嘚吧嘚,请听下回分解

看到这里大伙也发现中间件代码部分实际上不复杂,复杂是一些概念上的知识。

作者原是phper,对于中间件的了解基本属于黑洞。所以这章基础知识讲解的多了一些,一自己巩固知识点,二让看文章的您也可以跟着我的节奏复习一下。

文章还会陆续更新,最近在研究数据库连接池相关知识,准备利用这个框架的轮廓,我们在写一个关于连接池相关的包。

作者本人golang相关知识有限,如讲解不正确,还希望大牛们能帮忙指正,晓亮先感谢您~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章