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