源碼來源: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()
}