源码来源:negroni-gzip
gzip能对数据进行压缩,从而使服务端向客户端传输数据的速度加快。以下对源码的分析是基于源码注释加上自己在谷歌上搜索入header和Sec-WebSocket-Key等知识后的理解,由于时间原因没有深入使用过gzip,很可能存在错误请指正~
// 以下的压缩常量来自于compress/gzip 包
const (
encodingGzip = "gzip" // 编码方式为gzip
// request或response的header
headerAcceptEncoding = "Accept-Encoding" // 指定浏览器可以支持的web服务器返回内容压缩编码类型
headerContentEncoding = "Content-Encoding" //web服务器支持的返回内容压缩编码类型
headerContentLength = "Content-Length" // 响应体的长度
headerContentType = "Content-Type" // 返回内容的MIME类型
headerVary = "Vary"
headerSecWebSocketKey = "Sec-WebSocket-Key"
// 压缩级别
BestCompression = gzip.BestCompression
BestSpeed = gzip.BestSpeed
DefaultCompression = gzip.DefaultCompression
NoCompression = gzip.NoCompression
)
// gzipResponseWriter 包含 negroni.ResponseWriter
type gzipResponseWriter struct {
w *gzip.Writer
negroni.ResponseWriter
wroteHeader bool
}
// 检查并设置response的header,并把wroteHeader 设为true,
func (grw *gzipResponseWriter) WriteHeader(code int) {
headers := grw.ResponseWriter.Header()
if headers.Get(headerContentEncoding) == "" {
headers.Set(headerContentEncoding, encodingGzip)
headers.Add(headerVary, headerAcceptEncoding)
} else {
grw.w.Reset(ioutil.Discard)
grw.w = nil
}
grw.ResponseWriter.WriteHeader(code)// 设置状态码
grw.wroteHeader = true
}
// 把byte数据写入 gzip.Writer并设置Content-Type
func (grw *gzipResponseWriter) Write(b []byte) (int, error) {
if !grw.wroteHeader {// 判断是否已经设置过了response的header
grw.WriteHeader(http.StatusOK) // 默认返回码200
}
if grw.w == nil {
return grw.ResponseWriter.Write(b)
}
// 设置Content-Type
if len(grw.Header().Get(headerContentType)) == 0 {
grw.Header().Set(headerContentType, http.DetectContentType(b))
}
return grw.w.Write(b)
}
// handler结构体包含ServeHTTP方法,sync.Pool用来缓存对象
type handler struct {
pool sync.Pool
}
// 根据压缩level来进行,返回一个handler去处理ServeHTTP中的gzip压缩
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以包括http.ResponseWriter
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
// 判断客户端的AcceptEncoding,如果不接受gzip的编码格式则跳过压缩
if !strings.Contains(r.Header.Get(headerAcceptEncoding), encodingGzip) {
next(w, r)
return
}
// 如果客户端是发送Sec-WebSocket-Key过来也无需压缩 if len(r.Header.Get(headerSecWebSocketKey)) > 0 {
next(w, r)
return
}
gz := h.pool.Get().(*gzip.Writer)// 从pool中获取Writer
defer h.pool.Put(gz)// 使用完后放回pool池等待重新被利用
gz.Reset(w)// 用ResponseWriter完成Reset
// 新建一个包含negroni.ResponseWriter和Write的gzipResponseWriter对象,并把wroteHeader初始化为false
nrw := negroni.NewResponseWriter(w)
grw := gzipResponseWriter{gz, nrw, false}
// gzipResponseWriter取代原来的中间件去应用下一个handlerFunc
next(&grw, r)
// 删除header的ContentLength
grw.Header().Del(headerContentLength)
gz.Close()
}