Gzip中间件实现的代码很短,加上大量注释也才126行。但如果要看懂它在做什么,事先要看Negroni的源码,来理解这些代码的意义。
Negroni的代码也不长,但是构造很巧妙,一环扣一环,需要一些思考来知道他在干什么。
首先,New()返回的Negroni结构本身是一个包含所有中间件的struct。
而中间件(middleware)是什么呢?他是一个实现了ServeHTTP方法的结构,是一个http.Handler接口的实现。
我们平时实现的http.Handler只是一个Handler对应一个请求,而MiddleWare却可以链式地一层层处理http请求。如何实现的呢?
func (m middleware) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
m.handler.ServeHTTP(rw, r, m.next.ServeHTTP)
}
上面就是Middleware的ServeHTTP方法,我们可以看到它实际上并不对请求做任何处理,真正处理请求的是它包裹的Negroni.Handler
这个Handler并不是http.Handler, 只是长得像而已,它的ServeHTTP方法有3个参数,最后多出来的参数是下一个要执行的处理函数(就是下一个中间件的Handler的ServeHTTP)
一个Handler自己的ServeHTTP方法处理完后会执行next函数,将请求传递下去。
Negroni自己的Wrap方法可以把http.Handler转换为Negroni.Handler,可以找源码看看。
好,简单了解了Negroni的中间件的机制后,我们看看Gzip中间件最主要的部分ServeHTTP方法,我们可以在看到整个包实际上就是在实现一个Negroni.Handler:
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()
}
大部分都是注释,handler的本体如下:
type handler struct {
pool sync.Pool
}
一个对象池,主要是为了来提高效率
首先跳过了两个不需要Gzip压缩的情况,然后从pool中取一个gzip.writer,注释也说了,用defer,在退出或者出错时,把空间还回去,避免多次GC对性能的损耗
之后把它包进gzipResponseWriter里,定义如下:
type gzipResponseWriter struct {
w *gzip.Writer
negroni.ResponseWriter
wroteHeader bool
}
negroni.ResponseWriter包含了http.ResponseWriter,有点像继承的意思。这个可以直接填进next函数当参数。
gzipResponseWriter要实现自己的Write方法:
// 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.
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)
}
注意当grw.w也就是gzip.writer指针是空的话,会直接用negroni.ResponseWriter,不进行gzip压缩了。
基本上这个组件就是这样实现了Gzip压缩的功能,具体算法怎么实现的要看"compress/gzip"的代码这就很复杂了。