服務計算系列——gzip中間件分析

服務計算課程拓展

gzip包分析

寫在前面

以上僅僅爲個人總結,如果有錯誤,希望大家可以指出來,以避免舞蹈初學者,謝謝

正文

github鏈接
這是一個negroni的中間件,所以接下來從它的調用開始,慢慢來追蹤具體流程,代碼很短,只有126行而已
1. gzip.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
}

其實這段代碼就只是構造了一個handler,這個handler可以之後將進行相應的操作,正如negroni的Use傳入的參數一樣,它需要實現ServeHTTP函數,從而實現相應的接口,具體的gzip內容我們不關注,這裏我們就看看handler的ServeHttp函數的實現
2. ServeHTTP

func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
    // Skip compression if the client doesn't accept gzip encoding.
    if !strings.Contains(r.Header.Get(headerAcceptEncoding), encodingGzip) {
        next(w, r)
        return
    }

    // Skip compression if client attempt WebSocket connection
    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)
    defer h.pool.Put(gz)
    gz.Reset(w)

    // Wrap the original http.ResponseWriter with negroni.ResponseWriter
    // and create the gzipResponseWriter.
    nrw := negroni.NewResponseWriter(w)
    grw := gzipResponseWriter{gz, nrw, false}

    // Call the next handler supplying the gzipResponseWriter instead of
    // the original.
    next(&grw, r)

    // Delete the content length after we know we have been written to.
    grw.Header().Del(headerContentLength)

    gz.Close()
}

前面兩個就是簡單地跳過不能使用的情況,而後面就是對內容進行壓縮,爲了複用壓縮器,也就是避免重新申請空間去使用壓縮器,這裏使用了Reset,這裏我有個疑問,那就是爲什麼要把ResponseWriter給包裹起來再傳給下一個中間件,這裏就要涉及到golang的繼承問題了,感興趣的可以自己查查瞭解瞭解,還是挺好玩的,具體就體現在這裏的倒數第二段代碼,你可能會想,grw並沒有Header啊,爲什麼可以直接調用,其實,是因爲grw有nrw,而nrw有w,w具有Header函數,因此grw可以直接調用Header(),很神奇是不是?我也覺得,因爲我第一次接觸也迷迷糊糊,看到了別人的代碼和測試了自己的代碼才相信這是真的。我們注意到,這裏的gzipResponseWriter實現了http.ResponseWriter接口,那我們接下來就對具體的這三個函數進行分析一下
3. Write()

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

這裏就是實現了Writer接口的Write函數,首先就是對於Header的處理,之後判斷有沒有定義好的Writer,如果沒有的話,就用原本的Writer來進行處理;如果存在,則用我們定義好的壓縮的Writer來進行數據的寫入。
至於另外兩個函數,一個是直接使用了傳入的w的Header函數,另外一個看名字就知道了,所以這裏不再提及
4. 總結
以上的內容分析結束後我們就差不多解決了這個中間件的處理邏輯,即判斷並構建writer->包裝成新的HttpWriter->判斷並處理數據

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章