mirror of https://github.com/grpc/grpc-java.git
core: discard outbound content-length header (#8522)
Since netty version v4.1.67, content-lenght header validation will be enforced. So once grpc upgrades netty to that version or above, RPCs with invalid content-length header will fail. Some libraries such as HTTP to gRPC adapters blindly copy all HTTP headers to gRPC metadata, but the content-length header is one of those that shouldn't be forwarded because gRPC uses different encoding. This mistake has already been in existence for a long time. Discard outbound content-length headers in gRPC, so that users who encounter invalid content-length issue when upgrading grpc-java version on server/client side would be able to workaround by upgrading grpc-java on client/server side as well without fixing the HTTP adapter.
This commit is contained in:
parent
876f56e2ea
commit
3b237339c7
|
|
@ -24,6 +24,7 @@ import static io.grpc.Contexts.statusFromCancelled;
|
||||||
import static io.grpc.Status.DEADLINE_EXCEEDED;
|
import static io.grpc.Status.DEADLINE_EXCEEDED;
|
||||||
import static io.grpc.internal.GrpcUtil.CONTENT_ACCEPT_ENCODING_KEY;
|
import static io.grpc.internal.GrpcUtil.CONTENT_ACCEPT_ENCODING_KEY;
|
||||||
import static io.grpc.internal.GrpcUtil.CONTENT_ENCODING_KEY;
|
import static io.grpc.internal.GrpcUtil.CONTENT_ENCODING_KEY;
|
||||||
|
import static io.grpc.internal.GrpcUtil.CONTENT_LENGTH_KEY;
|
||||||
import static io.grpc.internal.GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY;
|
import static io.grpc.internal.GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY;
|
||||||
import static io.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY;
|
import static io.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
|
|
@ -163,6 +164,7 @@ final class ClientCallImpl<ReqT, RespT> extends ClientCall<ReqT, RespT> {
|
||||||
DecompressorRegistry decompressorRegistry,
|
DecompressorRegistry decompressorRegistry,
|
||||||
Compressor compressor,
|
Compressor compressor,
|
||||||
boolean fullStreamDecompression) {
|
boolean fullStreamDecompression) {
|
||||||
|
headers.discardAll(CONTENT_LENGTH_KEY);
|
||||||
headers.discardAll(MESSAGE_ENCODING_KEY);
|
headers.discardAll(MESSAGE_ENCODING_KEY);
|
||||||
if (compressor != Codec.Identity.NONE) {
|
if (compressor != Codec.Identity.NONE) {
|
||||||
headers.put(MESSAGE_ENCODING_KEY, compressor.getMessageEncoding());
|
headers.put(MESSAGE_ENCODING_KEY, compressor.getMessageEncoding());
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,9 @@ public final class GrpcUtil {
|
||||||
public static final Metadata.Key<byte[]> CONTENT_ACCEPT_ENCODING_KEY =
|
public static final Metadata.Key<byte[]> CONTENT_ACCEPT_ENCODING_KEY =
|
||||||
InternalMetadata.keyOf(GrpcUtil.CONTENT_ACCEPT_ENCODING, new AcceptEncodingMarshaller());
|
InternalMetadata.keyOf(GrpcUtil.CONTENT_ACCEPT_ENCODING, new AcceptEncodingMarshaller());
|
||||||
|
|
||||||
|
static final Metadata.Key<String> CONTENT_LENGTH_KEY =
|
||||||
|
Metadata.Key.of("content-length", Metadata.ASCII_STRING_MARSHALLER);
|
||||||
|
|
||||||
private static final class AcceptEncodingMarshaller implements TrustedAsciiMarshaller<byte[]> {
|
private static final class AcceptEncodingMarshaller implements TrustedAsciiMarshaller<byte[]> {
|
||||||
@Override
|
@Override
|
||||||
public byte[] toAsciiString(byte[] value) {
|
public byte[] toAsciiString(byte[] value) {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static io.grpc.internal.GrpcUtil.ACCEPT_ENCODING_SPLITTER;
|
import static io.grpc.internal.GrpcUtil.ACCEPT_ENCODING_SPLITTER;
|
||||||
|
import static io.grpc.internal.GrpcUtil.CONTENT_LENGTH_KEY;
|
||||||
import static io.grpc.internal.GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY;
|
import static io.grpc.internal.GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY;
|
||||||
import static io.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY;
|
import static io.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY;
|
||||||
|
|
||||||
|
|
@ -107,6 +108,7 @@ final class ServerCallImpl<ReqT, RespT> extends ServerCall<ReqT, RespT> {
|
||||||
checkState(!sendHeadersCalled, "sendHeaders has already been called");
|
checkState(!sendHeadersCalled, "sendHeaders has already been called");
|
||||||
checkState(!closeCalled, "call is closed");
|
checkState(!closeCalled, "call is closed");
|
||||||
|
|
||||||
|
headers.discardAll(CONTENT_LENGTH_KEY);
|
||||||
headers.discardAll(MESSAGE_ENCODING_KEY);
|
headers.discardAll(MESSAGE_ENCODING_KEY);
|
||||||
if (compressor == null) {
|
if (compressor == null) {
|
||||||
compressor = Codec.Identity.NONE;
|
compressor = Codec.Identity.NONE;
|
||||||
|
|
|
||||||
|
|
@ -469,6 +469,15 @@ public class ClientCallImplTest {
|
||||||
assertNull(m.get(GrpcUtil.MESSAGE_ENCODING_KEY));
|
assertNull(m.get(GrpcUtil.MESSAGE_ENCODING_KEY));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void prepareHeaders_ignoreContentLength() {
|
||||||
|
Metadata m = new Metadata();
|
||||||
|
m.put(GrpcUtil.CONTENT_LENGTH_KEY, "123");
|
||||||
|
ClientCallImpl.prepareHeaders(m, decompressorRegistry, Codec.Identity.NONE, false);
|
||||||
|
|
||||||
|
assertNull(m.get(GrpcUtil.CONTENT_LENGTH_KEY));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void prepareHeaders_acceptedMessageEncodingsAdded() {
|
public void prepareHeaders_acceptedMessageEncodingsAdded() {
|
||||||
Metadata m = new Metadata();
|
Metadata m = new Metadata();
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package io.grpc.internal;
|
package io.grpc.internal;
|
||||||
|
|
||||||
import static com.google.common.base.Charsets.UTF_8;
|
import static com.google.common.base.Charsets.UTF_8;
|
||||||
|
import static io.grpc.internal.GrpcUtil.CONTENT_LENGTH_KEY;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
@ -151,6 +152,16 @@ public class ServerCallImplTest {
|
||||||
verify(stream).writeHeaders(headers);
|
verify(stream).writeHeaders(headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sendHeader_contentLengthDiscarded() {
|
||||||
|
Metadata headers = new Metadata();
|
||||||
|
headers.put(CONTENT_LENGTH_KEY, "123");
|
||||||
|
call.sendHeaders(headers);
|
||||||
|
|
||||||
|
verify(stream).writeHeaders(headers);
|
||||||
|
assertNull(headers.get(CONTENT_LENGTH_KEY));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void sendHeader_failsOnSecondCall() {
|
public void sendHeader_failsOnSecondCall() {
|
||||||
call.sendHeaders(new Metadata());
|
call.sendHeaders(new Metadata());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue