服務計算課程拓展
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->判斷並處理數據