blob: 6306e8bb0f0add210c6543633a285cb5f0a4d88b [file] [log] [blame]
Abhay Kumara2ae5992025-11-10 14:02:24 +00001/*
2 *
3 * Copyright 2017 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19// Package gzip implements and registers the gzip compressor
20// during the initialization.
21//
22// # Experimental
23//
24// Notice: This package is EXPERIMENTAL and may be changed or removed in a
25// later release.
26package gzip
27
28import (
29 "compress/gzip"
30 "encoding/binary"
31 "fmt"
32 "io"
33 "sync"
34
35 "google.golang.org/grpc/encoding"
36)
37
38// Name is the name registered for the gzip compressor.
39const Name = "gzip"
40
41func init() {
42 c := &compressor{}
43 c.poolCompressor.New = func() any {
44 return &writer{Writer: gzip.NewWriter(io.Discard), pool: &c.poolCompressor}
45 }
46 encoding.RegisterCompressor(c)
47}
48
49type writer struct {
50 *gzip.Writer
51 pool *sync.Pool
52}
53
54// SetLevel updates the registered gzip compressor to use the compression level specified (gzip.HuffmanOnly is not supported).
55// NOTE: this function must only be called during initialization time (i.e. in an init() function),
56// and is not thread-safe.
57//
58// The error returned will be nil if the specified level is valid.
59func SetLevel(level int) error {
60 if level < gzip.DefaultCompression || level > gzip.BestCompression {
61 return fmt.Errorf("grpc: invalid gzip compression level: %d", level)
62 }
63 c := encoding.GetCompressor(Name).(*compressor)
64 c.poolCompressor.New = func() any {
65 w, err := gzip.NewWriterLevel(io.Discard, level)
66 if err != nil {
67 panic(err)
68 }
69 return &writer{Writer: w, pool: &c.poolCompressor}
70 }
71 return nil
72}
73
74func (c *compressor) Compress(w io.Writer) (io.WriteCloser, error) {
75 z := c.poolCompressor.Get().(*writer)
76 z.Writer.Reset(w)
77 return z, nil
78}
79
80func (z *writer) Close() error {
81 defer z.pool.Put(z)
82 return z.Writer.Close()
83}
84
85type reader struct {
86 *gzip.Reader
87 pool *sync.Pool
88}
89
90func (c *compressor) Decompress(r io.Reader) (io.Reader, error) {
91 z, inPool := c.poolDecompressor.Get().(*reader)
92 if !inPool {
93 newZ, err := gzip.NewReader(r)
94 if err != nil {
95 return nil, err
96 }
97 return &reader{Reader: newZ, pool: &c.poolDecompressor}, nil
98 }
99 if err := z.Reset(r); err != nil {
100 c.poolDecompressor.Put(z)
101 return nil, err
102 }
103 return z, nil
104}
105
106func (z *reader) Read(p []byte) (n int, err error) {
107 n, err = z.Reader.Read(p)
108 if err == io.EOF {
109 z.pool.Put(z)
110 }
111 return n, err
112}
113
114// RFC1952 specifies that the last four bytes "contains the size of
115// the original (uncompressed) input data modulo 2^32."
116// gRPC has a max message size of 2GB so we don't need to worry about wraparound.
117func (c *compressor) DecompressedSize(buf []byte) int {
118 last := len(buf)
119 if last < 4 {
120 return -1
121 }
122 return int(binary.LittleEndian.Uint32(buf[last-4 : last]))
123}
124
125func (c *compressor) Name() string {
126 return Name
127}
128
129type compressor struct {
130 poolCompressor sync.Pool
131 poolDecompressor sync.Pool
132}