Skip to content

Commit 816fa5b

Browse files
authored
Add proper support for 'identity' encoding type (grpc#1664)
1 parent c1fc296 commit 816fa5b

File tree

11 files changed

+370
-118
lines changed

11 files changed

+370
-118
lines changed

Documentation/compression.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Compression
2+
3+
The preferred method for configuring message compression on both clients and
4+
servers is to use
5+
[`encoding.RegisterCompressor`](https://godoc.org/google.golang.org/grpc/encoding#RegisterCompressor)
6+
to register an implementation of a compression algorithm. See
7+
`grpc/encoding/gzip/gzip.go` for an example of how to implement one.
8+
9+
Once a compressor has been registered on the client-side, RPCs may be sent using
10+
it via the
11+
[`UseCompressor`](https://godoc.org/google.golang.org/grpc#UseCompressor)
12+
`CallOption`. Remember that `CallOption`s may be turned into defaults for all
13+
calls from a `ClientConn` by using the
14+
[`WithDefaultCallOptions`](https://godoc.org/google.golang.org/grpc#WithDefaultCallOptions)
15+
`DialOption`. If `UseCompressor` is used and the corresponding compressor has
16+
not been installed, an `Internal` error will be returned to the application
17+
before the RPC is sent.
18+
19+
Server-side, registered compressors will be used automatically to decode request
20+
messages and encode the responses. Servers currently always respond using the
21+
same compression method specified by the client. If the corresponding
22+
compressor has not been registered, an `Unimplemented` status will be returned
23+
to the client.
24+
25+
## Deprecated API
26+
27+
There is a deprecated API for setting compression as well. It is not
28+
recommended for use. However, if you were previously using it, the following
29+
section may be helpful in understanding how it works in combination with the new
30+
API.
31+
32+
### Client-Side
33+
34+
There are two legacy functions and one new function to configure compression:
35+
36+
```go
37+
func WithCompressor(grpc.Compressor) DialOption {}
38+
func WithDecompressor(grpc.Decompressor) DialOption {}
39+
func UseCompressor(name) CallOption {}
40+
```
41+
42+
For outgoing requests, the following rules are applied in order:
43+
1. If `UseCompressor` is used, messages will be compressed using the compressor
44+
named.
45+
* If the compressor named is not registered, an Internal error is returned
46+
back to the client before sending the RPC.
47+
* If UseCompressor("identity"), no compressor will be used, but "identity"
48+
will be sent in the header to the server.
49+
1. If `WithCompressor` is used, messages will be compressed using that
50+
compressor implementation.
51+
1. Otherwise, outbound messages will be uncompressed.
52+
53+
For incoming responses, the following rules are applied in order:
54+
1. If `WithDecompressor` is used and it matches the message's encoding, it will
55+
be used.
56+
1. If a registered compressor matches the response's encoding, it will be used.
57+
1. Otherwise, the stream will be closed and an `Unimplemented` status error will
58+
be returned to the application.
59+
60+
### Server-Side
61+
62+
There are two legacy functions to configure compression:
63+
```go
64+
func RPCCompressor(grpc.Compressor) ServerOption {}
65+
func RPCDecompressor(grpc.Decompressor) ServerOption {}
66+
```
67+
68+
For incoming requests, the following rules are applied in order:
69+
1. If `RPCDecompressor` is used and that decompressor matches the request's
70+
encoding: it will be used.
71+
1. If a registered compressor matches the request's encoding, it will be used.
72+
1. Otherwise, an `Unimplemented` status will be returned to the client.
73+
74+
For outgoing responses, the following rules are applied in order:
75+
1. If `RPCCompressor` is used, that compressor will be used to compress all
76+
response messages.
77+
1. If compression was used for the incoming request and a registered compressor
78+
supports it, that same compression method will be used for the outgoing
79+
response.
80+
1. Otherwise, no compression will be used for the outgoing response.

call.go

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,17 @@ func recvResponse(ctx context.Context, dopts dialOptions, t transport.ClientTran
6161
if c.maxReceiveMessageSize == nil {
6262
return Errorf(codes.Internal, "callInfo maxReceiveMessageSize field uninitialized(nil)")
6363
}
64-
if err = recv(p, dopts.codec, stream, dopts.dc, reply, *c.maxReceiveMessageSize, inPayload, encoding.GetCompressor(c.compressorType)); err != nil {
64+
65+
// Set dc if it exists and matches the message compression type used,
66+
// otherwise set comp if a registered compressor exists for it.
67+
var comp encoding.Compressor
68+
var dc Decompressor
69+
if rc := stream.RecvCompress(); dopts.dc != nil && dopts.dc.Type() == rc {
70+
dc = dopts.dc
71+
} else if rc != "" && rc != encoding.Identity {
72+
comp = encoding.GetCompressor(rc)
73+
}
74+
if err = recv(p, dopts.codec, stream, dc, reply, *c.maxReceiveMessageSize, inPayload, comp); err != nil {
6575
if err == io.EOF {
6676
break
6777
}
@@ -95,10 +105,18 @@ func sendRequest(ctx context.Context, dopts dialOptions, compressor Compressor,
95105
Client: true,
96106
}
97107
}
98-
if c.compressorType != "" && encoding.GetCompressor(c.compressorType) == nil {
99-
return Errorf(codes.Internal, "grpc: Compressor is not installed for grpc-encoding %q", c.compressorType)
108+
// Set comp and clear compressor if a registered compressor matches the type
109+
// specified via UseCompressor. (And error if a matching compressor is not
110+
// registered.)
111+
var comp encoding.Compressor
112+
if ct := c.compressorType; ct != "" && ct != encoding.Identity {
113+
compressor = nil // Disable the legacy compressor.
114+
comp = encoding.GetCompressor(ct)
115+
if comp == nil {
116+
return Errorf(codes.Internal, "grpc: Compressor is not installed for grpc-encoding %q", ct)
117+
}
100118
}
101-
hdr, data, err := encode(dopts.codec, args, compressor, outPayload, encoding.GetCompressor(c.compressorType))
119+
hdr, data, err := encode(dopts.codec, args, compressor, outPayload, comp)
102120
if err != nil {
103121
return err
104122
}
@@ -211,9 +229,6 @@ func invoke(ctx context.Context, method string, args, reply interface{}, cc *Cli
211229
Host: cc.authority,
212230
Method: method,
213231
}
214-
if cc.dopts.cp != nil {
215-
callHdr.SendCompress = cc.dopts.cp.Type()
216-
}
217232
if c.creds != nil {
218233
callHdr.Creds = c.creds
219234
}

clientconn.go

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -107,16 +107,6 @@ const (
107107
// DialOption configures how we set up the connection.
108108
type DialOption func(*dialOptions)
109109

110-
// UseCompressor returns a CallOption which sets the compressor used when sending the request.
111-
// If WithCompressor is set, UseCompressor has higher priority.
112-
// This API is EXPERIMENTAL.
113-
func UseCompressor(name string) CallOption {
114-
return beforeCall(func(c *callInfo) error {
115-
c.compressorType = name
116-
return nil
117-
})
118-
}
119-
120110
// WithWriteBufferSize lets you set the size of write buffer, this determines how much data can be batched
121111
// before doing a write on the wire.
122112
func WithWriteBufferSize(s int) DialOption {
@@ -168,18 +158,26 @@ func WithCodec(c Codec) DialOption {
168158
}
169159
}
170160

171-
// WithCompressor returns a DialOption which sets a CompressorGenerator for generating message
172-
// compressor. It has lower priority than the compressor set by RegisterCompressor.
173-
// This function is deprecated.
161+
// WithCompressor returns a DialOption which sets a Compressor to use for
162+
// message compression. It has lower priority than the compressor set by
163+
// the UseCompressor CallOption.
164+
//
165+
// Deprecated: use UseCompressor instead.
174166
func WithCompressor(cp Compressor) DialOption {
175167
return func(o *dialOptions) {
176168
o.cp = cp
177169
}
178170
}
179171

180-
// WithDecompressor returns a DialOption which sets a DecompressorGenerator for generating
181-
// message decompressor. It has higher priority than the decompressor set by RegisterCompressor.
182-
// This function is deprecated.
172+
// WithDecompressor returns a DialOption which sets a Decompressor to use for
173+
// incoming message decompression. If incoming response messages are encoded
174+
// using the decompressor's Type(), it will be used. Otherwise, the message
175+
// encoding will be used to look up the compressor registered via
176+
// encoding.RegisterCompressor, which will then be used to decompress the
177+
// message. If no compressor is registered for the encoding, an Unimplemented
178+
// status error will be returned.
179+
//
180+
// Deprecated: use encoding.RegisterCompressor instead.
183181
func WithDecompressor(dc Decompressor) DialOption {
184182
return func(o *dialOptions) {
185183
o.dc = dc

encoding/encoding.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,7 @@ func RegisterCompressor(c Compressor) {
5555
func GetCompressor(name string) Compressor {
5656
return registerCompressor[name]
5757
}
58+
59+
// Identity specifies the optional encoding for uncompressed streams.
60+
// It is intended for grpc internal use only.
61+
const Identity = "identity"

rpc_util.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,18 @@ func PerRPCCredentials(creds credentials.PerRPCCredentials) CallOption {
236236
})
237237
}
238238

239+
// UseCompressor returns a CallOption which sets the compressor used when
240+
// sending the request. If WithCompressor is also set, UseCompressor has
241+
// higher priority.
242+
//
243+
// This API is EXPERIMENTAL.
244+
func UseCompressor(name string) CallOption {
245+
return beforeCall(func(c *callInfo) error {
246+
c.compressorType = name
247+
return nil
248+
})
249+
}
250+
239251
// The format of the payload: compressed or not?
240252
type payloadFormat uint8
241253

@@ -359,32 +371,38 @@ func encode(c Codec, msg interface{}, cp Compressor, outPayload *stats.OutPayloa
359371
return bufHeader, b, nil
360372
}
361373

362-
func checkRecvPayload(pf payloadFormat, recvCompress string, dc Decompressor) error {
374+
func checkRecvPayload(pf payloadFormat, recvCompress string, haveCompressor bool) *status.Status {
363375
switch pf {
364376
case compressionNone:
365377
case compressionMade:
366-
if (dc == nil || recvCompress != dc.Type()) && encoding.GetCompressor(recvCompress) == nil {
367-
return Errorf(codes.Unimplemented, "grpc: Decompressor is not installed for grpc-encoding %q", recvCompress)
378+
if recvCompress == "" || recvCompress == encoding.Identity {
379+
return status.New(codes.Internal, "grpc: compressed flag set with identity or empty encoding")
380+
}
381+
if !haveCompressor {
382+
return status.Newf(codes.Unimplemented, "grpc: Decompressor is not installed for grpc-encoding %q", recvCompress)
368383
}
369384
default:
370-
return Errorf(codes.Internal, "grpc: received unexpected payload format %d", pf)
385+
return status.Newf(codes.Internal, "grpc: received unexpected payload format %d", pf)
371386
}
372387
return nil
373388
}
374389

375-
// TODO(ddyihai): eliminate extra Compressor parameter.
376-
func recv(p *parser, c Codec, s *transport.Stream, dc Decompressor, m interface{}, maxReceiveMessageSize int,
377-
inPayload *stats.InPayload, compressor encoding.Compressor) error {
390+
// For the two compressor parameters, both should not be set, but if they are,
391+
// dc takes precedence over compressor.
392+
// TODO(dfawley): wrap the old compressor/decompressor using the new API?
393+
func recv(p *parser, c Codec, s *transport.Stream, dc Decompressor, m interface{}, maxReceiveMessageSize int, inPayload *stats.InPayload, compressor encoding.Compressor) error {
378394
pf, d, err := p.recvMsg(maxReceiveMessageSize)
379395
if err != nil {
380396
return err
381397
}
382398
if inPayload != nil {
383399
inPayload.WireLength = len(d)
384400
}
385-
if err := checkRecvPayload(pf, s.RecvCompress(), dc); err != nil {
386-
return err
401+
402+
if st := checkRecvPayload(pf, s.RecvCompress(), compressor != nil || dc != nil); st != nil {
403+
return st.Err()
387404
}
405+
388406
if pf == compressionMade {
389407
// To match legacy behavior, if the decompressor is set by WithDecompressor or RPCDecompressor,
390408
// use this decompressor as the default.

0 commit comments

Comments
 (0)