diff --git a/netty/src/main/java/io/grpc/netty/GrpcHttp2HeadersDecoder.java b/netty/src/main/java/io/grpc/netty/GrpcHttp2HeadersDecoder.java index aed397f164..77377c683d 100644 --- a/netty/src/main/java/io/grpc/netty/GrpcHttp2HeadersDecoder.java +++ b/netty/src/main/java/io/grpc/netty/GrpcHttp2HeadersDecoder.java @@ -349,6 +349,27 @@ abstract class GrpcHttp2HeadersDecoder implements Http2HeadersDecoder, public int size() { return numHeaders(); } + + protected static void appendNameAndValue(StringBuilder builder, CharSequence name, + CharSequence value, boolean prependSeparator) { + if (prependSeparator) { + builder.append(", "); + } + builder.append(name).append(": ").append(value); + } + + protected final String namesAndValuesToString() { + StringBuilder builder = new StringBuilder(); + boolean prependSeparator = false; + for (int i = 0; i < namesAndValuesIdx; i += 2) { + String name = new String(namesAndValues[i], US_ASCII); + // If binary headers, the value is base64 encoded. + AsciiString value = values[i / 2]; + appendNameAndValue(builder, name, value, prependSeparator); + prependSeparator = true; + } + return builder.toString(); + } } /** @@ -439,7 +460,6 @@ abstract class GrpcHttp2HeadersDecoder implements Http2HeadersDecoder, return scheme; } - /** * This method is called in tests only. */ @@ -480,6 +500,43 @@ abstract class GrpcHttp2HeadersDecoder implements Http2HeadersDecoder, size += super.size(); return size; } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(getClass().getSimpleName()).append('['); + boolean prependSeparator = false; + + if (path != null) { + appendNameAndValue(builder, PATH_HEADER, path, prependSeparator); + prependSeparator = true; + } + if (authority != null) { + appendNameAndValue(builder, AUTHORITY_HEADER, authority, prependSeparator); + prependSeparator = true; + } + if (method != null) { + appendNameAndValue(builder, METHOD_HEADER, method, prependSeparator); + prependSeparator = true; + } + if (scheme != null) { + appendNameAndValue(builder, SCHEME_HEADER, scheme, prependSeparator); + prependSeparator = true; + } + if (te != null) { + appendNameAndValue(builder, TE_HEADER, te, prependSeparator); + } + + String namesAndValues = namesAndValuesToString(); + + if (builder.length() > 0 && namesAndValues.length() > 0) { + builder.append(", "); + } + + builder.append(namesAndValues); + builder.append(']'); + + return builder.toString(); + } } /** @@ -507,5 +564,12 @@ abstract class GrpcHttp2HeadersDecoder implements Http2HeadersDecoder, AsciiString name = requireAsciiString(csName); return get(name); } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(getClass().getSimpleName()).append('['); + builder.append(namesAndValuesToString()).append(']'); + return builder.toString(); + } } } diff --git a/netty/src/test/java/io/grpc/netty/GrpcHttp2HeadersDecoderTest.java b/netty/src/test/java/io/grpc/netty/GrpcHttp2HeadersDecoderTest.java index 6652579514..7955021c14 100644 --- a/netty/src/test/java/io/grpc/netty/GrpcHttp2HeadersDecoderTest.java +++ b/netty/src/test/java/io/grpc/netty/GrpcHttp2HeadersDecoderTest.java @@ -33,7 +33,9 @@ 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.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import io.grpc.netty.GrpcHttp2HeadersDecoder.GrpcHttp2ClientHeadersDecoder; import io.grpc.netty.GrpcHttp2HeadersDecoder.GrpcHttp2ServerHeadersDecoder; @@ -86,6 +88,13 @@ public class GrpcHttp2HeadersDecoderTest { assertEquals(headers.get(of(":authority")), decodedHeaders.authority()); assertEquals(headers.get(of("custom")), decodedHeaders.get(of("custom"))); assertEquals(headers.size(), decodedHeaders.size()); + + String toString = decodedHeaders.toString(); + assertContainsKeyAndValue(toString, ":scheme", decodedHeaders.scheme()); + assertContainsKeyAndValue(toString, ":method", decodedHeaders.method()); + assertContainsKeyAndValue(toString, ":path", decodedHeaders.path()); + assertContainsKeyAndValue(toString, ":authority", decodedHeaders.authority()); + assertContainsKeyAndValue(toString, "custom", decodedHeaders.get(of("custom"))); } @Test @@ -103,5 +112,28 @@ public class GrpcHttp2HeadersDecoderTest { assertEquals(headers.get(of(":status")), decodedHeaders.get(of(":status"))); assertEquals(headers.get(of("custom")), decodedHeaders.get(of("custom"))); assertEquals(headers.size(), decodedHeaders.size()); + + String toString = decodedHeaders.toString(); + assertContainsKeyAndValue(toString, ":status", decodedHeaders.get(of(":status"))); + assertContainsKeyAndValue(toString, "custom", decodedHeaders.get(of("custom"))); + } + + @Test + public void decode_emptyHeaders() throws Http2Exception { + Http2HeadersDecoder decoder = new GrpcHttp2ClientHeadersDecoder(8192); + Http2HeadersEncoder encoder = + new DefaultHttp2HeadersEncoder(DEFAULT_HEADER_TABLE_SIZE, NEVER_SENSITIVE); + + ByteBuf encodedHeaders = ReferenceCountUtil.releaseLater(Unpooled.buffer()); + encoder.encodeHeaders(new DefaultHttp2Headers(false), encodedHeaders); + + Http2Headers decodedHeaders = decoder.decodeHeaders(encodedHeaders); + assertEquals(0, decodedHeaders.size()); + assertThat(decodedHeaders.toString(), containsString("[]")); + } + + private static void assertContainsKeyAndValue(String str, CharSequence key, CharSequence value) { + assertThat(str, containsString(key.toString())); + assertThat(str, containsString(value.toString())); } }