if [ $HTTP != NO ]; then
ngx_module_type=HTTP_AUX_FILTER
- ngx_module_name=ngx_http_js_module
+ ngx_module_name="ngx_http_js_module ngx_http_js_core_module"
ngx_module_incs="$ngx_addon_dir/../src $ngx_addon_dir/../build \
$NJS_QUICKJS_INC"
ngx_module_deps="$NJS_ENGINE_DEP $NJS_DEPS $QJS_DEPS"
if [ $STREAM != NO ]; then
ngx_module_type=STREAM
- ngx_module_name=ngx_stream_js_module
+ ngx_module_name="ngx_stream_js_module ngx_stream_js_core_module"
ngx_module_incs="$ngx_addon_dir/../src $ngx_addon_dir/../build \
$NJS_QUICKJS_INC"
ngx_module_deps="$NJS_ENGINE_DEP $NJS_DEPS $QJS_DEPS"
};
+static ngx_command_t ngx_js_core_commands[] = {
+
+ { ngx_string("js_load_http_native_module"),
+ NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE13,
+ ngx_js_core_load_native_module,
+ 0,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_core_module_t ngx_js_core_module_ctx = {
+ ngx_string("ngx_http_js_core"),
+ ngx_js_core_create_conf,
+ NULL
+};
+
+
+ngx_module_t ngx_http_js_core_module = {
+ NGX_MODULE_V1,
+ &ngx_js_core_module_ctx, /* module context */
+ ngx_js_core_commands, /* module directives */
+ NGX_CORE_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
static ngx_http_module_t ngx_http_js_module_ctx = {
NULL, /* preconfiguration */
ngx_http_js_init, /* postconfiguration */
options.u.qjs.metas = ngx_http_js_uptr;
options.u.qjs.addons = njs_http_qjs_addon_modules;
options.clone = ngx_engine_qjs_clone;
+
+ options.core_conf = (ngx_js_core_conf_t *)
+ ngx_get_conf(cf->cycle->conf_ctx, ngx_http_js_core_module);
}
#endif
#include <ngx_config.h>
#include <ngx_core.h>
#include <math.h>
+#include <dlfcn.h>
#include "ngx_js.h"
#include "ngx_js_http.h"
engine->string = ngx_engine_qjs_string;
engine->destroy = opts->destroy ? opts->destroy
: ngx_engine_qjs_destroy;
+
+ engine->core_conf = opts->core_conf;
break;
#endif
ngx_int_t rc;
JSRuntime *rt;
JSContext *cx;
+ qjs_module_t *mod;
ngx_engine_t *engine;
ngx_js_code_entry_t *pc;
JS_SetHostPromiseRejectionTracker(rt, ngx_qjs_rejection_tracker, ctx);
+ if (engine->native_modules != NULL) {
+ mod = engine->native_modules->start;
+ length = engine->native_modules->items;
+
+ for (i = 0; i < length; i++) {
+ if (mod[i].init(cx, mod[i].name) == NULL) {
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+ "js native module init failed: %s", mod[i].name);
+ goto destroy;
+ }
+ }
+ }
+
rv = JS_UNDEFINED;
pc = engine->precompiled->start;
length = engine->precompiled->items;
}
+static JSModuleDef *
+ngx_qjs_native_module_lookup(JSContext *cx, const char *module_name,
+ ngx_js_loc_conf_t *conf)
+{
+ ngx_uint_t i;
+ JSModuleDef *m;
+ qjs_module_t *mod, *modules;
+ ngx_js_core_conf_t *jccf;
+
+ jccf = conf->engine->core_conf;
+ if (jccf == NULL || jccf->native_modules == NULL) {
+ return NULL;
+ }
+
+ modules = jccf->native_modules->elts;
+
+ for (i = 0; i < jccf->native_modules->nelts; i++) {
+ if (ngx_strcmp(modules[i].name, module_name) == 0) {
+ m = modules[i].init(cx, module_name);
+ if (m == NULL) {
+ return NULL;
+ }
+
+ if (conf->engine->native_modules == NULL) {
+ conf->engine->native_modules = njs_arr_create(
+ conf->engine->pool, 4,
+ sizeof(qjs_module_t));
+ if (conf->engine->native_modules == NULL) {
+ JS_ThrowOutOfMemory(cx);
+ return NULL;
+ }
+ }
+
+ mod = njs_arr_add(conf->engine->native_modules);
+ if (mod == NULL) {
+ JS_ThrowOutOfMemory(cx);
+ return NULL;
+ }
+
+ *mod = modules[i];
+
+ return m;
+ }
+ }
+
+ return NULL;
+}
+
+
static JSModuleDef *
ngx_qjs_module_loader(JSContext *cx, const char *module_name, void *opaque)
{
conf = opaque;
+ m = ngx_qjs_native_module_lookup(cx, module_name, conf);
+ if (m != NULL) {
+ return m;
+ }
+
njs_memzero(&info, sizeof(njs_module_info_t));
info.name.start = (u_char *) module_name;
}
+void *
+ngx_js_core_create_conf(ngx_cycle_t *cycle)
+{
+ ngx_js_core_conf_t *jccf;
+
+ jccf = ngx_pcalloc(cycle->pool, sizeof(ngx_js_core_conf_t));
+ if (jccf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * jccf->native_modules = NULL;
+ */
+
+ return jccf;
+}
+
+
+void
+ngx_js_native_module_cleanup(void *data)
+{
+ void *handle = data;
+
+ if (dlclose(handle) != 0) {
+ ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+ "dlclose() failed: %s", dlerror());
+ }
+}
+
+
+char *
+ngx_js_core_load_native_module(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+#if (NJS_HAVE_QUICKJS)
+ void *handle;
+ u_char *p;
+ ngx_str_t *value, file, name;
+ qjs_module_t *module;
+ qjs_addon_init_pt init;
+ ngx_pool_cleanup_t *cln;
+
+ ngx_js_core_conf_t *jccf = conf;
+
+ if (cf->cycle->modules_used) {
+ return "is specified too late";
+ }
+
+ value = cf->args->elts;
+ file = value[1];
+
+ if (ngx_conf_full_name(cf->cycle, &file, 0) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cf->args->nelts == 4) {
+ if (ngx_strcmp(value[2].data, "as") != 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\", expected \"as\"",
+ &value[2]);
+ return NGX_CONF_ERROR;
+ }
+
+ name = value[3];
+
+ } else {
+ name = file;
+
+ for (p = file.data + file.len - 1; p >= file.data; p--) {
+ if (*p == '/') {
+ name.data = p + 1;
+ name.len = file.data + file.len - name.data;
+ break;
+ }
+ }
+ }
+
+ handle = dlopen((char *) file.data, RTLD_NOW | RTLD_LOCAL);
+ if (handle == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "dlopen(\"%V\") failed: %s", &file, dlerror());
+ return NGX_CONF_ERROR;
+ }
+
+ cln = ngx_pool_cleanup_add(cf->cycle->pool, 0);
+ if (cln == NULL) {
+ dlclose(handle);
+ return NGX_CONF_ERROR;
+ }
+
+ cln->handler = ngx_js_native_module_cleanup;
+ cln->data = handle;
+
+ init = dlsym(handle, "js_init_module");
+ if (init == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "dlsym(\"%V\", \"js_init_module\") failed: %s",
+ &file, dlerror());
+ return NGX_CONF_ERROR;
+ }
+
+ if (jccf->native_modules == NULL) {
+ jccf->native_modules = ngx_array_create(cf->cycle->pool, 4,
+ sizeof(qjs_module_t));
+ if (jccf->native_modules == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ module = ngx_array_push(jccf->native_modules);
+ if (module == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ p = ngx_palloc(cf->cycle->pool, name.len + 1);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memcpy(p, name.data, name.len);
+ p[name.len] = '\0';
+
+ module->name = (const char *) p;
+ module->init = init;
+
+ return NGX_CONF_OK;
+
+#else
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"load_js_native_module\" requires QuickJS support");
+ return NGX_CONF_ERROR;
+
+#endif
+}
+
+
static uint64_t
ngx_js_monotonic_time(void)
{
typedef ngx_js_ctx_t *(*ngx_js_external_ctx_pt)(njs_external_ptr_t e);
+typedef struct {
+ ngx_array_t *native_modules;
+} ngx_js_core_conf_t;
+
+
typedef struct {
ngx_str_t name;
ngx_str_t path;
} u;
njs_str_t file;
+ ngx_js_core_conf_t *core_conf;
ngx_js_loc_conf_t *conf;
ngx_engine_t *(*clone)(ngx_js_ctx_t *ctx,
ngx_js_loc_conf_t *cf, njs_int_t pr_id,
const char *name;
njs_mp_t *pool;
njs_arr_t *precompiled;
+ njs_arr_t *native_modules;
+ ngx_js_core_conf_t *core_conf;
};
char *ngx_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf,
void *tag);
+void *ngx_js_core_create_conf(ngx_cycle_t *cycle);
+char *ngx_js_core_load_native_module(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+void ngx_js_native_module_cleanup(void *data);
+
njs_int_t ngx_js_ext_string(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused,
njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
njs_int_t ngx_js_ext_uint(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused,
};
+static ngx_command_t ngx_js_core_commands[] = {
+
+ { ngx_string("js_load_stream_native_module"),
+ NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE13,
+ ngx_js_core_load_native_module,
+ 0,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_core_module_t ngx_js_core_module_ctx = {
+ ngx_string("ngx_stream_js_core"),
+ ngx_js_core_create_conf,
+ NULL
+};
+
+
+ngx_module_t ngx_stream_js_core_module = {
+ NGX_MODULE_V1,
+ &ngx_js_core_module_ctx, /* module context */
+ ngx_js_core_commands, /* module directives */
+ NGX_CORE_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
static ngx_stream_module_t ngx_stream_js_module_ctx = {
NULL, /* preconfiguration */
ngx_stream_js_init, /* postconfiguration */
options.u.qjs.addons = njs_stream_qjs_addon_modules;
options.clone = ngx_engine_qjs_clone;
options.destroy = ngx_stream_qjs_destroy;
+
+ options.core_conf = (ngx_js_core_conf_t *)
+ ngx_get_conf(cf->cycle->conf_ctx, ngx_stream_js_core_module);
}
#endif
--- /dev/null
+#!/usr/bin/perl
+
+# (C) Dmitry Volyntsev
+# (C) F5, Inc.
+
+# Tests for QuickJS native module support.
+
+###############################################################################
+
+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 $cc;
+for my $c ('gcc', 'clang') {
+ if (system("which $c >/dev/null 2>&1") == 0) {
+ $cc = $c;
+ last;
+ }
+}
+
+plan(skip_all => "gcc or clang not found") unless defined $cc;
+
+my $configure_args = `$Test::Nginx::NGINX -V 2>&1`;
+my $m32 = $configure_args =~ /-m32/ ? '-m32' : '';
+my $quickjs_inc = $configure_args =~ /(-I\S*quickjs(?:-ng)?[^\s'"]*)/
+ ? $1 : undef;
+
+plan(skip_all => "QuickJS development files not found") unless $quickjs_inc;
+
+my $t = Test::Nginx->new()->has(qw/http/)
+ ->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+js_load_http_native_module %%TESTDIR%%/test.so;
+js_load_http_native_module %%TESTDIR%%/test.so as test;
+
+daemon off;
+
+events {
+}
+
+http {
+ %%TEST_GLOBALS_HTTP%%
+
+ js_import main from test.js;
+
+ server {
+ listen 127.0.0.1:8080;
+ server_name localhost;
+
+ location /add {
+ js_content main.test_add;
+ }
+
+ location /reverse {
+ js_content main.test_reverse;
+ }
+ }
+}
+
+EOF
+
+my $d = $t->testdir();
+
+$t->write_file('test.js', <<EOF);
+ import * as native from 'test.so';
+ import * as native2 from 'test';
+
+ function test_add(r) {
+ let a = Number(r.args.a);
+ let b = Number(r.args.b);
+ r.return(200, native.add(a, b).toString());
+ }
+
+ function test_reverse(r) {
+ r.return(200, native2.reverseString(r.args.str));
+ }
+
+ export default { test_add, test_reverse };
+
+EOF
+
+$t->write_file('test.c', <<EOF);
+#include <quickjs.h>
+
+#define countof(x) (sizeof(x) / sizeof((x)[0]))
+
+static JSValue
+js_add(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
+{
+ int a, b;
+
+ if (argc < 2) {
+ return JS_ThrowTypeError(ctx, "expected 2 arguments");
+ }
+
+ if (JS_ToInt32(ctx, &a, argv[0]) < 0) {
+ return JS_EXCEPTION;
+ }
+
+ if (JS_ToInt32(ctx, &b, argv[1]) < 0) {
+ return JS_EXCEPTION;
+ }
+
+ return JS_NewInt32(ctx, a + b);
+}
+
+
+static JSValue
+js_reverse_string(JSContext *ctx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ char *reversed;
+ size_t i, len;
+ JSValue result;
+ const char *str;
+
+ if (argc < 1) {
+ return JS_ThrowTypeError(ctx, "expected 1 argument");
+ }
+
+ str = JS_ToCStringLen(ctx, &len, argv[0]);
+ if (!str) {
+ return JS_EXCEPTION;
+ }
+
+ reversed = js_malloc(ctx, len + 1);
+ if (!reversed) {
+ JS_FreeCString(ctx, str);
+ return JS_EXCEPTION;
+ }
+
+ for (i = 0; i < len; i++) {
+ reversed[i] = str[len - 1 - i];
+ }
+
+ reversed[len] = 0;
+
+ result = JS_NewString(ctx, reversed);
+
+ js_free(ctx, reversed);
+ JS_FreeCString(ctx, str);
+
+ return result;
+}
+
+
+static const JSCFunctionListEntry js_test_native_funcs[] = {
+ JS_CFUNC_DEF("add", 2, js_add),
+ JS_CFUNC_DEF("reverseString", 1, js_reverse_string),
+};
+
+
+static int
+js_test_native_init(JSContext *ctx, JSModuleDef *m)
+{
+ return JS_SetModuleExportList(ctx, m, js_test_native_funcs,
+ countof(js_test_native_funcs));
+}
+
+
+JSModuleDef *
+js_init_module(JSContext *ctx, const char *module_name)
+{
+ int rc;
+ JSModuleDef *m;
+
+ m = JS_NewCModule(ctx, module_name, js_test_native_init);
+ if (!m) {
+ return NULL;
+ }
+
+ rc = JS_AddModuleExportList(ctx, m, js_test_native_funcs,
+ countof(js_test_native_funcs));
+ if (rc < 0) {
+ return NULL;
+ }
+
+ rc = JS_AddModuleExport(ctx, m, "default");
+ if (rc < 0) {
+ return NULL;
+ }
+
+ return m;
+}
+EOF
+
+system("$cc -fPIC $m32 -O $quickjs_inc -shared -o $d/test.so $d/test.c") == 0
+ or die "failed to build QuickJS native module: $!\n";
+
+$t->try_run('no QuickJS native module support')->plan(2);
+
+###############################################################################
+
+like(http_get('/add?a=7&b=9'), qr/16$/, 'native module add');
+like(http_get('/reverse?str=hello'), qr/olleh$/, 'native module reverseString');
+
+###############################################################################
--- /dev/null
+#!/usr/bin/perl
+
+# (C) Dmitry Volyntsev
+# (C) F5, Inc.
+
+# Tests for QuickJS native module support in stream.
+
+###############################################################################
+
+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 $cc;
+for my $c ('gcc', 'clang') {
+ if (system("which $c >/dev/null 2>&1") == 0) {
+ $cc = $c;
+ last;
+ }
+}
+
+plan(skip_all => "gcc or clang not found") unless defined $cc;
+
+my $configure_args = `$Test::Nginx::NGINX -V 2>&1`;
+my $m32 = $configure_args =~ /-m32/ ? '-m32' : '';
+my $quickjs_inc = $configure_args =~ /(-I\S*quickjs(?:-ng)?[^\s'"]*)/
+ ? $1 : undef;
+
+plan(skip_all => "QuickJS development files not found") unless $quickjs_inc;
+
+my $t = Test::Nginx->new()->has(qw/stream stream_return/)
+ ->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+js_load_stream_native_module %%TESTDIR%%/test.so;
+js_load_stream_native_module %%TESTDIR%%/test.so as test;
+
+daemon off;
+
+events {
+}
+
+stream {
+ %%TEST_GLOBALS_STREAM%%
+
+ js_set $reverse test.reverse;
+ js_set $duplicate test.duplicate;
+
+ js_import test.js;
+
+ server {
+ listen 127.0.0.1:8081;
+ return $reverse;
+ }
+
+ server {
+ listen 127.0.0.1:8082;
+ return $duplicate;
+ }
+}
+
+EOF
+
+my $d = $t->testdir();
+
+$t->write_file('test.js', <<EOF);
+ import * as native from 'test.so';
+ import * as native2 from 'test';
+
+ function reverse(s) {
+ return native.reverseString(s.remoteAddress);
+ }
+
+ function duplicate(s) {
+ return native2.duplicate(s.remoteAddress);
+ }
+
+ export default { reverse, duplicate };
+
+EOF
+
+$t->write_file('test.c', <<EOF);
+#include <quickjs.h>
+#include <string.h>
+
+#define countof(x) (sizeof(x) / sizeof((x)[0]))
+
+static JSValue
+js_reverse_string(JSContext *ctx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ char *reversed;
+ size_t i, len;
+ JSValue result;
+ const char *str;
+
+ if (argc < 1) {
+ return JS_ThrowTypeError(ctx, "expected 1 argument");
+ }
+
+ str = JS_ToCStringLen(ctx, &len, argv[0]);
+ if (!str) {
+ return JS_EXCEPTION;
+ }
+
+ reversed = js_malloc(ctx, len + 1);
+ if (!reversed) {
+ JS_FreeCString(ctx, str);
+ return JS_EXCEPTION;
+ }
+
+ for (i = 0; i < len; i++) {
+ reversed[i] = str[len - 1 - i];
+ }
+
+ reversed[len] = 0;
+
+ result = JS_NewString(ctx, reversed);
+
+ js_free(ctx, reversed);
+ JS_FreeCString(ctx, str);
+
+ return result;
+}
+
+
+static JSValue
+js_duplicate(JSContext *ctx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ char *dup;
+ size_t len;
+ JSValue result;
+ const char *str;
+
+ if (argc < 1) {
+ return JS_ThrowTypeError(ctx, "expected 1 argument");
+ }
+
+ str = JS_ToCStringLen(ctx, &len, argv[0]);
+ if (!str) {
+ return JS_EXCEPTION;
+ }
+
+ dup = js_malloc(ctx, len * 2 + 1);
+ if (!dup) {
+ JS_FreeCString(ctx, str);
+ return JS_EXCEPTION;
+ }
+
+ memcpy(dup, str, len);
+ memcpy(dup + len, str, len);
+ dup[len * 2] = 0;
+
+ result = JS_NewString(ctx, dup);
+
+ js_free(ctx, dup);
+ JS_FreeCString(ctx, str);
+
+ return result;
+}
+
+
+static const JSCFunctionListEntry js_test_native_funcs[] = {
+ JS_CFUNC_DEF("reverseString", 1, js_reverse_string),
+ JS_CFUNC_DEF("duplicate", 1, js_duplicate),
+};
+
+
+static int
+js_test_native_init(JSContext *ctx, JSModuleDef *m)
+{
+ return JS_SetModuleExportList(ctx, m, js_test_native_funcs,
+ countof(js_test_native_funcs));
+}
+
+
+JSModuleDef *
+js_init_module(JSContext *ctx, const char *module_name)
+{
+ int rc;
+ JSModuleDef *m;
+
+ m = JS_NewCModule(ctx, module_name, js_test_native_init);
+ if (!m) {
+ return NULL;
+ }
+
+ rc = JS_AddModuleExportList(ctx, m, js_test_native_funcs,
+ countof(js_test_native_funcs));
+ if (rc < 0) {
+ return NULL;
+ }
+
+ rc = JS_AddModuleExport(ctx, m, "default");
+ if (rc < 0) {
+ return NULL;
+ }
+
+ return m;
+}
+EOF
+
+system("$cc -fPIC $m32 -O $quickjs_inc -shared -o $d/test.so $d/test.c") == 0
+ or die "failed to build QuickJS native module: $!\n";
+
+$t->try_run('no QuickJS native module support')->plan(2);
+
+###############################################################################
+
+like(stream('127.0.0.1:' . port(8081))->read(), qr/1\.0\.0\.721$/,
+ 'native module reverseString');
+like(stream('127.0.0.1:' . port(8082))->read(), qr/127\.0\.0\.1127\.0\.0\.1$/,
+ 'native module duplicate');
+
+###############################################################################