V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
nanmu42
V2EX  ›  分享创造

适用于 Gin 和 net/http 的 gzip 中间件,基于 Content-Type、Content-Length、扩展名等要素自动判断是否启用压缩

  •  
  •   nanmu42 ·
    nanmu42 · 2019-12-10 18:36:25 +08:00 · 2862 次点击
    这是一个创建于 1845 天前的主题,其中的信息可能已经有所发展或是发生改变。

    gzip 中间件

    项目地址: https://github.com/nanmu42/gzip

    适用于Ginnet/http的 gzip 中间件。基于 Content-TypeContent-Length、扩展名等要素自动判断是否启用压缩。

    使用示例

    GoDoc

    Gin

    import github.com/nanmu42/gzip
    
    func main() {
    	g := gin.Default()
    	
        // 使用默认设定
    	g.Use(gzip.DefaultHandler().Gin)
    
    	g.GET("/", func(c *gin.Context) {
    		c.JSON( http.StatusOK, map[string]interface{}{
    			"code": 0,
    			"msg":  "hello",
    			"data": fmt.Sprintf("l%sng!", strings.Repeat("o", 1000)),
    		})
    	})
    
    	log.Println(g.Run(fmt.Sprintf(":%d", 3000)))
    }
    

    net/http

    import github.com/nanmu42/gzip
    
    func main() {
    	mux := http.NewServeMux()
    	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    		writeString(w, fmt.Sprintf("This content is compressed: l%sng!", strings.Repeat("o", 1000)))
    	})
    
        // 使用默认设定
    	log.Println( http.ListenAndServe(fmt.Sprintf(":%d", 3001), gzip.DefaultHandler().WrapHandler(mux)))
    }
    
    func writeString(w http.ResponseWriter, payload string) {
    	w.Header().Set("Content-Type", "text/plain; charset=utf8")
    	_, _ = io.WriteString(w, payload+"\n")
    }
    

    定制Handler

    在创建Handler时,可以定制参数以满足你的需要:

    import github.com/nanmu42/gzip
    
    handler := gzip.NewHandler(gzip.Config{
        // gzip 压缩等级
    	CompressionLevel: 6,
        // 使用 gzip 的最小 body 体积,单位:byte
    	MinContentLength: 256,
        // 请求过滤器基于请求来判断是否对这条请求的返回启用 gzip,
        // 过滤器按其定义顺序执行,下同。
    	RequestFilter: []RequestFilter{
    	    NewCommonRequestFilter(),
    	    DefaultExtensionFilter(),
    	},
        // 返回 header 过滤器基于返回的 header 判断是否对这条请求的返回启用 gzip
    	ResponseHeaderFilter: []ResponseHeaderFilter{
    		NewSkipCompressedFilter(),
    		DefaultContentTypeFilter(),
    	},
    })
    

    RequestFilterResponseHeaderFilter 是 interface. 你可以实现你自己的过滤器。

    Handler 的局限性

    • 总是在返回中提供Content-Type,Handler 不会对未知Content-Type的返回进行类型猜测;
    • Handler 会先在返回的 Content-Length 中查询 body 体积,如果没有就再查看http.ResponseWriter.Write(data []byte)在首次调用时的 len(data)作为参考。倘若返回的 Content-Length不存在,且http.ResponseWriter.Write(data []byte)首次调用时的len(data)较小,这条返回会被错误认为无需压缩。

    如果你使用的是 Gin 的c.JSON()c.PureJSON(),无需担心上述问题。

    如果你直接使用net/http,请妥善处理上述问题。

    2 条回复    2019-12-11 10:26:46 +08:00
    missdeer
        1
    missdeer  
       2019-12-11 08:47:53 +08:00
    让前置 nginx 处理如何?
    nanmu42
        2
    nanmu42  
    OP
       2019-12-11 10:26:46 +08:00
    @missdeer 当返回的 JSON 大于 2KB 时,Golang 会使用 chunk 的形式传输,这个时候没有`Content-Length`,Nginx 的 `ngx_http_gzip_module` 的 `gzip_min_length` 仅从 `Content-Length` 判断是否压缩,导致内容不被压缩。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2487 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 05:01 · PVG 13:01 · LAX 21:01 · JFK 00:01
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.