Go的gzip包的简单解析和gbk包的实现
- gzip包的解析
- gbk包的粗略实现(待测试)
a.首先看一下gzip包
这里gzip包有几个内容
首先是几个结构
type gzipResponseWriter struct {
w *gzip.Writer
negroni.ResponseWriter
wroteHeader bool
}
type handler struct {
pool sync.Pool
}
然后是几个函数
func (grw *gzipResponseWriter) WriteHeader(code int)
func (grw *gzipResponseWriter) Write(b []byte) (int, error)
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)
func Gzip(level int) *handler
b.函数的解析
由上次我们解析Negroni库可知关键在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()
}
这里我们看到,该函数首先根据request的情况做相应判断,然后从sync池取出Writer并且reset使用它,然后就把next中的responseWriter替换为本文件中的grw,相当于做了一层代理,这样当http请求如果用到Write方法或者WriteHead方法时使用的就是这里实现的对应方法。
c.接下来我们看看WriteHead和Write函数
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
}
可以看到,根据条件,我们设置对应的grw,当header里的ContentEncoding为空时设置对应的参数,否则把grw的gzip.Writer置为nil,然后调用negrino的responseWriter的WriteHeader方法
接下来我们看看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)
}
这个函数也很好理解,当gzip.Writer是nil的时候说明不用gzip就使用grw.ResponseWriter.Write(b),然后是否已经写了头部等等。。。
至于最后的Gzip它是返回一个handle这里也就不详述了
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
}
小结
通过以上代码分析和根据之前我们另一篇博客对negroni的分析,当http调用ServeHttp的时候,该包会用grw代理原来的responseWrite于是到后面使用Write方法的时候就可以同gzip方式了,利用这种思维我模仿写了一个gbk(未测试)
gbk包
这是github地址https://github.com/caijh23/goWeb/tree/master/web/negroni-gbk
这是代码
package gbk
import (
"net/http"
"strings"
"io/ioutil"
"golang.org/x/text/transform"
"golang.org/x/text/encoding/simplifiedchinese"
"github.com/urfave/negroni"
)
const (
headerContentType = "Content-type"
encodingGbk = "gbk"
encodingUTF8 = "UTF-8"
)
type gbkResponseWriter struct {
w *transform.Writer
negroni.ResponseWriter
wroteHeader bool
}
func (grw *gbkResponseWriter) WriteHeader(code int) {
headers := grw.ResponseWriter.Header()
if headers.Get(headerContentType) == "" {
headers.Set(headerContentType,encodingGbk)
} else if strings.Contains(headers.Get(headerContentType),encodingUTF8) {
headers.Set(headerContentType,strings.Replace(headers.Get(headerContentType),encodingUTF8,encodingGbk,-1))
} else {
grw.w = nil
}
grw.ResponseWriter.WriteHeader(code)
grw.wroteHeader = true
}
func (grw *gbkResponseWriter) 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)
}
type handler struct {
}
func Gbk() *handler {
h := &handler{}
return h
}
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if len(r.Header.Get(headerContentType)) == 0 {
next(w, r)
return
}
var nrw negroni.ResponseWriter
var gb *transform.Writer
if strings.Contains(r.Header.Get(headerContentType), encodingUTF8) {
nrw = negroni.NewResponseWriter(w)
gb = transform.NewWriter(nrw, nil)
}
if strings.Contains(r.Header.Get(headerContentType), encodingGbk) {
rd := transform.NewReader(r.Body, simplifiedchinese.GBK.NewDecoder())
r.Body = ioutil.NopCloser(rd)
nrw = negroni.NewResponseWriter(w)
gb = transform.NewWriter(nrw, simplifiedchinese.GBK.NewEncoder())
}
grw := gbkResponseWriter{gb, nrw, false}
next(&grw, r)
}