Skip to content

Commit dfcfb7b

Browse files
author
nmittler
committed
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 grpc#163
1 parent 456216b commit dfcfb7b

2 files changed

Lines changed: 119 additions & 4 deletions

File tree

‎core/src/main/java/io/grpc/transport/HttpUtil.java‎

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,5 +121,88 @@ public static Status httpStatusToGrpcStatus(int httpStatusCode) {
121121
return Status.UNKNOWN;
122122
}
123123

124+
/**
125+
* All error codes identified by the HTTP/2 spec.
126+
*/
127+
public enum Http2Error {
128+
NO_ERROR(0x0, Status.INTERNAL),
129+
PROTOCOL_ERROR(0x1, Status.INTERNAL),
130+
INTERNAL_ERROR(0x2, Status.INTERNAL),
131+
FLOW_CONTROL_ERROR(0x3, Status.INTERNAL),
132+
SETTINGS_TIMEOUT(0x4, Status.INTERNAL),
133+
STREAM_CLOSED(0x5, Status.INTERNAL),
134+
FRAME_SIZE_ERROR(0x6, Status.INTERNAL),
135+
REFUSED_STREAM(0x7, Status.UNAVAILABLE),
136+
CANCEL(0x8, Status.CANCELLED),
137+
COMPRESSION_ERROR(0x9, Status.INTERNAL),
138+
CONNECT_ERROR(0xA, Status.INTERNAL),
139+
ENHANCE_YOUR_CALM(0xB, Status.RESOURCE_EXHAUSTED.withDescription("Bandwidth exhausted")),
140+
INADEQUATE_SECURITY(0xC, Status.PERMISSION_DENIED.withDescription("Permission denied as "
141+
+ "protocol is not secure enough to call")),
142+
HTTP_1_1_REQUIRED(0xD, Status.UNKNOWN);
143+
144+
// Populate a mapping of code to enum value for quick look-up.
145+
private static final Http2Error[] codeMap;
146+
static {
147+
Http2Error[] errors = Http2Error.values();
148+
int size = (int) errors[errors.length - 1].code() + 1;
149+
codeMap = new Http2Error[size];
150+
for (Http2Error error : errors) {
151+
int index = (int) error.code();
152+
codeMap[index] = error;
153+
}
154+
}
155+
156+
private final int code;
157+
private final Status status;
158+
159+
private Http2Error(int code, Status status) {
160+
this.code = code;
161+
this.status = status.augmentDescription("HTTP/2 error code: " + this.name());
162+
}
163+
164+
/**
165+
* Gets the code for this error used on the wire.
166+
*/
167+
public long code() {
168+
return code;
169+
}
170+
171+
/**
172+
* Gets the {@link Status} associated with this HTTP/2 code.
173+
*/
174+
public Status status() {
175+
return status;
176+
}
177+
178+
/**
179+
* Looks up the HTTP/2 error code enum value for the specified code.
180+
*
181+
* @param code an HTTP/2 error code value.
182+
* @return the HTTP/2 error code enum or {@code null} if not found.
183+
*/
184+
public static Http2Error forCode(long code) {
185+
if (code >= codeMap.length || code < 0) {
186+
return null;
187+
}
188+
return codeMap[(int) code];
189+
}
190+
191+
/**
192+
* Looks up the {@link Status} from the given HTTP/2 error code.
193+
*
194+
* @param errorCode the HTTP/2 error code.
195+
* @return a {@link Status} representing the given error.
196+
*/
197+
public static Status statusForCode(int code) {
198+
Http2Error error = forCode(code);
199+
if (error == null) {
200+
return Status.UNKNOWN.withDescription("Unrecognized HTTP/2 error: " + code);
201+
}
202+
203+
return error.status();
204+
}
205+
}
206+
124207
private HttpUtil() {}
125208
}

‎netty/src/main/java/io/grpc/transport/netty/NettyClientHandler.java‎

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,13 @@
3131

3232
package io.grpc.transport.netty;
3333

34+
import static io.netty.util.CharsetUtil.UTF_8;
35+
3436
import com.google.common.base.Preconditions;
3537

3638
import io.grpc.Metadata;
3739
import io.grpc.Status;
40+
import io.grpc.transport.HttpUtil;
3841
import io.netty.buffer.ByteBuf;
3942
import io.netty.channel.ChannelFuture;
4043
import io.netty.channel.ChannelFutureListener;
@@ -82,6 +85,7 @@ public PendingStream(CreateStreamCommand command, ChannelPromise promise) {
8285
private final Deque<PendingStream> pendingStreams = new ArrayDeque<PendingStream>();
8386
private final Http2LocalFlowController inboundFlow;
8487
private Throwable connectionError;
88+
private Status goAwayStatus;
8589
private ChannelHandlerContext ctx;
8690

8791
public NettyClientHandler(Http2Connection connection,
@@ -185,14 +189,31 @@ private void onRstStreamRead(int streamId)
185189
stream.transportReportStatus(Status.UNKNOWN, false, new Metadata.Trailers());
186190
}
187191

192+
private void onGoAwayRead(long errorCode, ByteBuf debugData) {
193+
Status status = HttpUtil.Http2Error.statusForCode((int) errorCode);
194+
if (debugData.isReadable()) {
195+
// If a debug message was provided, use it.
196+
String msg = debugData.toString(UTF_8);
197+
status = status.withDescription(msg);
198+
}
199+
goAwayStatus(status);
200+
}
201+
202+
@Override
203+
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
204+
goAwayStatus(Status.UNAVAILABLE.withDescription("Network channel closed by the client"));
205+
206+
super.close(ctx, promise);
207+
}
208+
188209
/**
189210
* Handler for the Channel shutting down.
190211
*/
191212
@Override
192213
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
193214
try {
194215
// Fail any streams that are awaiting creation.
195-
Status goAwayStatus = goAwayStatus().withDescription("network channel closed");
216+
goAwayStatus(goAwayStatus().augmentDescription("Network channel closed"));
196217
failPendingStreams(goAwayStatus);
197218

198219
// Report status to the application layer for any open streams
@@ -210,6 +231,7 @@ protected void onConnectionError(ChannelHandlerContext ctx, Throwable cause,
210231
Http2Exception http2Ex) {
211232
// Save the error.
212233
connectionError = cause;
234+
goAwayStatus(Status.fromThrowable(connectionError));
213235

214236
super.onConnectionError(ctx, cause, http2Ex);
215237
}
@@ -317,7 +339,7 @@ private void goingAway() {
317339
*/
318340
private void createPendingStreams() {
319341
Http2Connection connection = connection();
320-
Http2Connection.Endpoint local = connection.local();
342+
Http2Connection.Endpoint<Http2LocalFlowController> local = connection.local();
321343
Status goAwayStatus = goAwayStatus();
322344
while (!pendingStreams.isEmpty()) {
323345
final int streamId = local.nextStreamId();
@@ -362,12 +384,16 @@ public void operationComplete(ChannelFuture future) throws Exception {
362384
* Returns the appropriate status used to represent the cause for GOAWAY.
363385
*/
364386
private Status goAwayStatus() {
365-
if (connectionError != null) {
366-
return Status.fromThrowable(connectionError);
387+
if (goAwayStatus != null) {
388+
return goAwayStatus;
367389
}
368390
return Status.UNAVAILABLE;
369391
}
370392

393+
private void goAwayStatus(Status status) {
394+
goAwayStatus = goAwayStatus == null ? status : goAwayStatus;
395+
}
396+
371397
/**
372398
* Handles the successful creation of a new stream.
373399
*/
@@ -452,5 +478,11 @@ public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorC
452478
throws Http2Exception {
453479
handler.onRstStreamRead(streamId);
454480
}
481+
482+
@Override
483+
public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode,
484+
ByteBuf debugData) throws Http2Exception {
485+
handler.onGoAwayRead(errorCode, debugData);
486+
}
455487
}
456488
}

0 commit comments

Comments
 (0)