在 Go 语言里,Negroni 是一个很地道的 Web 中间件,它是一个具备微型、非嵌入式、鼓励使用原生 net/http 库特征的中间件。利用它地Use功能,我们可以很简单地自定义中间件并使用。其中,gzip就是一个很好地例子,它实现了服务器对gzip的响应。
  我们可以通过一个简单的例子,来了解gzip的使用:

package mainimport ("fmt""net/http""github.com/urfave/negroni""github.com/phyber/negroni-gzip/gzip"
)func main() {mux := http.NewServeMux()mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {fmt.Fprintf(w, "Welcome to the home page!")})n := negroni.Classic()n.Use(gzip.Gzip(gzip.DefaultCompression))n.UseHandler(mux)n.Run(":3000")
}

  你只需要安装对应的包,并运行该程序,然后利用curl工具访问该服务器观察以下结果:

$ curl -H "Accept:gzip" -v http://localhost:3000/
* timeout on name lookup is not supported
*   Trying ::1...% Total    % Received % Xferd  Average Speed   Time    Time     Time  CurrentDload  Upload   Total   Spent    Left  Speed0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to localhost (::1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.49.1
> Accept:gzip
>
< HTTP/1.1 200 OK
< Date: Thu, 07 Dec 2017 02:07:23 GMT
< Content-Length: 25
< Content-Type: text/plain; charset=utf-8
<
{ [25 bytes data]
100    25  100    25    0     0   1562      0 --:--:-- --:--:-- --:--:--  1562Welcome to the home page!
* Connection #0 to host localhost left intact

附:linux crul命令指南:http://man.linuxde.net/curl

  根据倒数第二行的结果,可以发现,它的确实现了对于gzip流的响应。


  想要更加清晰了解本文内容,请先了解negroni对于第三方中间件的使用:  

    • golang学习之negroni对于第三方中间件的使用分析


  gzip中间件是如何实现服务器对于gzip流的响应的呢?可以通过阅读源码来了解。源代码仅有126行,简单易懂。为了方便理解,代码阅读阅读顺序推荐为:

+func Gzip(level int) *handler|h := &handler{}|h.pool.New = func() interface{} {...}
+func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)|gz := h.pool.Get().(*gzip.Writer)|gz.Reset(w)|nrw := negroni.NewResponseWriter(w)|grw := gzipResponseWriter{gz, nrw, false}
+func (grw *gzipResponseWriter) WriteHeader(code int)
+func (grw *gzipResponseWriter) Write(b []byte) (int, error)

  只有四个函数,是不是很简单呢?我们便开始分析吧。

1.数据结构的定义

//const的定义包含了一部分http.Request和http.Response的头部内容
//以及compress/gzip的一部分内容
const (encodingGzip = "gzip"headerAcceptEncoding  = "Accept-Encoding"headerContentEncoding = "Content-Encoding"headerContentLength   = "Content-Length"headerContentType     = "Content-Type"headerVary            = "Vary"headerSecWebSocketKey = "Sec-WebSocket-Key"BestCompression    = gzip.BestCompressionBestSpeed          = gzip.BestSpeedDefaultCompression = gzip.DefaultCompressionNoCompression      = gzip.NoCompression
)

如果你想了解更多关于上面定义的概念,请参考:

  • compress/gzip:go语言学习之gzip包解读
  • WebSocket:WebSocket 是什么原理?为什么可以实现持久连接?
  • Vary头部:http vary 头的作用
//gzipResponseWriter包含了gzip.Writer以及negroni.ResponseWriter
//同时还有一个变量判断头部是否已经被设置
type gzipResponseWriter struct {w *gzip.Writernegroni.ResponseWriterwroteHeader bool
}
//建立一个临时对象池,用于存放gzip.Writer
type handler struct {pool sync.Pool
}

关于临时对象池:go的临时对象池–sync.Pool

2. Gzip

  该函数返回一个handler处理gzip压缩。但是,这个函数只是创建了一个gzip.Writer,并存放到临时对象池中;真正的处理在ServeHTTP中进行。

func Gzip(level int) *handler {h := &handler{}h.pool.New = func() interface{} {//创建一个gzip.Writer//丢弃一切向io.Writer的输入并为level赋值//gz为*gzip.Writer类型gz, err := gzip.NewWriterLevel(ioutil.Discard, level)if err != nil {panic(err)}return gz}return h
}

关于ioutil.Discard:

ioutil包中的Discard对象实现了接口io.Writer,但是抛弃了所有写入的数据。可以将其当做/dev/null:用于发送需要读取但不想存储的数据。该对象被广泛使用于io.Copy(),目的是耗尽读取端的数据。

3. ServeHTTP

  任何一个server都有Handler处理器,而只要http.Handler接口的对象都可作为一个处理器。Handler接口定义如下:

type Handler interface {ServeHTTP(ResponseWriter, *Request)
}

  很明显,ServeHTTP是Handler的核心方法。所以,对于gzip中间件来说,ServeHTTP定义了gzip的处理器,即服务器如何处理客户端的gzip流请求。虽然并没有直接实现http.Handler接口,但是negroni封装中实现了它到http.Handler对接。

func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { //如果请求头部没有包含“Accept:gzip",则跳过gzip压缩 //next Handler有negroni提供;next函数和路由中的HandlerFunc对接,如本文开头例子中输出"Welcome to the home page!"的函数if !strings.Contains(r.Header.Get(headerAcceptEncoding), encodingGzip) {next(w, r)return} // 如果客户端请求建立WebSocket连接,则跳过gzip压缩if len(r.Header.Get(headerSecWebSocketKey)) > 0 {next(w, r)return} //从对象临时池中取出一个*gzip.Writer,并作类型断言 //使用完毕后,将*gzip.Writer回收 //使用前,重置gzip.Writergz := h.pool.Get().(*gzip.Writer)defer h.pool.Put(gz)gz.Reset(w) //创建一个gzipResponseWriternrw := negroni.NewResponseWriter(w)grw := gzipResponseWriter{gz, nrw, false} //调用next Handler处理gzipResponseWriter //具体调度由negroni完成next(&grw, r) //写入完成,删除headerContentLength的值grw.Header().Del(headerContentLength) //关闭gz.Close()
}

4. WriteHeader

  该函数检查Response是否已经预编译,如果已经预编译则在正文写入前禁用gzip.Writer。即如果没有设置Header,则设置Header;否则,等待正文写入。

func (grw *gzipResponseWriter) WriteHeader(code int) {headers := grw.ResponseWriter.Header()if headers.Get(headerContentEncoding) == "" {headers.Set(headerContentEncoding, encodingGzip)headers.Add(headerVary, headerAcceptEncoding)} else {//重置gzip.Writer,并且一切io.Writer的写入都会被丢弃grw.w.Reset(ioutil.Discard)grw.w = nil}grw.ResponseWriter.WriteHeader(code)grw.wroteHeader = true
}

5. Write

  该函数把数据写入gzip.Writer,同时设置Content-Type和状态码。

func (grw *gzipResponseWriter) Write(b []byte) (int, error) {if !grw.wroteHeader {grw.WriteHeader(http.StatusOK)}if grw.w == nil {return grw.ResponseWriter.Write(b)}if len(grw.Header().Get(headerContentType)) == 0 {grw.Header().Set(headerContentType, http.DetectContentType(b))}return grw.w.Write(b)
}

  以上便是negroni/gizp源码的所有内容了。通过解读源码,你也已经清楚了negroni中间件的编写方法,那么也可以尝试自己编写一些中间件了。

golang学习之negroni/gizp源码分析相关推荐

  1. 深度学习库 caffe使用 源码分析 依赖库分析 caffe glog gflags openBlas prototxt yolo_darknet 转 caffe

    深度学习库 caffe使用 源码分析 依赖库分析 caffe glog gflags openBlas 本文github链接 yolo_darknet 转 caffe caffe 安装 Caffe代码 ...

  2. Golang日志框架lumberjack包源码分析

    github地址:  https://github.com/natefinch/lumberjack 获取源码 go get gopkg.in/natefinch/lumberjack.v2 介绍 l ...

  3. Golang|区块链UTXO集源码分析

    区块链UTXO集源码分析 资源 go实现区块链 前提 在未实现UTXO集之前,假设系统需要查询某个钱包地址的余额,系统需要遍历区块链的所有区块,当区块链非常长时,这种做法的成本太高了. UTXO集是未 ...

  4. 了不起的 Webpack HMR 学习指南(含源码分析)

    学习时间:2020.06.14 学习章节:<Webpack HMR 原理解析> 一.HMR 介绍 Hot Module Replacement(以下简称:HMR 模块热替换)是 Webpa ...

  5. FFmpeg 源码学习(一):avformat_open_input 源码分析

    一.源码方法参数分析 下面是avformat_open_input的方法及参数: /** * Open an input stream and read the header. The codecs ...

  6. Combres库 学习小结以及部分源码分析

    昨天看到有博客园网友发布了一篇关于<使用Combres 库 ASP.NET 网站优化>的文章,觉得是个挺不错的脚本优化的类库,而且目前我的项目公务员考试应战平台也有使用到Js以及css合并 ...

  7. Negroni中间件源码分析

    概述 我们可以先看一下HTTP Server的处理逻辑: 在这个逻辑处理的环节中,Negroni充当一个HTTP Handler的角色,并对于所有的HTTP Request的处理都会通过Negroni ...

  8. Netty学习笔记 - 1 (带源码分析部分)

    2021年12月 北京 xxd 一.Netty是什么 Netty 是由 JBOSS 提供的一个 Java 开源框架,现为 Github 上的独立项目. Netty 是一个异步的.基于事件驱动的网络应用 ...

  9. boost学习之boost::lock_guard源码分析

    boost::lock_guard可以说是一种比boost::unique_lock轻量级的lock, 简单一些场景可以用它就行了.源码如下: template<typename Mutex&g ...

最新文章

  1. 多机多卡训练基本原理
  2. oracle的表几种连接比较,几种表连接方式的使用场景
  3. Fiddler (三) Composer创建和发送HTTP Request
  4. Oracle数据类型简介【转贴】
  5. Elasticsearch查询相关总结以及timestamp和时区问题
  6. 解决 vue路由跳转到新页面底部而不是顶部和后退到首页就不让他继续后退了
  7. Scrapy-css选择器
  8. (飞鸽传书绿色版)网站
  9. SpringScerity的使用
  10. 在SpringBoot中优雅的实现定时任务
  11. nodejs python性能_监控Nodejs的性能
  12. 中山大学本科偏微分方程试题
  13. 用TreeWalk提高网速及其在vista中的安装方法
  14. App创业者分享:如何攒到你的1亿用户?(前期土豪推广,后期节操全碎)
  15. 被繁杂的数据搞到头大?让 Google Cloud 大数据平台帮你实现快准狠!
  16. React-Native 高仿“掘金”App 注册和登录界面
  17. JDK8系列之Lambda表达式教程和示例
  18. vue中v-for写在template上,不能加key怎么办
  19. 前端通过序列帧实现动画
  20. 2D弹簧质点系统的隐式求解

热门文章

  1. 验证input和textarea的输入是否有效,也就是不为空,也不都是空格
  2. 笨方法学python6-10
  3. java面试看这一篇就够了
  4. 交付管理——怎样管控项目成本
  5. Qt 常用控件样式表及遇到的问题
  6. 【ybt高效进阶1-5-2】【luogu P3456】山峰和山谷 / GRZ-Ridges and Valleys
  7. 【源码】核磁共振成像的脑部肿瘤检测与分类
  8. Σoφoς:简单但有效的前向安全对称可搜索加密
  9. 重置计算机网络配置后上不了网,路由器重置后上不了网怎么办 怎么重新设置路由器...
  10. [未来成长] 分享:《麦肯锡教我的写作武器》如何写出一篇具有逻辑表现力的文案...