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  }