概述
negroni-gzip源码分析
源码架构分析
gzip过滤器的很多功能起始都是基于negroni.ResponseWriter和gzip.Writer的实现。其中negroni.ResponseWriter主要是实现了对包的Header的修改(WriterHeader)、不压缩情况下的直接写入(Write函数)。gzip.Writer负责具体的压缩(Write函数)。
使用的时候直接调用Gzip函数,比如:
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")
gzip.Gzip会返回一个重写了ServeHTTP的handler,ServeHTTP使用gzipResponseWriter来取代原有的http.ResponseWriter
查看net/http代码可知http.ResponseWriter是一个接口,作用是使用一个HTTP handler来建立一个HTTP response。要求实现Header() Header方法,Write([]byte) (int,error)方法,WriteHeader(statusCode int)方法。在本库中Header()由negroni.ResponseWriter实现,其他方法都已经实现。所以gzipResponseWriter同样是符合http.ResponseWriter。
源代码分析
从源代码中学习到的一些知识:
- 避免magic value,尽量使用常量定义,方便理解变量意思及修改
- 在分析架构的时候应该先找到入口,与其他中间件进行类比。
- 不要重复造轮子。
// Package gzip implements a gzip compression handler middleware for Negroni.
package gzip
import (
"compress/gzip"
"io/ioutil"
"net/http"
"strings"
"sync"
"github.com/urfave/negroni"
)
// These compression constants are copied from the compress/gzip package.
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
)
/*gzipResponseWriter, 包含了negoroni.ResponseWriter和gzip.Writer
还有wroteHeader,表示是否修改了HTTP头*/
type gzipResponseWriter struct {
w *gzip.Writer
negroni.ResponseWriter
wroteHeader bool
}
// Check whether underlying response is already pre-encoded and disable
// gzipWriter before the body gets written, otherwise encoding headers
/*检查response是否已经被编码,在body被写前禁用gzipWriter,不然就编码HTTP头
使用negroni.ResponseWriter.WriteHeader来实现功能
*/
func (grw *gzipResponseWriter) WriteHeader(code int) {
headers := grw.ResponseWriter.Header()//通过negroni.ResponseWriter拿到HTTP头
if headers.Get(headerContentEncoding) == "" {//如果没有完成压缩,进行压缩
headers.Set(headerContentEncoding, encodingGzip)
headers.Add(headerVary, headerAcceptEncoding)
} else {//如果完成了压缩,则跳过
grw.w.Reset(ioutil.Discard)//gzip.Writer重置
grw.w = nil
}
// Avoid sending Content-Length header before compression. The length would
// be invalid, and some browsers like Safari will report
// "The network connection was lost." errors
//避免在压缩前发出包长度
grw.Header().Del(headerContentLength)
grw.ResponseWriter.WriteHeader(code)
grw.wroteHeader = true
}
// Write writes bytes to the gzip.Writer. It will also set the Content-Type
// header using the net/http library content type detection if the Content-Type
// header was not set yet.
/*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.ResponseWriter可以通过接口转换为http.CloseNotifier,则用gzipResponseWriterCloseNotifier进行包装
http.CloseNotifier支持在所处连接关闭的时候进行检测。这个机制可以用来在Client断开连接的时候终止正在进行的response操作。
*/
type gzipResponseWriterCloseNotifier struct {
*gzipResponseWriter
}
func (rw *gzipResponseWriterCloseNotifier) CloseNotify() <-chan bool {
return rw.ResponseWriter.(http.CloseNotifier).CloseNotify()
}
/*产生gzipResponseWriter*/
func newGzipResponseWriter(rw negroni.ResponseWriter, w *gzip.Writer) negroni.ResponseWriter {
wr := &gzipResponseWriter{w: w, ResponseWriter: rw}
if _, ok := rw.(http.CloseNotifier); ok {
return &gzipResponseWriterCloseNotifier{gzipResponseWriter: wr}
}
return wr
}
// handler struct contains the ServeHTTP method
// handler结构体包含ServeHTTP方法,用pool来缓存对象
/*注意,negroni中的Handler接口要求ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc),也就是说handler结构体是符合这个Handler接口的*/
type handler struct {
pool sync.Pool
}
// Gzip returns a handler which will handle the Gzip compression in ServeHTTP.
// Valid values for level are identical to those in the compress/gzip package.
/*Gzip函数返回一个handler,这个handler会在ServeHTTP中处理Gzip压缩。level指定了压缩的等级*/
func Gzip(level int) *handler {
h := &handler{}
h.pool.New = func() interface{} {
gz, err := gzip.NewWriterLevel(ioutil.Discard, level)
if err != nil {
panic(err)
}
return gz
}
return h
}
// 中间件核心函数
// ServeHTTP wraps the http.ResponseWriter with a gzip.Writer.
// ServeHTTP 用gzip.Writer来用以修饰http.ResponseWriter
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
// Skip compression if the client doesn't accept gzip encoding.
//判断客户端的Accept-Encoding,如果不支持gzip则无需压缩
if !strings.Contains(r.Header.Get(headerAcceptEncoding), encodingGzip) {
next(w, r)
return
}
// Skip compression if client attempt WebSocket connection
// 如果客户端是发送Sec-WebSocket-Key过来,也无需压缩
if len(r.Header.Get(headerSecWebSocketKey)) > 0 {
next(w, r)
return
}
// Retrieve gzip writer from the pool. Reset it to use the ResponseWriter.
// This allows us to re-use an already allocated buffer rather than
// allocating a new buffer for every request.
// We defer g.pool.Put here so that the gz writer is returned to the
// pool if any thing after here fails for some reason (functions in
// next could potentially panic, etc)
gz := h.pool.Get().(*gzip.Writer)
//从pool中获取Writer,这样可以循环利用已经申请的缓存区而不是每次都重新申请新的缓存。
defer h.pool.Put(gz)
//在函数结束后放回缓存区以待下次使用
gz.Reset(w)
//用ResponseWriter完成Reset
// Wrap the original http.ResponseWriter with negroni.ResponseWriter
// and create the gzipResponseWriter.
nrw := negroni.NewResponseWriter(w)
grw := newGzipResponseWriter(nrw, gz)//新建一个包含negroni.ResponseWriter的gzipResponseWriter对象
// Call the next handler supplying the gzipResponseWriter instead of
// the original.
// 用gzipResponseWriter取代原来的中间件去下一个handleFunc
next(grw, r)
gz.Close()
}
最后
以上就是香蕉摩托为你收集整理的negroni-gzip源代码分析negroni-gzip源码分析的全部内容,希望文章能够帮你解决negroni-gzip源代码分析negroni-gzip源码分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复