• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 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 /*
18  * Copyright 2014 The Netty Project
19  *
20  * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
21  * "License"); you may not use this file except in compliance with the License. You may obtain a
22  * copy of the License at:
23  *
24  * http://www.apache.org/licenses/LICENSE-2.0
25  *
26  * Unless required by applicable law or agreed to in writing, software distributed under the License
27  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
28  * or implied. See the License for the specific language governing permissions and limitations under
29  * the License.
30  */
31 
32 package io.grpc.netty;
33 
34 import static com.google.common.base.Charsets.US_ASCII;
35 import static com.google.common.base.Preconditions.checkArgument;
36 import static io.grpc.netty.Utils.TE_HEADER;
37 import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
38 import static io.netty.handler.codec.http2.Http2Exception.connectionError;
39 import static io.netty.util.AsciiString.isUpperCase;
40 
41 import com.google.common.io.BaseEncoding;
42 import com.google.errorprone.annotations.CanIgnoreReturnValue;
43 import io.grpc.Metadata;
44 import io.netty.handler.codec.CharSequenceValueConverter;
45 import io.netty.handler.codec.http2.DefaultHttp2HeadersDecoder;
46 import io.netty.handler.codec.http2.Http2Headers;
47 import io.netty.util.AsciiString;
48 import io.netty.util.internal.PlatformDependent;
49 import java.util.AbstractMap;
50 import java.util.ArrayList;
51 import java.util.Collections;
52 import java.util.Iterator;
53 import java.util.List;
54 import java.util.Map;
55 
56 /**
57  * A headers utils providing custom gRPC implementations of {@link DefaultHttp2HeadersDecoder}.
58  */
59 class GrpcHttp2HeadersUtils {
60   static final class GrpcHttp2ServerHeadersDecoder extends DefaultHttp2HeadersDecoder {
61 
GrpcHttp2ServerHeadersDecoder(long maxHeaderListSize)62     GrpcHttp2ServerHeadersDecoder(long maxHeaderListSize) {
63       super(true, maxHeaderListSize);
64     }
65 
66     @Override
newHeaders()67     protected GrpcHttp2InboundHeaders newHeaders() {
68       return new GrpcHttp2RequestHeaders(numberOfHeadersGuess());
69     }
70   }
71 
72   static final class GrpcHttp2ClientHeadersDecoder extends DefaultHttp2HeadersDecoder {
73 
GrpcHttp2ClientHeadersDecoder(long maxHeaderListSize)74     GrpcHttp2ClientHeadersDecoder(long maxHeaderListSize) {
75       super(true, maxHeaderListSize);
76     }
77 
78     @Override
newHeaders()79     protected GrpcHttp2InboundHeaders newHeaders() {
80       return new GrpcHttp2ResponseHeaders(numberOfHeadersGuess());
81     }
82   }
83 
84   /**
85    * A {@link Http2Headers} implementation optimized for inbound/received headers.
86    *
87    * <p>Header names and values are stored in simple arrays, which makes insert run in O(1)
88    * and retrievial a O(n). Header name equality is not determined by the equals implementation of
89    * {@link CharSequence} type, but by comparing two names byte to byte.
90    *
91    * <p>All {@link CharSequence} input parameters and return values are required to be of type
92    * {@link AsciiString}.
93    */
94   abstract static class GrpcHttp2InboundHeaders extends AbstractHttp2Headers {
95 
96     private static final AsciiString binaryHeaderSuffix =
97         new AsciiString(Metadata.BINARY_HEADER_SUFFIX.getBytes(US_ASCII));
98 
99     private byte[][] namesAndValues;
100     private AsciiString[] values;
101     private int namesAndValuesIdx;
102 
GrpcHttp2InboundHeaders(int numHeadersGuess)103     GrpcHttp2InboundHeaders(int numHeadersGuess) {
104       checkArgument(numHeadersGuess > 0, "numHeadersGuess needs to be positive: %s",
105           numHeadersGuess);
106       namesAndValues = new byte[numHeadersGuess * 2][];
107       values = new AsciiString[numHeadersGuess];
108     }
109 
add(AsciiString name, AsciiString value)110     protected Http2Headers add(AsciiString name, AsciiString value) {
111       byte[] nameBytes = bytes(name);
112       byte[] valueBytes;
113       if (!name.endsWith(binaryHeaderSuffix)) {
114         valueBytes = bytes(value);
115         addHeader(value, nameBytes, valueBytes);
116         return this;
117       }
118       int startPos = 0;
119       int endPos = -1;
120       while (endPos < value.length()) {
121         int indexOfComma = value.indexOf(',', startPos);
122         endPos = indexOfComma == AsciiString.INDEX_NOT_FOUND ? value.length() : indexOfComma;
123         AsciiString curVal = value.subSequence(startPos, endPos, false);
124         valueBytes = BaseEncoding.base64().decode(curVal);
125         startPos = indexOfComma + 1;
126         addHeader(curVal, nameBytes, valueBytes);
127       }
128       return this;
129     }
130 
addHeader(AsciiString value, byte[] nameBytes, byte[] valueBytes)131     private void addHeader(AsciiString value, byte[] nameBytes, byte[] valueBytes) {
132       if (namesAndValuesIdx == namesAndValues.length) {
133         expandHeadersAndValues();
134       }
135       values[namesAndValuesIdx / 2] = value;
136       namesAndValues[namesAndValuesIdx] = nameBytes;
137       namesAndValuesIdx++;
138       namesAndValues[namesAndValuesIdx] = valueBytes;
139       namesAndValuesIdx++;
140     }
141 
get(AsciiString name)142     protected CharSequence get(AsciiString name) {
143       for (int i = 0; i < namesAndValuesIdx; i += 2) {
144         if (equals(name, namesAndValues[i])) {
145           return values[i / 2];
146         }
147       }
148       return null;
149     }
150 
151     @Override
contains(CharSequence name)152     public boolean contains(CharSequence name) {
153       return get(name) != null;
154     }
155 
156     @Override
status()157     public CharSequence status() {
158       return get(Http2Headers.PseudoHeaderName.STATUS.value());
159     }
160 
161     @Override
getAll(CharSequence csName)162     public List<CharSequence> getAll(CharSequence csName) {
163       AsciiString name = requireAsciiString(csName);
164       List<CharSequence> returnValues = new ArrayList<>(4);
165       for (int i = 0; i < namesAndValuesIdx; i += 2) {
166         if (equals(name, namesAndValues[i])) {
167           returnValues.add(values[i / 2]);
168         }
169       }
170       return returnValues;
171     }
172 
173     @CanIgnoreReturnValue
174     @Override
remove(CharSequence csName)175     public boolean remove(CharSequence csName) {
176       AsciiString name = requireAsciiString(csName);
177       int i = 0;
178       for (; i < namesAndValuesIdx; i += 2) {
179         if (equals(name, namesAndValues[i])) {
180           break;
181         }
182       }
183       if (i >= namesAndValuesIdx) {
184         return false;
185       }
186       int dest = i;
187       for (; i < namesAndValuesIdx; i += 2) {
188         if (equals(name, namesAndValues[i])) {
189           continue;
190         }
191         values[dest / 2] = values[i / 2];
192         namesAndValues[dest] = namesAndValues[i];
193         namesAndValues[dest + 1] = namesAndValues[i + 1];
194         dest += 2;
195       }
196       namesAndValuesIdx = dest;
197       return true;
198     }
199 
200     @Override
set(CharSequence name, CharSequence value)201     public Http2Headers set(CharSequence name, CharSequence value) {
202       remove(name);
203       return add(name, value);
204     }
205 
206     @Override
setLong(CharSequence name, long value)207     public Http2Headers setLong(CharSequence name, long value) {
208       return set(name, AsciiString.of(CharSequenceValueConverter.INSTANCE.convertLong(value)));
209     }
210 
211     /**
212      * Returns the header names and values as bytes. An even numbered index contains the
213      * {@code byte[]} representation of a header name (in insertion order), and the subsequent
214      * odd index number contains the corresponding header value.
215      *
216      * <p>The values of binary headers (with a -bin suffix), are already base64 decoded.
217      *
218      * <p>The array may contain several {@code null} values at the end. A {@code null} value an
219      * index means that all higher numbered indices also contain {@code null} values.
220      */
namesAndValues()221     byte[][] namesAndValues() {
222       return namesAndValues;
223     }
224 
225     /**
226      * Returns the number of none-null headers in {@link #namesAndValues()}.
227      */
numHeaders()228     protected int numHeaders() {
229       return namesAndValuesIdx / 2;
230     }
231 
equals(AsciiString str0, byte[] str1)232     protected static boolean equals(AsciiString str0, byte[] str1) {
233       return equals(str0.array(), str0.arrayOffset(), str0.length(), str1, 0, str1.length);
234     }
235 
equals(AsciiString str0, AsciiString str1)236     protected static boolean equals(AsciiString str0, AsciiString str1) {
237       return equals(str0.array(), str0.arrayOffset(), str0.length(), str1.array(),
238           str1.arrayOffset(), str1.length());
239     }
240 
equals(byte[] bytes0, int offset0, int length0, byte[] bytes1, int offset1, int length1)241     protected static boolean equals(byte[] bytes0, int offset0, int length0, byte[] bytes1,
242         int offset1, int length1) {
243       if (length0 != length1) {
244         return false;
245       }
246       return PlatformDependent.equals(bytes0, offset0, bytes1, offset1, length0);
247     }
248 
bytes(AsciiString str)249     protected static byte[] bytes(AsciiString str) {
250       return str.isEntireArrayUsed() ? str.array() : str.toByteArray();
251     }
252 
requireAsciiString(CharSequence cs)253     protected static AsciiString requireAsciiString(CharSequence cs) {
254       if (!(cs instanceof AsciiString)) {
255         throw new IllegalArgumentException("AsciiString expected. Was: " + cs.getClass().getName());
256       }
257       return (AsciiString) cs;
258     }
259 
isPseudoHeader(AsciiString str)260     protected static boolean isPseudoHeader(AsciiString str) {
261       return !str.isEmpty() && str.charAt(0) == ':';
262     }
263 
validateName(AsciiString str)264     protected AsciiString validateName(AsciiString str) {
265       int offset = str.arrayOffset();
266       int length = str.length();
267       final byte[] data = str.array();
268       for (int i = offset; i < offset + length; i++) {
269         if (isUpperCase(data[i])) {
270           PlatformDependent.throwException(connectionError(PROTOCOL_ERROR,
271               "invalid header name '%s'", str));
272         }
273       }
274       return str;
275     }
276 
expandHeadersAndValues()277     private void expandHeadersAndValues() {
278       int newValuesLen = Math.max(2, values.length + values.length / 2);
279       int newNamesAndValuesLen = newValuesLen * 2;
280 
281       byte[][] newNamesAndValues = new byte[newNamesAndValuesLen][];
282       AsciiString[] newValues = new AsciiString[newValuesLen];
283       System.arraycopy(namesAndValues, 0, newNamesAndValues, 0, namesAndValues.length);
284       System.arraycopy(values, 0, newValues, 0, values.length);
285       namesAndValues = newNamesAndValues;
286       values = newValues;
287     }
288 
289     @Override
size()290     public int size() {
291       return numHeaders();
292     }
293 
294     @Override
iterator()295     public Iterator<Map.Entry<CharSequence, CharSequence>> iterator() {
296       return namesAndValuesToImmutableList().iterator();
297     }
298 
appendNameAndValue(StringBuilder builder, CharSequence name, CharSequence value, boolean prependSeparator)299     protected static void appendNameAndValue(StringBuilder builder, CharSequence name,
300         CharSequence value, boolean prependSeparator) {
301       if (prependSeparator) {
302         builder.append(", ");
303       }
304       builder.append(name).append(": ").append(value);
305     }
306 
namesAndValuesToImmutableList()307     private List<Map.Entry<CharSequence, CharSequence>> namesAndValuesToImmutableList() {
308       ArrayList<Map.Entry<CharSequence, CharSequence>> list = new ArrayList<>(values.length);
309       for (int i = 0; i < namesAndValuesIdx; i += 2) {
310         String name = new String(namesAndValues[i], US_ASCII);
311         // If binary headers, the value is base64 encoded.
312         AsciiString value = values[i / 2];
313         list.add(new AbstractMap.SimpleImmutableEntry<CharSequence, CharSequence>(name, value));
314       }
315       return Collections.unmodifiableList(list);
316     }
317 
namesAndValuesToString()318     protected final String namesAndValuesToString() {
319       StringBuilder builder = new StringBuilder();
320       boolean prependSeparator = false;
321       for (Map.Entry<CharSequence, CharSequence> entry : namesAndValuesToImmutableList()) {
322         appendNameAndValue(builder, entry.getKey(), entry.getValue(), prependSeparator);
323         prependSeparator = true;
324       }
325       return builder.toString();
326     }
327   }
328 
329   /**
330    * A {@link GrpcHttp2InboundHeaders} implementation, optimized for HTTP/2 request headers. That
331    * is, HTTP/2 request pseudo headers are stored in dedicated fields and are NOT part of the
332    * array returned by {@link #namesAndValues()}.
333    *
334    * <p>This class only implements the methods used by {@link NettyServerHandler} and tests. All
335    * other methods throw an {@link UnsupportedOperationException}.
336    */
337   static final class GrpcHttp2RequestHeaders extends GrpcHttp2InboundHeaders {
338 
339     private static final AsciiString PATH_HEADER = AsciiString.of(":path");
340     private static final AsciiString AUTHORITY_HEADER = AsciiString.of(":authority");
341     private static final AsciiString METHOD_HEADER = AsciiString.of(":method");
342     private static final AsciiString SCHEME_HEADER = AsciiString.of(":scheme");
343 
344     private AsciiString path;
345     private AsciiString authority;
346     private AsciiString method;
347     private AsciiString scheme;
348     private AsciiString te;
349 
GrpcHttp2RequestHeaders(int numHeadersGuess)350     GrpcHttp2RequestHeaders(int numHeadersGuess) {
351       super(numHeadersGuess);
352     }
353 
354     @Override
add(CharSequence csName, CharSequence csValue)355     public Http2Headers add(CharSequence csName, CharSequence csValue) {
356       AsciiString name = validateName(requireAsciiString(csName));
357       AsciiString value = requireAsciiString(csValue);
358       if (isPseudoHeader(name)) {
359         AsciiString previous = getPseudoHeader(name);
360         if (previous != null) {
361           PlatformDependent.throwException(
362               connectionError(PROTOCOL_ERROR, "Duplicate %s header", name));
363         }
364         setPseudoHeader(name, value);
365         return this;
366       }
367       if (equals(TE_HEADER, name)) {
368         te = value;
369         return this;
370       }
371       return add(name, value);
372     }
373 
374     @Override
get(CharSequence csName)375     public CharSequence get(CharSequence csName) {
376       AsciiString name = requireAsciiString(csName);
377       if (isPseudoHeader(name)) {
378         return getPseudoHeader(name);
379       }
380       if (equals(TE_HEADER, name)) {
381         return te;
382       }
383       return get(name);
384     }
385 
getPseudoHeader(AsciiString name)386     private AsciiString getPseudoHeader(AsciiString name) {
387       if (equals(PATH_HEADER, name)) {
388         return path;
389       } else if (equals(AUTHORITY_HEADER, name)) {
390         return authority;
391       } else if (equals(METHOD_HEADER, name)) {
392         return method;
393       } else if (equals(SCHEME_HEADER, name)) {
394         return scheme;
395       } else {
396         return null;
397       }
398     }
399 
setPseudoHeader(AsciiString name, AsciiString value)400     private void setPseudoHeader(AsciiString name, AsciiString value) {
401       if (equals(PATH_HEADER, name)) {
402         path = value;
403       } else if (equals(AUTHORITY_HEADER, name)) {
404         authority = value;
405       } else if (equals(METHOD_HEADER, name)) {
406         method = value;
407       } else if (equals(SCHEME_HEADER, name)) {
408         scheme = value;
409       } else {
410         PlatformDependent.throwException(
411             connectionError(PROTOCOL_ERROR, "Illegal pseudo-header '%s' in request.", name));
412         throw new AssertionError(); // Make flow control obvious to javac
413       }
414     }
415 
416     @Override
path()417     public CharSequence path() {
418       return path;
419     }
420 
421     @Override
authority()422     public CharSequence authority() {
423       return authority;
424     }
425 
426     @Override
method()427     public CharSequence method() {
428       return method;
429     }
430 
431     @Override
scheme()432     public CharSequence scheme() {
433       return scheme;
434     }
435 
436     @Override
getAll(CharSequence csName)437     public List<CharSequence> getAll(CharSequence csName) {
438       AsciiString name = requireAsciiString(csName);
439       if (isPseudoHeader(name)) {
440         AsciiString value = getPseudoHeader(name);
441         if (value == null) {
442           return Collections.emptyList();
443         } else {
444           return Collections.singletonList(value);
445         }
446       }
447       if (equals(TE_HEADER, name)) {
448         return Collections.singletonList((CharSequence) te);
449       }
450       return super.getAll(csName);
451     }
452 
453     @Override
remove(CharSequence csName)454     public boolean remove(CharSequence csName) {
455       AsciiString name = requireAsciiString(csName);
456       if (isPseudoHeader(name)) {
457         if (getPseudoHeader(name) == null) {
458           return false;
459         } else {
460           setPseudoHeader(name, null);
461           return true;
462         }
463       }
464       if (equals(TE_HEADER, name)) {
465         boolean wasPresent = te != null;
466         te = null;
467         return wasPresent;
468       }
469       return super.remove(name);
470     }
471 
472     /**
473      * This method is called in tests only.
474      */
475     @Override
size()476     public int size() {
477       int size = 0;
478       if (path != null) {
479         size++;
480       }
481       if (authority != null) {
482         size++;
483       }
484       if (method != null) {
485         size++;
486       }
487       if (scheme != null) {
488         size++;
489       }
490       if (te != null) {
491         size++;
492       }
493       size += super.size();
494       return size;
495     }
496 
497     @Override
toString()498     public String toString() {
499       StringBuilder builder = new StringBuilder(getClass().getSimpleName()).append('[');
500       boolean prependSeparator = false;
501 
502       if (path != null) {
503         appendNameAndValue(builder, PATH_HEADER, path, prependSeparator);
504         prependSeparator = true;
505       }
506       if (authority != null) {
507         appendNameAndValue(builder, AUTHORITY_HEADER, authority, prependSeparator);
508         prependSeparator = true;
509       }
510       if (method != null) {
511         appendNameAndValue(builder, METHOD_HEADER, method, prependSeparator);
512         prependSeparator = true;
513       }
514       if (scheme != null) {
515         appendNameAndValue(builder, SCHEME_HEADER, scheme, prependSeparator);
516         prependSeparator = true;
517       }
518       if (te != null) {
519         appendNameAndValue(builder, TE_HEADER, te, prependSeparator);
520       }
521 
522       String namesAndValues = namesAndValuesToString();
523 
524       if (builder.length() > 0 && namesAndValues.length() > 0) {
525         builder.append(", ");
526       }
527 
528       builder.append(namesAndValues);
529       builder.append(']');
530 
531       return builder.toString();
532     }
533   }
534 
535   /**
536    * This class only implements the methods used by {@link NettyClientHandler} and tests. All
537    * other methods throw an {@link UnsupportedOperationException}.
538    *
539    * <p>Unlike in {@link GrpcHttp2ResponseHeaders} the {@code :status} pseudo-header is not treated
540    * special and is part of {@link #namesAndValues}.
541    */
542   static final class GrpcHttp2ResponseHeaders extends GrpcHttp2InboundHeaders {
543 
GrpcHttp2ResponseHeaders(int numHeadersGuess)544     GrpcHttp2ResponseHeaders(int numHeadersGuess) {
545       super(numHeadersGuess);
546     }
547 
548     @Override
add(CharSequence csName, CharSequence csValue)549     public Http2Headers add(CharSequence csName, CharSequence csValue) {
550       AsciiString name = validateName(requireAsciiString(csName));
551       AsciiString value = requireAsciiString(csValue);
552       return add(name, value);
553     }
554 
555     @Override
get(CharSequence csName)556     public CharSequence get(CharSequence csName) {
557       AsciiString name = requireAsciiString(csName);
558       return get(name);
559     }
560 
561     @Override
toString()562     public String toString() {
563       StringBuilder builder = new StringBuilder(getClass().getSimpleName()).append('[');
564       builder.append(namesAndValuesToString()).append(']');
565       return builder.toString();
566     }
567   }
568 }
569