写在前面
最近遇到一个业务场景, 用户在第一次使用时会上传大量数据, 未优化前历史2年的数据需要20分钟
, 和FE一起沟通后, 选择使用gzip压缩优化, 将时间压缩到5分钟内, 实测下来gzip能达到 3-10倍
的压缩率
Go中使用gzip
以 hertz 框架为例, 使用 cwgo
脚手架初始化项目, 默认开启 gzip 中间件
func registerMiddleware(h *server.Hertz) {
// gzip
if conf.GetConf().Hertz.EnableGzip {
h.Use(gzip.Gzip(gzip.DefaultCompression))
}
}
社区提供的使用示例: gzip/gzip_test.go at main · hertz-contrib/gzip
func TestDecompressGzip(t *testing.T) {
buf := &bytes.Buffer{}
gz := compress.AcquireStacklessGzipWriter(buf, gzip.DefaultCompression)
if _, err := gz.Write([]byte(testResponse)); err != nil {
gz.Close()
t.Fatal(err)
}
gz.Close()
router := route.NewEngine(config.NewOptions([]config.Option{}))
router.Use(Gzip(DefaultCompression, WithDecompressFn(DefaultDecompressHandle)))
router.POST("/", func(ctx context.Context, c *app.RequestContext) {
if v := c.Request.Header.Get("Content-Encoding"); v != "" {
t.Errorf("unexpected `Content-Encoding`: %s header", v)
}
if v := c.Request.Header.Get("Content-Length"); v != "" {
t.Errorf("unexpected `Content-Length`: %s header", v)
}
data := c.GetRawData()
c.Data(200, "text/plain", data)
})
request := ut.PerformRequest(router, consts.MethodPost, "/", &ut.Body{Body: buf, Len: buf.Len()}, ut.Header{
Key: "Content-Encoding", Value: "gzip",
})
w := request.Result()
assert.Equal(t, http.StatusOK, w.StatusCode())
assert.Equal(t, "", w.Header.Get("Content-Encoding"))
assert.Equal(t, "", w.Header.Get("Vary"))
assert.Equal(t, testResponse, string(w.Body()))
assert.Equal(t, "18", w.Header.Get("Content-Length"))
}
最佳实践
- FE上传数据:
json = jsonDecode(utf8.decode(GZipCodec().decode(params)));
- BE接收时是2 进制数据, 使用
debug + base64Encode
保存了一份, 方便后续调试 - 使用上面的
DefaultDecompressHandle
, 发现获取的数据和步骤 2 中一样 - 和FE联调, 获取到了所有中间步骤的数据
flutter: postUseGzip params:
{metaInfo: {timezone: Asia/Shanghai}...}
flutter: postUseGzip jsonData:
{"metaInfo":{"timezone":"Asia/Shanghai"}...}
flutter: postUseGzip utf8List: [123, 34, 109, 101, 116, ...]
flutter: postUseGzip gzip: [31, 139, 8, 0, 0, 0, 0, 0, 0, ...]
- Go 中对 gzip 数据进行处理
func DataGzip(ctx context.Context, c *app.RequestContext) {
b := c.Request.Body()
g, err := gzip.NewReader(bytes.NewBuffer(b))
if err != nil {
hutil.RespErr(c, err)
return
}
data, err := io.ReadAll(g) // 获取原始 json 数据
if err != nil {
hutil.RespErr(c, err)
return
}
hlog.CtxInfof(ctx, fmt.Sprintf("gzip data: %s", string(data)))
// do something...
hutil.RespData(c, res)
}
- 和 FE 联调, FE 反馈请求无返回, 实测发现是步骤 2 中的
DefaultDecompressHandle
引起的, 会导致 response 使用 gzip 压缩后返回, 导致 FE 请求库无法解析导致的
写在最后
- 了解web gzip 的原理和框架处理的机制, 可以少走很多弯路
- debug 过程中保留所有中间步骤并使用 testcase 进行验证, 其实
快很多