Tightening up error message for GO_AWAY.

The attempt here is to identify all causes of GO_AWAY and to ensure
there is a reasonable description to help understand the cause.

Fixes #163
This commit is contained in:
nmittler 2015-03-03 10:58:30 -08:00
parent 456216b364
commit dfcfb7bca1
2 changed files with 119 additions and 4 deletions

View File

@ -121,5 +121,88 @@ public final class HttpUtil {
return Status.UNKNOWN; 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() {} private HttpUtil() {}
} }

View File

@ -31,10 +31,13 @@
package io.grpc.transport.netty; package io.grpc.transport.netty;
import static io.netty.util.CharsetUtil.UTF_8;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import io.grpc.Metadata; import io.grpc.Metadata;
import io.grpc.Status; import io.grpc.Status;
import io.grpc.transport.HttpUtil;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
@ -82,6 +85,7 @@ class NettyClientHandler extends Http2ConnectionHandler {
private final Deque<PendingStream> pendingStreams = new ArrayDeque<PendingStream>(); private final Deque<PendingStream> pendingStreams = new ArrayDeque<PendingStream>();
private final Http2LocalFlowController inboundFlow; private final Http2LocalFlowController inboundFlow;
private Throwable connectionError; private Throwable connectionError;
private Status goAwayStatus;
private ChannelHandlerContext ctx; private ChannelHandlerContext ctx;
public NettyClientHandler(Http2Connection connection, public NettyClientHandler(Http2Connection connection,
@ -185,6 +189,23 @@ class NettyClientHandler extends Http2ConnectionHandler {
stream.transportReportStatus(Status.UNKNOWN, false, new Metadata.Trailers()); 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. * Handler for the Channel shutting down.
*/ */
@ -192,7 +213,7 @@ class NettyClientHandler extends Http2ConnectionHandler {
public void channelInactive(ChannelHandlerContext ctx) throws Exception { public void channelInactive(ChannelHandlerContext ctx) throws Exception {
try { try {
// Fail any streams that are awaiting creation. // Fail any streams that are awaiting creation.
Status goAwayStatus = goAwayStatus().withDescription("network channel closed"); goAwayStatus(goAwayStatus().augmentDescription("Network channel closed"));
failPendingStreams(goAwayStatus); failPendingStreams(goAwayStatus);
// Report status to the application layer for any open streams // Report status to the application layer for any open streams
@ -210,6 +231,7 @@ class NettyClientHandler extends Http2ConnectionHandler {
Http2Exception http2Ex) { Http2Exception http2Ex) {
// Save the error. // Save the error.
connectionError = cause; connectionError = cause;
goAwayStatus(Status.fromThrowable(connectionError));
super.onConnectionError(ctx, cause, http2Ex); super.onConnectionError(ctx, cause, http2Ex);
} }
@ -317,7 +339,7 @@ class NettyClientHandler extends Http2ConnectionHandler {
*/ */
private void createPendingStreams() { private void createPendingStreams() {
Http2Connection connection = connection(); Http2Connection connection = connection();
Http2Connection.Endpoint local = connection.local(); Http2Connection.Endpoint<Http2LocalFlowController> local = connection.local();
Status goAwayStatus = goAwayStatus(); Status goAwayStatus = goAwayStatus();
while (!pendingStreams.isEmpty()) { while (!pendingStreams.isEmpty()) {
final int streamId = local.nextStreamId(); final int streamId = local.nextStreamId();
@ -362,12 +384,16 @@ class NettyClientHandler extends Http2ConnectionHandler {
* Returns the appropriate status used to represent the cause for GOAWAY. * Returns the appropriate status used to represent the cause for GOAWAY.
*/ */
private Status goAwayStatus() { private Status goAwayStatus() {
if (connectionError != null) { if (goAwayStatus != null) {
return Status.fromThrowable(connectionError); return goAwayStatus;
} }
return Status.UNAVAILABLE; return Status.UNAVAILABLE;
} }
private void goAwayStatus(Status status) {
goAwayStatus = goAwayStatus == null ? status : goAwayStatus;
}
/** /**
* Handles the successful creation of a new stream. * Handles the successful creation of a new stream.
*/ */
@ -452,5 +478,11 @@ class NettyClientHandler extends Http2ConnectionHandler {
throws Http2Exception { throws Http2Exception {
handler.onRstStreamRead(streamId); handler.onRstStreamRead(streamId);
} }
@Override
public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode,
ByteBuf debugData) throws Http2Exception {
handler.onGoAwayRead(errorCode, debugData);
}
} }
} }