@@ -140,6 +140,8 @@ export class Http2SubchannelCall implements SubchannelCall {
140
140
141
141
private serverEndedCall = false ;
142
142
143
+ private connectionDropped = false ;
144
+
143
145
constructor (
144
146
private readonly http2Stream : http2 . ClientHttp2Stream ,
145
147
private readonly callEventTracker : CallEventTracker ,
@@ -188,7 +190,22 @@ export class Http2SubchannelCall implements SubchannelCall {
188
190
try {
189
191
messages = this . decoder . write ( data ) ;
190
192
} catch ( e ) {
191
- this . cancelWithStatus ( Status . RESOURCE_EXHAUSTED , ( e as Error ) . message ) ;
193
+ /* Some servers send HTML error pages along with HTTP status codes.
194
+ * When the client attempts to parse this as a length-delimited
195
+ * message, the parsed message size is greater than the default limit,
196
+ * resulting in a message decoding error. In that situation, the HTTP
197
+ * error code information is more useful to the user than the
198
+ * RESOURCE_EXHAUSTED error is, so we report that instead. Normally,
199
+ * we delay processing the HTTP status until after the stream ends, to
200
+ * prioritize reporting the gRPC status from trailers if it is present,
201
+ * but when there is a message parsing error we end the stream early
202
+ * before processing trailers. */
203
+ if ( this . httpStatusCode !== undefined && this . httpStatusCode !== 200 ) {
204
+ const mappedStatus = mapHttpStatusCode ( this . httpStatusCode ) ;
205
+ this . cancelWithStatus ( mappedStatus . code , mappedStatus . details ) ;
206
+ } else {
207
+ this . cancelWithStatus ( Status . RESOURCE_EXHAUSTED , ( e as Error ) . message ) ;
208
+ }
192
209
return ;
193
210
}
194
211
@@ -240,8 +257,16 @@ export class Http2SubchannelCall implements SubchannelCall {
240
257
details = 'Stream refused by server' ;
241
258
break ;
242
259
case http2 . constants . NGHTTP2_CANCEL :
243
- code = Status . CANCELLED ;
244
- details = 'Call cancelled' ;
260
+ /* Bug reports indicate that Node synthesizes a NGHTTP2_CANCEL
261
+ * code from connection drops. We want to prioritize reporting
262
+ * an unavailable status when that happens. */
263
+ if ( this . connectionDropped ) {
264
+ code = Status . UNAVAILABLE ;
265
+ details = 'Connection dropped' ;
266
+ } else {
267
+ code = Status . CANCELLED ;
268
+ details = 'Call cancelled' ;
269
+ }
245
270
break ;
246
271
case http2 . constants . NGHTTP2_ENHANCE_YOUR_CALM :
247
272
code = Status . RESOURCE_EXHAUSTED ;
@@ -321,10 +346,15 @@ export class Http2SubchannelCall implements SubchannelCall {
321
346
}
322
347
323
348
public onDisconnect ( ) {
324
- this . endCall ( {
325
- code : Status . UNAVAILABLE ,
326
- details : 'Connection dropped' ,
327
- metadata : new Metadata ( ) ,
349
+ this . connectionDropped = true ;
350
+ /* Give the call an event loop cycle to finish naturally before reporting
351
+ * the disconnection as an error. */
352
+ setImmediate ( ( ) => {
353
+ this . endCall ( {
354
+ code : Status . UNAVAILABLE ,
355
+ details : 'Connection dropped' ,
356
+ metadata : new Metadata ( ) ,
357
+ } ) ;
328
358
} ) ;
329
359
}
330
360
0 commit comments