微信号:flysnow_org

介绍:打杂师,观察家.

Go语言经典库使用分析(三)| Gorilla Handlers 详细介绍

2017-08-06 22:00 飞雪无情

Go语言经典库使用分析,未完待续,欢迎扫码关注公众号flysnow_org或者网站http://www.flysnow.org/,第一时间看后续系列。觉得有帮助的话,顺手分享到朋友圈吧,感谢支持。

在我们编写web服务端程序的时候,我们可能会对一些甚至全部的Http Request统一处理,比如我们记录每个访问的Request,对提交的Form表单进行映射等,要达到这些目的,比较优雅的做法是Http 中间件。

中间件,顾名思义,强调的是中间,他是一种业务无关的,在正常的的业务handler处理前后的,独立的逻辑处理片段。一般调用顺序如下:

ServeMux路由分发->调用中间件1->调用中间件2……->调用真正的业务处理逻辑

因为中间件非常独立,可以我们不用的时候,去掉即可;需要用的时候,加上,并不会修改真正的业务处理逻辑代码,可谓非常简洁方便。

这里我选用Gorilla Handlers这个中间件库演示如何使用和定义一个中间件,这一篇主要讲Gorilla Handlers的使用,下一篇会讲Gorilla Handlers里每个中间件的实现原理。

安装

Gorilla Handlers是一个很简单,但是很有代表性的中间件库,所以拿他来分析,更容易理解handler中间件。在使用之前,我们要先安装,该中间件库已经托管在Github上,所以我们直接使用go get即可。

$ go get github.com/gorilla/handlers

安装之后,我们在代码里使用如下代码即可导入使用。

import "github.com/gorilla/handlers"

Gorilla Handlers以函数的方式提供了好几种中间件,比如CombinedLoggingHandler、CompressHandler、ContentTypeHandler、LoggingHandler等,下面我们就一一介绍他们。

LoggingHandler

我们应该都用过Nginx,Nginx的访问日志,类似于如下这样:

[05/Aug/2017:21:06:24 +0800] "GET /favicon.ico HTTP/1.1" 200 11

从中我们可以看到访问的什么资源,使用的什么协议,返回的HTTP状态以及请求的大小是多少,这种一种日志风格,和Apache Common Log Format很像,LoggingHandler中间件就是帮我们做这个事情的。

它可以记录request的日志,输出到一个io.Writer里。

func main() {
    http.Handle("/",useLoggingHandler(handler()))
    http.ListenAndServe(":1234",nil)
}
func handler() http.Handler{
   return http.HandlerFunc(myHandler) }
func myHandler(rw http.ResponseWriter, r *http.Request) {    rw.WriteHeader(http.StatusOK)    io.WriteString(rw,"Hello World") }
func useLoggingHandler(next http.Handler) http.Handler {
   return handlers.LoggingHandler(os.Stdout,next) }

我这里的io.Writer是一个os.Stdout,也就是标准的控制台输出,所以我们访问http://localhost:1234/的时候,可以看到控制台打印的Request日志信息输出。

::1 - - [05/Aug/2017:21:06:24 +0800] "GET / HTTP/1.1" 200 11
::1 - - [05/Aug/2017:21:06:24 +0800] "GET /favicon.ico HTTP/1.1" 200 11

handlers.LoggingHandler函数的参数我们可以看出,它接受一个Handler,然后返回一个Handler,其实就是对现有的Handler的一次包装,这就是中间件。

这里的输出参数类型是io.Writer,所以我们可以输出文件等实现了该接口的任何类型。

Go语言经典库使用分析,未完待续,欢迎扫码关注公众号flysnow_org或者网站http://www.flysnow.org/,第一时间看后续系列。觉得有帮助的话,顺手分享到朋友圈吧,感谢支持。

CombinedLoggingHandler

还有一种日志格式,这种日志格式输出的日志信息更详细,更全面,比如包含UA等信息,这种格式被称为Apache Combined Log Format。

CombinedLoggingHandler就是为我们提供输出一种这种格式的中间件,使用方式和LoggingHandler一样。

func useCombinedLoggingHandler(next http.Handler) http.Handler {
   return handlers.CombinedLoggingHandler(os.Stdout,next) }

看下这个中间件输出的日志信息

::1 - - [05/Aug/2017:21:39:45 +0800] "GET /favicon.ico HTTP/1.1" 200 11 "http://localhost:1234/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36"

UA+HOST,日志信息更全面了。

CompressHandler

这是一个压缩response的中间件,支持gzip和deflate压缩。如果客户端的请求头里包含Accept-Encoding,并且值为gzip或者deflate,该中间件就会压缩返回的response,这样就可以减少response的大小,减少响应的时间,使用访问也很简单,这里简单举个例子。

func main() {
    http.Handle("/gzip",useCompressHandler(handler()))
    http.ListenAndServe(":1234",nil)
}
func handler() http.Handler{
   return http.HandlerFunc(myHandler) }
func myHandler(rw http.ResponseWriter, r *http.Request) {    rw.WriteHeader(http.StatusOK)    rw.Header().Set("Content-Type", "text/plain")    io.WriteString(rw,"Hello World") }
func useCompressHandler(next http.Handler) http.Handler {
   return handlers.CompressHandler(next) }

这里尤其注意的是我们返回的response的内容类型要特别指定一下,不然就会被自动解析为gzip,就变成下载一个gz文件了。我这里强制指定文本类型,这样就可以看到返回的内容Hello World

该中间件还有一个函数CompressHandlerLevel可以指定压缩的级别,级别是gzip.BestSpeed和gzip.BestCompression之间的值,如果大家不想用默认压缩级别,可以使用这个函数指定。

还记得Nginx可以开启Gzip加速吧,差不多也是这么个实现。

ContentTypeHandler

这也是一个很有意思的中间件,他的作用是只处理支持的内容类型,如果不支持,则返回415状态码。该中间件只对PUT,POST,PATCH方法有效,其他方法则不做处理,也就相当于没有使用这个中间件。

func useContentTypeHandler(next http.Handler) http.Handler {
   return handlers.ContentTypeHandler(next,"application/x-www-form-urlencoded") }

最后一个参数是可变参数,我们可以指定多个ContextType类型,这些类型是被我们支持的,其他类型则不支持。如果我们使用PUT、POST、PATCH方法提交的请求的内容类型不在我们支持的内容类型范围内,则返回415错误。

CanonicalHost

这是一个重定向的中间件,他可以把一个Request请求重新定向到另外一个域名上,并且会带上原请求的Path和Query。

func main() {
    http.Handle("/flysnow",useCanonicalHost(handler()))
    http.ListenAndServe(":1234",nil)
}
func handler() http.Handler{
   return http.HandlerFunc(myHandler) }
func myHandler(rw http.ResponseWriter, r *http.Request) {    rw.WriteHeader(http.StatusOK)    rw.Header().Set("Content-Type", "text/plain")    io.WriteString(rw,"Hello World") }
func useCanonicalHost(next http.Handler) http.Handler {
   return handlers.CanonicalHost("http://www.flysnow.org/",http.StatusFound)(next) }

上面的示例,当我们在浏览器内输入http://localhost:1234/flysnow的访问的时候,会自动跳转到http://www.flysnow.org/flysnow

小结

其他的一些不常用的中间的使用方式也类似,大家可以自己看下文档测试下。Go Http的中间件,有点类似于HTTP的拦截器,通过一层层的包装,我们可以组成一个中间件的处理链,便于我们处理我们需要处理的通用性问题,如果哪个中间件不想要,去掉即可,不用对业务代码做任何修改。

例子中的演示,都是一个url对应一个中间件的处理,这个主要是为了演示方面。如果我们相对所有的请求都使用某个中间件怎么做呢?肯定不能使用我们例子中的方式了,因为这样会写很多个。

对于以上这种方式,我们可以借助HTTP Mux路由,因为一个路由也是一个Handler,只用对这个路由应用中间件,就可以拦截处理所有的请求了。

下一篇开始分享这些常用中间件的实现原理和源代码的分析。

Go语言经典库使用分析,未完待续,欢迎扫码关注公众号flysnow_org或者网站http://www.flysnow.org/,第一时间看后续系列。觉得有帮助的话,顺手分享到朋友圈吧,感谢支持。


 
飞雪无情 更多文章 Go语言经典库使用分析(一)| 开篇 Go语言经典库使用分析(二)| Gorilla Context 《Go语言实战》笔记(二十八) | 后记 水库文章目录 《Go语言实战》笔记(二十七) | Go unsafe Pointer
猜您喜欢 Freetalk成都站|万圣节,选技术流还是超能力 Python中关于集合(set)的思考 赠票10张:第九届中国云计算大会 如何规划私有云的网络模型 最棒的leader,把团队成长放在第一位