Created
March 9, 2026 22:28
-
-
Save xeioex/ecdad59bd9cd47adf43f967e5e48da10 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| From 55165c8b2834ca46901b38a2910f0502a35a9bc3 Mon Sep 17 00:00:00 2001 | |
| From: Dmitry Volyntsev <xeioex@nginx.com> | |
| Date: Thu, 5 Mar 2026 20:44:02 -0800 | |
| Subject: [PATCH 1/2] Modules: added var alias for variables object. | |
| This allows to reference nginx variables with a shorter | |
| syntax: `r.var.uri` or `s.var.bytes_sent`. | |
| --- | |
| nginx/ngx_http_js_module.c | 24 ++++++++++++++++++++++++ | |
| nginx/ngx_stream_js_module.c | 24 ++++++++++++++++++++++++ | |
| nginx/t/js_variables.t | 30 +++++++++++++++++++++++++++--- | |
| nginx/t/stream_js_variables.t | 33 +++++++++++++++++++++++++++++++-- | |
| 4 files changed, 106 insertions(+), 5 deletions(-) | |
| diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c | |
| index 20308a81..20bc1a64 100644 | |
| --- a/nginx/ngx_http_js_module.c | |
| +++ b/nginx/ngx_http_js_module.c | |
| @@ -990,6 +990,16 @@ static njs_external_t ngx_http_js_ext_request[] = { | |
| } | |
| }, | |
| + { | |
| + .flags = NJS_EXTERN_OBJECT, | |
| + .name.string = njs_str("var"), | |
| + .u.object = { | |
| + .writable = 1, | |
| + .prop_handler = ngx_http_js_ext_variables, | |
| + .magic32 = NGX_JS_STRING, | |
| + } | |
| + }, | |
| + | |
| { | |
| .flags = NJS_EXTERN_OBJECT, | |
| .name.string = njs_str("variables"), | |
| @@ -1034,6 +1044,16 @@ static njs_external_t ngx_http_js_ext_periodic_session[] = { | |
| } | |
| }, | |
| + { | |
| + .flags = NJS_EXTERN_OBJECT, | |
| + .name.string = njs_str("var"), | |
| + .u.object = { | |
| + .writable = 1, | |
| + .prop_handler = ngx_http_js_periodic_session_variables, | |
| + .magic32 = NGX_JS_STRING, | |
| + } | |
| + }, | |
| + | |
| { | |
| .flags = NJS_EXTERN_OBJECT, | |
| .name.string = njs_str("variables"), | |
| @@ -1155,6 +1175,8 @@ static const JSCFunctionListEntry ngx_http_qjs_ext_request[] = { | |
| JS_CFUNC_DEF("subrequest", 3, ngx_http_qjs_ext_subrequest), | |
| JS_CGETSET_MAGIC_DEF("uri", ngx_http_qjs_ext_string, NULL, | |
| offsetof(ngx_http_request_t, uri)), | |
| + JS_CGETSET_MAGIC_DEF("var", ngx_http_qjs_ext_variables, | |
| + NULL, NGX_JS_STRING), | |
| JS_CGETSET_MAGIC_DEF("variables", ngx_http_qjs_ext_variables, | |
| NULL, NGX_JS_STRING), | |
| JS_CFUNC_MAGIC_DEF("warn", 1, ngx_http_qjs_ext_log, NGX_LOG_WARN), | |
| @@ -1166,6 +1188,8 @@ static const JSCFunctionListEntry ngx_http_qjs_ext_periodic[] = { | |
| JS_PROP_CONFIGURABLE), | |
| JS_CGETSET_MAGIC_DEF("rawVariables", ngx_http_qjs_ext_periodic_variables, | |
| NULL, NGX_JS_BUFFER), | |
| + JS_CGETSET_MAGIC_DEF("var", ngx_http_qjs_ext_periodic_variables, | |
| + NULL, NGX_JS_STRING), | |
| JS_CGETSET_MAGIC_DEF("variables", ngx_http_qjs_ext_periodic_variables, | |
| NULL, NGX_JS_STRING), | |
| }; | |
| diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c | |
| index b21b701d..76d8907d 100644 | |
| --- a/nginx/ngx_stream_js_module.c | |
| +++ b/nginx/ngx_stream_js_module.c | |
| @@ -685,6 +685,16 @@ static njs_external_t ngx_stream_js_ext_session[] = { | |
| } | |
| }, | |
| + { | |
| + .flags = NJS_EXTERN_OBJECT, | |
| + .name.string = njs_str("var"), | |
| + .u.object = { | |
| + .writable = 1, | |
| + .prop_handler = ngx_stream_js_ext_variables, | |
| + .magic32 = NGX_JS_STRING, | |
| + } | |
| + }, | |
| + | |
| { | |
| .flags = NJS_EXTERN_OBJECT, | |
| .name.string = njs_str("variables"), | |
| @@ -729,6 +739,16 @@ static njs_external_t ngx_stream_js_ext_periodic_session[] = { | |
| } | |
| }, | |
| + { | |
| + .flags = NJS_EXTERN_OBJECT, | |
| + .name.string = njs_str("var"), | |
| + .u.object = { | |
| + .writable = 1, | |
| + .prop_handler = ngx_stream_js_periodic_variables, | |
| + .magic32 = NGX_JS_STRING, | |
| + } | |
| + }, | |
| + | |
| { | |
| .flags = NJS_EXTERN_OBJECT, | |
| .name.string = njs_str("variables"), | |
| @@ -853,6 +873,8 @@ static const JSCFunctionListEntry ngx_stream_qjs_ext_session[] = { | |
| JS_CFUNC_DEF("setReturnValue", 1, ngx_stream_qjs_ext_set_return_value), | |
| JS_CGETSET_MAGIC_DEF("status", ngx_stream_qjs_ext_uint, NULL, | |
| offsetof(ngx_stream_session_t, status)), | |
| + JS_CGETSET_MAGIC_DEF("var", ngx_stream_qjs_ext_variables, | |
| + NULL, NGX_JS_STRING), | |
| JS_CGETSET_MAGIC_DEF("variables", ngx_stream_qjs_ext_variables, | |
| NULL, NGX_JS_STRING), | |
| JS_CFUNC_MAGIC_DEF("warn", 1, ngx_stream_qjs_ext_log, NGX_LOG_WARN), | |
| @@ -864,6 +886,8 @@ static const JSCFunctionListEntry ngx_stream_qjs_ext_periodic[] = { | |
| JS_PROP_CONFIGURABLE), | |
| JS_CGETSET_MAGIC_DEF("rawVariables", ngx_stream_qjs_ext_periodic_variables, | |
| NULL, NGX_JS_BUFFER), | |
| + JS_CGETSET_MAGIC_DEF("var", ngx_stream_qjs_ext_periodic_variables, | |
| + NULL, NGX_JS_STRING), | |
| JS_CGETSET_MAGIC_DEF("variables", ngx_stream_qjs_ext_periodic_variables, | |
| NULL, NGX_JS_STRING), | |
| }; | |
| diff --git a/nginx/t/js_variables.t b/nginx/t/js_variables.t | |
| index 6f1eb173..a22bbced 100644 | |
| --- a/nginx/t/js_variables.t | |
| +++ b/nginx/t/js_variables.t | |
| @@ -35,7 +35,8 @@ events { | |
| http { | |
| %%TEST_GLOBALS_HTTP%% | |
| - js_set $test_var test.variable; | |
| + js_set $test_var test.variable; | |
| + js_set $test_short_var test.short_var; | |
| js_import test.js; | |
| @@ -50,10 +51,18 @@ http { | |
| return 200 $test_var$foo; | |
| } | |
| + location /var_set_short { | |
| + return 200 $test_short_var$foo; | |
| + } | |
| + | |
| location /content_set { | |
| js_content test.content_set; | |
| } | |
| + location /content_set_short { | |
| + js_content test.short_content_set; | |
| + } | |
| + | |
| location /not_found_set { | |
| js_content test.not_found_set; | |
| } | |
| @@ -72,11 +81,21 @@ $t->write_file('test.js', <<EOF); | |
| return 'test_var'; | |
| } | |
| + function short_var(r) { | |
| + r.var.foo = r.var.arg_a; | |
| + return 'short_var'; | |
| + } | |
| + | |
| function content_set(r) { | |
| r.variables.foo = r.variables.arg_a; | |
| r.return(200, r.variables.foo); | |
| } | |
| + function short_content_set(r) { | |
| + r.var.foo = r.var.arg_a; | |
| + r.return(200, r.var.foo); | |
| + } | |
| + | |
| function not_found_set(r) { | |
| try { | |
| r.variables.unknown = 1; | |
| @@ -98,16 +117,21 @@ $t->write_file('test.js', <<EOF); | |
| r.return(200, name); | |
| } | |
| - export default {variable, content_set, not_found_set, variable_lowkey}; | |
| + export default {variable, short_var, content_set, short_content_set, | |
| + not_found_set, variable_lowkey}; | |
| EOF | |
| -$t->try_run('no njs')->plan(5); | |
| +$t->try_run('no njs')->plan(7); | |
| ############################################################################### | |
| like(http_get('/var_set?a=bar'), qr/test_varbar/, 'var set'); | |
| +like(http_get('/var_set_short?a=bar'), qr/short_varbar/, | |
| + 'short var set via r.var'); | |
| like(http_get('/content_set?a=bar'), qr/bar/, 'content set'); | |
| +like(http_get('/content_set_short?a=bar'), qr/bar/, | |
| + 'short content set via r.var'); | |
| like(http_get('/not_found_set'), qr/variable not found/, 'not found exception'); | |
| like(http_get('/variable_lowkey'), qr/X{16}/, | |
| 'variable name is not overwritten while reading'); | |
| diff --git a/nginx/t/stream_js_variables.t b/nginx/t/stream_js_variables.t | |
| index 29e6c33e..ff6804b1 100644 | |
| --- a/nginx/t/stream_js_variables.t | |
| +++ b/nginx/t/stream_js_variables.t | |
| @@ -37,7 +37,9 @@ stream { | |
| %%TEST_GLOBALS_STREAM%% | |
| js_set $test_var test.variable; | |
| + js_set $test_short_var test.short_var; | |
| js_set $test_not_found test.not_found; | |
| + js_set $test_short_not_found test.short_not_found; | |
| js_import test.js; | |
| @@ -50,6 +52,16 @@ stream { | |
| listen 127.0.0.1:8082; | |
| return $test_not_found; | |
| } | |
| + | |
| + server { | |
| + listen 127.0.0.1:8083; | |
| + return $test_short_var$status; | |
| + } | |
| + | |
| + server { | |
| + listen 127.0.0.1:8084; | |
| + return $test_short_not_found; | |
| + } | |
| } | |
| EOF | |
| @@ -60,6 +72,11 @@ $t->write_file('test.js', <<EOF); | |
| return 'test_var'; | |
| } | |
| + function short_var(s) { | |
| + s.var.status = 401; | |
| + return 'short_var'; | |
| + } | |
| + | |
| function not_found(s) { | |
| try { | |
| s.variables.unknown = 1; | |
| @@ -68,16 +85,28 @@ $t->write_file('test.js', <<EOF); | |
| } | |
| } | |
| - export default {variable, not_found}; | |
| + function short_not_found(s) { | |
| + try { | |
| + s.var.unknown = 1; | |
| + } catch (e) { | |
| + return 'short_not_found'; | |
| + } | |
| + } | |
| + | |
| + export default {variable, short_var, not_found, short_not_found}; | |
| EOF | |
| -$t->try_run('no stream njs available')->plan(2); | |
| +$t->try_run('no stream njs available')->plan(4); | |
| ############################################################################### | |
| is(stream('127.0.0.1:' . port(8081))->read(), 'test_var400', 'var set'); | |
| is(stream('127.0.0.1:' . port(8082))->read(), 'not_found', 'not found set'); | |
| +is(stream('127.0.0.1:' . port(8083))->read(), 'short_var401', | |
| + 'short var set via s.var'); | |
| +is(stream('127.0.0.1:' . port(8084))->read(), 'short_not_found', | |
| + 'short not found set via s.var'); | |
| $t->stop(); | |
| -- | |
| 2.43.0 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| From 4abe43c4b470cc140e36c0cc949971751d08180f Mon Sep 17 00:00:00 2001 | |
| From: Dmitry Volyntsev <xeioex@nginx.com> | |
| Date: Wed, 11 Feb 2026 13:12:44 -0800 | |
| Subject: [PATCH 2/2] Modules: js_set inline JavaScript expressions. | |
| Added support for inline JavaScript expressions in the js_set directive. | |
| Previously, js_set only accepted function references: | |
| js_set $var main.handler; | |
| Now it also accepts inline expressions: | |
| js_set $var '(r.uri)'; | |
| js_set $var 'r.headersIn["Host"] || "none"'; | |
| Additionally, nginx-style $variable references are expanded to | |
| the corresponding JavaScript variable access. For example: | |
| js_set $var '$uri.toUpperCase()'; | |
| is equivalent to: | |
| js_set $var 'r.variables.uri.toUpperCase()'; | |
| In stream context, $var expands to s.variables.var. | |
| --- | |
| nginx/ngx_http_js_module.c | 32 +- | |
| nginx/ngx_js.c | 526 ++++++++++++++++++++++++--- | |
| nginx/ngx_js.h | 14 + | |
| nginx/ngx_stream_js_module.c | 31 +- | |
| nginx/t/js_inline.t | 179 +++++++++ | |
| nginx/t/js_inline_only.t | 76 ++++ | |
| nginx/t/js_variables_location.t | 5 +- | |
| nginx/t/stream_js_variables_server.t | 5 +- | |
| 8 files changed, 783 insertions(+), 85 deletions(-) | |
| create mode 100644 nginx/t/js_inline.t | |
| create mode 100644 nginx/t/js_inline_only.t | |
| diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c | |
| index 20bc1a64..05aa3121 100644 | |
| --- a/nginx/ngx_http_js_module.c | |
| +++ b/nginx/ngx_http_js_module.c | |
| @@ -1644,8 +1644,9 @@ ngx_http_js_variable_set(ngx_http_request_t *r, ngx_http_variable_value_t *v, | |
| if (rc == NGX_DECLINED) { | |
| ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, | |
| - "no \"js_import\" directives found for \"js_set\" handler" | |
| - " \"%V\" in the current scope", fname); | |
| + "no \"js_import\" or inline expression found" | |
| + " for \"js_set\" handler \"%V\" at %s:%ui", | |
| + fname, vdata->file_name, vdata->line); | |
| v->not_found = 1; | |
| return NGX_OK; | |
| } | |
| @@ -1732,6 +1733,7 @@ ngx_http_js_init_vm(ngx_http_request_t *r, njs_int_t proto_id) | |
| } | |
| ngx_js_ctx_init((ngx_js_ctx_t *) ctx, r->connection->log); | |
| + ctx->conf = (ngx_js_loc_conf_t *) jlcf; | |
| ngx_http_set_ctx(r, ctx, ngx_http_js_module); | |
| } | |
| @@ -8040,9 +8042,12 @@ invalid: | |
| static char * | |
| ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) | |
| { | |
| - ngx_str_t *value; | |
| - ngx_js_set_t *data, *prev; | |
| - ngx_http_variable_t *v; | |
| + ngx_str_t *value; | |
| + ngx_js_set_t *data, *prev; | |
| + ngx_http_variable_t *v; | |
| + ngx_http_js_loc_conf_t *jlcf; | |
| + | |
| + static ngx_uint_t ngx_http_js_inline_index; | |
| value = cf->args->elts; | |
| @@ -8065,20 +8070,25 @@ ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) | |
| return NGX_CONF_ERROR; | |
| } | |
| - data->fname = value[2]; | |
| - data->flags = 0; | |
| - data->file_name = cf->conf_file->file.name.data; | |
| - data->line = cf->conf_file->line; | |
| + jlcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_js_module); | |
| + | |
| + if (ngx_js_set_init(cf, &jlcf->inlines, &ngx_http_js_inline_index, | |
| + &value[2], "r", data) | |
| + != NGX_OK) | |
| + { | |
| + return NGX_CONF_ERROR; | |
| + } | |
| if (v->get_handler == ngx_http_js_variable_set) { | |
| prev = (ngx_js_set_t *) v->data; | |
| if (data->fname.len != prev->fname.len | |
| - || ngx_strncmp(data->fname.data, prev->fname.data, data->fname.len) != 0) | |
| + || ngx_strncmp(data->fname.data, prev->fname.data, | |
| + data->fname.len) != 0) | |
| { | |
| ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | |
| "variable \"%V\" is redeclared with " | |
| - "different function name", &value[1]); | |
| + "different handler", &value[1]); | |
| return NGX_CONF_ERROR; | |
| } | |
| } | |
| diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c | |
| index bd1b5f23..25f59f77 100644 | |
| --- a/nginx/ngx_js.c | |
| +++ b/nginx/ngx_js.c | |
| @@ -571,6 +571,7 @@ ngx_engine_njs_init(ngx_engine_t *engine, ngx_engine_opts_t *opts) | |
| vm_options.argc = ngx_argc; | |
| vm_options.init = 1; | |
| + vm_options.file.length = opts->file.length; | |
| vm_options.file.start = njs_mp_alloc(engine->pool, opts->file.length); | |
| if (vm_options.file.start == NULL) { | |
| return NGX_ERROR; | |
| @@ -599,21 +600,154 @@ ngx_engine_njs_init(ngx_engine_t *engine, ngx_engine_opts_t *opts) | |
| } | |
| +/* | |
| + * Parse line number from JS exception stack trace. | |
| + * | |
| + * njs format: | |
| + * SyntaxError: ...\n at /path:LINE\n | |
| + * | |
| + * QuickJS format: | |
| + * SyntaxError: ...\n at <main>:LINE:COL\n | |
| + * | |
| + * Returns 0 if no line number found. | |
| + */ | |
| +static ngx_uint_t | |
| +ngx_js_error_line(u_char *start, size_t len) | |
| +{ | |
| + u_char *p, *end; | |
| + ngx_uint_t line; | |
| + | |
| + end = start + len; | |
| + | |
| + p = ngx_strlchr(start, end, '\n'); | |
| + if (p == NULL) { | |
| + return 0; | |
| + } | |
| + | |
| + p++; | |
| + | |
| + while (p < end && *p == ' ') { | |
| + p++; | |
| + } | |
| + | |
| + if (end - p < 3 || p[0] != 'a' || p[1] != 't' || p[2] != ' ') { | |
| + return 0; | |
| + } | |
| + | |
| + p += 3; | |
| + | |
| + /* find first ':' followed by a digit */ | |
| + | |
| + while (p < end && *p != '\n') { | |
| + if (*p == ':' && p + 1 < end && p[1] >= '0' && p[1] <= '9') { | |
| + p++; | |
| + | |
| + line = 0; | |
| + | |
| + while (p < end && *p >= '0' && *p <= '9') { | |
| + line = line * 10 + (*p - '0'); | |
| + p++; | |
| + } | |
| + | |
| + return line; | |
| + } | |
| + | |
| + p++; | |
| + } | |
| + | |
| + return 0; | |
| +} | |
| + | |
| + | |
| +static ngx_js_inline_t * | |
| +ngx_js_inline_map(ngx_js_loc_conf_t *conf, u_char *start, size_t len) | |
| +{ | |
| + ngx_uint_t i, line; | |
| + ngx_js_inline_t *inl; | |
| + | |
| + line = ngx_js_error_line(start, len); | |
| + | |
| + if (line == 0 || conf->inlines == NGX_CONF_UNSET_PTR) { | |
| + return NULL; | |
| + } | |
| + | |
| + i = line - 1; | |
| + | |
| + if (conf->imports != NGX_CONF_UNSET_PTR) { | |
| + if (i < conf->imports->nelts) { | |
| + return NULL; | |
| + } | |
| + | |
| + i -= conf->imports->nelts; | |
| + } | |
| + | |
| + if (i >= conf->inlines->nelts) { | |
| + return NULL; | |
| + } | |
| + | |
| + inl = conf->inlines->elts; | |
| + | |
| + return &inl[i]; | |
| +} | |
| + | |
| + | |
| +static ngx_js_inline_t * | |
| +ngx_js_inline_from_stack(ngx_js_loc_conf_t *conf, u_char *start, size_t len) | |
| +{ | |
| + u_char *p, *end; | |
| + ngx_uint_t index; | |
| + ngx_js_inline_t *inl; | |
| + | |
| + static const u_char prefix[] = "__js_set_"; | |
| + | |
| + if (conf == NULL || conf->inlines == NGX_CONF_UNSET_PTR) { | |
| + return NULL; | |
| + } | |
| + | |
| + end = start + len; | |
| + p = start; | |
| + | |
| + while (p + (sizeof(prefix) - 1) <= end) { | |
| + if (ngx_strncmp(p, prefix, sizeof(prefix) - 1) == 0) { | |
| + p += sizeof(prefix) - 1; | |
| + | |
| + if (p >= end || *p < '0' || *p > '9') { | |
| + return NULL; | |
| + } | |
| + | |
| + index = 0; | |
| + | |
| + while (p < end && *p >= '0' && *p <= '9') { | |
| + index = index * 10 + (*p - '0'); | |
| + p++; | |
| + } | |
| + | |
| + if (index >= conf->inlines->nelts) { | |
| + return NULL; | |
| + } | |
| + | |
| + inl = conf->inlines->elts; | |
| + | |
| + return &inl[index]; | |
| + } | |
| + | |
| + p++; | |
| + } | |
| + | |
| + return NULL; | |
| +} | |
| + | |
| + | |
| static ngx_int_t | |
| ngx_engine_njs_compile(ngx_js_loc_conf_t *conf, ngx_log_t *log, u_char *start, | |
| size_t size) | |
| { | |
| - u_char *end; | |
| - njs_vm_t *vm; | |
| - njs_int_t rc; | |
| - njs_str_t text; | |
| - ngx_uint_t i; | |
| - njs_value_t *value; | |
| - njs_opaque_value_t exception, lvalue; | |
| - ngx_js_named_path_t *import; | |
| - | |
| - static const njs_str_t line_number_key = njs_str("lineNumber"); | |
| - static const njs_str_t file_name_key = njs_str("fileName"); | |
| + u_char *end; | |
| + njs_vm_t *vm; | |
| + njs_int_t rc; | |
| + njs_str_t text; | |
| + ngx_js_inline_t *inl; | |
| + njs_opaque_value_t exception; | |
| vm = conf->engine->u.njs.vm; | |
| @@ -633,26 +767,16 @@ ngx_engine_njs_compile(ngx_js_loc_conf_t *conf, ngx_log_t *log, u_char *start, | |
| njs_vm_exception_get(vm, njs_value_arg(&exception)); | |
| njs_vm_value_string(vm, &text, njs_value_arg(&exception)); | |
| - value = njs_vm_object_prop(vm, njs_value_arg(&exception), | |
| - &file_name_key, &lvalue); | |
| - if (value == NULL) { | |
| - value = njs_vm_object_prop(vm, njs_value_arg(&exception), | |
| - &line_number_key, &lvalue); | |
| + inl = ngx_js_inline_map(conf, text.start, text.length); | |
| + if (inl != NULL) { | |
| + ngx_log_error(NGX_LOG_EMERG, log, 0, "%*s, included at %s:%ui", | |
| + text.length, text.start, inl->file, inl->line); | |
| - if (value != NULL) { | |
| - i = njs_value_number(value) - 1; | |
| - | |
| - if (i < conf->imports->nelts) { | |
| - import = conf->imports->elts; | |
| - ngx_log_error(NGX_LOG_EMERG, log, 0, | |
| - "%*s, included in %s:%ui", text.length, | |
| - text.start, import[i].file, import[i].line); | |
| - return NGX_ERROR; | |
| - } | |
| - } | |
| + } else { | |
| + ngx_log_error(NGX_LOG_EMERG, log, 0, "%*s", | |
| + text.length, text.start); | |
| } | |
| - ngx_log_error(NGX_LOG_EMERG, log, 0, "%*s", text.length, text.start); | |
| return NGX_ERROR; | |
| } | |
| @@ -763,10 +887,12 @@ static ngx_int_t | |
| ngx_engine_njs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname, | |
| njs_opaque_value_t *args, njs_uint_t nargs) | |
| { | |
| - njs_vm_t *vm; | |
| - njs_int_t ret; | |
| - njs_str_t name; | |
| - njs_function_t *func; | |
| + njs_vm_t *vm; | |
| + njs_int_t ret; | |
| + njs_str_t name, str; | |
| + ngx_str_t s; | |
| + ngx_js_inline_t *inl; | |
| + njs_function_t *func; | |
| name.start = fname->data; | |
| name.length = fname->len; | |
| @@ -783,7 +909,24 @@ ngx_engine_njs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname, | |
| ret = njs_vm_invoke(vm, func, njs_value_arg(args), nargs, | |
| njs_value_arg(&ctx->retval)); | |
| if (ret == NJS_ERROR) { | |
| - ngx_js_log_exception(vm, ctx->log, "exception"); | |
| + if (njs_vm_exception_string(vm, &str) != NJS_OK) { | |
| + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, | |
| + "js exception"); | |
| + return NGX_ERROR; | |
| + } | |
| + | |
| + s.data = str.start; | |
| + s.len = str.length; | |
| + | |
| + inl = ngx_js_inline_from_stack(ctx->conf, s.data, s.len); | |
| + if (inl != NULL) { | |
| + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, | |
| + "js exception: %V, included at %s:%ui", | |
| + &s, inl->file, inl->line); | |
| + } else { | |
| + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, | |
| + "js exception: %V", &s); | |
| + } | |
| return NGX_ERROR; | |
| } | |
| @@ -963,9 +1106,11 @@ static ngx_int_t | |
| ngx_engine_qjs_compile(ngx_js_loc_conf_t *conf, ngx_log_t *log, u_char *start, | |
| size_t size) | |
| { | |
| - JSValue code; | |
| + ngx_str_t text; | |
| + JSValue code, exception; | |
| JSContext *cx; | |
| ngx_engine_t *engine; | |
| + ngx_js_inline_t *inl; | |
| ngx_js_code_entry_t *pc; | |
| engine = conf->engine; | |
| @@ -975,10 +1120,36 @@ ngx_engine_qjs_compile(ngx_js_loc_conf_t *conf, ngx_log_t *log, u_char *start, | |
| JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); | |
| if (JS_IsException(code)) { | |
| - ngx_qjs_log_exception(engine, log, "compile"); | |
| + exception = JS_GetException(cx); | |
| + | |
| + if (ngx_qjs_dump_obj(engine, exception, &text) == NGX_OK) { | |
| + inl = ngx_js_inline_map(conf, text.data, text.len); | |
| + if (inl != NULL) { | |
| + ngx_log_error(NGX_LOG_EMERG, log, 0, "%V, included at %s:%ui", | |
| + &text, inl->file, inl->line); | |
| + | |
| + } else { | |
| + ngx_log_error(NGX_LOG_EMERG, log, 0, "js compile: %V", &text); | |
| + } | |
| + | |
| + } else { | |
| + ngx_log_error(NGX_LOG_EMERG, log, 0, "js compile error"); | |
| + } | |
| + | |
| + JS_FreeValue(cx, exception); | |
| return NGX_ERROR; | |
| } | |
| + if (engine->precompiled == NULL) { | |
| + engine->precompiled = njs_arr_create(engine->pool, 4, | |
| + sizeof(ngx_js_code_entry_t)); | |
| + if (engine->precompiled == NULL) { | |
| + JS_FreeValue(cx, code); | |
| + ngx_log_error(NGX_LOG_EMERG, log, 0, "njs_arr_create() failed"); | |
| + return NGX_ERROR; | |
| + } | |
| + } | |
| + | |
| pc = njs_arr_add(engine->precompiled); | |
| if (pc == NULL) { | |
| JS_FreeValue(cx, code); | |
| @@ -1120,9 +1291,11 @@ static ngx_int_t | |
| ngx_engine_qjs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname, | |
| njs_opaque_value_t *args, njs_uint_t nargs) | |
| { | |
| - JSValue fn, val; | |
| - ngx_int_t rc; | |
| - JSContext *cx; | |
| + JSValue fn, val, exc; | |
| + ngx_int_t rc; | |
| + ngx_str_t s; | |
| + JSContext *cx; | |
| + ngx_js_inline_t *inl; | |
| cx = ctx->engine->u.qjs.ctx; | |
| @@ -1138,7 +1311,24 @@ ngx_engine_qjs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname, | |
| val = JS_Call(cx, fn, JS_UNDEFINED, nargs, &ngx_qjs_arg(args[0])); | |
| JS_FreeValue(cx, fn); | |
| if (JS_IsException(val)) { | |
| - ngx_qjs_log_exception(ctx->engine, ctx->log, "call exception"); | |
| + exc = JS_GetException(cx); | |
| + | |
| + if (ngx_qjs_dump_obj(ctx->engine, exc, &s) == NGX_OK) { | |
| + inl = ngx_js_inline_from_stack(ctx->conf, s.data, s.len); | |
| + if (inl != NULL) { | |
| + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, | |
| + "js exception: %V, included at %s:%ui", | |
| + &s, inl->file, inl->line); | |
| + } else { | |
| + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, | |
| + "js exception: %V", &s); | |
| + } | |
| + | |
| + } else { | |
| + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception"); | |
| + } | |
| + | |
| + JS_FreeValue(cx, exc); | |
| return NGX_ERROR; | |
| } | |
| @@ -3668,6 +3858,131 @@ ngx_js_init_preload_vm(njs_vm_t *vm, ngx_js_loc_conf_t *conf) | |
| } | |
| +ngx_int_t | |
| +ngx_js_is_function_ref(ngx_str_t *str) | |
| +{ | |
| + u_char *p, *end; | |
| + | |
| + p = str->data; | |
| + end = p + str->len; | |
| + | |
| + if (p == end) { | |
| + return 0; | |
| + } | |
| + | |
| + for ( ;; ) { | |
| + if ((*p < 'a' || *p > 'z') | |
| + && (*p < 'A' || *p > 'Z') | |
| + && *p != '_' && *p != '$') | |
| + { | |
| + return 0; | |
| + } | |
| + | |
| + p++; | |
| + | |
| + while (p < end && *p != '.') { | |
| + if ((*p < 'a' || *p > 'z') | |
| + && (*p < 'A' || *p > 'Z') | |
| + && (*p < '0' || *p > '9') | |
| + && *p != '_' && *p != '$') | |
| + { | |
| + return 0; | |
| + } | |
| + | |
| + p++; | |
| + } | |
| + | |
| + if (p == end) { | |
| + return 1; | |
| + } | |
| + | |
| + /* skip '.' */ | |
| + p++; | |
| + | |
| + if (p == end) { | |
| + return 0; | |
| + } | |
| + } | |
| +} | |
| + | |
| + | |
| +static ngx_int_t | |
| +ngx_js_set_inline(ngx_conf_t *cf, ngx_array_t **inlines, ngx_uint_t *index, | |
| + ngx_str_t *code, const char *arg, ngx_str_t *fname) | |
| +{ | |
| + size_t arg_len; | |
| + ngx_js_inline_t *inl; | |
| + | |
| + if (*inlines == NGX_CONF_UNSET_PTR) { | |
| + *inlines = ngx_array_create(cf->pool, 4, sizeof(ngx_js_inline_t)); | |
| + if (*inlines == NULL) { | |
| + return NGX_ERROR; | |
| + } | |
| + } | |
| + | |
| + inl = ngx_array_push(*inlines); | |
| + if (inl == NULL) { | |
| + return NGX_ERROR; | |
| + } | |
| + | |
| + arg_len = ngx_strlen(arg); | |
| + inl->code = *code; | |
| + inl->file = cf->conf_file->file.name.data; | |
| + inl->line = cf->conf_file->line; | |
| + inl->arg.len = arg_len; | |
| + inl->arg.data = (u_char *) arg; | |
| + | |
| + inl->fname.data = ngx_pnalloc(cf->pool, sizeof("__js_set_65535") - 1); | |
| + if (inl->fname.data == NULL) { | |
| + return NGX_ERROR; | |
| + } | |
| + | |
| + inl->fname.len = ngx_sprintf(inl->fname.data, "__js_set_%ui", (*index)++) | |
| + - inl->fname.data; | |
| + | |
| + *fname = inl->fname; | |
| + | |
| + return NGX_OK; | |
| +} | |
| + | |
| + | |
| +ngx_int_t | |
| +ngx_js_set_init(ngx_conf_t *cf, ngx_array_t **inlines, ngx_uint_t *index, | |
| + ngx_str_t *handler, const char *arg, ngx_js_set_t *set) | |
| +{ | |
| + u_char *p, *end; | |
| + | |
| + set->flags = 0; | |
| + set->file_name = cf->conf_file->file.name.data; | |
| + set->line = cf->conf_file->line; | |
| + | |
| + if (ngx_js_is_function_ref(handler)) { | |
| + | |
| + p = handler->data; | |
| + end = p + handler->len; | |
| + | |
| + while (p < end) { | |
| + if (*p == '$' && p + 1 < end | |
| + && ((p[1] >= 'a' && p[1] <= 'z') | |
| + || (p[1] >= 'A' && p[1] <= 'Z') | |
| + || p[1] == '_')) | |
| + { | |
| + goto inline_expr; | |
| + } | |
| + | |
| + p++; | |
| + } | |
| + | |
| + set->fname = *handler; | |
| + return NGX_OK; | |
| + } | |
| + | |
| +inline_expr: | |
| + | |
| + return ngx_js_set_inline(cf, inlines, index, handler, arg, &set->fname); | |
| +} | |
| + | |
| + | |
| /* | |
| * Merge configuration values used at configuration time. | |
| */ | |
| @@ -3686,10 +4001,14 @@ ngx_js_merge_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, | |
| { | |
| ngx_str_t *path, *s; | |
| ngx_uint_t i; | |
| - ngx_array_t *imports, *preload_objects, *paths; | |
| + ngx_array_t *imports, *inlines, *preload_objects, *paths; | |
| + ngx_js_inline_t *inl, *ili; | |
| ngx_js_named_path_t *import, *pi, *pij, *preload; | |
| - if (prev->imports != NGX_CONF_UNSET_PTR && prev->engine == NULL) { | |
| + if ((prev->imports != NGX_CONF_UNSET_PTR | |
| + || prev->inlines != NGX_CONF_UNSET_PTR) | |
| + && prev->engine == NULL) | |
| + { | |
| /* | |
| * special handling to preserve conf->engine | |
| * in the "http" or "stream" section to inherit it to all servers | |
| @@ -3703,6 +4022,7 @@ ngx_js_merge_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, | |
| } | |
| if (conf->imports == NGX_CONF_UNSET_PTR | |
| + && conf->inlines == NGX_CONF_UNSET_PTR | |
| && conf->type == prev->type | |
| && conf->paths == NGX_CONF_UNSET_PTR | |
| && conf->preload_objects == NGX_CONF_UNSET_PTR) | |
| @@ -3710,6 +4030,7 @@ ngx_js_merge_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, | |
| if (prev->engine != NULL) { | |
| conf->preload_objects = prev->preload_objects; | |
| conf->imports = prev->imports; | |
| + conf->inlines = prev->inlines; | |
| conf->type = prev->type; | |
| conf->paths = prev->paths; | |
| conf->engine = prev->engine; | |
| @@ -3791,6 +4112,43 @@ ngx_js_merge_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, | |
| } | |
| } | |
| + if (prev->inlines != NGX_CONF_UNSET_PTR) { | |
| + if (conf->inlines == NGX_CONF_UNSET_PTR) { | |
| + conf->inlines = prev->inlines; | |
| + | |
| + } else { | |
| + inlines = ngx_array_create(cf->pool, 4, | |
| + sizeof(ngx_js_inline_t)); | |
| + if (inlines == NULL) { | |
| + return NGX_ERROR; | |
| + } | |
| + | |
| + ili = prev->inlines->elts; | |
| + | |
| + for (i = 0; i < prev->inlines->nelts; i++) { | |
| + inl = ngx_array_push(inlines); | |
| + if (inl == NULL) { | |
| + return NGX_ERROR; | |
| + } | |
| + | |
| + *inl = ili[i]; | |
| + } | |
| + | |
| + ili = conf->inlines->elts; | |
| + | |
| + for (i = 0; i < conf->inlines->nelts; i++) { | |
| + inl = ngx_array_push(inlines); | |
| + if (inl == NULL) { | |
| + return NGX_ERROR; | |
| + } | |
| + | |
| + *inl = ili[i]; | |
| + } | |
| + | |
| + conf->inlines = inlines; | |
| + } | |
| + } | |
| + | |
| if (prev->paths != NGX_CONF_UNSET_PTR) { | |
| if (conf->paths == NGX_CONF_UNSET_PTR) { | |
| conf->paths = prev->paths; | |
| @@ -3827,7 +4185,9 @@ ngx_js_merge_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, | |
| } | |
| } | |
| - if (conf->imports == NGX_CONF_UNSET_PTR) { | |
| + if (conf->imports == NGX_CONF_UNSET_PTR | |
| + && conf->inlines == NGX_CONF_UNSET_PTR) | |
| + { | |
| return NGX_OK; | |
| } | |
| @@ -4155,6 +4515,7 @@ ngx_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, | |
| size_t size; | |
| ngx_str_t *m, file; | |
| ngx_uint_t i; | |
| + ngx_js_inline_t *inl; | |
| ngx_pool_cleanup_t *cln; | |
| ngx_js_named_path_t *import; | |
| @@ -4164,14 +4525,34 @@ ngx_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, | |
| size = 0; | |
| - import = conf->imports->elts; | |
| - for (i = 0; i < conf->imports->nelts; i++) { | |
| + if (conf->imports != NGX_CONF_UNSET_PTR) { | |
| + import = conf->imports->elts; | |
| + | |
| + for (i = 0; i < conf->imports->nelts; i++) { | |
| - /* import <name> from '<path>'; globalThis.<name> = <name>; */ | |
| + /* import <name> from '<path>'; globalThis.<name> = <name>; */ | |
| - size += sizeof("import from '';") - 1 + import[i].name.len * 3 | |
| - + import[i].path.len | |
| - + sizeof(" globalThis. = ;\n") - 1; | |
| + size += sizeof("import from '';") - 1 + import[i].name.len * 3 | |
| + + import[i].path.len | |
| + + sizeof(" globalThis. = ;\n") - 1; | |
| + } | |
| + } | |
| + | |
| + if (conf->inlines != NGX_CONF_UNSET_PTR) { | |
| + inl = conf->inlines->elts; | |
| + | |
| + for (i = 0; i < conf->inlines->nelts; i++) { | |
| + | |
| + /* | |
| + * function <fname>(<arg>) { return (<code>); } | |
| + * globalThis.<fname> = <fname>;\n | |
| + */ | |
| + | |
| + size += sizeof("function () { return (); }") - 1 | |
| + + sizeof(" globalThis. = ;\n") - 1 | |
| + + inl[i].fname.len * 3 | |
| + + inl[i].arg.len + inl[i].code.len; | |
| + } | |
| } | |
| start = ngx_pnalloc(cf->pool, size + 1); | |
| @@ -4180,20 +4561,44 @@ ngx_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, | |
| } | |
| p = start; | |
| - import = conf->imports->elts; | |
| - for (i = 0; i < conf->imports->nelts; i++) { | |
| - /* import <name> from '<path>'; globalThis.<name> = <name>; */ | |
| + if (conf->imports != NGX_CONF_UNSET_PTR) { | |
| + import = conf->imports->elts; | |
| - p = ngx_cpymem(p, "import ", sizeof("import ") - 1); | |
| - p = ngx_cpymem(p, import[i].name.data, import[i].name.len); | |
| - p = ngx_cpymem(p, " from '", sizeof(" from '") - 1); | |
| - p = ngx_cpymem(p, import[i].path.data, import[i].path.len); | |
| - p = ngx_cpymem(p, "'; globalThis.", sizeof("'; globalThis.") - 1); | |
| - p = ngx_cpymem(p, import[i].name.data, import[i].name.len); | |
| - p = ngx_cpymem(p, " = ", sizeof(" = ") - 1); | |
| - p = ngx_cpymem(p, import[i].name.data, import[i].name.len); | |
| - p = ngx_cpymem(p, ";\n", sizeof(";\n") - 1); | |
| + for (i = 0; i < conf->imports->nelts; i++) { | |
| + | |
| + /* import <name> from '<path>'; globalThis.<name> = <name>; */ | |
| + | |
| + p = ngx_cpymem(p, "import ", sizeof("import ") - 1); | |
| + p = ngx_cpymem(p, import[i].name.data, import[i].name.len); | |
| + p = ngx_cpymem(p, " from '", sizeof(" from '") - 1); | |
| + p = ngx_cpymem(p, import[i].path.data, import[i].path.len); | |
| + p = ngx_cpymem(p, "'; globalThis.", | |
| + sizeof("'; globalThis.") - 1); | |
| + p = ngx_cpymem(p, import[i].name.data, import[i].name.len); | |
| + p = ngx_cpymem(p, " = ", sizeof(" = ") - 1); | |
| + p = ngx_cpymem(p, import[i].name.data, import[i].name.len); | |
| + p = ngx_cpymem(p, ";\n", sizeof(";\n") - 1); | |
| + } | |
| + } | |
| + | |
| + if (conf->inlines != NGX_CONF_UNSET_PTR) { | |
| + inl = conf->inlines->elts; | |
| + | |
| + for (i = 0; i < conf->inlines->nelts; i++) { | |
| + p = ngx_cpymem(p, "function ", sizeof("function ") - 1); | |
| + p = ngx_cpymem(p, inl[i].fname.data, inl[i].fname.len); | |
| + p = ngx_cpymem(p, "(", 1); | |
| + p = ngx_cpymem(p, inl[i].arg.data, inl[i].arg.len); | |
| + p = ngx_cpymem(p, ") { return (", sizeof(") { return (") - 1); | |
| + p = ngx_cpymem(p, inl[i].code.data, inl[i].code.len); | |
| + p = ngx_cpymem(p, "); } globalThis.", | |
| + sizeof("); } globalThis.") - 1); | |
| + p = ngx_cpymem(p, inl[i].fname.data, inl[i].fname.len); | |
| + p = ngx_cpymem(p, " = ", sizeof(" = ") - 1); | |
| + p = ngx_cpymem(p, inl[i].fname.data, inl[i].fname.len); | |
| + p = ngx_cpymem(p, ";\n", sizeof(";\n") - 1); | |
| + } | |
| } | |
| *p = '\0'; | |
| @@ -4288,6 +4693,7 @@ ngx_js_create_conf(ngx_conf_t *cf, size_t size) | |
| conf->paths = NGX_CONF_UNSET_PTR; | |
| conf->type = NGX_CONF_UNSET_UINT; | |
| conf->imports = NGX_CONF_UNSET_PTR; | |
| + conf->inlines = NGX_CONF_UNSET_PTR; | |
| conf->preload_objects = NGX_CONF_UNSET_PTR; | |
| conf->reuse = NGX_CONF_UNSET_SIZE; | |
| diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h | |
| index a25dc65a..04f95c22 100644 | |
| --- a/nginx/ngx_js.h | |
| +++ b/nginx/ngx_js.h | |
| @@ -133,6 +133,7 @@ typedef struct { | |
| ngx_js_queue_t *reuse_queue; \ | |
| ngx_str_t cwd; \ | |
| ngx_array_t *imports; \ | |
| + ngx_array_t *inlines; \ | |
| ngx_array_t *paths; \ | |
| \ | |
| ngx_array_t *preload_objects; \ | |
| @@ -184,6 +185,7 @@ typedef struct { | |
| #define NGX_JS_COMMON_CTX \ | |
| ngx_engine_t *engine; \ | |
| + ngx_js_loc_conf_t *conf; \ | |
| ngx_log_t *log; \ | |
| njs_opaque_value_t args[3]; \ | |
| njs_opaque_value_t retval; \ | |
| @@ -224,6 +226,15 @@ typedef struct { | |
| } ngx_js_set_t; | |
| +typedef struct { | |
| + ngx_str_t code; | |
| + ngx_str_t fname; | |
| + ngx_str_t arg; | |
| + u_char *file; | |
| + ngx_uint_t line; | |
| +} ngx_js_inline_t; | |
| + | |
| + | |
| struct ngx_js_ctx_s { | |
| NGX_JS_COMMON_CTX; | |
| }; | |
| @@ -452,6 +463,9 @@ char * ngx_js_preload_object(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); | |
| char * ngx_js_fetch_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); | |
| ngx_int_t ngx_js_parse_proxy_url(ngx_pool_t *pool, ngx_log_t *log, | |
| ngx_str_t *url_str, ngx_url_t **url_out, ngx_str_t *auth_header_out); | |
| +ngx_int_t ngx_js_is_function_ref(ngx_str_t *str); | |
| +ngx_int_t ngx_js_set_init(ngx_conf_t *cf, ngx_array_t **inlines, | |
| + ngx_uint_t *index, ngx_str_t *handler, const char *arg, ngx_js_set_t *set); | |
| ngx_int_t ngx_js_merge_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, | |
| ngx_js_loc_conf_t *prev, | |
| ngx_int_t (*init_vm)(ngx_conf_t *cf, ngx_js_loc_conf_t *conf)); | |
| diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c | |
| index 76d8907d..89699cc8 100644 | |
| --- a/nginx/ngx_stream_js_module.c | |
| +++ b/nginx/ngx_stream_js_module.c | |
| @@ -1157,8 +1157,9 @@ ngx_stream_js_variable_set(ngx_stream_session_t *s, | |
| if (rc == NGX_DECLINED) { | |
| ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, | |
| - "no \"js_import\" directives found for \"js_set\" handler" | |
| - " \"%V\" in the current scope", fname); | |
| + "no \"js_import\" or inline expression found" | |
| + " for \"js_set\" handler \"%V\" at %s:%ui", | |
| + fname, vdata->file_name, vdata->line); | |
| v->not_found = 1; | |
| return NGX_OK; | |
| } | |
| @@ -1245,6 +1246,7 @@ ngx_stream_js_init_vm(ngx_stream_session_t *s, njs_int_t proto_id) | |
| } | |
| ngx_js_ctx_init((ngx_js_ctx_t *) ctx, s->connection->log); | |
| + ctx->conf = (ngx_js_loc_conf_t *) jscf; | |
| ngx_stream_set_ctx(s, ctx, ngx_stream_js_module); | |
| } | |
| @@ -3539,9 +3541,12 @@ invalid: | |
| static char * | |
| ngx_stream_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) | |
| { | |
| - ngx_str_t *value; | |
| - ngx_js_set_t *data, *prev; | |
| - ngx_stream_variable_t *v; | |
| + ngx_str_t *value; | |
| + ngx_js_set_t *data, *prev; | |
| + ngx_stream_variable_t *v; | |
| + ngx_stream_js_srv_conf_t *jscf; | |
| + | |
| + static ngx_uint_t ngx_stream_js_inline_index; | |
| value = cf->args->elts; | |
| @@ -3564,19 +3569,25 @@ ngx_stream_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) | |
| return NGX_CONF_ERROR; | |
| } | |
| - data->fname = value[2]; | |
| - data->file_name = cf->conf_file->file.name.data; | |
| - data->line = cf->conf_file->line; | |
| + jscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_js_module); | |
| + | |
| + if (ngx_js_set_init(cf, &jscf->inlines, &ngx_stream_js_inline_index, | |
| + &value[2], "s", data) | |
| + != NGX_OK) | |
| + { | |
| + return NGX_CONF_ERROR; | |
| + } | |
| if (v->get_handler == ngx_stream_js_variable_set) { | |
| prev = (ngx_js_set_t *) v->data; | |
| if (data->fname.len != prev->fname.len | |
| - || ngx_strncmp(data->fname.data, prev->fname.data, data->fname.len) != 0) | |
| + || ngx_strncmp(data->fname.data, prev->fname.data, | |
| + data->fname.len) != 0) | |
| { | |
| ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | |
| "variable \"%V\" is redeclared with " | |
| - "different function name", &value[1]); | |
| + "different handler", &value[1]); | |
| return NGX_CONF_ERROR; | |
| } | |
| } | |
| diff --git a/nginx/t/js_inline.t b/nginx/t/js_inline.t | |
| new file mode 100644 | |
| index 00000000..c6de3158 | |
| --- /dev/null | |
| +++ b/nginx/t/js_inline.t | |
| @@ -0,0 +1,179 @@ | |
| +#!/usr/bin/perl | |
| + | |
| +# (C) Dmitry Volyntsev | |
| +# (C) F5, Inc. | |
| + | |
| +# Tests for http njs module, js_set inline expressions. | |
| + | |
| +############################################################################### | |
| + | |
| +use warnings; | |
| +use strict; | |
| + | |
| +use Test::More; | |
| + | |
| +use Socket qw/ CRLF /; | |
| + | |
| +BEGIN { use FindBin; chdir($FindBin::Bin); } | |
| + | |
| +use lib 'lib'; | |
| +use Test::Nginx; | |
| + | |
| +############################################################################### | |
| + | |
| +select STDERR; $| = 1; | |
| +select STDOUT; $| = 1; | |
| + | |
| +my $t = Test::Nginx->new()->has(qw/http rewrite/)->plan(13); | |
| + | |
| +use constant TEMPLATE_CONF => <<'EOF'; | |
| + | |
| +%%TEST_GLOBALS%% | |
| + | |
| +daemon off; | |
| + | |
| +events { | |
| +} | |
| + | |
| +http { | |
| + %%TEST_GLOBALS_HTTP%% | |
| + | |
| + # oridinary variable set (function reference) | |
| + js_set $test_var test.variable; | |
| + | |
| + js_set $var 'r.var.test_var.toUpperCase()'; | |
| + js_set $header 'r.headersIn["Foo"] || "none"'; | |
| + js_set $template `/p${r.uri}/post`; | |
| + js_set $expression '1 + 2'; | |
| + js_set $force_expr (r.var.uri); | |
| + | |
| + js_set $runtime_err '(o.a.a)'; | |
| + | |
| + %%EXTRA_CONF%% | |
| + | |
| + js_import test.js; | |
| + | |
| + server { | |
| + listen 127.0.0.1:8080; | |
| + server_name localhost; | |
| + | |
| + location /inline { | |
| + return 200 "uri=$template foo=$header sum=$expression"; | |
| + } | |
| + | |
| + location /var { | |
| + return 200 "var=$var"; | |
| + } | |
| + | |
| + location /mixed { | |
| + return 200 "test_var=$test_var uri=$template"; | |
| + } | |
| + | |
| + location /force_expr { | |
| + return 200 "uri=$force_expr"; | |
| + } | |
| + | |
| + location /runtime_err { | |
| + return 200 "err=$runtime_err"; | |
| + } | |
| + } | |
| +} | |
| + | |
| +EOF | |
| + | |
| +############################################################################### | |
| + | |
| +$t->write_file('test.js', <<EOF); | |
| + function variable(r) { | |
| + return 'from_func'; | |
| + } | |
| + | |
| + export default {variable}; | |
| + | |
| +EOF | |
| + | |
| +write_conf($t, ''); | |
| + | |
| +$t->try_run('no njs'); | |
| + | |
| +############################################################################### | |
| + | |
| +like(http_get('/inline'), qr/uri=\/p\/inline\/post/, 'template inline'); | |
| +like(http_get('/inline'), qr/foo=none/, 'inline headerIn'); | |
| +like(http( | |
| + 'GET /inline HTTP/1.0' . CRLF | |
| + . 'Foo: bar' . CRLF | |
| + . 'Host: localhost' . CRLF . CRLF | |
| +), qr/foo=bar/, 'inline headerIn with header'); | |
| + | |
| +like(http_get('/inline'), qr/sum=3/, 'inline sum'); | |
| +like(http_get('/var'), qr/var=FROM_FUNC/, 'inline var func ref'); | |
| +like(http_get('/mixed'), qr/test_var=from_func/, 'mixed func ref'); | |
| +like(http_get('/mixed'), qr/uri=\/p\/mixed\/post/, 'mixed inline'); | |
| + | |
| +like(http_get('/force_expr'), qr/uri=\/force_expr/, 'forced expression syntax'); | |
| + | |
| +http_get('/runtime_err'); | |
| +like($t->read_file('error.log'), qr/included at.*nginx\.conf:/s, | |
| + 'runtime error location'); | |
| + | |
| +$t->stop(); | |
| + | |
| +############################################################################### | |
| + | |
| +like(check($t, "js_set \$bad 'return 1';"), | |
| + qr/SyntaxError.*included at.*nginx\.conf:/s, | |
| + 'inline syntax error location'); | |
| + | |
| +like(check($t, "js_set \$bad '1 +';"), | |
| + qr/SyntaxError.*included at.*nginx\.conf:/s, | |
| + 'inline syntax error unexpected end'); | |
| + | |
| +$t->write_file('bad.js', 'export default {INVALID SYNTAX'); | |
| + | |
| +like(check($t, 'js_import bad.js;'), | |
| + qr/\[emerg\].*SyntaxError/s, | |
| + 'file syntax error'); | |
| + | |
| +unlike(check($t, 'js_import bad.js;'), | |
| + qr/included at/s, | |
| + 'file syntax error no inline location'); | |
| + | |
| +open my $fh, '>', $t->testdir() . '/error.log'; | |
| +close $fh; | |
| + | |
| +############################################################################### | |
| + | |
| +sub write_conf { | |
| + my ($t, $extra) = @_; | |
| + | |
| + $t->write_file_expand('nginx.conf', | |
| + TEMPLATE_CONF =~ s/%%EXTRA_CONF%%/$extra/r); | |
| +} | |
| + | |
| +sub check { | |
| + my ($t, $extra) = @_; | |
| + | |
| + $t->stop(); | |
| + unlink $t->testdir() . '/error.log'; | |
| + | |
| + write_conf($t, $extra); | |
| + | |
| + eval { | |
| + open OLDERR, ">&", \*STDERR; close STDERR; | |
| + $t->run(); | |
| + open STDERR, ">&", \*OLDERR; | |
| + }; | |
| + | |
| + return unless $@; | |
| + | |
| + my $log = $t->read_file('error.log'); | |
| + | |
| + if ($ENV{TEST_NGINX_VERBOSE}) { | |
| + map { Test::Nginx::log_core($_) } split(/^/m, $log); | |
| + } | |
| + | |
| + return $log; | |
| +} | |
| + | |
| +############################################################################### | |
| diff --git a/nginx/t/js_inline_only.t b/nginx/t/js_inline_only.t | |
| new file mode 100644 | |
| index 00000000..f2144822 | |
| --- /dev/null | |
| +++ b/nginx/t/js_inline_only.t | |
| @@ -0,0 +1,76 @@ | |
| +#!/usr/bin/perl | |
| + | |
| +# (C) Dmitry Volyntsev | |
| +# (C) F5, Inc. | |
| + | |
| +# Tests for http njs module, js_set inline expressions without js_import. | |
| + | |
| +############################################################################### | |
| + | |
| +use warnings; | |
| +use strict; | |
| + | |
| +use Test::More; | |
| + | |
| +BEGIN { use FindBin; chdir($FindBin::Bin); } | |
| + | |
| +use lib 'lib'; | |
| +use Test::Nginx; | |
| + | |
| +############################################################################### | |
| + | |
| +select STDERR; $| = 1; | |
| +select STDOUT; $| = 1; | |
| + | |
| +my $t = Test::Nginx->new()->has(qw/http rewrite/) | |
| + ->write_file_expand('nginx.conf', <<'EOF'); | |
| + | |
| +%%TEST_GLOBALS%% | |
| + | |
| +daemon off; | |
| + | |
| +events { | |
| +} | |
| + | |
| +http { | |
| + %%TEST_GLOBALS_HTTP%% | |
| + | |
| + js_set $template `/p${r.uri}/post`; | |
| + | |
| + server { | |
| + listen 127.0.0.1:8080; | |
| + server_name localhost; | |
| + | |
| + js_set $a '(r.args.a || "none")'; | |
| + js_set $expanded 'r.var.uri.toUpperCase()'; | |
| + | |
| + location /inline { | |
| + js_set $method '(r.method)'; | |
| + | |
| + return 200 "uri=$template a=$a method=$method"; | |
| + } | |
| + | |
| + location /expanded { | |
| + return 200 "expanded=$expanded"; | |
| + } | |
| + } | |
| +} | |
| + | |
| +EOF | |
| + | |
| +$t->try_run('no njs')->plan(6); | |
| + | |
| +############################################################################### | |
| + | |
| +like(http_get('/inline'), qr/uri=\/p\/inline\/post/, 'inline only uri'); | |
| +like(http_get('/inline'), qr/a=none/, 'inline only args'); | |
| +like(http_get('/inline?a=1'), qr/a=1/, 'inline only args with value'); | |
| +like(http_get('/inline'), qr/method=GET/, 'inline only method'); | |
| +like(http_get('/expanded'), qr/expanded=\/EXPANDED/, | |
| + 'inline only expanded r.var'); | |
| + | |
| +$t->stop(); | |
| + | |
| +ok(index($t->read_file('error.log'), 'SyntaxError') < 0, 'no syntax errors'); | |
| + | |
| +############################################################################### | |
| diff --git a/nginx/t/js_variables_location.t b/nginx/t/js_variables_location.t | |
| index 02bd85ef..92496a50 100644 | |
| --- a/nginx/t/js_variables_location.t | |
| +++ b/nginx/t/js_variables_location.t | |
| @@ -90,7 +90,8 @@ like(http_get('/not_found'), qr/NOT_FOUND:$/, 'not found is empty'); | |
| $t->stop(); | |
| ok(index($t->read_file('error.log'), | |
| - 'no "js_import" directives found for "js_set" handler "main.variable" ' | |
| - . 'in the current scope') > 0, 'log error for js_set without js_import'); | |
| + 'no "js_import" or inline expression found for "js_set" handler ' | |
| + . '"main.variable"') > 0, | |
| + 'log error for js_set without js_import'); | |
| ############################################################################### | |
| diff --git a/nginx/t/stream_js_variables_server.t b/nginx/t/stream_js_variables_server.t | |
| index a7d53718..abf8c180 100644 | |
| --- a/nginx/t/stream_js_variables_server.t | |
| +++ b/nginx/t/stream_js_variables_server.t | |
| @@ -92,7 +92,8 @@ is(stream('127.0.0.1:' . port(8083))->read(), 'NOT_FOUND:', 'not found var'); | |
| $t->stop(); | |
| ok(index($t->read_file('error.log'), | |
| - 'no "js_import" directives found for "js_set" handler "main.variable" ' | |
| - . 'in the current scope') > 0, 'log error for js_set without js_import'); | |
| + 'no "js_import" or inline expression found for "js_set" handler ' | |
| + . '"main.variable"') > 0, | |
| + 'log error for js_set without js_import'); | |
| ############################################################################### | |
| -- | |
| 2.43.0 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment