淺析negroni-gzip 過濾器的源碼

  • 源碼來源: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()
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章