Skip to content

Commit 7e7b1a2

Browse files
committed
feat: use klauspost/compress for gzip
1 parent cf75199 commit 7e7b1a2

12 files changed

+435
-7
lines changed

client_middleware.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ import (
4646
"path/filepath"
4747
"strings"
4848

49+
"github.com/hertz-contrib/gzip/compress"
50+
4951
"github.com/cloudwego/hertz/pkg/app/client"
50-
"github.com/cloudwego/hertz/pkg/common/compress"
5152
"github.com/cloudwego/hertz/pkg/protocol"
5253
)
5354

compress/compress.go

+268
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
/*
2+
* Copyright 2023 CloudWeGo Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* The MIT License (MIT)
17+
*
18+
* Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors
19+
*
20+
* Permission is hereby granted, free of charge, to any person obtaining a copy
21+
* of this software and associated documentation files (the "Software"), to deal
22+
* in the Software without restriction, including without limitation the rights
23+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
24+
* copies of the Software, and to permit persons to whom the Software is
25+
* furnished to do so, subject to the following conditions:
26+
*
27+
* The above copyright notice and this permission notice shall be included in
28+
* all copies or substantial portions of the Software.
29+
*
30+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
36+
* THE SOFTWARE.
37+
*
38+
* This file may have been modified by CloudWeGo authors. All CloudWeGo
39+
* Modifications are Copyright 2022 CloudWeGo Authors.
40+
*/
41+
42+
package compress
43+
44+
import (
45+
"bytes"
46+
"fmt"
47+
"io"
48+
"sync"
49+
50+
"github.com/klauspost/compress/gzip"
51+
52+
"github.com/cloudwego/hertz/pkg/common/bytebufferpool"
53+
"github.com/cloudwego/hertz/pkg/common/stackless"
54+
"github.com/cloudwego/hertz/pkg/common/utils"
55+
"github.com/cloudwego/hertz/pkg/network"
56+
)
57+
58+
const CompressDefaultCompression = 6 // flate.DefaultCompression
59+
60+
var gzipReaderPool sync.Pool
61+
62+
var (
63+
stacklessGzipWriterPoolMap = newCompressWriterPoolMap()
64+
realGzipWriterPoolMap = newCompressWriterPoolMap()
65+
)
66+
67+
func newCompressWriterPoolMap() []*sync.Pool {
68+
// Initialize pools for all the compression levels defined
69+
// in https://golang.org/pkg/compress/flate/#pkg-constants .
70+
// Compression levels are normalized with normalizeCompressLevel,
71+
// so the fit [0..11].
72+
var m []*sync.Pool
73+
for i := 0; i < 12; i++ {
74+
m = append(m, &sync.Pool{})
75+
}
76+
return m
77+
}
78+
79+
type compressCtx struct {
80+
w io.Writer
81+
p []byte
82+
level int
83+
}
84+
85+
// AppendGunzipBytes appends gunzipped src to dst and returns the resulting dst.
86+
func AppendGunzipBytes(dst, src []byte) ([]byte, error) {
87+
w := &byteSliceWriter{dst}
88+
_, err := WriteGunzip(w, src)
89+
return w.b, err
90+
}
91+
92+
type byteSliceWriter struct {
93+
b []byte
94+
}
95+
96+
func (w *byteSliceWriter) Write(p []byte) (int, error) {
97+
w.b = append(w.b, p...)
98+
return len(p), nil
99+
}
100+
101+
// WriteGunzip writes gunzipped p to w and returns the number of uncompressed
102+
// bytes written to w.
103+
func WriteGunzip(w io.Writer, p []byte) (int, error) {
104+
r := &byteSliceReader{p}
105+
zr, err := AcquireGzipReader(r)
106+
if err != nil {
107+
return 0, err
108+
}
109+
zw := network.NewWriter(w)
110+
n, err := utils.CopyZeroAlloc(zw, zr)
111+
ReleaseGzipReader(zr)
112+
nn := int(n)
113+
if int64(nn) != n {
114+
return 0, fmt.Errorf("too much data gunzipped: %d", n)
115+
}
116+
return nn, err
117+
}
118+
119+
type byteSliceReader struct {
120+
b []byte
121+
}
122+
123+
func (r *byteSliceReader) Read(p []byte) (int, error) {
124+
if len(r.b) == 0 {
125+
return 0, io.EOF
126+
}
127+
n := copy(p, r.b)
128+
r.b = r.b[n:]
129+
return n, nil
130+
}
131+
132+
func AcquireGzipReader(r io.Reader) (*gzip.Reader, error) {
133+
v := gzipReaderPool.Get()
134+
if v == nil {
135+
return gzip.NewReader(r)
136+
}
137+
zr := v.(*gzip.Reader)
138+
if err := zr.Reset(r); err != nil {
139+
return nil, err
140+
}
141+
return zr, nil
142+
}
143+
144+
func ReleaseGzipReader(zr *gzip.Reader) {
145+
zr.Close()
146+
gzipReaderPool.Put(zr)
147+
}
148+
149+
// AppendGzipBytes appends gzipped src to dst and returns the resulting dst.
150+
func AppendGzipBytes(dst, src []byte) []byte {
151+
return AppendGzipBytesLevel(dst, src, CompressDefaultCompression)
152+
}
153+
154+
// AppendGzipBytesLevel appends gzipped src to dst using the given
155+
// compression level and returns the resulting dst.
156+
//
157+
// Supported compression levels are:
158+
//
159+
// - CompressNoCompression
160+
// - CompressBestSpeed
161+
// - CompressBestCompression
162+
// - CompressDefaultCompression
163+
// - CompressHuffmanOnly
164+
func AppendGzipBytesLevel(dst, src []byte, level int) []byte {
165+
w := &byteSliceWriter{dst}
166+
WriteGzipLevel(w, src, level) //nolint:errcheck
167+
return w.b
168+
}
169+
170+
var stacklessWriteGzip = stackless.NewFunc(nonblockingWriteGzip)
171+
172+
func nonblockingWriteGzip(ctxv interface{}) {
173+
ctx := ctxv.(*compressCtx)
174+
zw := acquireRealGzipWriter(ctx.w, ctx.level)
175+
176+
_, err := zw.Write(ctx.p)
177+
if err != nil {
178+
panic(fmt.Sprintf("BUG: gzip.Writer.Write for len(p)=%d returned unexpected error: %s", len(ctx.p), err))
179+
}
180+
181+
releaseRealGzipWriter(zw, ctx.level)
182+
}
183+
184+
func releaseRealGzipWriter(zw *gzip.Writer, level int) {
185+
zw.Close()
186+
nLevel := normalizeCompressLevel(level)
187+
p := realGzipWriterPoolMap[nLevel]
188+
p.Put(zw)
189+
}
190+
191+
func acquireRealGzipWriter(w io.Writer, level int) *gzip.Writer {
192+
nLevel := normalizeCompressLevel(level)
193+
p := realGzipWriterPoolMap[nLevel]
194+
v := p.Get()
195+
if v == nil {
196+
zw, err := gzip.NewWriterLevel(w, level)
197+
if err != nil {
198+
panic(fmt.Sprintf("BUG: unexpected error from gzip.NewWriterLevel(%d): %s", level, err))
199+
}
200+
return zw
201+
}
202+
zw := v.(*gzip.Writer)
203+
zw.Reset(w)
204+
return zw
205+
}
206+
207+
// normalizes compression level into [0..11], so it could be used as an index
208+
// in *PoolMap.
209+
func normalizeCompressLevel(level int) int {
210+
// -2 is the lowest compression level - CompressHuffmanOnly
211+
// 9 is the highest compression level - CompressBestCompression
212+
if level < -2 || level > 9 {
213+
level = CompressDefaultCompression
214+
}
215+
return level + 2
216+
}
217+
218+
// WriteGzipLevel writes gzipped p to w using the given compression level
219+
// and returns the number of compressed bytes written to w.
220+
//
221+
// Supported compression levels are:
222+
//
223+
// - CompressNoCompression
224+
// - CompressBestSpeed
225+
// - CompressBestCompression
226+
// - CompressDefaultCompression
227+
// - CompressHuffmanOnly
228+
func WriteGzipLevel(w io.Writer, p []byte, level int) (int, error) {
229+
switch w.(type) {
230+
case *byteSliceWriter,
231+
*bytes.Buffer,
232+
*bytebufferpool.ByteBuffer:
233+
// These writers don't block, so we can just use stacklessWriteGzip
234+
ctx := &compressCtx{
235+
w: w,
236+
p: p,
237+
level: level,
238+
}
239+
stacklessWriteGzip(ctx)
240+
return len(p), nil
241+
default:
242+
zw := AcquireStacklessGzipWriter(w, level)
243+
n, err := zw.Write(p)
244+
ReleaseStacklessGzipWriter(zw, level)
245+
return n, err
246+
}
247+
}
248+
249+
func AcquireStacklessGzipWriter(w io.Writer, level int) stackless.Writer {
250+
nLevel := normalizeCompressLevel(level)
251+
p := stacklessGzipWriterPoolMap[nLevel]
252+
v := p.Get()
253+
if v == nil {
254+
return stackless.NewWriter(w, func(w io.Writer) stackless.Writer {
255+
return acquireRealGzipWriter(w, level)
256+
})
257+
}
258+
sw := v.(stackless.Writer)
259+
sw.Reset(w)
260+
return sw
261+
}
262+
263+
func ReleaseStacklessGzipWriter(sw stackless.Writer, level int) {
264+
sw.Close()
265+
nLevel := normalizeCompressLevel(level)
266+
p := stacklessGzipWriterPoolMap[nLevel]
267+
p.Put(sw)
268+
}

0 commit comments

Comments
 (0)