gRPC: RST_STREAM(NO_ERROR) handling (ticket #1792).

As per https://tools.ietf.org/html/rfc7540#section-8.1,

: A server can send a complete response prior to the client
: sending an entire request if the response does not depend on
: any portion of the request that has not been sent and
: received.  When this is true, a server MAY request that the
: client abort transmission of a request without error by
: sending a RST_STREAM with an error code of NO_ERROR after
: sending a complete response (i.e., a frame with the
: END_STREAM flag).  Clients MUST NOT discard responses as a
: result of receiving such a RST_STREAM, though clients can
: always discard responses at their discretion for other
: reasons.

Previously, RST_STREAM(NO_ERROR) received from upstream after
a frame with the END_STREAM flag was incorrectly treated as an
error.  Now, a single RST_STREAM(NO_ERROR) is properly handled.

This fixes problems observed with modern grpc-c [1], as well
as with the Go gRPC module.

[1] https://github.com/grpc/grpc/pull/1661
This commit is contained in:
Ruslan Ermilov 2020-04-23 15:10:24 +03:00
parent 8c0a49472c
commit 4c8abb84e3

View File

@ -120,6 +120,7 @@ typedef struct {
unsigned end_stream:1; unsigned end_stream:1;
unsigned done:1; unsigned done:1;
unsigned status:1; unsigned status:1;
unsigned rst:1;
ngx_http_request_t *request; ngx_http_request_t *request;
@ -1205,6 +1206,7 @@ ngx_http_grpc_reinit_request(ngx_http_request_t *r)
ctx->end_stream = 0; ctx->end_stream = 0;
ctx->done = 0; ctx->done = 0;
ctx->status = 0; ctx->status = 0;
ctx->rst = 0;
ctx->connection = NULL; ctx->connection = NULL;
return NGX_OK; return NGX_OK;
@ -2088,7 +2090,9 @@ ngx_http_grpc_filter(void *data, ssize_t bytes)
return NGX_ERROR; return NGX_ERROR;
} }
if (ctx->stream_id && ctx->done) { if (ctx->stream_id && ctx->done
&& ctx->type != NGX_HTTP_V2_RST_STREAM_FRAME)
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"upstream sent frame for closed stream %ui", "upstream sent frame for closed stream %ui",
ctx->stream_id); ctx->stream_id);
@ -2131,13 +2135,23 @@ ngx_http_grpc_filter(void *data, ssize_t bytes)
return NGX_ERROR; return NGX_ERROR;
} }
if (ctx->error || !ctx->done) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"upstream rejected request with error %ui", "upstream rejected request with error %ui",
ctx->error); ctx->error);
return NGX_ERROR; return NGX_ERROR;
} }
if (ctx->rst) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"upstream sent frame for closed stream %ui",
ctx->stream_id);
return NGX_ERROR;
}
ctx->rst = 1;
}
if (ctx->type == NGX_HTTP_V2_GOAWAY_FRAME) { if (ctx->type == NGX_HTTP_V2_GOAWAY_FRAME) {
rc = ngx_http_grpc_parse_goaway(r, ctx, b); rc = ngx_http_grpc_parse_goaway(r, ctx, b);