http 中使用 gzip 输出内容时,如何预先压缩前一半页面?

发布时间 2023-10-10 15:10:43作者: ahfuzhang

作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!


背景是这样:要输出一个很大的动态页面,不开 gzip 压缩会使得流量很大;开了 gzip 压缩又会导致 cpu 升高。
这个页面的前半部分一般不会变化,变化的是后半部分。

因此,能不能把页面的前半部分预先压缩好,输出的时候只压缩后半部分?

目前已经实现了,具体如下:
1.我修改了github.com/klauspost/compress这个项目,增加了WriteGzipedData()这样的方法,即数据流中的某个片段是已经压缩好的内容,便不会消耗 cpu 进行压缩。
可以获取这部分代码:go get github.com/ahfuzhang/compress@v1.17.1
2.在自己项目的 go.mod 中,增加 replace:

replace (
	github.com/klauspost/compress => github.com/ahfuzhang/compress v1.17.1
)

require (
	github.com/klauspost/compress v1.16.3
    github.com/valyala/bytebufferpool v1.0.0
	github.com/valyala/fasthttp v1.50.0
)

3.http服务的代码如下:

package main

import (
	"bytes"
	_ "embed"
	"fmt"
	"gzip_part/stringsutil"
	"log"
	"os"

	"github.com/klauspost/compress/gzip"
	"github.com/valyala/bytebufferpool"
	"github.com/valyala/fasthttp"
)

//go:embed raw.html
var html string

//go:embed raw.js
var js string

func getGziped(b []byte) []byte {  // 用于预先压缩某个片段
	buf := bytebufferpool.Get()
	buf.Reset()
	w, _ := gzip.NewWriterLevel(buf, gzip.BestCompression)
	w.Write(nil)
	w.Flush()
	headLen := buf.Len()
	w.Write(b)
	w.Flush()
	out := make([]byte, buf.Len()-headLen)
	copy(out, buf.B[headLen:])
	w.Close()
	bytebufferpool.Put(buf)
	return out
}

func testGzipedHttp() {
    topHalf := getGziped([]byte(html))  // cache 页面的前一半
	requestHandler := func(ctx *fasthttp.RequestCtx) {
		ctx.Response.Header.Add("Content-Type", "text/plain")
		ctx.Response.Header.Add("Content-Encoding", "gzip")

		switch string(ctx.Request.URI().RequestURI()) {
		case "/1":   // direct output
			w, _ := gzip.NewWriterLevel(ctx, gzip.BestCompression)
			w.Write([]byte(html))
			w.Write([]byte(js))
			w.Close()
		case "/2":
			w, _ := gzip.NewWriterLevel(ctx, gzip.BestCompression)
			w.WriteGzipedData([]byte(html), topHalf)
			w.Write([]byte(js))
			w.Close()
		}
	}

	s := &fasthttp.Server{
		Handler: requestHandler,
	}
	if err := s.ListenAndServe(":8080"); err != nil {
		log.Fatalf("error in ListenAndServe: %v", err)
	}
}

func main() {
	testGzipedHttp()
}

测试如下:

curl -G "http://127.0.0.1:8080/1" -v --compressed

curl -G "http://127.0.0.1:8080/2" -v --compressed

两个页面都正常。