github.com/cloudwego/hertz@v0.9.3/pkg/common/compress/compress.go (about) 1 /* 2 * Copyright 2022 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 "compress/gzip" 47 "fmt" 48 "io" 49 "sync" 50 51 "github.com/cloudwego/hertz/pkg/common/bytebufferpool" 52 "github.com/cloudwego/hertz/pkg/common/stackless" 53 "github.com/cloudwego/hertz/pkg/common/utils" 54 "github.com/cloudwego/hertz/pkg/network" 55 ) 56 57 const CompressDefaultCompression = 6 // flate.DefaultCompression 58 59 var gzipReaderPool sync.Pool 60 61 var ( 62 stacklessGzipWriterPoolMap = newCompressWriterPoolMap() 63 realGzipWriterPoolMap = newCompressWriterPoolMap() 64 ) 65 66 func newCompressWriterPoolMap() []*sync.Pool { 67 // Initialize pools for all the compression levels defined 68 // in https://golang.org/pkg/compress/flate/#pkg-constants . 69 // Compression levels are normalized with normalizeCompressLevel, 70 // so the fit [0..11]. 71 var m []*sync.Pool 72 for i := 0; i < 12; i++ { 73 m = append(m, &sync.Pool{}) 74 } 75 return m 76 } 77 78 type compressCtx struct { 79 w io.Writer 80 p []byte 81 level int 82 } 83 84 // AppendGunzipBytes appends gunzipped src to dst and returns the resulting dst. 85 func AppendGunzipBytes(dst, src []byte) ([]byte, error) { 86 w := &byteSliceWriter{dst} 87 _, err := WriteGunzip(w, src) 88 return w.b, err 89 } 90 91 type byteSliceWriter struct { 92 b []byte 93 } 94 95 func (w *byteSliceWriter) Write(p []byte) (int, error) { 96 w.b = append(w.b, p...) 97 return len(p), nil 98 } 99 100 // WriteGunzip writes gunzipped p to w and returns the number of uncompressed 101 // bytes written to w. 102 func WriteGunzip(w io.Writer, p []byte) (int, error) { 103 r := &byteSliceReader{p} 104 zr, err := AcquireGzipReader(r) 105 if err != nil { 106 return 0, err 107 } 108 zw := network.NewWriter(w) 109 n, err := utils.CopyZeroAlloc(zw, zr) 110 ReleaseGzipReader(zr) 111 nn := int(n) 112 if int64(nn) != n { 113 return 0, fmt.Errorf("too much data gunzipped: %d", n) 114 } 115 return nn, err 116 } 117 118 type byteSliceReader struct { 119 b []byte 120 } 121 122 func (r *byteSliceReader) Read(p []byte) (int, error) { 123 if len(r.b) == 0 { 124 return 0, io.EOF 125 } 126 n := copy(p, r.b) 127 r.b = r.b[n:] 128 return n, nil 129 } 130 131 func AcquireGzipReader(r io.Reader) (*gzip.Reader, error) { 132 v := gzipReaderPool.Get() 133 if v == nil { 134 return gzip.NewReader(r) 135 } 136 zr := v.(*gzip.Reader) 137 if err := zr.Reset(r); err != nil { 138 return nil, err 139 } 140 return zr, nil 141 } 142 143 func ReleaseGzipReader(zr *gzip.Reader) { 144 zr.Close() 145 gzipReaderPool.Put(zr) 146 } 147 148 // AppendGzipBytes appends gzipped src to dst and returns the resulting dst. 149 func AppendGzipBytes(dst, src []byte) []byte { 150 return AppendGzipBytesLevel(dst, src, CompressDefaultCompression) 151 } 152 153 // AppendGzipBytesLevel appends gzipped src to dst using the given 154 // compression level and returns the resulting dst. 155 // 156 // Supported compression levels are: 157 // 158 // - CompressNoCompression 159 // - CompressBestSpeed 160 // - CompressBestCompression 161 // - CompressDefaultCompression 162 // - CompressHuffmanOnly 163 func AppendGzipBytesLevel(dst, src []byte, level int) []byte { 164 w := &byteSliceWriter{dst} 165 WriteGzipLevel(w, src, level) //nolint:errcheck 166 return w.b 167 } 168 169 var stacklessWriteGzip = stackless.NewFunc(nonblockingWriteGzip) 170 171 func nonblockingWriteGzip(ctxv interface{}) { 172 ctx := ctxv.(*compressCtx) 173 zw := acquireRealGzipWriter(ctx.w, ctx.level) 174 175 _, err := zw.Write(ctx.p) 176 if err != nil { 177 panic(fmt.Sprintf("BUG: gzip.Writer.Write for len(p)=%d returned unexpected error: %s", len(ctx.p), err)) 178 } 179 180 releaseRealGzipWriter(zw, ctx.level) 181 } 182 183 func releaseRealGzipWriter(zw *gzip.Writer, level int) { 184 zw.Close() 185 nLevel := normalizeCompressLevel(level) 186 p := realGzipWriterPoolMap[nLevel] 187 p.Put(zw) 188 } 189 190 func acquireRealGzipWriter(w io.Writer, level int) *gzip.Writer { 191 nLevel := normalizeCompressLevel(level) 192 p := realGzipWriterPoolMap[nLevel] 193 v := p.Get() 194 if v == nil { 195 zw, err := gzip.NewWriterLevel(w, level) 196 if err != nil { 197 panic(fmt.Sprintf("BUG: unexpected error from gzip.NewWriterLevel(%d): %s", level, err)) 198 } 199 return zw 200 } 201 zw := v.(*gzip.Writer) 202 zw.Reset(w) 203 return zw 204 } 205 206 // normalizes compression level into [0..11], so it could be used as an index 207 // in *PoolMap. 208 func normalizeCompressLevel(level int) int { 209 // -2 is the lowest compression level - CompressHuffmanOnly 210 // 9 is the highest compression level - CompressBestCompression 211 if level < -2 || level > 9 { 212 level = CompressDefaultCompression 213 } 214 return level + 2 215 } 216 217 // WriteGzipLevel writes gzipped p to w using the given compression level 218 // and returns the number of compressed bytes written to w. 219 // 220 // Supported compression levels are: 221 // 222 // - CompressNoCompression 223 // - CompressBestSpeed 224 // - CompressBestCompression 225 // - CompressDefaultCompression 226 // - CompressHuffmanOnly 227 func WriteGzipLevel(w io.Writer, p []byte, level int) (int, error) { 228 switch w.(type) { 229 case *byteSliceWriter, 230 *bytes.Buffer, 231 *bytebufferpool.ByteBuffer: 232 // These writers don't block, so we can just use stacklessWriteGzip 233 ctx := &compressCtx{ 234 w: w, 235 p: p, 236 level: level, 237 } 238 stacklessWriteGzip(ctx) 239 return len(p), nil 240 default: 241 zw := AcquireStacklessGzipWriter(w, level) 242 n, err := zw.Write(p) 243 ReleaseStacklessGzipWriter(zw, level) 244 return n, err 245 } 246 } 247 248 func AcquireStacklessGzipWriter(w io.Writer, level int) stackless.Writer { 249 nLevel := normalizeCompressLevel(level) 250 p := stacklessGzipWriterPoolMap[nLevel] 251 v := p.Get() 252 if v == nil { 253 return stackless.NewWriter(w, func(w io.Writer) stackless.Writer { 254 return acquireRealGzipWriter(w, level) 255 }) 256 } 257 sw := v.(stackless.Writer) 258 sw.Reset(w) 259 return sw 260 } 261 262 func ReleaseStacklessGzipWriter(sw stackless.Writer, level int) { 263 sw.Close() 264 nLevel := normalizeCompressLevel(level) 265 p := stacklessGzipWriterPoolMap[nLevel] 266 p.Put(sw) 267 }