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;
}
/**
* 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() {}
}

View File

@ -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<PendingStream> pendingStreams = new ArrayDeque<PendingStream>();
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<Http2LocalFlowController> 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);
}
}
}