golang学习之negroni/gizp源码分析
来源:互联网 发布:djvu转pdf软件 编辑:程序博客网 时间:2024/05/18 10:46
在 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 Current Dload Upload Total Spent Left Speed 0 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.BestCompression BestSpeed = gzip.BestSpeed DefaultCompression = gzip.DefaultCompression NoCompression = gzip.NoCompression)
如果你想了解更多关于上面定义的概念,请参考:
- compress/gzip:go语言学习之gzip包解读
- WebSocket:WebSocket 是什么原理?为什么可以实现持久连接?
- Vary头部:http vary 头的作用
//gzipResponseWriter包含了gzip.Writer以及negroni.ResponseWriter//同时还有一个变量判断头部是否已经被设置type gzipResponseWriter struct { w *gzip.Writer negroni.ResponseWriter wroteHeader bool}//建立一个临时对象池,用于存放gzip.Writertype 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.Writer gz := h.pool.Get().(*gzip.Writer) defer h.pool.Put(gz) gz.Reset(w) //创建一个gzipResponseWriter nrw := 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源码分析
- golang学习之negroni对于第三方中间件的使用分析
- golang 使用negroni,实现server
- 简单阅读golang的net/http包和Negroni的源码
- golang: Martini之inject源码分析
- golang: Martini之inject源码分析
- Negroni和Gorilla/mux 解析 Golang
- golang学习之web服务流程分析
- golang-net/http源码分析之http server
- golang cache 源码学习
- 浅析negroni-gzip 过滤器的源码
- 【GOLANG】第一章 RPC 源码分析
- Golang 学习之路二:工作区,源码文件,源码包,初始化函数
- 【GOLANG】第二章 RPC client源码分析
- 【golang 源码分析】内存分配与管理
- Golang日志库源码分析:Glog
- golang学习(一)之安装
- golang学习之--Hello GO
- mongodb centos7.3 初探
- 15算法课程 258. Add Digits
- HDOJ1326 Box of Bricks
- Qt 5 WindowFlags枚举类型
- 顺序排列的集合排序为树形集合
- golang学习之negroni/gizp源码分析
- windows安装mysql-5.7压缩版详细教程
- 二维数组输出
- windows下配置nginx+php环境
- js以逗号分隔的字符串怎么转化为数组
- 自定义ViewGroup实现流式布局
- JS图片预加载
- poi导出exce表实例
- python基础-yield与装饰器、yield并发切换(非io)、greenlet实现切换(非io)