天天看点

Go语言的Http 中间件实现

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

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

servemux => middleware handler => application handler 

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

基本原则:

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

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

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

我来解释如何实现:

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

func messagehandler(message string) http.handler { 

return http.handlerfunc(func(w http.responsewriter, r *http.request) { 

w.write([]byte(message) 

}) 

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

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

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

func examplemiddleware(next http.handler) http.handler { 

// our middleware logic goes here... 

next.servehttp(w, r) 

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

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

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

比如:

http.handle("/", middlewareone(middlewaretwo(finalhandler))) 

控制流说明:

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

package main 

import ( 

"log" 

"net/http" 

func middlewareone(next http.handler) http.handler { 

log.println("executing middlewareone") 

log.println("executing middlewareone again") 

func middlewaretwo(next http.handler) http.handler { 

log.println("executing middlewaretwo") 

if r.url.path != "/" { 

return 

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.listenandserve(":3000", nil) 

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

Go语言的Http 中间件实现

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

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

2015/12/19 04:21:57 executing middlewareone 

2015/12/19 04:21:57 executing middlewaretwo 

2015/12/19 04:21:57 executing middlewareone again 

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

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

"bytes" 

func enforcexmlhandler(next http.handler) http.handler { 

// check for a request body 

if r.contentlength == 0 { 

http.error(w, http.statustext(400), 400) 

// 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) 

http.handle("/", enforcexmlhandler(finalhandler)) 

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

$ cat > books.xml  

h. g. wells  

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-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 

ok 

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

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

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

$ go get github.com/goji/httpauth 

"github.com/goji/httpauth" 

authhandler := httpauth.simplebasicauth("username", "password") 

http.handle("/", authhandler(finalhandler)) 

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

$ curl -i username:password@localhost:3000 

$ curl -i username:wrongpassword@localhost:3000 

http/1.1 401 unauthorized 

www-authenticate: basic realm=""restricted"" 

content-length: 13 

unauthorized 

gorilla’s logginghandler和apache-style logs有一些区别

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

首先还是引入第三包

go get github.com/gorilla/handlers 

"github.com/gorilla/handlers" 

"os" 

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)) 

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

http.handle("/", handlers.logginghandler(logfile, authhandler(enforcexmlhandler(finalhandler)))) 

不过这看起来太糟糕了。

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

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

func mylogginghandler(h http.handler) http.handler { 

return handlers.logginghandler(logfile, h) 

http.handle("/", mylogginghandler(finalhandler)) 

$ cat server.log 

127.0.0.1 - - [21/oct/2014:18:56:43 +0100] "get / http/1.1" 200 2 

127.0.0.1 - - [21/oct/2014:18:56:36 +0100] "post / http/1.1" 200 2 

127.0.0.1 - - [21/oct/2014:18:56:43 +0100] "put / http/1.1" 200 2 

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

logfile, err := os.openfile("server.log", os.o_rdwr|os.o_create|os.o_append, 0666) 

indexhandler := http.handlerfunc(index) 

http.handle("/", mylogginghandler(authhandler(enforcexmlhandler(indexhandler)))) 

func index(w http.responsewriter, r *http.request) { 

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

作者:何妍 

来源:51cto

继续阅读