GRPC Java clients will send the "te: trailers" header, and the server will

check for it, so that we can detect intermediate proxies that do not support
trailers.

-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=81084983
This commit is contained in:
zhangkun 2014-12-01 11:58:20 -08:00 committed by Eric Anderson
parent 4332c2f56e
commit 8375cd00e8
6 changed files with 44 additions and 7 deletions

View File

@ -26,6 +26,16 @@ public final class HttpUtil {
*/
public static final String HTTP_METHOD = "POST";
/**
* The TE header name. Defined here since it is not explicitly defined by the HTTP/2 spec.
*/
public static final Metadata.Key<String> TE = Metadata.Key.of("te", Metadata.STRING_MARSHALLER);
/**
* The TE (transport encoding) header for requests over HTTP/2
*/
public static final String TE_TRAILERS = "trailers";
/**
* Maps HTTP error response status codes to transport codes.
*/

View File

@ -3,6 +3,8 @@ package com.google.net.stubby.transport.netty;
import static com.google.net.stubby.transport.netty.Utils.CONTENT_TYPE_GRPC;
import static com.google.net.stubby.transport.netty.Utils.CONTENT_TYPE_HEADER;
import static com.google.net.stubby.transport.netty.Utils.HTTP_METHOD;
import static com.google.net.stubby.transport.netty.Utils.TE_HEADER;
import static com.google.net.stubby.transport.netty.Utils.TE_TRAILERS;
import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
import static io.netty.handler.codec.http2.Http2CodecUtil.toByteBuf;
import static io.netty.handler.codec.http2.Http2Error.NO_ERROR;
@ -18,6 +20,7 @@ import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.http2.Http2Connection;
import io.netty.handler.codec.http2.Http2ConnectionHandler;
import io.netty.handler.codec.http2.Http2Error;
@ -50,6 +53,7 @@ class NettyServerHandler extends Http2ConnectionHandler {
private final ServerTransportListener transportListener;
private Throwable connectionError;
private ChannelHandlerContext ctx;
private boolean teWarningLogged;
NettyServerHandler(ServerTransportListener transportListener,
Http2Connection connection,
@ -92,6 +96,13 @@ class NettyServerHandler extends Http2ConnectionHandler {
private void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers)
throws Http2Exception {
if (!teWarningLogged && !TE_TRAILERS.equals(headers.get(TE_HEADER))) {
logger.warning(String.format("Expected header TE: %s, but %s is received. This means "
+ "some intermediate proxy may not support trailers",
TE_TRAILERS, headers.get(TE_HEADER)));
teWarningLogged = true;
}
try {
NettyServerStream stream = new NettyServerStream(ctx.channel(), streamId, this);
// The Http2Stream object was put by AbstractHttp2ConnectionHandler before calling this
@ -268,11 +279,7 @@ class NettyServerHandler extends Http2ConnectionHandler {
throw new Http2StreamException(streamId, Http2Error.REFUSED_STREAM,
String.format("Method '%s' is not supported", headers.method()));
}
if (!CONTENT_TYPE_GRPC.equals(headers.get(CONTENT_TYPE_HEADER))) {
throw new Http2StreamException(streamId, Http2Error.REFUSED_STREAM, String.format(
"Header '%s'='%s', while '%s' is expected", CONTENT_TYPE_HEADER,
headers.get(CONTENT_TYPE_HEADER), CONTENT_TYPE_GRPC));
}
checkHeader(streamId, headers, CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC);
String methodName = TransportFrameUtil.getFullMethodNameFromPath(headers.path().toString());
if (methodName == null) {
throw new Http2StreamException(streamId, Http2Error.REFUSED_STREAM,
@ -281,6 +288,14 @@ class NettyServerHandler extends Http2ConnectionHandler {
return methodName;
}
private static void checkHeader(int streamId, Http2Headers headers,
AsciiString header, AsciiString expectedValue) throws Http2StreamException {
if (!expectedValue.equals(headers.get(header))) {
throw new Http2StreamException(streamId, Http2Error.REFUSED_STREAM, String.format(
"Header '%s'='%s', while '%s' is expected", header, headers.get(header), expectedValue));
}
}
/**
* Returns the server stream associated to the given HTTP/2 stream object
*/

View File

@ -33,6 +33,8 @@ class Utils {
new AsciiString(HttpUtil.CONTENT_TYPE.name());
public static final AsciiString CONTENT_TYPE_GRPC =
new AsciiString(HttpUtil.CONTENT_TYPE_GRPC);
public static final AsciiString TE_HEADER = new AsciiString(HttpUtil.TE.name());
public static final AsciiString TE_TRAILERS = new AsciiString(HttpUtil.TE_TRAILERS);
public static final Resource<EventLoopGroup> DEFAULT_CHANNEL_EVENT_LOOP_GROUP =
new DefaultEventLoopGroupResource("grpc-default-channel-ELG");
@ -93,7 +95,8 @@ class Utils {
.path(defaultPath)
.method(HTTP_METHOD)
.scheme(ssl ? HTTPS : HTTP)
.set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC);
.set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC)
.set(TE_HEADER, TE_TRAILERS);
// Override the default authority and path if provided by the headers.
if (headers.getAuthority() != null) {

View File

@ -6,6 +6,8 @@ import static com.google.net.stubby.transport.netty.Utils.CONTENT_TYPE_HEADER;
import static com.google.net.stubby.transport.netty.Utils.HTTPS;
import static com.google.net.stubby.transport.netty.Utils.HTTP_METHOD;
import static com.google.net.stubby.transport.netty.Utils.STATUS_OK;
import static com.google.net.stubby.transport.netty.Utils.TE_HEADER;
import static com.google.net.stubby.transport.netty.Utils.TE_TRAILERS;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
@ -82,7 +84,8 @@ public class NettyClientHandlerTest extends NettyHandlerTestBase {
.path(as("/fakemethod"))
.method(HTTP_METHOD)
.add(as("auth"), as("sometoken"))
.add(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC);
.add(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC)
.add(TE_HEADER, TE_TRAILERS);
// Simulate activation of the handler to force writing of the initial settings
handler.handlerAdded(ctx);
@ -121,6 +124,7 @@ public class NettyClientHandlerTest extends NettyHandlerTestBase {
assertEquals(HTTP_METHOD, headers.method());
assertEquals("www.fake.com", headers.authority().toString());
assertEquals(CONTENT_TYPE_GRPC, headers.get(CONTENT_TYPE_HEADER));
assertEquals(TE_TRAILERS, headers.get(TE_HEADER));
assertEquals("/fakemethod", headers.path().toString());
assertEquals("sometoken", headers.get(as("auth")).toString());
}

View File

@ -4,6 +4,8 @@ import static com.google.common.base.Charsets.UTF_8;
import static com.google.net.stubby.transport.netty.Utils.CONTENT_TYPE_GRPC;
import static com.google.net.stubby.transport.netty.Utils.CONTENT_TYPE_HEADER;
import static com.google.net.stubby.transport.netty.Utils.HTTP_METHOD;
import static com.google.net.stubby.transport.netty.Utils.TE_HEADER;
import static com.google.net.stubby.transport.netty.Utils.TE_TRAILERS;
import static io.netty.handler.codec.http2.Http2CodecUtil.toByteBuf;
import static io.netty.handler.codec.http2.Http2Exception.protocolError;
import static org.junit.Assert.assertArrayEquals;
@ -224,6 +226,7 @@ public class NettyServerHandlerTest extends NettyHandlerTestBase {
Http2Headers headers = new DefaultHttp2Headers()
.method(HTTP_METHOD)
.set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC)
.set(TE_HEADER, TE_TRAILERS)
.path(new AsciiString("/foo.bar"));
ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
handler.channelRead(ctx, headersFrame);

View File

@ -20,6 +20,7 @@ public class Headers {
public static final Header METHOD_HEADER = new Header(Header.TARGET_METHOD, HttpUtil.HTTP_METHOD);
public static final Header CONTENT_TYPE_HEADER =
new Header(HttpUtil.CONTENT_TYPE.name(), HttpUtil.CONTENT_TYPE_GRPC);
public static final Header TE_HEADER = new Header(HttpUtil.TE.name(), HttpUtil.TE_TRAILERS);
/**
* Serializes the given headers and creates a list of OkHttp {@link Header}s to be used when
@ -44,6 +45,7 @@ public class Headers {
// All non-pseudo headers must come after pseudo headers.
okhttpHeaders.add(CONTENT_TYPE_HEADER);
okhttpHeaders.add(TE_HEADER);
// Now add any application-provided headers.
byte[][] serializedHeaders = headers.serialize();