• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015 The gRPC 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 
17 package io.grpc;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 
22 import com.google.common.base.Joiner;
23 import java.nio.charset.Charset;
24 import java.util.Collections;
25 import java.util.HashSet;
26 import java.util.LinkedHashMap;
27 import java.util.Map;
28 import java.util.Set;
29 import javax.annotation.Nullable;
30 import javax.annotation.concurrent.ThreadSafe;
31 
32 /**
33  * Encloses classes related to the compression and decompression of messages.
34  */
35 @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
36 @ThreadSafe
37 public final class DecompressorRegistry {
38   static final Joiner ACCEPT_ENCODING_JOINER = Joiner.on(',');
39 
emptyInstance()40   public static DecompressorRegistry emptyInstance() {
41     return new DecompressorRegistry();
42   }
43 
44   private static final DecompressorRegistry DEFAULT_INSTANCE =
45       emptyInstance()
46       .with(new Codec.Gzip(), true)
47       .with(Codec.Identity.NONE, false);
48 
getDefaultInstance()49   public static DecompressorRegistry getDefaultInstance() {
50     return DEFAULT_INSTANCE;
51   }
52 
53   private final Map<String, DecompressorInfo> decompressors;
54   private final byte[] advertisedDecompressors;
55 
56   /**
57    * Registers a decompressor for both decompression and message encoding negotiation.  Returns a
58    * new registry.
59    *
60    * @param d The decompressor to register
61    * @param advertised If true, the message encoding will be listed in the Accept-Encoding header.
62    */
with(Decompressor d, boolean advertised)63   public DecompressorRegistry with(Decompressor d, boolean advertised) {
64     return new DecompressorRegistry(d, advertised, this);
65   }
66 
DecompressorRegistry(Decompressor d, boolean advertised, DecompressorRegistry parent)67   private DecompressorRegistry(Decompressor d, boolean advertised, DecompressorRegistry parent) {
68     String encoding = d.getMessageEncoding();
69     checkArgument(!encoding.contains(","), "Comma is currently not allowed in message encoding");
70 
71     int newSize = parent.decompressors.size();
72     if (!parent.decompressors.containsKey(d.getMessageEncoding())) {
73       newSize++;
74     }
75     Map<String, DecompressorInfo> newDecompressors =
76         new LinkedHashMap<>(newSize);
77     for (DecompressorInfo di : parent.decompressors.values()) {
78       String previousEncoding = di.decompressor.getMessageEncoding();
79       if (!previousEncoding.equals(encoding)) {
80         newDecompressors.put(
81             previousEncoding, new DecompressorInfo(di.decompressor, di.advertised));
82       }
83     }
84     newDecompressors.put(encoding, new DecompressorInfo(d, advertised));
85 
86     decompressors = Collections.unmodifiableMap(newDecompressors);
87     advertisedDecompressors = ACCEPT_ENCODING_JOINER.join(getAdvertisedMessageEncodings())
88         .getBytes(Charset.forName("US-ASCII"));
89   }
90 
DecompressorRegistry()91   private DecompressorRegistry() {
92     decompressors = new LinkedHashMap<>(0);
93     advertisedDecompressors = new byte[0];
94   }
95 
96   /**
97    * Provides a list of all message encodings that have decompressors available.
98    */
getKnownMessageEncodings()99   public Set<String> getKnownMessageEncodings() {
100     return decompressors.keySet();
101   }
102 
103 
getRawAdvertisedMessageEncodings()104   byte[] getRawAdvertisedMessageEncodings() {
105     return advertisedDecompressors;
106   }
107 
108   /**
109    * Provides a list of all message encodings that have decompressors available and should be
110    * advertised.
111    *
112    * <p>The specification doesn't say anything about ordering, or preference, so the returned codes
113    * can be arbitrary.
114    */
115   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
getAdvertisedMessageEncodings()116   public Set<String> getAdvertisedMessageEncodings() {
117     Set<String> advertisedDecompressors = new HashSet<>(decompressors.size());
118     for (Map.Entry<String, DecompressorInfo> entry : decompressors.entrySet()) {
119       if (entry.getValue().advertised) {
120         advertisedDecompressors.add(entry.getKey());
121       }
122     }
123     return Collections.unmodifiableSet(advertisedDecompressors);
124   }
125 
126   /**
127    * Returns a decompressor for the given message encoding, or {@code null} if none has been
128    * registered.
129    *
130    * <p>This ignores whether the compressor is advertised.  According to the spec, if we know how
131    * to process this encoding, we attempt to, regardless of whether or not it is part of the
132    * encodings sent to the remote host.
133    */
134   @Nullable
lookupDecompressor(String messageEncoding)135   public Decompressor lookupDecompressor(String messageEncoding) {
136     DecompressorInfo info = decompressors.get(messageEncoding);
137     return info != null ? info.decompressor : null;
138   }
139 
140   /**
141    * Information about a decompressor.
142    */
143   private static final class DecompressorInfo {
144     final Decompressor decompressor;
145     final boolean advertised;
146 
DecompressorInfo(Decompressor decompressor, boolean advertised)147     DecompressorInfo(Decompressor decompressor, boolean advertised) {
148       this.decompressor = checkNotNull(decompressor, "decompressor");
149       this.advertised = advertised;
150     }
151   }
152 }
153