diff --git a/core/src/main/java/io/grpc/transport/HttpUtil.java b/core/src/main/java/io/grpc/transport/HttpUtil.java index 5c303a10ab..48bce6d948 100644 --- a/core/src/main/java/io/grpc/transport/HttpUtil.java +++ b/core/src/main/java/io/grpc/transport/HttpUtil.java @@ -121,5 +121,88 @@ public final class HttpUtil { return Status.UNKNOWN; } + /** + * All error codes identified by the HTTP/2 spec. + */ + public enum Http2Error { + NO_ERROR(0x0, Status.INTERNAL), + PROTOCOL_ERROR(0x1, Status.INTERNAL), + INTERNAL_ERROR(0x2, Status.INTERNAL), + FLOW_CONTROL_ERROR(0x3, Status.INTERNAL), + SETTINGS_TIMEOUT(0x4, Status.INTERNAL), + STREAM_CLOSED(0x5, Status.INTERNAL), + FRAME_SIZE_ERROR(0x6, Status.INTERNAL), + REFUSED_STREAM(0x7, Status.UNAVAILABLE), + CANCEL(0x8, Status.CANCELLED), + COMPRESSION_ERROR(0x9, Status.INTERNAL), + CONNECT_ERROR(0xA, Status.INTERNAL), + ENHANCE_YOUR_CALM(0xB, Status.RESOURCE_EXHAUSTED.withDescription("Bandwidth exhausted")), + INADEQUATE_SECURITY(0xC, Status.PERMISSION_DENIED.withDescription("Permission denied as " + + "protocol is not secure enough to call")), + HTTP_1_1_REQUIRED(0xD, Status.UNKNOWN); + + // Populate a mapping of code to enum value for quick look-up. + private static final Http2Error[] codeMap; + static { + Http2Error[] errors = Http2Error.values(); + int size = (int) errors[errors.length - 1].code() + 1; + codeMap = new Http2Error[size]; + for (Http2Error error : errors) { + int index = (int) error.code(); + codeMap[index] = error; + } + } + + private final int code; + private final Status status; + + private Http2Error(int code, Status status) { + this.code = code; + this.status = status.augmentDescription("HTTP/2 error code: " + this.name()); + } + + /** + * Gets the code for this error used on the wire. + */ + public long code() { + return code; + } + + /** + * Gets the {@link Status} associated with this HTTP/2 code. + */ + public Status status() { + return status; + } + + /** + * Looks up the HTTP/2 error code enum value for the specified code. + * + * @param code an HTTP/2 error code value. + * @return the HTTP/2 error code enum or {@code null} if not found. + */ + public static Http2Error forCode(long code) { + if (code >= codeMap.length || code < 0) { + return null; + } + return codeMap[(int) code]; + } + + /** + * Looks up the {@link Status} from the given HTTP/2 error code. + * + * @param errorCode the HTTP/2 error code. + * @return a {@link Status} representing the given error. + */ + public static Status statusForCode(int code) { + Http2Error error = forCode(code); + if (error == null) { + return Status.UNKNOWN.withDescription("Unrecognized HTTP/2 error: " + code); + } + + return error.status(); + } + } + private HttpUtil() {} } diff --git a/netty/src/main/java/io/grpc/transport/netty/NettyClientHandler.java b/netty/src/main/java/io/grpc/transport/netty/NettyClientHandler.java index c44c45afc6..b8ed06fa40 100644 --- a/netty/src/main/java/io/grpc/transport/netty/NettyClientHandler.java +++ b/netty/src/main/java/io/grpc/transport/netty/NettyClientHandler.java @@ -31,10 +31,13 @@ package io.grpc.transport.netty; +import static io.netty.util.CharsetUtil.UTF_8; + import com.google.common.base.Preconditions; import io.grpc.Metadata; import io.grpc.Status; +import io.grpc.transport.HttpUtil; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; @@ -82,6 +85,7 @@ class NettyClientHandler extends Http2ConnectionHandler { private final Deque pendingStreams = new ArrayDeque(); private final Http2LocalFlowController inboundFlow; private Throwable connectionError; + private Status goAwayStatus; private ChannelHandlerContext ctx; public NettyClientHandler(Http2Connection connection, @@ -185,6 +189,23 @@ class NettyClientHandler extends Http2ConnectionHandler { stream.transportReportStatus(Status.UNKNOWN, false, new Metadata.Trailers()); } + private void onGoAwayRead(long errorCode, ByteBuf debugData) { + Status status = HttpUtil.Http2Error.statusForCode((int) errorCode); + if (debugData.isReadable()) { + // If a debug message was provided, use it. + String msg = debugData.toString(UTF_8); + status = status.withDescription(msg); + } + goAwayStatus(status); + } + + @Override + public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { + goAwayStatus(Status.UNAVAILABLE.withDescription("Network channel closed by the client")); + + super.close(ctx, promise); + } + /** * Handler for the Channel shutting down. */ @@ -192,7 +213,7 @@ class NettyClientHandler extends Http2ConnectionHandler { public void channelInactive(ChannelHandlerContext ctx) throws Exception { try { // Fail any streams that are awaiting creation. - Status goAwayStatus = goAwayStatus().withDescription("network channel closed"); + goAwayStatus(goAwayStatus().augmentDescription("Network channel closed")); failPendingStreams(goAwayStatus); // Report status to the application layer for any open streams @@ -210,6 +231,7 @@ class NettyClientHandler extends Http2ConnectionHandler { Http2Exception http2Ex) { // Save the error. connectionError = cause; + goAwayStatus(Status.fromThrowable(connectionError)); super.onConnectionError(ctx, cause, http2Ex); } @@ -317,7 +339,7 @@ class NettyClientHandler extends Http2ConnectionHandler { */ private void createPendingStreams() { Http2Connection connection = connection(); - Http2Connection.Endpoint local = connection.local(); + Http2Connection.Endpoint local = connection.local(); Status goAwayStatus = goAwayStatus(); while (!pendingStreams.isEmpty()) { final int streamId = local.nextStreamId(); @@ -362,12 +384,16 @@ class NettyClientHandler extends Http2ConnectionHandler { * Returns the appropriate status used to represent the cause for GOAWAY. */ private Status goAwayStatus() { - if (connectionError != null) { - return Status.fromThrowable(connectionError); + if (goAwayStatus != null) { + return goAwayStatus; } return Status.UNAVAILABLE; } + private void goAwayStatus(Status status) { + goAwayStatus = goAwayStatus == null ? status : goAwayStatus; + } + /** * Handles the successful creation of a new stream. */ @@ -452,5 +478,11 @@ class NettyClientHandler extends Http2ConnectionHandler { throws Http2Exception { handler.onRstStreamRead(streamId); } + + @Override + public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode, + ByteBuf debugData) throws Http2Exception { + handler.onGoAwayRead(errorCode, debugData); + } } }