netty: use custom http2 headers for decoding.

The DefaultHttp2Headers class is a general-purpose Http2Headers implementation
and provides much more functionality than we need in gRPC. In gRPC, when reading
headers off the wire, we only inspect a handful of them, before converting to
Metadata.

This commit introduces a Http2Headers implementation that aims for insertion
efficiency, a low memory footprint and fast conversion to Metadata.

  - Header names and values are stored in plain byte[].
  - Insertion is O(1), while lookup is now O(n).
  - Binary header values are base64 decoded as they are inserted.
  - The byte[][] returned by namesAndValues() can directly be used to construct
    a new Metadata object.
  - For HTTP/2 request headers, the pseudo headers are no longer carried over to
    Metadata.

A microbenchmark aiming to replicate the usage of Http2Headers in NettyClientHandler
and NettyServerHandler shows decent throughput gains when compared to DefaultHttp2Headers.

Benchmark                                             Mode  Cnt     Score    Error  Units
InboundHeadersBenchmark.defaultHeaders_clientHandler  avgt   10   283.830 ±  4.063  ns/op
InboundHeadersBenchmark.defaultHeaders_serverHandler  avgt   10  1179.975 ± 21.810  ns/op
InboundHeadersBenchmark.grpcHeaders_clientHandler     avgt   10   190.108 ±  3.510  ns/op
InboundHeadersBenchmark.grpcHeaders_serverHandler     avgt   10   561.426 ±  9.079  ns/op

Additionally, the memory footprint is reduced by more than 50%!

gRPC Request Headers: 864 bytes
Netty Request Headers: 1728 bytes
gRPC Response Headers: 216 bytes
Netty Response Headers: 528 bytes

Furthermore, this change does most of the gRPC groundwork necessary to be able
to cache higher ordered objects in HPACK's dynamic table, as discussed in [1].

[1] https://github.com/grpc/grpc-java/issues/2217
This commit is contained in:
Jakob Buchgraber 2016-09-09 23:15:18 +02:00 committed by GitHub
parent de9c320196
commit 8c18a0d355
13 changed files with 961 additions and 28 deletions

View File

@ -0,0 +1,202 @@
/*
* Copyright 2016, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package io.grpc.netty;
import static io.grpc.netty.Utils.CONTENT_TYPE_HEADER;
import static io.grpc.netty.Utils.TE_TRAILERS;
import static io.netty.util.AsciiString.of;
import io.grpc.netty.GrpcHttp2HeadersDecoder.GrpcHttp2RequestHeaders;
import io.grpc.netty.GrpcHttp2HeadersDecoder.GrpcHttp2ResponseHeaders;
import io.netty.handler.codec.http2.DefaultHttp2Headers;
import io.netty.handler.codec.http2.Http2Headers;
import io.netty.util.AsciiString;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.CompilerControl;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.infra.Blackhole;
import java.util.concurrent.TimeUnit;
/**
* Benchmarks for {@link GrpcHttp2RequestHeaders} and {@link GrpcHttp2ResponseHeaders}.
*/
@State(Scope.Thread)
public class InboundHeadersBenchmark {
private static AsciiString[] requestHeaders;
private static AsciiString[] responseHeaders;
static {
setupRequestHeaders();
setupResponseHeaders();
}
// Headers taken from the gRPC spec.
private static void setupRequestHeaders() {
requestHeaders = new AsciiString[18];
int i = 0;
requestHeaders[i++] = of(":method");
requestHeaders[i++] = of("POST");
requestHeaders[i++] = of(":scheme");
requestHeaders[i++] = of("http");
requestHeaders[i++] = of(":path");
requestHeaders[i++] = of("/google.pubsub.v2.PublisherService/CreateTopic");
requestHeaders[i++] = of(":authority");
requestHeaders[i++] = of("pubsub.googleapis.com");
requestHeaders[i++] = of("te");
requestHeaders[i++] = of("trailers");
requestHeaders[i++] = of("grpc-timeout");
requestHeaders[i++] = of("1S");
requestHeaders[i++] = of("content-type");
requestHeaders[i++] = of("application/grpc+proto");
requestHeaders[i++] = of("grpc-encoding");
requestHeaders[i++] = of("gzip");
requestHeaders[i++] = of("authorization");
requestHeaders[i] = of("Bearer y235.wef315yfh138vh31hv93hv8h3v");
}
private static void setupResponseHeaders() {
responseHeaders = new AsciiString[4];
int i = 0;
responseHeaders[i++] = of(":status");
responseHeaders[i++] = of("200");
responseHeaders[i++] = of("grpc-encoding");
responseHeaders[i] = of("gzip");
}
/**
* Checkstyle.
*/
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void grpcHeaders_serverHandler(Blackhole bh) {
serverHandler(bh, new GrpcHttp2RequestHeaders(4));
}
/**
* Checkstyle.
*/
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void defaultHeaders_serverHandler(Blackhole bh) {
serverHandler(bh, new DefaultHttp2Headers(true, 9));
}
/**
* Checkstyle.
*/
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void grpcHeaders_clientHandler(Blackhole bh) {
clientHandler(bh, new GrpcHttp2ResponseHeaders(2));
}
/**
* Checkstyle.
*/
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void defaultHeaders_clientHandler(Blackhole bh) {
clientHandler(bh, new DefaultHttp2Headers(true, 2));
}
@CompilerControl(CompilerControl.Mode.INLINE)
private static void serverHandler(Blackhole bh, Http2Headers headers) {
for (int i = 0; i < requestHeaders.length; i += 2) {
bh.consume(headers.add(requestHeaders[i], requestHeaders[i + 1]));
}
// Sequence of headers accessed in NettyServerHandler
bh.consume(headers.get(TE_TRAILERS));
bh.consume(headers.get(CONTENT_TYPE_HEADER));
bh.consume(headers.method());
bh.consume(headers.get(CONTENT_TYPE_HEADER));
bh.consume(headers.path());
bh.consume(Utils.convertHeaders(headers));
}
@CompilerControl(CompilerControl.Mode.INLINE)
private static void clientHandler(Blackhole bh, Http2Headers headers) {
// NettyClientHandler does not directly access headers, but convert to Metadata immediately.
bh.consume(headers.add(responseHeaders[0], responseHeaders[1]));
bh.consume(headers.add(responseHeaders[2], responseHeaders[3]));
bh.consume(Utils.convertHeaders(headers));
}
// /**
// * Prints the size of the header objects in bytes. Needs JOL (Java Object Layout) as a
// * dependency.
// */
// public static void main(String... args) {
// Http2Headers grpcRequestHeaders = new GrpcHttp2RequestHeaders(4);
// Http2Headers defaultRequestHeaders = new DefaultHttp2Headers(true, 9);
// for (int i = 0; i < requestHeaders.length; i += 2) {
// grpcRequestHeaders.add(requestHeaders[i], requestHeaders[i + 1]);
// defaultRequestHeaders.add(requestHeaders[i], requestHeaders[i + 1]);
// }
// long c = 10L;
// int m = ((int) c) / 20;
//
// long grpcRequestHeadersBytes = GraphLayout.parseInstance(grpcRequestHeaders).totalSize();
// long defaultRequestHeadersBytes =
// GraphLayout.parseInstance(defaultRequestHeaders).totalSize();
//
// System.out.printf("gRPC Request Headers: %d bytes%nNetty Request Headers: %d bytes%n",
// grpcRequestHeadersBytes, defaultRequestHeadersBytes);
//
// Http2Headers grpcResponseHeaders = new GrpcHttp2RequestHeaders(4);
// Http2Headers defaultResponseHeaders = new DefaultHttp2Headers(true, 9);
// for (int i = 0; i < responseHeaders.length; i += 2) {
// grpcResponseHeaders.add(responseHeaders[i], responseHeaders[i + 1]);
// defaultResponseHeaders.add(responseHeaders[i], responseHeaders[i + 1]);
// }
//
// long grpcResponseHeadersBytes = GraphLayout.parseInstance(grpcResponseHeaders).totalSize();
// long defaultResponseHeadersBytes =
// GraphLayout.parseInstance(defaultResponseHeaders).totalSize();
//
// System.out.printf("gRPC Response Headers: %d bytes%nNetty Response Headers: %d bytes%n",
// grpcResponseHeadersBytes, defaultResponseHeadersBytes);
// }
}

View File

@ -56,7 +56,7 @@ import java.util.concurrent.TimeUnit;
* Header encoding benchmark.
*/
@State(Scope.Benchmark)
public class HeadersBenchmark {
public class OutboundHeadersBenchmark {
@Param({"1", "5", "10", "20"})
public int headerCount;

View File

@ -147,6 +147,10 @@ public final class Metadata {
checkArgument(binaryValues.length % 2 == 0,
"Odd number of key-value pairs: %s", binaryValues.length);
for (int i = 0; i < binaryValues.length; i += 2) {
// The transport might provide an array with null values at the end.
if (binaryValues[i] == null) {
break;
}
String name = new String(binaryValues[i], US_ASCII);
storeAdd(name, new MetadataEntry(name.endsWith(BINARY_HEADER_SUFFIX), binaryValues[i + 1]));
}

View File

@ -0,0 +1,511 @@
/*
* Copyright 2016, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package io.grpc.netty;
import static com.google.common.base.Charsets.US_ASCII;
import static com.google.common.base.Preconditions.checkArgument;
import static io.grpc.netty.Utils.TE_HEADER;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.Http2Error.COMPRESSION_ERROR;
import static io.netty.handler.codec.http2.Http2Error.ENHANCE_YOUR_CALM;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import static io.netty.util.AsciiString.isUpperCase;
import com.google.common.io.BaseEncoding;
import io.grpc.Metadata;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http2.DefaultHttp2HeadersDecoder;
import io.netty.handler.codec.http2.Http2Exception;
import io.netty.handler.codec.http2.Http2HeaderTable;
import io.netty.handler.codec.http2.Http2Headers;
import io.netty.handler.codec.http2.Http2HeadersDecoder;
import io.netty.handler.codec.http2.internal.hpack.Decoder;
import io.netty.util.AsciiString;
import io.netty.util.internal.PlatformDependent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A {@link Http2HeadersDecoder} that allows to use custom {@link Http2Headers} implementations.
*
* <p>Some of the code is copied from Netty's {@link DefaultHttp2HeadersDecoder}.
*/
abstract class GrpcHttp2HeadersDecoder implements Http2HeadersDecoder,
Http2HeadersDecoder.Configuration {
private static final float HEADERS_COUNT_WEIGHT_NEW = 1 / 5f;
private static final float HEADERS_COUNT_WEIGHT_HISTORICAL = 1 - HEADERS_COUNT_WEIGHT_NEW;
private final int maxHeaderSize;
private final Decoder decoder;
private final Http2HeaderTable headerTable;
private float numHeadersGuess = 8;
GrpcHttp2HeadersDecoder(int maxHeaderSize) {
this.maxHeaderSize = maxHeaderSize;
decoder = new Decoder(maxHeaderSize, DEFAULT_HEADER_TABLE_SIZE, 32);
headerTable = new GrpcHttp2HeaderTable();
}
@Override
public Http2HeaderTable headerTable() {
return headerTable;
}
@Override
public int maxHeaderSize() {
return maxHeaderSize;
}
@Override
public Http2Headers decodeHeaders(ByteBuf headerBlock) throws Http2Exception {
try {
GrpcHttp2InboundHeaders headers = newHeaders(1 + (int) numHeadersGuess);
decoder.decode(headerBlock, headers);
if (decoder.endHeaderBlock()) {
maxHeaderSizeExceeded();
}
numHeadersGuess = HEADERS_COUNT_WEIGHT_NEW * headers.numHeaders()
+ HEADERS_COUNT_WEIGHT_HISTORICAL * numHeadersGuess;
return headers;
} catch (IOException e) {
throw connectionError(COMPRESSION_ERROR, e, e.getMessage());
}
}
abstract GrpcHttp2InboundHeaders newHeaders(int numHeadersGuess);
/**
* Respond to headers block resulting in the maximum header size being exceeded.
* @throws Http2Exception If we can not recover from the truncation.
*/
private void maxHeaderSizeExceeded() throws Http2Exception {
throw connectionError(ENHANCE_YOUR_CALM, "Header size exceeded max allowed bytes (%d)",
maxHeaderSize);
}
@Override
public Configuration configuration() {
return this;
}
private final class GrpcHttp2HeaderTable implements Http2HeaderTable {
private int maxHeaderListSize = Integer.MAX_VALUE;
@Override
public void maxHeaderTableSize(int max) throws Http2Exception {
if (max < 0) {
throw connectionError(PROTOCOL_ERROR, "Header Table Size must be non-negative but was %d",
max);
}
try {
decoder.setMaxHeaderTableSize(max);
} catch (Throwable t) {
throw connectionError(PROTOCOL_ERROR, t.getMessage(), t);
}
}
@Override
public int maxHeaderTableSize() {
return decoder.getMaxHeaderTableSize();
}
@Override
public void maxHeaderListSize(int max) throws Http2Exception {
if (max < 0) {
// Over 2^31 - 1 (minus in integer) size is set to the maximun value
maxHeaderListSize = Integer.MAX_VALUE;
} else {
maxHeaderListSize = max;
}
}
@Override
public int maxHeaderListSize() {
return maxHeaderListSize;
}
}
static final class GrpcHttp2ServerHeadersDecoder extends GrpcHttp2HeadersDecoder {
GrpcHttp2ServerHeadersDecoder(int maxHeaderSize) {
super(maxHeaderSize);
}
@Override GrpcHttp2InboundHeaders newHeaders(int numHeadersGuess) {
return new GrpcHttp2RequestHeaders(numHeadersGuess);
}
}
static final class GrpcHttp2ClientHeadersDecoder extends GrpcHttp2HeadersDecoder {
GrpcHttp2ClientHeadersDecoder(int maxHeaderSize) {
super(maxHeaderSize);
}
@Override GrpcHttp2InboundHeaders newHeaders(int numHeadersGuess) {
return new GrpcHttp2ResponseHeaders(numHeadersGuess);
}
}
/**
* A {@link Http2Headers} implementation optimized for inbound/received headers.
*
* <p>Header names and values are stored in simple arrays, which makes insert run in O(1)
* and retrievial a O(n). Header name equality is not determined by the equals implementation of
* {@link CharSequence} type, but by comparing two names byte to byte.
*
* <p>All {@link CharSequence} input parameters and return values are required to be of type
* {@link AsciiString}.
*/
abstract static class GrpcHttp2InboundHeaders extends AbstractHttp2Headers {
private static final AsciiString binaryHeaderSuffix =
new AsciiString(Metadata.BINARY_HEADER_SUFFIX.getBytes(US_ASCII));
private byte[][] namesAndValues;
private AsciiString[] values;
private int namesAndValuesIdx;
GrpcHttp2InboundHeaders(int numHeadersGuess) {
checkArgument(numHeadersGuess > 0, "numHeadersGuess needs to be gt zero.");
namesAndValues = new byte[numHeadersGuess * 2][];
values = new AsciiString[numHeadersGuess];
}
protected Http2Headers add(AsciiString name, AsciiString value) {
if (namesAndValuesIdx == namesAndValues.length) {
expandHeadersAndValues();
}
byte[] nameBytes = bytes(name);
byte[] valueBytes = toBinaryValue(name, value);
values[namesAndValuesIdx / 2] = value;
namesAndValues[namesAndValuesIdx] = nameBytes;
namesAndValuesIdx++;
namesAndValues[namesAndValuesIdx] = valueBytes;
namesAndValuesIdx++;
return this;
}
protected CharSequence get(AsciiString name) {
for (int i = 0; i < namesAndValuesIdx; i += 2) {
if (equals(name, namesAndValues[i])) {
return values[i / 2];
}
}
return null;
}
@Override
public List<CharSequence> getAll(CharSequence csName) {
AsciiString name = requireAsciiString(csName);
List<CharSequence> returnValues = new ArrayList<CharSequence>(4);
for (int i = 0; i < namesAndValuesIdx; i += 2) {
if (equals(name, namesAndValues[i])) {
returnValues.add(values[i / 2]);
}
}
return returnValues;
}
/**
* Returns the header names and values as bytes. An even numbered index contains the
* {@code byte[]} representation of a header name (in insertion order), and the subsequent
* odd index number contains the corresponding header value.
*
* <p>The values of binary headers (with a -bin suffix), are already base64 decoded.
*
* <p>The array may contain several {@code null} values at the end. A {@code null} value an
* index means that all higher numbered indices also contain {@code null} values.
*/
byte[][] namesAndValues() {
return namesAndValues;
}
/**
* Returns the number of none-null headers in {@link #namesAndValues()}.
*/
protected int numHeaders() {
return namesAndValuesIdx / 2;
}
protected static boolean equals(AsciiString str0, byte[] str1) {
return equals(str0.array(), str0.arrayOffset(), str0.length(), str1, 0, str1.length);
}
protected static boolean equals(AsciiString str0, AsciiString str1) {
return equals(str0.array(), str0.arrayOffset(), str0.length(), str1.array(),
str1.arrayOffset(), str1.length());
}
protected static boolean equals(byte[] bytes0, int offset0, int length0, byte[] bytes1,
int offset1, int length1) {
if (length0 != length1) {
return false;
}
return PlatformDependent.equals(bytes0, offset0, bytes1, offset1, length0);
}
private static byte[] toBinaryValue(AsciiString name, AsciiString value) {
return name.endsWith(binaryHeaderSuffix)
? BaseEncoding.base64().decode(value)
: bytes(value);
}
protected static byte[] bytes(AsciiString str) {
return str.isEntireArrayUsed() ? str.array() : str.toByteArray();
}
protected static AsciiString requireAsciiString(CharSequence cs) {
if (!(cs instanceof AsciiString)) {
throw new IllegalArgumentException("AsciiString expected. Was: " + cs.getClass().getName());
}
return (AsciiString) cs;
}
protected static boolean isPseudoHeader(AsciiString str) {
return !str.isEmpty() && str.charAt(0) == ':';
}
protected AsciiString validateName(AsciiString str) {
int offset = str.arrayOffset();
int length = str.length();
final byte[] data = str.array();
for (int i = offset; i < offset + length; i++) {
if (isUpperCase(data[i])) {
PlatformDependent.throwException(connectionError(PROTOCOL_ERROR,
"invalid header name '%s'", str));
}
}
return str;
}
private void expandHeadersAndValues() {
int newValuesLen = Math.max(2, values.length + values.length / 2);
int newNamesAndValuesLen = newValuesLen * 2;
byte[][] newNamesAndValues = new byte[newNamesAndValuesLen][];
AsciiString[] newValues = new AsciiString[newValuesLen];
System.arraycopy(namesAndValues, 0, newNamesAndValues, 0, namesAndValues.length);
System.arraycopy(values, 0, newValues, 0, values.length);
namesAndValues = newNamesAndValues;
values = newValues;
}
@Override
public int size() {
return numHeaders();
}
}
/**
* A {@link GrpcHttp2InboundHeaders} implementation, optimized for HTTP/2 request headers. That
* is, HTTP/2 request pseudo headers are stored in dedicated fields and are NOT part of the
* array returned by {@link #namesAndValues()}.
*
* <p>This class only implements the methods used by {@link NettyServerHandler} and tests. All
* other methods throw an {@link UnsupportedOperationException}.
*/
static final class GrpcHttp2RequestHeaders extends GrpcHttp2InboundHeaders {
private static final AsciiString PATH_HEADER = AsciiString.of(":path");
private static final AsciiString AUTHORITY_HEADER = AsciiString.of(":authority");
private static final AsciiString METHOD_HEADER = AsciiString.of(":method");
private static final AsciiString SCHEME_HEADER = AsciiString.of(":scheme");
private AsciiString path;
private AsciiString authority;
private AsciiString method;
private AsciiString scheme;
private AsciiString te;
GrpcHttp2RequestHeaders(int numHeadersGuess) {
super(numHeadersGuess);
}
@Override
public Http2Headers add(CharSequence csName, CharSequence csValue) {
AsciiString name = validateName(requireAsciiString(csName));
AsciiString value = requireAsciiString(csValue);
if (isPseudoHeader(name)) {
addPseudoHeader(name, value);
return this;
}
if (equals(TE_HEADER, name)) {
te = value;
return this;
}
return add(name, value);
}
@Override
public CharSequence get(CharSequence csName) {
AsciiString name = requireAsciiString(csName);
checkArgument(!isPseudoHeader(name), "Use direct accessor methods for pseudo headers.");
if (equals(TE_HEADER, name)) {
return te;
}
return get(name);
}
private void addPseudoHeader(CharSequence csName, CharSequence csValue) {
AsciiString name = requireAsciiString(csName);
AsciiString value = requireAsciiString(csValue);
if (equals(PATH_HEADER, name)) {
path = value;
} else if (equals(AUTHORITY_HEADER, name)) {
authority = value;
} else if (equals(METHOD_HEADER, name)) {
method = value;
} else if (equals(SCHEME_HEADER, name)) {
scheme = value;
} else {
PlatformDependent.throwException(
connectionError(PROTOCOL_ERROR, "Illegal pseudo-header '%s' in request.", name));
}
}
@Override
public CharSequence path() {
return path;
}
@Override
public CharSequence authority() {
return authority;
}
@Override
public CharSequence method() {
return method;
}
@Override
public CharSequence scheme() {
return scheme;
}
/**
* This method is called in tests only.
*/
@Override
public List<CharSequence> getAll(CharSequence csName) {
AsciiString name = requireAsciiString(csName);
if (isPseudoHeader(name)) {
// This code should never be reached.
throw new IllegalArgumentException("Use direct accessor methods for pseudo headers.");
}
if (equals(TE_HEADER, name)) {
return Collections.singletonList((CharSequence) te);
}
return super.getAll(csName);
}
/**
* This method is called in tests only.
*/
@Override
public int size() {
int size = 0;
if (path != null) {
size++;
}
if (authority != null) {
size++;
}
if (method != null) {
size++;
}
if (scheme != null) {
size++;
}
if (te != null) {
size++;
}
size += super.size();
return size;
}
}
/**
* This class only implements the methods used by {@link NettyClientHandler} and tests. All
* other methods throw an {@link UnsupportedOperationException}.
*
* <p>Unlike in {@link GrpcHttp2ResponseHeaders} the {@code :status} pseudo-header is not treated
* special and is part of {@link #namesAndValues}.
*/
static final class GrpcHttp2ResponseHeaders extends GrpcHttp2InboundHeaders {
GrpcHttp2ResponseHeaders(int numHeadersGuess) {
super(numHeadersGuess);
}
@Override
public Http2Headers add(CharSequence csName, CharSequence csValue) {
AsciiString name = validateName(requireAsciiString(csName));
AsciiString value = requireAsciiString(csValue);
return add(name, value);
}
@Override
public CharSequence get(CharSequence csName) {
AsciiString name = requireAsciiString(csName);
return get(name);
}
}
}

View File

@ -41,14 +41,15 @@ import java.util.NoSuchElementException;
/**
* A custom implementation of Http2Headers that only includes methods used by gRPC.
*/
final class GrpcHttp2Headers extends AbstractHttp2Headers {
final class GrpcHttp2OutboundHeaders extends AbstractHttp2Headers {
private final AsciiString[] normalHeaders;
private final AsciiString[] preHeaders;
private static final AsciiString[] EMPTY = new AsciiString[]{};
static GrpcHttp2Headers clientRequestHeaders(byte[][] serializedMetadata, AsciiString authority,
AsciiString path, AsciiString method, AsciiString scheme, AsciiString userAgent) {
static GrpcHttp2OutboundHeaders clientRequestHeaders(byte[][] serializedMetadata,
AsciiString authority, AsciiString path, AsciiString method, AsciiString scheme,
AsciiString userAgent) {
AsciiString[] preHeaders = new AsciiString[] {
Http2Headers.PseudoHeaderName.AUTHORITY.value(), authority,
Http2Headers.PseudoHeaderName.PATH.value(), path,
@ -58,22 +59,22 @@ final class GrpcHttp2Headers extends AbstractHttp2Headers {
Utils.TE_HEADER, Utils.TE_TRAILERS,
Utils.USER_AGENT, userAgent,
};
return new GrpcHttp2Headers(preHeaders, serializedMetadata);
return new GrpcHttp2OutboundHeaders(preHeaders, serializedMetadata);
}
static GrpcHttp2Headers serverResponseHeaders(byte[][] serializedMetadata) {
static GrpcHttp2OutboundHeaders serverResponseHeaders(byte[][] serializedMetadata) {
AsciiString[] preHeaders = new AsciiString[] {
Http2Headers.PseudoHeaderName.STATUS.value(), Utils.STATUS_OK,
Utils.CONTENT_TYPE_HEADER, Utils.CONTENT_TYPE_GRPC,
};
return new GrpcHttp2Headers(preHeaders, serializedMetadata);
return new GrpcHttp2OutboundHeaders(preHeaders, serializedMetadata);
}
static GrpcHttp2Headers serverResponseTrailers(byte[][] serializedMetadata) {
return new GrpcHttp2Headers(EMPTY, serializedMetadata);
static GrpcHttp2OutboundHeaders serverResponseTrailers(byte[][] serializedMetadata) {
return new GrpcHttp2OutboundHeaders(EMPTY, serializedMetadata);
}
private GrpcHttp2Headers(AsciiString[] preHeaders, byte[][] serializedMetadata) {
private GrpcHttp2OutboundHeaders(AsciiString[] preHeaders, byte[][] serializedMetadata) {
normalHeaders = new AsciiString[serializedMetadata.length];
for (int i = 0; i < normalHeaders.length; i++) {
normalHeaders[i] = new AsciiString(serializedMetadata[i], false);

View File

@ -45,6 +45,7 @@ import io.grpc.StatusException;
import io.grpc.internal.ClientTransport.PingCallback;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.Http2Ping;
import io.grpc.netty.GrpcHttp2HeadersDecoder.GrpcHttp2ClientHeadersDecoder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
@ -58,9 +59,7 @@ import io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder;
import io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder;
import io.netty.handler.codec.http2.DefaultHttp2FrameReader;
import io.netty.handler.codec.http2.DefaultHttp2FrameWriter;
import io.netty.handler.codec.http2.DefaultHttp2HeadersDecoder;
import io.netty.handler.codec.http2.DefaultHttp2LocalFlowController;
import io.netty.handler.codec.http2.Http2CodecUtil;
import io.netty.handler.codec.http2.Http2Connection;
import io.netty.handler.codec.http2.Http2ConnectionAdapter;
import io.netty.handler.codec.http2.Http2ConnectionDecoder;
@ -116,8 +115,7 @@ class NettyClientHandler extends AbstractNettyHandler {
int flowControlWindow, int maxHeaderListSize,
Ticker ticker) {
Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive");
Http2HeadersDecoder headersDecoder = new DefaultHttp2HeadersDecoder(
maxHeaderListSize, Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE, true, 32);
Http2HeadersDecoder headersDecoder = new GrpcHttp2ClientHeadersDecoder(maxHeaderListSize);
Http2FrameReader frameReader = new DefaultHttp2FrameReader(headersDecoder);
Http2FrameWriter frameWriter = new DefaultHttp2FrameWriter();
Http2Connection connection = new DefaultHttp2Connection(false);

View File

@ -50,6 +50,7 @@ import io.grpc.Status;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.ServerStreamListener;
import io.grpc.internal.ServerTransportListener;
import io.grpc.netty.GrpcHttp2HeadersDecoder.GrpcHttp2ServerHeadersDecoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
@ -61,9 +62,7 @@ import io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder;
import io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder;
import io.netty.handler.codec.http2.DefaultHttp2FrameReader;
import io.netty.handler.codec.http2.DefaultHttp2FrameWriter;
import io.netty.handler.codec.http2.DefaultHttp2HeadersDecoder;
import io.netty.handler.codec.http2.DefaultHttp2LocalFlowController;
import io.netty.handler.codec.http2.Http2CodecUtil;
import io.netty.handler.codec.http2.Http2Connection;
import io.netty.handler.codec.http2.Http2ConnectionDecoder;
import io.netty.handler.codec.http2.Http2ConnectionEncoder;
@ -112,8 +111,7 @@ class NettyServerHandler extends AbstractNettyHandler {
int maxMessageSize) {
Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive");
Http2FrameLogger frameLogger = new Http2FrameLogger(LogLevel.DEBUG, NettyServerHandler.class);
Http2HeadersDecoder headersDecoder = new DefaultHttp2HeadersDecoder(
maxHeaderListSize, Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE, true, 32);
Http2HeadersDecoder headersDecoder = new GrpcHttp2ServerHeadersDecoder(maxHeaderListSize);
Http2FrameReader frameReader = new Http2InboundFrameLogger(
new DefaultHttp2FrameReader(headersDecoder), frameLogger);
Http2FrameWriter frameWriter =

View File

@ -33,6 +33,8 @@ package io.grpc.netty;
import static io.grpc.internal.GrpcUtil.CONTENT_TYPE_KEY;
import static io.grpc.internal.GrpcUtil.USER_AGENT_KEY;
import static io.grpc.internal.TransportFrameUtil.toHttp2Headers;
import static io.grpc.internal.TransportFrameUtil.toRawSerializedHeaders;
import static io.netty.util.CharsetUtil.UTF_8;
import com.google.common.annotations.VisibleForTesting;
@ -42,7 +44,7 @@ import io.grpc.Metadata;
import io.grpc.Status;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.SharedResourceHolder.Resource;
import io.grpc.internal.TransportFrameUtil;
import io.grpc.netty.GrpcHttp2HeadersDecoder.GrpcHttp2InboundHeaders;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.codec.http2.Http2Exception;
@ -88,6 +90,9 @@ class Utils {
static boolean validateHeaders = false;
public static Metadata convertHeaders(Http2Headers http2Headers) {
if (http2Headers instanceof GrpcHttp2InboundHeaders) {
return new Metadata(((GrpcHttp2InboundHeaders) http2Headers).namesAndValues());
}
return new Metadata(convertHeadersToArray(http2Headers));
}
@ -100,7 +105,7 @@ class Utils {
headerValues[i++] = bytes(entry.getKey());
headerValues[i++] = bytes(entry.getValue());
}
return TransportFrameUtil.toRawSerializedHeaders(headerValues);
return toRawSerializedHeaders(headerValues);
}
private static byte[] bytes(CharSequence seq) {
@ -121,8 +126,8 @@ class Utils {
Preconditions.checkNotNull(defaultPath, "defaultPath");
Preconditions.checkNotNull(authority, "authority");
return GrpcHttp2Headers.clientRequestHeaders(
TransportFrameUtil.toHttp2Headers(headers),
return GrpcHttp2OutboundHeaders.clientRequestHeaders(
toHttp2Headers(headers),
authority,
defaultPath,
HTTP_METHOD,
@ -131,10 +136,13 @@ class Utils {
}
public static Http2Headers convertServerHeaders(Metadata headers) {
return GrpcHttp2Headers.serverResponseHeaders(TransportFrameUtil.toHttp2Headers(headers));
return GrpcHttp2OutboundHeaders.serverResponseHeaders(toHttp2Headers(headers));
}
public static Metadata convertTrailers(Http2Headers http2Headers) {
if (http2Headers instanceof GrpcHttp2InboundHeaders) {
return new Metadata(((GrpcHttp2InboundHeaders) http2Headers).namesAndValues());
}
return new Metadata(convertHeadersToArray(http2Headers));
}
@ -142,7 +150,7 @@ class Utils {
if (!headersSent) {
return convertServerHeaders(trailers);
}
return GrpcHttp2Headers.serverResponseTrailers(TransportFrameUtil.toHttp2Headers(trailers));
return GrpcHttp2OutboundHeaders.serverResponseTrailers(toHttp2Headers(trailers));
}
public static Status statusFromThrowable(Throwable t) {

View File

@ -0,0 +1,107 @@
/*
* Copyright 2016, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package io.grpc.netty;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE;
import static io.netty.util.AsciiString.of;
import static org.junit.Assert.assertEquals;
import io.grpc.netty.GrpcHttp2HeadersDecoder.GrpcHttp2ClientHeadersDecoder;
import io.grpc.netty.GrpcHttp2HeadersDecoder.GrpcHttp2ServerHeadersDecoder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http2.DefaultHttp2Headers;
import io.netty.handler.codec.http2.DefaultHttp2HeadersEncoder;
import io.netty.handler.codec.http2.Http2Exception;
import io.netty.handler.codec.http2.Http2Headers;
import io.netty.handler.codec.http2.Http2HeadersDecoder;
import io.netty.handler.codec.http2.Http2HeadersEncoder;
import io.netty.handler.codec.http2.Http2HeadersEncoder.SensitivityDetector;
import io.netty.util.ReferenceCountUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link GrpcHttp2HeadersDecoder}.
*/
@RunWith(JUnit4.class)
public class GrpcHttp2HeadersDecoderTest {
private static final SensitivityDetector NEVER_SENSITIVE = new SensitivityDetector() {
@Override
public boolean isSensitive(CharSequence name, CharSequence value) {
return false;
}
};
@Test
public void decode_requestHeaders() throws Http2Exception {
Http2HeadersDecoder decoder = new GrpcHttp2ServerHeadersDecoder(8192);
Http2HeadersEncoder encoder =
new DefaultHttp2HeadersEncoder(DEFAULT_HEADER_TABLE_SIZE, NEVER_SENSITIVE);
Http2Headers headers = new DefaultHttp2Headers(false);
headers.add(of(":scheme"), of("https")).add(of(":method"), of("GET"))
.add(of(":path"), of("index.html")).add(of(":authority"), of("foo.grpc.io"))
.add(of("custom"), of("header"));
ByteBuf encodedHeaders = ReferenceCountUtil.releaseLater(Unpooled.buffer());
encoder.encodeHeaders(headers, encodedHeaders);
Http2Headers decodedHeaders = decoder.decodeHeaders(encodedHeaders);
assertEquals(headers.get(of(":scheme")), decodedHeaders.scheme());
assertEquals(headers.get(of(":method")), decodedHeaders.method());
assertEquals(headers.get(of(":path")), decodedHeaders.path());
assertEquals(headers.get(of(":authority")), decodedHeaders.authority());
assertEquals(headers.get(of("custom")), decodedHeaders.get(of("custom")));
assertEquals(headers.size(), decodedHeaders.size());
}
@Test
public void decode_responseHeaders() throws Http2Exception {
Http2HeadersDecoder decoder = new GrpcHttp2ClientHeadersDecoder(8192);
Http2HeadersEncoder encoder =
new DefaultHttp2HeadersEncoder(DEFAULT_HEADER_TABLE_SIZE, NEVER_SENSITIVE);
Http2Headers headers = new DefaultHttp2Headers(false);
headers.add(of(":status"), of("200")).add(of("custom"), of("header"));
ByteBuf encodedHeaders = ReferenceCountUtil.releaseLater(Unpooled.buffer());
encoder.encodeHeaders(headers, encodedHeaders);
Http2Headers decodedHeaders = decoder.decodeHeaders(encodedHeaders);
assertEquals(headers.get(of(":status")), decodedHeaders.get(of(":status")));
assertEquals(headers.get(of("custom")), decodedHeaders.get(of("custom")));
assertEquals(headers.size(), decodedHeaders.size());
}
}

View File

@ -0,0 +1,99 @@
/*
* Copyright 2016, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package io.grpc.netty;
import static io.netty.util.AsciiString.of;
import static junit.framework.TestCase.assertNotSame;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import com.google.common.io.BaseEncoding;
import io.grpc.netty.GrpcHttp2HeadersDecoder.GrpcHttp2InboundHeaders;
import io.grpc.netty.GrpcHttp2HeadersDecoder.GrpcHttp2RequestHeaders;
import io.grpc.netty.GrpcHttp2HeadersDecoder.GrpcHttp2ResponseHeaders;
import io.netty.handler.codec.http2.Http2Headers;
import io.netty.util.AsciiString;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.Random;
/**
* Tests for {@link GrpcHttp2RequestHeaders} and {@link GrpcHttp2ResponseHeaders}.
*/
@RunWith(JUnit4.class)
public class GrpcHttp2InboundHeadersTest {
@Test
public void basicCorrectness() {
Http2Headers headers = new GrpcHttp2RequestHeaders(1);
headers.add(of(":method"), of("POST"));
headers.add(of("content-type"), of("application/grpc+proto"));
headers.add(of(":path"), of("/google.pubsub.v2.PublisherService/CreateTopic"));
headers.add(of(":scheme"), of("https"));
headers.add(of("te"), of("trailers"));
headers.add(of(":authority"), of("pubsub.googleapis.com"));
headers.add(of("foo"), of("bar"));
assertEquals(7, headers.size());
// Number of headers without the pseudo headers and 'te' header.
assertEquals(2, ((GrpcHttp2InboundHeaders)headers).numHeaders());
assertEquals(of("application/grpc+proto"), headers.get(of("content-type")));
assertEquals(of("/google.pubsub.v2.PublisherService/CreateTopic"), headers.path());
assertEquals(of("https"), headers.scheme());
assertEquals(of("POST"), headers.method());
assertEquals(of("pubsub.googleapis.com"), headers.authority());
assertEquals(of("trailers"), headers.get(of("te")));
assertEquals(of("bar"), headers.get(of("foo")));
}
@Test
public void binaryHeadersShouldBeBase64Decoded() {
Http2Headers headers = new GrpcHttp2RequestHeaders(1);
byte[] data = new byte[100];
new Random().nextBytes(data);
headers.add(of("foo-bin"), of(BaseEncoding.base64().encode(data)));
assertEquals(1, headers.size());
byte[][] namesAndValues = ((GrpcHttp2InboundHeaders)headers).namesAndValues();
assertEquals(of("foo-bin"), new AsciiString(namesAndValues[0]));
assertNotSame(data, namesAndValues[1]);
assertArrayEquals(data, namesAndValues[1]);
}
}

View File

@ -62,6 +62,8 @@ import io.grpc.Status;
import io.grpc.StatusException;
import io.grpc.internal.ClientTransport;
import io.grpc.internal.ClientTransport.PingCallback;
import io.grpc.internal.GrpcUtil;
import io.grpc.netty.GrpcHttp2HeadersDecoder.GrpcHttp2ClientHeadersDecoder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
@ -115,7 +117,7 @@ public class NettyClientHandlerTest extends NettyHandlerTestBase<NettyClientHand
MockitoAnnotations.initMocks(this);
lifecycleManager = new ClientTransportLifecycleManager(listener);
initChannel();
initChannel(new GrpcHttp2ClientHeadersDecoder(GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE));
grpcHeaders = new DefaultHttp2Headers()
.scheme(HTTPS)

View File

@ -91,10 +91,10 @@ public abstract class NettyHandlerTestBase<T extends Http2ConnectionHandler> {
/**
* Must be called by subclasses to initialize the handler and channel.
*/
protected final void initChannel() throws Exception {
protected final void initChannel(GrpcHttp2HeadersDecoder headersDecoder) throws Exception {
content = Unpooled.copiedBuffer("hello world", UTF_8);
frameWriter = spy(new DefaultHttp2FrameWriter());
frameReader = new DefaultHttp2FrameReader();
frameReader = new DefaultHttp2FrameReader(headersDecoder);
handler = newHandler();

View File

@ -58,11 +58,13 @@ import io.grpc.Attributes;
import io.grpc.Metadata;
import io.grpc.Status;
import io.grpc.Status.Code;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.MessageFramer;
import io.grpc.internal.ServerStream;
import io.grpc.internal.ServerStreamListener;
import io.grpc.internal.ServerTransportListener;
import io.grpc.internal.WritableBuffer;
import io.grpc.netty.GrpcHttp2HeadersDecoder.GrpcHttp2ServerHeadersDecoder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
@ -120,7 +122,8 @@ public class NettyServerHandlerTest extends NettyHandlerTestBase<NettyServerHand
any(Metadata.class)))
.thenReturn(streamListener);
when(transportListener.transportReady(any(Attributes.class))).thenReturn(Attributes.EMPTY);
initChannel();
initChannel(new GrpcHttp2ServerHeadersDecoder(GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE));
// Simulate receipt of the connection preface
channelRead(Http2CodecUtil.connectionPrefaceBuf());