[njs] HTTP: added support for multi-valued headers in r.headersOut.
Dmitry Volyntsev
xeioex at nginx.com
Tue Apr 21 12:37:32 UTC 2020
details: https://hg.nginx.org/njs/rev/3df09cf5345c
branches:
changeset: 1379:3df09cf5345c
user: Dmitry Volyntsev <xeioex at nginx.com>
date: Tue Apr 21 12:37:00 2020 +0000
description:
HTTP: added support for multi-valued headers in r.headersOut.
1) Added support for an array of values in assignments:
r.headersOut['Set-Cookie'] = ['a', '', 'b'] will result in
Set-Cookie: a
Set-Cookie: b
headers in output. All previous Set-Cookie are deleted.
Only the last element in the table will take effect for standard
headers such as Content-Type that only accept a single value.
r.headersOut.foo = [] is the same as
delete r.headersOut.foo
2) Improved getting of special arrays:
Set-Cookie is always returned as an array.
Duplicates of Age, Content-Length, Content-Type, ETag, Expires,
Last-Modified, Location, Retry-After are ignored.
All other duplicate header values are joined together with ','.
This closes #266 issue on Github.
diffstat:
nginx/ngx_http_js_module.c | 516 ++++++++++++++++++++++++++++++++++++++------
1 files changed, 443 insertions(+), 73 deletions(-)
diffs (595 lines):
diff -r 55dd0c0acb66 -r 3df09cf5345c nginx/ngx_http_js_module.c
--- a/nginx/ngx_http_js_module.c Tue Apr 21 11:57:29 2020 +0000
+++ b/nginx/ngx_http_js_module.c Tue Apr 21 12:37:00 2020 +0000
@@ -57,6 +57,15 @@ typedef struct {
} ngx_http_js_event_t;
+typedef struct {
+ njs_str_t name;
+ njs_int_t (*handler)(njs_vm_t *vm, ngx_http_request_t *r,
+ njs_str_t *name, njs_value_t *setval,
+ njs_value_t *retval);
+
+} ngx_http_js_header_t;
+
+
static ngx_int_t ngx_http_js_content_handler(ngx_http_request_t *r);
static void ngx_http_js_content_event_handler(ngx_http_request_t *r);
static void ngx_http_js_content_write_event_handler(ngx_http_request_t *r);
@@ -78,6 +87,25 @@ static ngx_table_elt_t *ngx_http_js_get_
static njs_int_t ngx_http_js_ext_header_out(njs_vm_t *vm,
njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval,
njs_value_t *retval);
+static njs_int_t ngx_http_js_header_out_single(njs_vm_t *vm,
+ ngx_http_request_t *r, njs_str_t *v, njs_value_t *setval,
+ njs_value_t *retval);
+static njs_int_t ngx_http_js_header_out_special(njs_vm_t *vm,
+ ngx_http_request_t *r, njs_str_t *v, njs_value_t *setval,
+ njs_value_t *retval, ngx_table_elt_t **hh);
+static njs_int_t ngx_http_js_header_out_array(njs_vm_t *vm,
+ ngx_http_request_t *r, njs_str_t *v, njs_value_t *setval,
+ njs_value_t *retval);
+static njs_int_t ngx_http_js_header_out_generic(njs_vm_t *vm,
+ ngx_http_request_t *r, njs_str_t *v, njs_value_t *setval,
+ njs_value_t *retval);
+static njs_int_t ngx_http_js_content_length(njs_vm_t *vm, ngx_http_request_t *r,
+ njs_str_t *name, njs_value_t *setval, njs_value_t *retval);
+static njs_int_t ngx_http_js_content_encoding(njs_vm_t *vm,
+ ngx_http_request_t *r, njs_str_t *name, njs_value_t *setval,
+ njs_value_t *retval);
+static njs_int_t ngx_http_js_content_type(njs_vm_t *vm, ngx_http_request_t *r,
+ njs_str_t *name, njs_value_t *setval, njs_value_t *retval);
static njs_int_t ngx_http_js_ext_keys_header_out(njs_vm_t *vm,
njs_value_t *value, njs_value_t *keys);
static njs_int_t ngx_http_js_ext_status(njs_vm_t *vm, njs_object_prop_t *prop,
@@ -897,14 +925,24 @@ static njs_int_t
ngx_http_js_ext_header_out(njs_vm_t *vm, njs_object_prop_t *prop,
njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
{
- u_char *p, *start;
- njs_int_t rc;
- ngx_int_t n;
- njs_str_t *v, s, name;
- ngx_str_t *hdr;
- ngx_table_elt_t *h;
- ngx_http_request_t *r;
- u_char content_len[NGX_OFF_T_LEN];
+ njs_int_t rc;
+ njs_str_t name;
+ ngx_http_request_t *r;
+ ngx_http_js_header_t *h;
+
+ static ngx_http_js_header_t headers_out[] = {
+ { njs_str("Age"), ngx_http_js_header_out_single },
+ { njs_str("Content-Type"), ngx_http_js_content_type },
+ { njs_str("Content-Length"), ngx_http_js_content_length },
+ { njs_str("Content-Encoding"), ngx_http_js_content_encoding },
+ { njs_str("Etag"), ngx_http_js_header_out_single },
+ { njs_str("Expires"), ngx_http_js_header_out_single },
+ { njs_str("Last-Modified"), ngx_http_js_header_out_single },
+ { njs_str("Location"), ngx_http_js_header_out_single },
+ { njs_str("Set-Cookie"), ngx_http_js_header_out_array },
+ { njs_str("Retry-After"), ngx_http_js_header_out_single },
+ { njs_str(""), ngx_http_js_header_out_generic },
+ };
r = njs_vm_external(vm, value);
if (r == NULL) {
@@ -924,73 +962,76 @@ ngx_http_js_ext_header_out(njs_vm_t *vm,
return NJS_DECLINED;
}
- v = &name;
+ for (h = headers_out; h->name.length > 0; h++) {
+ if (h->name.length == name.length
+ && ngx_strncasecmp(h->name.start, name.start, name.length) == 0)
+ {
+ break;
+ }
+ }
+
+ return h->handler(vm, r, &name, setval, retval);
+}
+
+
+static njs_int_t
+ngx_http_js_header_out_single(njs_vm_t *vm, ngx_http_request_t *r,
+ njs_str_t *name, njs_value_t *setval, njs_value_t *retval)
+{
+ if (retval != NULL && setval == NULL) {
+ return ngx_http_js_header_out_special(vm, r, name, setval, retval,
+ NULL);
+ }
+
+ return ngx_http_js_header_out_generic(vm, r, name, setval, retval);
+}
+
+
+static njs_int_t
+ngx_http_js_header_out_special(njs_vm_t *vm, ngx_http_request_t *r,
+ njs_str_t *v, njs_value_t *setval, njs_value_t *retval,
+ ngx_table_elt_t **hh)
+{
+ u_char *p;
+ int64_t length;
+ njs_int_t rc;
+ njs_str_t s;
+ ngx_list_t *headers;
+ ngx_table_elt_t *h;
+ njs_opaque_value_t lvalue;
+
+ headers = &r->headers_out.headers;
if (retval != NULL && setval == NULL) {
- if (v->length == njs_length("Content-Type")
- && ngx_strncasecmp(v->start, (u_char *) "Content-Type",
- v->length) == 0)
- {
- hdr = &r->headers_out.content_type;
- return njs_vm_value_string_set(vm, retval, hdr->data, hdr->len);
- }
-
- if (v->length == njs_length("Content-Length")
- && ngx_strncasecmp(v->start, (u_char *) "Content-Length",
- v->length) == 0)
- {
- if (r->headers_out.content_length == NULL
- && r->headers_out.content_length_n >= 0)
- {
- p = ngx_sprintf(content_len, "%O",
- r->headers_out.content_length_n);
-
- start = njs_vm_value_string_alloc(vm, retval, p - content_len);
- if (start == NULL) {
- return NJS_ERROR;
- }
-
- ngx_memcpy(start, content_len, p - content_len);
-
- return NJS_OK;
- }
- }
-
- h = ngx_http_js_get_header(&r->headers_out.headers.part, v->start,
- v->length);
+ h = ngx_http_js_get_header(&headers->part, v->start, v->length);
if (h == NULL) {
njs_value_undefined_set(retval);
return NJS_DECLINED;
}
- return njs_vm_value_string_set(vm, retval, h->value.data, h->value.len);
- }
-
- if (setval != NULL) {
- rc = ngx_http_js_string(vm, setval, &s);
+ rc = njs_vm_value_string_set(vm, retval, h->value.data, h->value.len);
if (rc != NJS_OK) {
return NJS_ERROR;
}
- } else {
- s.length = 0;
- s.start = NULL;
- }
-
- if (v->length == njs_length("Content-Type")
- && ngx_strncasecmp(v->start, (u_char *) "Content-Type",
- v->length) == 0)
- {
- r->headers_out.content_type.len = s.length;
- r->headers_out.content_type_len = r->headers_out.content_type.len;
- r->headers_out.content_type.data = s.start;
- r->headers_out.content_type_lowcase = NULL;
-
return NJS_OK;
}
- h = ngx_http_js_get_header(&r->headers_out.headers.part, v->start,
- v->length);
+ if (setval != NULL && njs_value_is_array(setval)) {
+ rc = njs_vm_array_length(vm, setval, &length);
+ if (rc != NJS_OK) {
+ return NJS_ERROR;
+ }
+
+ setval = njs_vm_array_prop(vm, setval, length - 1, &lvalue);
+ }
+
+ rc = ngx_http_js_string(vm, setval, &s);
+ if (rc != NJS_OK) {
+ return NJS_ERROR;
+ }
+
+ h = ngx_http_js_get_header(&headers->part, v->start, v->length);
if (h != NULL && s.length == 0) {
h->hash = 0;
@@ -998,7 +1039,7 @@ ngx_http_js_ext_header_out(njs_vm_t *vm,
}
if (h == NULL && s.length != 0) {
- h = ngx_list_push(&r->headers_out.headers);
+ h = ngx_list_push(headers);
if (h == NULL) {
return NJS_ERROR;
}
@@ -1027,22 +1068,313 @@ ngx_http_js_ext_header_out(njs_vm_t *vm,
h->hash = 1;
}
- if (v->length == njs_length("Content-Encoding")
- && ngx_strncasecmp(v->start, (u_char *) "Content-Encoding",
- v->length) == 0)
- {
+ if (hh != NULL) {
+ *hh = h;
+ }
+
+ return NJS_OK;
+}
+
+
+static njs_int_t
+ngx_http_js_header_out_array(njs_vm_t *vm, ngx_http_request_t *r,
+ njs_str_t *name, njs_value_t *setval, njs_value_t *retval)
+{
+ size_t len;
+ u_char *data;
+ njs_int_t rc;
+ ngx_uint_t i;
+ ngx_list_t *headers;
+ njs_value_t *value;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header, *h;
+
+ headers = &r->headers_out.headers;
+
+ if (retval != NULL && setval == NULL) {
+ rc = njs_vm_array_alloc(vm, retval, 4);
+ if (rc != NJS_OK) {
+ return NJS_ERROR;
+ }
+
+ len = name->length;
+ data = name->start;
+
+ part = &headers->part;
+ header = part->elts;
+
+ for (i = 0; /* void */ ; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ h = &header[i];
+
+ if (h->hash == 0
+ || h->key.len != len
+ || ngx_strncasecmp(h->key.data, data, len) != 0)
+ {
+ continue;
+ }
+
+ value = njs_vm_array_push(vm, retval);
+ if (value == NULL) {
+ return NJS_ERROR;
+ }
+
+ rc = njs_vm_value_string_set(vm, value, h->value.data,
+ h->value.len);
+ if (rc != NJS_OK) {
+ return NJS_ERROR;
+ }
+ }
+
+ return NJS_OK;
+ }
+
+ return ngx_http_js_header_out_generic(vm, r, name, setval, retval);
+}
+
+
+static njs_int_t
+ngx_http_js_header_out_generic(njs_vm_t *vm, ngx_http_request_t *r,
+ njs_str_t *name, njs_value_t *setval, njs_value_t *retval)
+{
+ size_t len;
+ u_char *data, *p, *start, *end;
+ int64_t length;
+ njs_value_t *array;
+ njs_int_t rc;
+ njs_str_t s;
+ ngx_list_t *headers;
+ ngx_uint_t i;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header, *h;
+ njs_opaque_value_t lvalue;
+
+ headers = &r->headers_out.headers;
+ part = &headers->part;
+
+ if (retval != NULL && setval == NULL) {
+ header = part->elts;
+
+ p = NULL;
+ start = NULL;
+ end = NULL;
+
+ for (i = 0; /* void */ ; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ h = &header[i];
+
+ if (h->hash == 0
+ || h->key.len != name->length
+ || ngx_strncasecmp(h->key.data, name->start, name->length) != 0)
+ {
+ continue;
+ }
+
+ if (p == NULL) {
+ start = h->value.data;
+ end = h->value.data + h->value.len;
+ p = end;
+ continue;
+ }
+
+ if (p + h->value.len + 1 > end) {
+ len = njs_max(p + h->value.len + 1 - start, 2 * (end - start));
+
+ data = ngx_pnalloc(r->pool, len);
+ if (data == NULL) {
+ return NJS_ERROR;
+ }
+
+ p = ngx_cpymem(data, start, p - start);
+ start = data;
+ end = data + len;
+ }
+
+ *p++ = ',';
+ p = ngx_cpymem(p, h->value.data, h->value.len);
+ }
+
+ if (p == NULL) {
+ njs_value_undefined_set(retval);
+ return NJS_DECLINED;
+ }
+
+ return njs_vm_value_string_set(vm, retval, start, p - start);
+ }
+
+ header = part->elts;
+
+ for (i = 0; /* void */ ; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ h = &header[i];
+
+ if (h->hash == 0
+ || h->key.len != name->length
+ || ngx_strncasecmp(h->key.data, name->start, name->length) != 0)
+ {
+ continue;
+ }
+
+ h->hash = 0;
+ }
+
+ if (retval == NULL) {
+ return NJS_OK;
+ }
+
+ if (njs_value_is_array(setval)) {
+ array = setval;
+
+ rc = njs_vm_array_length(vm, array, &length);
+ if (rc != NJS_OK) {
+ return NJS_ERROR;
+ }
+
+ if (length == 0) {
+ return NJS_OK;
+ }
+
+ } else {
+ array = NULL;
+ length = 1;
+ }
+
+ i = 0;
+
+ for (i = 0; i < (ngx_uint_t) length; i++) {
+ if (array != NULL) {
+ setval = njs_vm_array_prop(vm, array, i, &lvalue);
+ }
+
+ rc = ngx_http_js_string(vm, setval, &s);
+ if (rc != NJS_OK) {
+ return NJS_ERROR;
+ }
+
+ if (s.length == 0) {
+ continue;
+ }
+
+ h = ngx_list_push(headers);
+ if (h == NULL) {
+ return NJS_ERROR;
+ }
+
+ p = ngx_pnalloc(r->pool, name->length);
+ if (p == NULL) {
+ return NJS_ERROR;
+ }
+
+ ngx_memcpy(p, name->start, name->length);
+
+ h->key.data = p;
+ h->key.len = name->length;
+
+ p = ngx_pnalloc(r->pool, s.length);
+ if (p == NULL) {
+ return NJS_ERROR;
+ }
+
+ ngx_memcpy(p, s.start, s.length);
+
+ h->value.data = p;
+ h->value.len = s.length;
+ h->hash = 1;
+ }
+
+ return NJS_OK;
+}
+
+
+static njs_int_t
+ngx_http_js_content_encoding(njs_vm_t *vm, ngx_http_request_t *r, njs_str_t *v,
+ njs_value_t *setval, njs_value_t *retval)
+{
+ njs_int_t rc;
+ ngx_table_elt_t *h;
+
+ rc = ngx_http_js_header_out_special(vm, r, v, setval, retval, &h);
+ if (rc != NJS_OK) {
+ return NJS_ERROR;
+ }
+
+ if (setval != NULL || retval == NULL) {
r->headers_out.content_encoding = h;
}
- if (v->length == njs_length("Content-Length")
- && ngx_strncasecmp(v->start, (u_char *) "Content-Length",
- v->length) == 0)
- {
+ return NJS_OK;
+}
+
+
+static njs_int_t
+ngx_http_js_content_length(njs_vm_t *vm, ngx_http_request_t *r, njs_str_t *v,
+ njs_value_t *setval, njs_value_t *retval)
+{
+ u_char *p, *start;
+ njs_int_t rc;
+ ngx_int_t n;
+ ngx_table_elt_t *h;
+ u_char content_len[NGX_OFF_T_LEN];
+
+ if (retval != NULL && setval == NULL) {
+ if (r->headers_out.content_length == NULL
+ && r->headers_out.content_length_n >= 0)
+ {
+ p = ngx_sprintf(content_len, "%O", r->headers_out.content_length_n);
+
+ start = njs_vm_value_string_alloc(vm, retval, p - content_len);
+ if (start == NULL) {
+ return NJS_ERROR;
+ }
+
+ ngx_memcpy(start, content_len, p - content_len);
+
+ return NJS_OK;
+ }
+ }
+
+ rc = ngx_http_js_header_out_special(vm, r, v, setval, retval, &h);
+ if (rc != NJS_OK) {
+ return NJS_ERROR;
+ }
+
+ if (setval != NULL || retval == NULL) {
if (h != NULL) {
- n = ngx_atoi(s.start, s.length);
+ n = ngx_atoi(h->value.data, h->value.len);
if (n == NGX_ERROR) {
h->hash = 0;
- njs_vm_error(vm, "failed converting argument to integer");
+ njs_vm_error(vm, "failed converting argument "
+ "to positive integer");
return NJS_ERROR;
}
@@ -1059,6 +1391,44 @@ ngx_http_js_ext_header_out(njs_vm_t *vm,
static njs_int_t
+ngx_http_js_content_type(njs_vm_t *vm, ngx_http_request_t *r, njs_str_t *v,
+ njs_value_t *setval, njs_value_t *retval)
+{
+ int64_t length;
+ njs_int_t rc;
+ njs_str_t s;
+ ngx_str_t *hdr;
+ njs_opaque_value_t lvalue;
+
+ if (retval != NULL && setval == NULL) {
+ hdr = &r->headers_out.content_type;
+ return njs_vm_value_string_set(vm, retval, hdr->data, hdr->len);
+ }
+
+ if (setval != NULL && njs_value_is_array(setval)) {
+ rc = njs_vm_array_length(vm, setval, &length);
+ if (rc != NJS_OK) {
+ return NJS_ERROR;
+ }
+
+ setval = njs_vm_array_prop(vm, setval, length - 1, &lvalue);
+ }
+
+ rc = ngx_http_js_string(vm, setval, &s);
+ if (rc != NJS_OK) {
+ return NJS_ERROR;
+ }
+
+ r->headers_out.content_type.len = s.length;
+ r->headers_out.content_type_len = r->headers_out.content_type.len;
+ r->headers_out.content_type.data = s.start;
+ r->headers_out.content_type_lowcase = NULL;
+
+ return NJS_OK;
+}
+
+
+static njs_int_t
ngx_http_js_ext_keys_header_out(njs_vm_t *vm, njs_value_t *value,
njs_value_t *keys)
{
@@ -2370,7 +2740,7 @@ ngx_http_js_handle_event(ngx_http_reques
static njs_int_t
ngx_http_js_string(njs_vm_t *vm, njs_value_t *value, njs_str_t *str)
{
- if (!njs_value_is_null_or_undefined(value)) {
+ if (value != NULL && !njs_value_is_null_or_undefined(value)) {
if (njs_vm_value_to_string(vm, str, value) == NJS_ERROR) {
return NJS_ERROR;
}
More information about the nginx-devel
mailing list