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