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()
}