频道栏目
首页 > 资讯 > 其他 > 正文

Go语言的Http 中间件实现

15-12-22        来源:[db:作者]  
收藏   我要投稿

当你正在构建一个Web应用程序有可能要运行许多(甚至全部)的HTTP请求一些共享功能,你可能想记录每一个request,gzip压缩的每个response,或者做一些繁重的处理或者缓存检查。

实现这个共享功能的一种方法是将其设置为中间件,他可以作为一个独立的程序,在正常的handlers处理之前。根本不需要重写代码:如果你想用一个中间件,就把它加上应用中;如果你改变主意了,去掉就好了。就这么简单。


  1. ServeMux => Middleware Handler => Application Handler 

这篇文章,我会给大家介绍怎么自己去实现一个自定义的middleware模式。以及通过使用第三方的中间件软件包的一些具体的实例。

基本原则:

在Go语言中实现和使用middleware是非常简单的。

使我们的中间件能搞满足 http.handlers 这个接口

建立一个 handlers 链,使其能够满足中间件的 handler 和 正常应用的 handler,并且能够注册到 http.ServeMux

我来解释如何实现:

首先你要知道go 的http handle,这里假设你是知道的


  1. func messageHandler(message string) http.Handler {  
  2. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {  
  3. w.Write([]byte(message)  
  4. })  

这上面这个代码片段里面我们的逻辑很简单只是一个简单的 w.Write() 然后我们使用 http.HandlerFunc 适配器来转化这个闭包,并返回。

我们可以使用一个相同的方法来创建一个 handler 链。可以使用 handler 代替参数 string 传进闭包,然后把控制 handler 给传进来的 handler,并且调用 ServeHTTP() 方法。

这给了我们一个完整的模式构建中间件:


  1. func exampleMiddleware(next http.Handler) http.Handler {  
  2. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {  
  3. // Our middleware logic goes here...  
  4. next.ServeHTTP(w, r)  
  5. })  

你注意到这个中间件有一个这样的函数结构 func(http.Handler) http.Handler 。它接受一个 handler 作为参数,并且返回一个 handler。这里有两个很有用的原因:

因为这个函数返回一个句柄可以直接供中间件注册

我们可以建立任意长度的 handler 链来通过中间件的方法互相嵌套

比如:


  1. http.Handle("/", middlewareOne(middlewareTwo(finalHandler))) 

控制流说明:

让我们来看一个带有多个中间件的例子,并且把日志输出到控制台:
 

    package main  
    import (  
    "log"  
    "net/http"  
    )  
    func middlewareOne(next http.Handler) http.Handler {  
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {  
    log.Println("Executing middlewareOne")  
    next.ServeHTTP(w, r)  
    log.Println("Executing middlewareOne again")  
    })  
    }  
    func middlewareTwo(next http.Handler) http.Handler {  
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {  
    log.Println("Executing middlewareTwo")  
    if r.URL.Path != "/" {  
    return  
    }  
    next.ServeHTTP(w, r)  
    log.Println("Executing middlewareTwo again")  
    })  
    }  
    func final(w http.ResponseWriter, r *http.Request) {  
    log.Println("Executing finalHandler")  
    w.Write([]byte("OK"))  
    }  
    func main() {  
    finalHandler := http.HandlerFunc(final)  
    http.Handle("/", middlewareOne(middlewareTwo(finalHandler)))  
    http.ListenAndServe(":3000", nil)  
    } 

然后我们执行 go run main.go 在浏览器打开http://localhost:3000。 你会看到下面的输出。

 4626AADF-4508-4094-B16B-59356D45FB55

我们能够很清楚的看到handle的流程控制。我们嵌套他们的返回顺序。我们可以通过中间件中得 return 随时停止handle链的控制。

在上面的代码中我们在middlewareTwo function包含了retrun 语句。我们在浏览器中打开http://localhost:3000/foo,我们会看到。


  1. 2015/12/19 04:21:57 Executing middlewareOne  
  2. 2015/12/19 04:21:57 Executing middlewareTwo  
  3. 2015/12/19 04:21:57 Executing middlewareOne again  
  4. 2015/12/19 04:21:57 Executing middlewareOne  
  5. 2015/12/19 04:21:57 Executing middlewareTwo  
  6. 2015/12/19 04:21:57 Executing middlewareOne again 

我们实现一个真实的项目的示例:

我们实现一个判断请求是不是XMl的功能,我们要实现一个中间件。用来检查的请求体的存在。检查请求体,以确保它是XML。如果其中检查失败,我希望我们的中间件输出错误信息然后终止我们的handle处理。


    package main  
    import (  
    "bytes"  
    "net/http"  
    )  
    func enforceXMLHandler(next http.Handler) http.Handler {  
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {  
    // Check for a request body  
    if r.ContentLength == 0 {  
    http.Error(w, http.StatusText(400), 400)  
    return  
    }  
    // Check its MIME type  
    buf := new(bytes.Buffer)  
    buf.ReadFrom(r.Body)  
    if http.DetectContentType(buf.Bytes()) != "text/xml; charset=utf-8" {  
    http.Error(w, http.StatusText(415), 415)  
    return  
    }  
    next.ServeHTTP(w, r)  
    })  
    }  
    func main() {  
    finalHandler := http.HandlerFunc(final)  
    http.Handle("/", enforceXMLHandler(finalHandler))  
    http.ListenAndServe(":3000", nil)  
    }  
    func final(w http.ResponseWriter, r *http.Request) {  
    w.Write([]byte("OK"))  
    } 


为了检验我们的中间件是否实现了这个功能,我们首先创建一个XML文件。


  1. $ cat > books.xml  
  2. H. G. Wells  
  3. 8.50   

然后通过使用cURL来进行模拟请求:
 

    $ curl -i localhost:3000  
    HTTP/1.1 400 Bad Request  
    Content-Type: text/plain; charset=utf-8  
    Content-Length: 12  
    Bad Request  
    $ curl -i -d "This is not XML" localhost:3000  
    HTTP/1.1 415 Unsupported Media Type  
    Content-Type: text/plain; charset=utf-8  
    Content-Length: 23  
    Unsupported Media Type  
    $ curl -i -d @books.xml localhost:3000  
    HTTP/1.1 200 OK  
    Date: Fri, 17 Oct 2014 13:42:10 GMT  
    Content-Length: 2  
    Content-Type: text/plain; charset=utf-8  
    OK 

接下来给大家介绍一下第三方中间件的使用:

秉承不造轮子的原则,其实在Github上有很多实现了一些功能的中间件。比如这里给大家介绍2个基础验证的中间件goji/httpauth和Gorilla’s LoggingHandler

首先我们需要引入第三方包


    $ go get github.com/goji/httpauth 

    package main  
    import (  
    "github.com/goji/httpauth"  
    "net/http"  
    )  
    func main() {  
    finalHandler := http.HandlerFunc(final)  
    authHandler := httpauth.SimpleBasicAuth("username", "password")  
    http.Handle("/", authHandler(finalHandler))  
    http.ListenAndServe(":3000", nil)  
    }  
    func final(w http.ResponseWriter, r *http.Request) {  
    w.Write([]byte("OK"))  
    } 

如果你运行这个例子,你应该得到你所期望的有效和无效的凭证响应


  1. $ curl -i username:password@localhost:3000  
  2. HTTP/1.1 200 OK  
  3. Content-Length: 2  
  4. Content-Type: text/plain; charset=utf-8  
  5. OK  
  6. $ curl -i username:wrongpassword@localhost:3000  
  7. HTTP/1.1 401 Unauthorized  
  8. Content-Type: text/plain; charset=utf-8  
  9. Www-Authenticate: Basic realm=""Restricted""  
  10. Content-Length: 13  
  11. Unauthorized 

Gorilla’s LoggingHandler和Apache-style logs有一些区别

以下是我们在其中写入日志到server.log文件一个简单的例子:

首先还是引入第三包


  1. go get github.com/gorilla/handlers 
    package main  
    import (  
    "github.com/gorilla/handlers"  
    "net/http"  
    "os"  
    )  
    func main() {  
    finalHandler := http.HandlerFunc(final)  
    logFile, err := os.OpenFile("server.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)  
    if err != nil {  
    panic(err)  
    }  
    http.Handle("/", handlers.LoggingHandler(logFile, finalHandler))  
    http.ListenAndServe(":3000", nil)  
    }  
    func final(w http.ResponseWriter, r *http.Request) {  
    w.Write([]byte("OK"))  
    } 

 


在一个简单的情况下,这样我们的代码是相当清楚的。但是,如果我们想用LoggingHandler作为一个更大的中间件链中的一部分会发生什么?我们可以很容易地结束了一个声明,看起来像这样:


  1. http.Handle("/", handlers.LoggingHandler(logFile, authHandler(enforceXMLHandler(finalHandler)))) 

不过这看起来太糟糕了。

我们可以通过创建一个构造函数打来整理一下我们给它取名为(myLoggingHandler)

和signature func(http.Handler) http.Handler.这样就会是我们的代码更加整洁和可读性:

 


  1. func myLoggingHandler(h http.Handler) http.Handler {  
  2. logFile, err := os.OpenFile("server.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)  
  3. if err != nil {  
  4. panic(err)  
  5.  
  6. return handlers.LoggingHandler(logFile, h)  
  7.  
  8. func main() {  
  9. finalHandler := http.HandlerFunc(final 
  10. http.Handle("/", myLoggingHandler(finalHandler))  
  11. http.ListenAndServe(":3000", nil)  

  1. $ cat server.log  
  2. 127.0.0.1 - - [21/Oct/2014:18:56:43 +0100] "GET / HTTP/1.1" 200 2  
  3. 127.0.0.1 - - [21/Oct/2014:18:56:36 +0100] "POST / HTTP/1.1" 200 2  
  4. 127.0.0.1 - - [21/Oct/2014:18:56:43 +0100] "PUT / HTTP/1.1" 200 2 

这里还有一个比较完整结构的中间件使用的示例:


    package main  
    import (  
    "bytes"  
    "github.com/goji/httpauth"  
    "github.com/gorilla/handlers"  
    "net/http"  
    "os"  
    )  
    func enforceXMLHandler(next http.Handler) http.Handler {  
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {  
    if r.ContentLength == 0 {  
    http.Error(w, http.StatusText(400), 400)  
    return  
    }  
    buf := new(bytes.Buffer)  
    buf.ReadFrom(r.Body)  
    if http.DetectContentType(buf.Bytes()) != "text/xml; charset=utf-8" {  
    http.Error(w, http.StatusText(415), 415)  
    return  
    }  
    next.ServeHTTP(w, r)  
    })  
    }  
    func myLoggingHandler(h http.Handler) http.Handler {  
    logFile, err := os.OpenFile("server.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)  
    if err != nil {  
    panic(err)  
    }  
    return handlers.LoggingHandler(logFile, h)  
    }  
    func main() {  
    indexHandler := http.HandlerFunc(index)  
    authHandler := httpauth.SimpleBasicAuth("username", "password")  
    http.Handle("/", myLoggingHandler(authHandler(enforceXMLHandler(indexHandler))))  
    http.ListenAndServe(":3000", nil)  
    }  
    func index(w http.ResponseWriter, r *http.Request) {  
    w.Write([]byte("OK"))  
    } 


有很多人不太喜欢中间件的设计模式,不过我还是慢喜欢的。

相关TAG标签
上一篇:透过“虚火”洞悉物联网的价值
下一篇:大道智简 未来智能办公时代大幕开启
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站