diff --git a/core/src/main/java/io/grpc/InternalMetadata.java b/core/src/main/java/io/grpc/InternalMetadata.java index 8a31874c58..4fb8a7f6ab 100644 --- a/core/src/main/java/io/grpc/InternalMetadata.java +++ b/core/src/main/java/io/grpc/InternalMetadata.java @@ -16,6 +16,7 @@ package io.grpc; +import io.grpc.Metadata.AsciiMarshaller; import io.grpc.Metadata.Key; import java.nio.charset.Charset; @@ -44,7 +45,14 @@ public final class InternalMetadata { @Internal public static Key keyOf(String name, TrustedAsciiMarshaller marshaller) { - return Metadata.Key.of(name, marshaller); + boolean isPseudo = name != null && !name.isEmpty() && name.charAt(0) == ':'; + return Metadata.Key.of(name, isPseudo, marshaller); + } + + @Internal + public static Key keyOf(String name, AsciiMarshaller marshaller) { + boolean isPseudo = name != null && !name.isEmpty() && name.charAt(0) == ':'; + return Metadata.Key.of(name, isPseudo, marshaller); } @Internal diff --git a/core/src/main/java/io/grpc/Metadata.java b/core/src/main/java/io/grpc/Metadata.java index 23b2cb2981..710ddb43f5 100644 --- a/core/src/main/java/io/grpc/Metadata.java +++ b/core/src/main/java/io/grpc/Metadata.java @@ -599,11 +599,15 @@ public final class Metadata { * not end with {@link #BINARY_HEADER_SUFFIX} */ public static Key of(String name, AsciiMarshaller marshaller) { - return new AsciiKey(name, marshaller); + return of(name, false, marshaller); } - static Key of(String name, TrustedAsciiMarshaller marshaller) { - return new TrustedAsciiKey(name, marshaller); + static Key of(String name, boolean pseudo, AsciiMarshaller marshaller) { + return new AsciiKey(name, pseudo, marshaller); + } + + static Key of(String name, boolean pseudo, TrustedAsciiMarshaller marshaller) { + return new TrustedAsciiKey(name, pseudo, marshaller); } private final String originalName; @@ -626,13 +630,12 @@ public final class Metadata { return valid; } - private static String validateName(String n) { + private static String validateName(String n, boolean pseudo) { checkNotNull(n, "name"); - checkArgument(n.length() != 0, "token must have at least 1 tchar"); + checkArgument(!n.isEmpty(), "token must have at least 1 tchar"); for (int i = 0; i < n.length(); i++) { char tChar = n.charAt(i); - // TODO(notcarl): remove this hack once pseudo headers are properly handled - if (tChar == ':' && i == 0) { + if (pseudo && tChar == ':' && i == 0) { continue; } @@ -642,9 +645,9 @@ public final class Metadata { return n; } - private Key(String name) { + private Key(String name, boolean pseudo) { this.originalName = checkNotNull(name, "name"); - this.name = validateName(this.originalName.toLowerCase(Locale.ROOT)); + this.name = validateName(this.originalName.toLowerCase(Locale.ROOT), pseudo); this.nameBytes = this.name.getBytes(US_ASCII); } @@ -722,7 +725,7 @@ public final class Metadata { /** Keys have a name and a binary marshaller used for serialization. */ private BinaryKey(String name, BinaryMarshaller marshaller) { - super(name); + super(name, false /* not pseudo */); checkArgument( name.endsWith(BINARY_HEADER_SUFFIX), "Binary header is named %s. It must end with %s", @@ -747,8 +750,8 @@ public final class Metadata { private final AsciiMarshaller marshaller; /** Keys have a name and an ASCII marshaller used for serialization. */ - private AsciiKey(String name, AsciiMarshaller marshaller) { - super(name); + private AsciiKey(String name, boolean pseudo, AsciiMarshaller marshaller) { + super(name, pseudo); Preconditions.checkArgument( !name.endsWith(BINARY_HEADER_SUFFIX), "ASCII header is named %s. Only binary headers may end with %s", @@ -772,8 +775,8 @@ public final class Metadata { private final TrustedAsciiMarshaller marshaller; /** Keys have a name and an ASCII marshaller used for serialization. */ - private TrustedAsciiKey(String name, TrustedAsciiMarshaller marshaller) { - super(name); + private TrustedAsciiKey(String name, boolean pseudo, TrustedAsciiMarshaller marshaller) { + super(name, pseudo); Preconditions.checkArgument( !name.endsWith(BINARY_HEADER_SUFFIX), "ASCII header is named %s. Only binary headers may end with %s", diff --git a/core/src/main/java/io/grpc/Status.java b/core/src/main/java/io/grpc/Status.java index dcbca2f45b..e76fc1d434 100644 --- a/core/src/main/java/io/grpc/Status.java +++ b/core/src/main/java/io/grpc/Status.java @@ -346,7 +346,7 @@ public final class Status { * Key to bind status code to trailing metadata. */ static final Metadata.Key CODE_KEY - = Metadata.Key.of("grpc-status", new StatusCodeMarshaller()); + = Metadata.Key.of("grpc-status", false /* not pseudo */, new StatusCodeMarshaller()); /** * Marshals status messages for ({@link #MESSAGE_KEY}. gRPC does not use binary coding of @@ -377,7 +377,7 @@ public final class Status { * Key to bind status message to trailing metadata. */ static final Metadata.Key MESSAGE_KEY = - Metadata.Key.of("grpc-message", STATUS_MESSAGE_MARSHALLER); + Metadata.Key.of("grpc-message", false /* not pseudo */, STATUS_MESSAGE_MARSHALLER); /** * Extract an error {@link Status} from the causal chain of a {@link Throwable}. diff --git a/core/src/test/java/io/grpc/MetadataTest.java b/core/src/test/java/io/grpc/MetadataTest.java index 541836eb75..d5cbfc5cde 100644 --- a/core/src/test/java/io/grpc/MetadataTest.java +++ b/core/src/test/java/io/grpc/MetadataTest.java @@ -63,6 +63,14 @@ public class MetadataTest { private static final byte[] LANCE_BYTES = LANCE.getBytes(US_ASCII); private static final Metadata.Key KEY = Metadata.Key.of("test-bin", FISH_MARSHALLER); + @Test + public void noPseudoHeaders() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Invalid character"); + + Metadata.Key.of(":test-bin", FISH_MARSHALLER); + } + @Test public void testMutations() { Fish lance = new Fish(LANCE); diff --git a/core/src/test/java/io/grpc/internal/Http2ClientStreamTransportStateTest.java b/core/src/test/java/io/grpc/internal/Http2ClientStreamTransportStateTest.java index 961a9ceafb..042785caf2 100644 --- a/core/src/test/java/io/grpc/internal/Http2ClientStreamTransportStateTest.java +++ b/core/src/test/java/io/grpc/internal/Http2ClientStreamTransportStateTest.java @@ -25,6 +25,7 @@ import static org.mockito.Matchers.same; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import io.grpc.InternalMetadata; import io.grpc.Metadata; import io.grpc.Status; import io.grpc.Status.Code; @@ -40,6 +41,10 @@ import org.mockito.MockitoAnnotations; /** Unit tests for {@link Http2ClientStreamTransportState}. */ @RunWith(JUnit4.class) public class Http2ClientStreamTransportStateTest { + + private final Metadata.Key testStatusMashaller = + InternalMetadata.keyOf(":status", Metadata.ASCII_STRING_MARSHALLER); + @Mock private ClientStreamListener mockListener; @Captor private ArgumentCaptor statusCaptor; @@ -53,7 +58,7 @@ public class Http2ClientStreamTransportStateTest { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); - headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); + headers.put(testStatusMashaller, "200"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); state.transportHeadersReceived(headers); @@ -67,7 +72,7 @@ public class Http2ClientStreamTransportStateTest { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); - headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "500"); + headers.put(testStatusMashaller, "500"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); state.transportHeadersReceived(headers); @@ -96,7 +101,7 @@ public class Http2ClientStreamTransportStateTest { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); - headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); + headers.put(testStatusMashaller, "200"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html"); state.transportHeadersReceived(headers); state.transportDataReceived(ReadableBuffers.empty(), true); @@ -112,7 +117,7 @@ public class Http2ClientStreamTransportStateTest { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); - headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "401"); + headers.put(testStatusMashaller, "401"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html"); state.transportHeadersReceived(headers); state.transportDataReceived(ReadableBuffers.empty(), true); @@ -130,14 +135,14 @@ public class Http2ClientStreamTransportStateTest { state.setListener(mockListener); Metadata infoHeaders = new Metadata(); - infoHeaders.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "100"); + infoHeaders.put(testStatusMashaller, "100"); state.transportHeadersReceived(infoHeaders); Metadata infoHeaders2 = new Metadata(); - infoHeaders2.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "101"); + infoHeaders2.put(testStatusMashaller, "101"); state.transportHeadersReceived(infoHeaders2); Metadata headers = new Metadata(); - headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); + headers.put(testStatusMashaller, "200"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); state.transportHeadersReceived(headers); @@ -151,7 +156,7 @@ public class Http2ClientStreamTransportStateTest { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); - headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); + headers.put(testStatusMashaller, "200"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); state.transportHeadersReceived(headers); @@ -170,7 +175,7 @@ public class Http2ClientStreamTransportStateTest { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); - headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); + headers.put(testStatusMashaller, "200"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html"); state.transportHeadersReceived(headers); Metadata headersAgain = new Metadata(); @@ -201,7 +206,7 @@ public class Http2ClientStreamTransportStateTest { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); - headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); + headers.put(testStatusMashaller, "200"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html"); state.transportHeadersReceived(headers); String testString = "This is a test"; @@ -216,7 +221,7 @@ public class Http2ClientStreamTransportStateTest { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata trailers = new Metadata(); - trailers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); + trailers.put(testStatusMashaller, "200"); trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); trailers.put(Metadata.Key.of("grpc-status", Metadata.ASCII_STRING_MARSHALLER), "0"); @@ -231,7 +236,7 @@ public class Http2ClientStreamTransportStateTest { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); - headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); + headers.put(testStatusMashaller, "200"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); state.transportHeadersReceived(headers); @@ -248,7 +253,7 @@ public class Http2ClientStreamTransportStateTest { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata trailers = new Metadata(); - trailers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); + trailers.put(testStatusMashaller, "200"); trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); trailers.put(Metadata.Key.of("grpc-status", Metadata.ASCII_STRING_MARSHALLER), "1"); @@ -263,7 +268,7 @@ public class Http2ClientStreamTransportStateTest { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata trailers = new Metadata(); - trailers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "401"); + trailers.put(testStatusMashaller, "401"); trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); state.transportTrailersReceived(trailers); @@ -308,12 +313,12 @@ public class Http2ClientStreamTransportStateTest { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); - headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); + headers.put(testStatusMashaller, "200"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); state.transportHeadersReceived(headers); Metadata trailers = new Metadata(); - trailers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "401"); + trailers.put(testStatusMashaller, "401"); state.transportTrailersReceived(trailers); verify(mockListener).headersRead(headers);