negroni-gzip源代码分析

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。

源代码分析

从源代码中学习到的一些知识:

  1. 避免magic value,尽量使用常量定义,方便理解变量意思及修改
  2. 在分析架构的时候应该先找到入口,与其他中间件进行类比。
  3. 不要重复造轮子。
// 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()
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章