go.uber.org/yarpc@v1.72.1/compressor/gzip/gzip.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 // Package yarpcgzip provides a YARPC binding for GZIP compression. 22 package yarpcgzip 23 24 import ( 25 "compress/gzip" 26 "encoding/binary" 27 "io" 28 "sync" 29 30 "go.uber.org/yarpc/api/transport" 31 ) 32 33 const name = "gzip" 34 35 // Option is an option argument for the Gzip compressor constructor, New. 36 type Option interface { 37 apply(*Compressor) 38 } 39 40 // Level sets the compression level for the compressor. 41 func Level(level int) Option { 42 return levelOption{level: level} 43 } 44 45 type levelOption struct { 46 level int 47 } 48 49 func (o levelOption) apply(opts *Compressor) { 50 opts.level = o.level 51 } 52 53 // New returns a GZIP compression strategy, suitable for configuring an 54 // outbound dialer. 55 // 56 // The compressor needs to be adapted and registered to be compatible 57 // with the gRPC compressor system. 58 // Since gRPC requires global registration of compressors, you must arrange for 59 // the compressor to be registered in your application initialization. 60 // The adapter converts an io.Reader into an io.ReadCloser so that reading EOF 61 // will implicitly trigger Close, a behavior gRPC-go relies upon to reuse 62 // readers. 63 // 64 // import ( 65 // "compress/gzip" 66 // 67 // "google.golang.org/grpc/encoding" 68 // "go.uber.org/yarpc/compressor/grpc" 69 // "go.uber.org/yarpc/compressor/gzip" 70 // ) 71 // 72 // var GZIPCompressor = yarpcgzip.New(yarpcgzip.Level(gzip.BestCompression)) 73 // 74 // func init() 75 // gz := yarpcgrpccompressor.New(GZIPCompressor) 76 // encoding.RegisterCompressor(gz) 77 // } 78 // 79 // If you are constructing your YARPC clients directly through the API, 80 // create a gRPC dialer with the Compressor option. 81 // 82 // trans := grpc.NewTransport() 83 // dialer := trans.NewDialer(GZIPCompressor) 84 // peers := roundrobin.New(dialer) 85 // outbound := trans.NewOutbound(peers) 86 // 87 // If you are using the YARPC configurator to create YARPC objects 88 // using config files, you will also need to register the compressor 89 // with your configurator. 90 // 91 // configurator := yarpcconfig.New() 92 // configurator.MustRegisterCompressor(GZIPCompressor) 93 // 94 // Then, using the compression strategy for outbound requests 95 // on a particular client, just set the compressor to gzip. 96 // 97 // outbounds: 98 // theirsecureservice: 99 // grpc: 100 // address: ":443" 101 // tls: 102 // enabled: true 103 // compressor: gzip 104 // 105 func New(opts ...Option) *Compressor { 106 c := &Compressor{ 107 level: gzip.DefaultCompression, 108 } 109 for _, opt := range opts { 110 opt.apply(c) 111 } 112 return c 113 } 114 115 // Compressor represents the gzip compression strategy. 116 type Compressor struct { 117 level int 118 compressors sync.Pool 119 decompressors sync.Pool 120 } 121 122 var _ transport.Compressor = (*Compressor)(nil) 123 124 // Name is gzip. 125 func (*Compressor) Name() string { 126 return name 127 } 128 129 // Compress creates a gzip compressor. 130 func (c *Compressor) Compress(w io.Writer) (io.WriteCloser, error) { 131 if cw, got := c.compressors.Get().(*writer); got { 132 cw.writer.Reset(w) 133 return cw, nil 134 } 135 136 cw, err := gzip.NewWriterLevel(w, c.level) 137 if err != nil { 138 return nil, err 139 } 140 141 return &writer{ 142 writer: cw, 143 pool: &c.compressors, 144 }, nil 145 } 146 147 type writer struct { 148 writer *gzip.Writer 149 pool *sync.Pool 150 } 151 152 var _ io.WriteCloser = (*writer)(nil) 153 154 func (w *writer) Write(buf []byte) (int, error) { 155 return w.writer.Write(buf) 156 } 157 158 func (w *writer) Close() error { 159 defer w.pool.Put(w) 160 return w.writer.Close() 161 } 162 163 // Decompress obtains a gzip decompressor. 164 func (c *Compressor) Decompress(r io.Reader) (io.ReadCloser, error) { 165 if dr, got := c.decompressors.Get().(*reader); got { 166 if err := dr.reader.Reset(r); err != nil { 167 c.decompressors.Put(r) 168 return nil, err 169 } 170 171 return dr, nil 172 } 173 174 dr, err := gzip.NewReader(r) 175 if err != nil { 176 return nil, err 177 } 178 179 return &reader{ 180 reader: dr, 181 pool: &c.decompressors, 182 }, nil 183 } 184 185 type reader struct { 186 reader *gzip.Reader 187 pool *sync.Pool 188 } 189 190 var _ io.ReadCloser = (*reader)(nil) 191 192 func (r *reader) Read(buf []byte) (n int, err error) { 193 return r.reader.Read(buf) 194 } 195 196 func (r *reader) Close() error { 197 r.pool.Put(r) 198 return nil 199 } 200 201 // DecompressedSize returns the decompressed size of the given GZIP compressed 202 // bytes. 203 // 204 // gRPC specifically casts the compressor to a DecompressedSizer 205 // to pre-check message length. 206 // 207 // Per gRPC-go, on which this is based: 208 // https://github.com/grpc/grpc-go/blob/master/encoding/gzip/gzip.go 209 // 210 // RFC1952 specifies that the last four bytes "contains the size of 211 // the original (uncompressed) input data modulo 2^32." 212 // gRPC has a max message size of 2GB so we don't need to worry about 213 // wraparound for that transport protocol. 214 func (c *Compressor) DecompressedSize(buf []byte) int { 215 last := len(buf) 216 if last < 4 { 217 return -1 218 } 219 return int(binary.LittleEndian.Uint32(buf[last-4 : last])) 220 }