gRPC: special handling of the TE request header.

According to the gRPC protocol specification, the "TE" header is used
to detect incompatible proxies, and at least grpc-c server rejects
requests without "TE: trailers".

To preserve the logic, we have to pass "TE: trailers" to the backend if
and only if the original request contains "trailers" in the "TE" header.
Note that no other TE values are allowed in HTTP/2, so we have to remove
anything else.
This commit is contained in:
Maxim Dounin 2018-03-17 23:04:25 +03:00
parent 56ad960e7a
commit 6a0d9e5b2d
3 changed files with 72 additions and 2 deletions

View File

@ -176,6 +176,10 @@ static void ngx_http_grpc_abort_request(ngx_http_request_t *r);
static void ngx_http_grpc_finalize_request(ngx_http_request_t *r,
ngx_int_t rc);
static ngx_int_t ngx_http_grpc_internal_trailers_variable(
ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_http_grpc_add_variables(ngx_conf_t *cf);
static void *ngx_http_grpc_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_grpc_merge_loc_conf(ngx_conf_t *cf,
void *parent, void *child);
@ -419,7 +423,7 @@ static ngx_command_t ngx_http_grpc_commands[] = {
static ngx_http_module_t ngx_http_grpc_module_ctx = {
NULL, /* preconfiguration */
ngx_http_grpc_add_variables, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
@ -463,10 +467,10 @@ static u_char ngx_http_grpc_connection_start[] =
static ngx_keyval_t ngx_http_grpc_headers[] = {
{ ngx_string("Content-Length"), ngx_string("$content_length") },
{ ngx_string("TE"), ngx_string("$grpc_internal_trailers") },
{ ngx_string("Host"), ngx_string("") },
{ ngx_string("Connection"), ngx_string("") },
{ ngx_string("Transfer-Encoding"), ngx_string("") },
{ ngx_string("TE"), ngx_string("") },
{ ngx_string("Keep-Alive"), ngx_string("") },
{ ngx_string("Expect"), ngx_string("") },
{ ngx_string("Upgrade"), ngx_string("") },
@ -486,6 +490,16 @@ static ngx_str_t ngx_http_grpc_hide_headers[] = {
};
static ngx_http_variable_t ngx_http_grpc_vars[] = {
{ ngx_string("grpc_internal_trailers"), NULL,
ngx_http_grpc_internal_trailers_variable, 0,
NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
ngx_http_null_variable
};
static ngx_int_t
ngx_http_grpc_handler(ngx_http_request_t *r)
{
@ -3996,6 +4010,57 @@ ngx_http_grpc_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
}
static ngx_int_t
ngx_http_grpc_internal_trailers_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data)
{
ngx_table_elt_t *te;
te = r->headers_in.te;
if (te == NULL) {
v->not_found = 1;
return NGX_OK;
}
if (ngx_strlcasestrn(te->value.data, te->value.data + te->value.len,
(u_char *) "trailers", 8 - 1)
== NULL)
{
v->not_found = 1;
return NGX_OK;
}
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
v->data = (u_char *) "trailers";
v->len = sizeof("trailers") - 1;
return NGX_OK;
}
static ngx_int_t
ngx_http_grpc_add_variables(ngx_conf_t *cf)
{
ngx_http_variable_t *var, *v;
for (v = ngx_http_grpc_vars; v->name.len; v++) {
var = ngx_http_add_variable(cf, &v->name, v->flags);
if (var == NULL) {
return NGX_ERROR;
}
var->get_handler = v->get_handler;
var->data = v->data;
}
return NGX_OK;
}
static void *
ngx_http_grpc_create_loc_conf(ngx_conf_t *cf)
{

View File

@ -132,6 +132,10 @@ ngx_http_header_t ngx_http_headers_in[] = {
offsetof(ngx_http_headers_in_t, transfer_encoding),
ngx_http_process_header_line },
{ ngx_string("TE"),
offsetof(ngx_http_headers_in_t, te),
ngx_http_process_header_line },
{ ngx_string("Expect"),
offsetof(ngx_http_headers_in_t, expect),
ngx_http_process_unique_header_line },

View File

@ -197,6 +197,7 @@ typedef struct {
ngx_table_elt_t *if_range;
ngx_table_elt_t *transfer_encoding;
ngx_table_elt_t *te;
ngx_table_elt_t *expect;
ngx_table_elt_t *upgrade;