static njs_int_t ngx_http_js_periodic_session_variables(njs_vm_t *vm,
njs_object_prop_t *prop, uint32_t atom_id, njs_value_t *value,
njs_value_t *setval, njs_value_t *retval);
+static njs_int_t ngx_http_js_ext_js_var_names(njs_vm_t *vm,
+ njs_value_t *args, njs_uint_t nargs, njs_index_t unused,
+ njs_value_t *retval);
static njs_int_t ngx_http_js_ext_subrequest(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
static ngx_int_t ngx_http_js_subrequest_done(ngx_http_request_t *r,
JSValueConst this_val, int out);
static JSValue ngx_http_qjs_ext_variables(JSContext *cx,
JSValueConst this_val, int type);
+static JSValue ngx_http_qjs_ext_js_var_names(JSContext *cx,
+ JSValueConst this_val, int argc, JSValueConst *argv);
static int ngx_http_qjs_variables_own_property(JSContext *cx,
JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop);
}
},
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("jsVarNames"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = ngx_http_js_ext_js_var_names,
+ }
+ },
+
{
.flags = NJS_EXTERN_PROPERTY,
.name.string = njs_str("status"),
JS_CFUNC_DEF("sendBuffer", 2, ngx_http_qjs_ext_send_buffer),
JS_CFUNC_DEF("sendHeader", 0, ngx_http_qjs_ext_send_header),
JS_CFUNC_DEF("setReturnValue", 1, ngx_http_qjs_ext_set_return_value),
+ JS_CFUNC_DEF("jsVarNames", 1, ngx_http_qjs_ext_js_var_names),
JS_CGETSET_DEF("status", ngx_http_qjs_ext_status_get,
ngx_http_qjs_ext_status_set),
JS_CFUNC_MAGIC_DEF("readRequestArrayBuffer", 0,
}
+static ngx_uint_t
+ngx_http_js_var_name_matches(ngx_str_t *name, const u_char *prefix,
+ size_t prefix_len)
+{
+ if (prefix_len == 0) {
+ return 1;
+ }
+
+ return name->len >= prefix_len
+ && ngx_memcmp(name->data, prefix, prefix_len) == 0;
+}
+
+
+static njs_int_t
+ngx_http_js_ext_js_var_names(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused, njs_value_t *retval)
+{
+ njs_int_t rc;
+ njs_str_t prefix;
+ njs_value_t *arg, *value;
+ ngx_uint_t i;
+ ngx_http_request_t *r;
+ ngx_http_variable_t *v;
+ ngx_http_core_main_conf_t *cmcf;
+
+ r = njs_vm_external(vm, ngx_http_js_request_proto_id,
+ njs_argument(args, 0));
+ if (r == NULL) {
+ njs_vm_error(vm, "\"this\" is not an external");
+ return NJS_ERROR;
+ }
+
+ prefix.start = NULL;
+ prefix.length = 0;
+
+ arg = njs_arg(args, nargs, 1);
+
+ if (!njs_value_is_undefined(arg)) {
+ if (!njs_value_is_string(arg)) {
+ njs_vm_type_error(vm, "\"prefix\" must be a string");
+ return NJS_ERROR;
+ }
+
+ njs_value_string_get(vm, arg, &prefix);
+ }
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ rc = njs_vm_array_alloc(vm, retval, 4);
+ if (rc != NJS_OK) {
+ return NJS_ERROR;
+ }
+
+ v = cmcf->variables.elts;
+
+ for (i = 0; i < cmcf->variables.nelts; i++) {
+ if (v[i].get_handler != ngx_http_js_variable_var) {
+ continue;
+ }
+
+ if (!ngx_http_js_var_name_matches(&v[i].name, prefix.start,
+ prefix.length))
+ {
+ continue;
+ }
+
+ value = njs_vm_array_push(vm, retval);
+ if (value == NULL) {
+ return NJS_ERROR;
+ }
+
+ rc = njs_vm_value_string_create(vm, value, v[i].name.data,
+ v[i].name.len);
+ if (rc != NJS_OK) {
+ return NJS_ERROR;
+ }
+ }
+
+ return NJS_OK;
+}
+
+
static njs_int_t
ngx_http_js_request_variables(njs_vm_t *vm, njs_object_prop_t *prop,
uint32_t atom_id, ngx_http_request_t *r, njs_value_t *setval,
}
+static JSValue
+ngx_http_qjs_ext_js_var_names(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ JSValue array, value;
+ uint32_t n;
+ size_t prefix_len;
+ const char *prefix;
+ ngx_uint_t i;
+ ngx_http_request_t *r;
+ ngx_http_variable_t *v;
+ ngx_http_core_main_conf_t *cmcf;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ prefix = NULL;
+ prefix_len = 0;
+
+ if (argc > 0 && !JS_IsUndefined(argv[0])) {
+ if (!JS_IsString(argv[0])) {
+ return JS_ThrowTypeError(cx, "\"prefix\" must be a string");
+ }
+
+ prefix = JS_ToCStringLen(cx, &prefix_len, argv[0]);
+ if (prefix == NULL) {
+ return JS_EXCEPTION;
+ }
+ }
+
+ array = JS_NewArray(cx);
+ if (JS_IsException(array)) {
+ JS_FreeCString(cx, prefix);
+ return JS_EXCEPTION;
+ }
+
+ n = 0;
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+ v = cmcf->variables.elts;
+
+ for (i = 0; i < cmcf->variables.nelts; i++) {
+ if (v[i].get_handler != ngx_http_js_variable_var) {
+ continue;
+ }
+
+ if (!ngx_http_js_var_name_matches(&v[i].name, (u_char *) prefix,
+ prefix_len))
+ {
+ continue;
+ }
+
+ value = qjs_string_create(cx, v[i].name.data, v[i].name.len);
+ if (JS_IsException(value)) {
+ JS_FreeCString(cx, prefix);
+ JS_FreeValue(cx, array);
+ return JS_EXCEPTION;
+ }
+
+ if (JS_DefinePropertyValueUint32(cx, array, n++, value,
+ JS_PROP_C_W_E) < 0)
+ {
+ JS_FreeCString(cx, prefix);
+ JS_FreeValue(cx, array);
+ return JS_EXCEPTION;
+ }
+ }
+
+ JS_FreeCString(cx, prefix);
+
+ return array;
+}
+
+
static int
ngx_http_qjs_variables_own_property(JSContext *cx, JSPropertyDescriptor *pdesc,
JSValueConst obj, JSAtom prop)
static njs_int_t ngx_stream_js_ext_set_return_value(njs_vm_t *vm,
njs_value_t *args, njs_uint_t nargs, njs_index_t unused,
njs_value_t *retval);
+static njs_int_t ngx_stream_js_ext_js_var_names(njs_vm_t *vm,
+ njs_value_t *args, njs_uint_t nargs, njs_index_t unused,
+ njs_value_t *retval);
static njs_int_t ngx_stream_js_ext_variables(njs_vm_t *vm,
njs_object_prop_t *prop, uint32_t atom_id, njs_value_t *value,
int argc, JSValueConst *argv, int from_upstream);
static JSValue ngx_stream_qjs_ext_set_return_value(JSContext *cx,
JSValueConst this_val, int argc, JSValueConst *argv);
+static JSValue ngx_stream_qjs_ext_js_var_names(JSContext *cx,
+ JSValueConst this_val, int argc, JSValueConst *argv);
static JSValue ngx_stream_qjs_ext_variables(JSContext *cx,
JSValueConst this_val, int type);
static JSValue ngx_stream_qjs_ext_uint(JSContext *cx, JSValueConst this_val,
}
},
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("jsVarNames"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = ngx_stream_js_ext_js_var_names,
+ }
+ },
+
{
.flags = NJS_EXTERN_PROPERTY,
.name.string = njs_str("status"),
JS_CFUNC_MAGIC_DEF("sendUpstream", 1, ngx_stream_qjs_ext_send,
NGX_JS_BOOL_FALSE),
JS_CFUNC_DEF("setReturnValue", 1, ngx_stream_qjs_ext_set_return_value),
+ JS_CFUNC_DEF("jsVarNames", 1, ngx_stream_qjs_ext_js_var_names),
JS_CGETSET_MAGIC_DEF("status", ngx_stream_qjs_ext_uint, NULL,
offsetof(ngx_stream_session_t, status)),
JS_CGETSET_MAGIC_DEF("variables", ngx_stream_qjs_ext_variables,
}
+static ngx_uint_t
+ngx_stream_js_var_name_matches(ngx_str_t *name, const u_char *prefix,
+ size_t prefix_len)
+{
+ if (prefix_len == 0) {
+ return 1;
+ }
+
+ return name->len >= prefix_len
+ && ngx_memcmp(name->data, prefix, prefix_len) == 0;
+}
+
+
+static njs_int_t
+ngx_stream_js_ext_js_var_names(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused, njs_value_t *retval)
+{
+ njs_int_t rc;
+ njs_str_t prefix;
+ njs_value_t *arg, *value;
+ ngx_uint_t i;
+ ngx_stream_session_t *s;
+ ngx_stream_variable_t *v;
+ ngx_stream_core_main_conf_t *cmcf;
+
+ s = njs_vm_external(vm, ngx_stream_js_session_proto_id,
+ njs_argument(args, 0));
+ if (s == NULL) {
+ njs_vm_error(vm, "\"this\" is not an external");
+ return NJS_ERROR;
+ }
+
+ prefix.start = NULL;
+ prefix.length = 0;
+
+ arg = njs_arg(args, nargs, 1);
+
+ if (!njs_value_is_undefined(arg)) {
+ if (!njs_value_is_string(arg)) {
+ njs_vm_type_error(vm, "\"prefix\" must be a string");
+ return NJS_ERROR;
+ }
+
+ njs_value_string_get(vm, arg, &prefix);
+ }
+
+ cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
+
+ rc = njs_vm_array_alloc(vm, retval, 4);
+ if (rc != NJS_OK) {
+ return NJS_ERROR;
+ }
+
+ v = cmcf->variables.elts;
+
+ for (i = 0; i < cmcf->variables.nelts; i++) {
+ if (v[i].get_handler != ngx_stream_js_variable_var) {
+ continue;
+ }
+
+ if (!ngx_stream_js_var_name_matches(&v[i].name, prefix.start,
+ prefix.length))
+ {
+ continue;
+ }
+
+ value = njs_vm_array_push(vm, retval);
+ if (value == NULL) {
+ return NJS_ERROR;
+ }
+
+ rc = njs_vm_value_string_create(vm, value, v[i].name.data,
+ v[i].name.len);
+ if (rc != NJS_OK) {
+ return NJS_ERROR;
+ }
+ }
+
+ return NJS_OK;
+}
+
+
static njs_int_t
ngx_stream_js_session_variables(njs_vm_t *vm, njs_object_prop_t *prop,
uint32_t atom_id, ngx_stream_session_t *s, njs_value_t *setval,
}
+static JSValue
+ngx_stream_qjs_ext_js_var_names(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ JSValue array, value;
+ uint32_t n;
+ size_t prefix_len;
+ const char *prefix;
+ ngx_uint_t i;
+ ngx_stream_session_t *s;
+ ngx_stream_variable_t *v;
+ ngx_stream_core_main_conf_t *cmcf;
+
+ s = ngx_stream_qjs_session(this_val);
+ if (s == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a session object");
+ }
+
+ prefix = NULL;
+ prefix_len = 0;
+
+ if (argc > 0 && !JS_IsUndefined(argv[0])) {
+ if (!JS_IsString(argv[0])) {
+ return JS_ThrowTypeError(cx, "\"prefix\" must be a string");
+ }
+
+ prefix = JS_ToCStringLen(cx, &prefix_len, argv[0]);
+ if (prefix == NULL) {
+ return JS_EXCEPTION;
+ }
+ }
+
+ array = JS_NewArray(cx);
+ if (JS_IsException(array)) {
+ JS_FreeCString(cx, prefix);
+ return JS_EXCEPTION;
+ }
+
+ n = 0;
+ cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
+ v = cmcf->variables.elts;
+
+ for (i = 0; i < cmcf->variables.nelts; i++) {
+ if (v[i].get_handler != ngx_stream_js_variable_var) {
+ continue;
+ }
+
+ if (!ngx_stream_js_var_name_matches(&v[i].name, (u_char *) prefix,
+ prefix_len))
+ {
+ continue;
+ }
+
+ value = qjs_string_create(cx, v[i].name.data, v[i].name.len);
+ if (JS_IsException(value)) {
+ JS_FreeCString(cx, prefix);
+ JS_FreeValue(cx, array);
+ return JS_EXCEPTION;
+ }
+
+ if (JS_DefinePropertyValueUint32(cx, array, n++, value,
+ JS_PROP_C_W_E) < 0)
+ {
+ JS_FreeCString(cx, prefix);
+ JS_FreeValue(cx, array);
+ return JS_EXCEPTION;
+ }
+ }
+
+ JS_FreeCString(cx, prefix);
+
+ return array;
+}
+
+
static JSValue
ngx_stream_qjs_ext_uint(JSContext *cx, JSValueConst this_val, int offset)
{
--- /dev/null
+#!/usr/bin/perl
+
+# (C) Dmitry Volyntsev
+# (C) F5, Inc.
+
+# Tests for http njs module, r.jsVarNames() method.
+
+###############################################################################
+
+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_import test.js;
+
+ js_var $foo;
+ js_var $test_method;
+ js_var $test_params_name;
+ js_var $test_params_arguments_workspace;
+ js_var $other_name;
+ js_set $js_set_var test.set_var;
+
+ server {
+ listen 127.0.0.1:8080;
+ server_name localhost;
+
+ set $set_var conf_set;
+
+ location /all {
+ js_content test.all;
+ }
+
+ location /prefix {
+ js_content test.prefix;
+ }
+
+ location /empty_prefix {
+ js_content test.empty_prefix;
+ }
+
+ location /none {
+ js_content test.none;
+ }
+
+ location /array {
+ js_content test.array;
+ }
+
+ location /excludes {
+ js_content test.excludes;
+ }
+
+ location /fresh {
+ js_content test.fresh;
+ }
+
+ location /type {
+ js_content test.type;
+ }
+ }
+}
+
+EOF
+
+$t->write_file('test.js', <<'EOF');
+ function render(names) {
+ return names.sort().join('|');
+ }
+
+ function all(r) {
+ r.return(200, render(r.jsVarNames()));
+ }
+
+ function prefix(r) {
+ r.return(200, render(r.jsVarNames('test_')));
+ }
+
+ function empty_prefix(r) {
+ r.return(200, render(r.jsVarNames('')));
+ }
+
+ function none(r) {
+ r.return(200, String(r.jsVarNames('none_').length));
+ }
+
+ function array(r) {
+ r.return(200, String(Array.isArray(r.jsVarNames())));
+ }
+
+ function excludes(r) {
+ let names = r.jsVarNames();
+
+ r.return(200, String(names.indexOf('js_set_var') == -1
+ && names.indexOf('set_var') == -1
+ && names.indexOf('remote_addr') == -1));
+ }
+
+ function fresh(r) {
+ let names = r.jsVarNames('test_');
+ names.push('test_bad');
+
+ r.return(200, r.jsVarNames('test_').indexOf('test_bad') == -1
+ ? 'fresh' : 'shared');
+ }
+
+ function type(r) {
+ try {
+ r.jsVarNames(1);
+ r.return(200, 'no error');
+
+ } catch (e) {
+ r.return(200, e.name + ':' + e.message);
+ }
+ }
+
+ function set_var(r) {
+ return 'set';
+ }
+
+ export default {all, prefix, empty_prefix, none, array, excludes, fresh,
+ type, set_var};
+EOF
+
+$t->try_run('no r.jsVarNames')->plan(8);
+
+###############################################################################
+
+my $all = 'foo|other_name|test_method|'
+ . 'test_params_arguments_workspace|test_params_name';
+
+my $prefix = 'test_method|test_params_arguments_workspace|test_params_name';
+
+is(http_get_body('/all'), $all, 'jsVarNames all js_var names');
+is(http_get_body('/prefix'), $prefix, 'jsVarNames prefix');
+is(http_get_body('/empty_prefix'), $all, 'jsVarNames empty prefix');
+is(http_get_body('/none'), '0', 'jsVarNames prefix no match');
+is(http_get_body('/array'), 'true', 'jsVarNames returns an array');
+is(http_get_body('/excludes'), 'true', 'jsVarNames excludes other variables');
+is(http_get_body('/fresh'), 'fresh', 'jsVarNames fresh array');
+like(http_get_body('/type'), qr/^TypeError:.*prefix.*string/,
+ 'jsVarNames prefix type');
+
+$t->stop();
+
+###############################################################################
+
+sub http_get_body {
+ my ($uri) = @_;
+
+ http_get($uri) =~ /\x0d\x0a?\x0d\x0a?(.*)/ms;
+ return $1;
+}
+
+###############################################################################
--- /dev/null
+#!/usr/bin/perl
+
+# (C) Dmitry Volyntsev
+# (C) F5, Inc.
+
+# Tests for stream njs module, s.jsVarNames() method.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::Stream qw/ stream /;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/stream stream_return/)
+ ->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+stream {
+ %%TEST_GLOBALS_STREAM%%
+
+ js_import test.js;
+
+ js_var $foo;
+ js_var $test_method;
+ js_var $test_params_name;
+ js_var $test_params_arguments_workspace;
+ js_var $other_name;
+ js_set $js_set_var test.set_var;
+ js_set $all test.all;
+ js_set $prefix test.prefix;
+ js_set $empty_prefix test.empty_prefix;
+ js_set $none test.none;
+ js_set $array test.array;
+ js_set $excludes test.excludes;
+ js_set $fresh test.fresh;
+ js_set $type test.type;
+ server {
+ listen 127.0.0.1:8081;
+ return $all;
+ }
+
+ server {
+ listen 127.0.0.1:8082;
+ return $prefix;
+ }
+
+ server {
+ listen 127.0.0.1:8083;
+ return $empty_prefix;
+ }
+
+ server {
+ listen 127.0.0.1:8084;
+ return $none;
+ }
+
+ server {
+ listen 127.0.0.1:8085;
+ return $array;
+ }
+
+ server {
+ listen 127.0.0.1:8086;
+ return $excludes;
+ }
+
+ server {
+ listen 127.0.0.1:8087;
+ return $fresh;
+ }
+
+ server {
+ listen 127.0.0.1:8088;
+ return $type;
+ }
+}
+
+EOF
+
+$t->write_file('test.js', <<'EOF');
+ function render(names) {
+ return names.sort().join('|');
+ }
+
+ function all(s) {
+ return render(s.jsVarNames());
+ }
+
+ function prefix(s) {
+ return render(s.jsVarNames('test_'));
+ }
+
+ function empty_prefix(s) {
+ return render(s.jsVarNames(''));
+ }
+
+ function none(s) {
+ return String(s.jsVarNames('none_').length);
+ }
+
+ function array(s) {
+ return String(Array.isArray(s.jsVarNames()));
+ }
+
+ function excludes(s) {
+ let names = s.jsVarNames();
+
+ return String(names.indexOf('js_set_var') == -1
+ && names.indexOf('remote_addr') == -1);
+ }
+
+ function fresh(s) {
+ let names = s.jsVarNames('test_');
+ names.push('test_bad');
+
+ return s.jsVarNames('test_').indexOf('test_bad') == -1
+ ? 'fresh' : 'shared';
+ }
+
+ function type(s) {
+ try {
+ s.jsVarNames(1);
+ return 'no error';
+
+ } catch (e) {
+ return e.name + ':' + e.message;
+ }
+ }
+
+ function set_var(s) {
+ return 'set';
+ }
+
+ export default {all, prefix, empty_prefix, none, array, excludes, fresh,
+ type, set_var};
+EOF
+
+$t->try_run('no s.jsVarNames')->plan(8);
+
+###############################################################################
+
+my $all = 'foo|other_name|test_method|'
+ . 'test_params_arguments_workspace|test_params_name';
+
+my $prefix = 'test_method|test_params_arguments_workspace|test_params_name';
+
+is(stream('127.0.0.1:' . port(8081))->read(), $all,
+ 'jsVarNames all js_var names');
+is(stream('127.0.0.1:' . port(8082))->read(), $prefix,
+ 'jsVarNames prefix');
+is(stream('127.0.0.1:' . port(8083))->read(), $all,
+ 'jsVarNames empty prefix');
+is(stream('127.0.0.1:' . port(8084))->read(), '0',
+ 'jsVarNames prefix no match');
+is(stream('127.0.0.1:' . port(8085))->read(), 'true',
+ 'jsVarNames returns an array');
+is(stream('127.0.0.1:' . port(8086))->read(), 'true',
+ 'jsVarNames excludes other variables');
+is(stream('127.0.0.1:' . port(8087))->read(), 'fresh',
+ 'jsVarNames fresh array');
+like(stream('127.0.0.1:' . port(8088))->read(),
+ qr/^TypeError:.*prefix.*string/, 'jsVarNames prefix type');
+
+$t->stop();
+
+###############################################################################