[njs] Modules: introduced QuickJS engine.
noreply at nginx.com
noreply at nginx.com
Wed Sep 18 01:06:02 UTC 2024
details: https://github.com/nginx/njs/commit/201b127679e27fe63eff5c1b4356ec4ed9ec4611
branches: master
commit: 201b127679e27fe63eff5c1b4356ec4ed9ec4611
user: Dmitry Volyntsev <xeioex at nginx.com>
date: Fri, 14 Jun 2024 20:54:28 -0700
description:
Modules: introduced QuickJS engine.
"js_engine" directive is introduced which sets JavaScript engine.
When the module is built with QuickJS library "js_engine qjs;" sets
QuickJS engine for the current block. By default njs engine is used.
For example,
nginx.conf:
location /a {
js_engine qjs;
# will be handled by QuickJS
js_content main.handler;
}
location /b {
# will be handled by njs
js_content main.handler;
}
QuickJS engine implements drop-in replacement for nginx/njs objects
with the following exceptions:
* nginx module API to be added later: ngx.fetch(), ngx.shared.dict.
* Built-in modules to be added later: fs, crypto, WebCrypto, xml.
* NJS specific API: njs.dump(), njs.on(), console.dump().
* js_preload_object directive.
---
.github/workflows/check-pr.yml | 65 +
nginx/config | 100 +-
nginx/config.make | 10 +-
nginx/ngx_http_js_module.c | 3121 +++++++++++++++++++++++++++++++++++-
nginx/ngx_js.c | 1838 +++++++++++++++++++--
nginx/ngx_js.h | 131 ++
nginx/ngx_stream_js_module.c | 1137 ++++++++++++-
nginx/t/js_console.t | 20 +-
nginx/t/js_dump.t | 22 +-
nginx/t/js_engine.t | 140 ++
nginx/t/js_fetch.t | 16 +-
nginx/t/js_fetch_https.t | 16 +-
nginx/t/js_fetch_objects.t | 18 +-
nginx/t/js_fetch_resolver.t | 16 +-
nginx/t/js_fetch_timeout.t | 17 +-
nginx/t/js_fetch_verify.t | 16 +-
nginx/t/js_object.t | 18 +-
nginx/t/js_periodic.t | 16 +-
nginx/t/js_preload_object.t | 16 +-
nginx/t/js_shared_dict.t | 18 +-
nginx/t/stream_js_console.t | 38 +-
nginx/t/stream_js_exit.t | 16 +-
nginx/t/stream_js_fetch.t | 16 +-
nginx/t/stream_js_fetch_https.t | 16 +-
nginx/t/stream_js_fetch_init.t | 16 +-
nginx/t/stream_js_object.t | 85 +-
nginx/t/stream_js_preload_object.t | 27 +-
nginx/t/stream_js_shared_dict.t | 16 +-
src/qjs.h | 6 +
src/qjs_buffer.c | 23 +-
30 files changed, 6751 insertions(+), 259 deletions(-)
diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml
index e26b1e29..7dd2bd57 100644
--- a/.github/workflows/check-pr.yml
+++ b/.github/workflows/check-pr.yml
@@ -84,3 +84,68 @@ jobs:
TEST_NGINX_BINARY: "${{ github.workspace }}/nginx-source/objs/nginx"
TEST_NGINX_GLOBALS: "load_module ${{ github.workspace }}/nginx-source/objs/ngx_http_js_module.so; load_module ${{ github.workspace }}/nginx-source/objs/ngx_stream_js_module.so;"
TEST_NGINX_VERBOSE: 1
+
+ - name: Create LSAN suppression file
+ run: |
+ cat << EOF > lsan_suppressions.txt
+ leak:ngx_event_process_init
+ EOF
+
+ - name: Configure and build nginx and njs modules with quickjs, static modules
+ run: |
+ cd nginx-source
+ $NGINX_CONFIGURE_CMD --with-cc-opt="$CC_OPT -I${{ github.workspace }}/quickjs -fsanitize=address" --with-ld-opt="$LD_OPT -L${{ github.workspace }}/quickjs -fsanitize=address" --add-module=../nginx || cat objs/autoconf.err
+ $MAKE_UTILITY -j$(nproc)
+
+ - name: Test njs modules, static modules
+ run: |
+ ulimit -c unlimited
+ prove -v -j$(nproc) -Inginx-tests/lib --state=save nginx/t . || prove -v -Inginx-tests/lib --state=failed
+ env:
+ TEST_NGINX_BINARY: "${{ github.workspace }}/nginx-source/objs/nginx"
+ TEST_NGINX_VERBOSE: 1
+ ASAN_OPTIONS: "detect_odr_violation=0:report_globals=0"
+ LSAN_OPTIONS: "suppressions=${{ github.workspace }}/lsan_suppressions.txt"
+
+ - name: Test njs modules (js_engine qjs), static modules
+ run: |
+ ulimit -c unlimited
+ prove -v -j$(nproc) -Inginx-tests/lib --state=save nginx/t . || prove -v -Inginx-tests/lib --state=failed
+ env:
+ TEST_NGINX_BINARY: "${{ github.workspace }}/nginx-source/objs/nginx"
+ TEST_NGINX_GLOBALS_HTTP: "js_engine qjs;"
+ TEST_NGINX_GLOBALS_STREAM: "js_engine qjs;"
+ TEST_NGINX_VERBOSE: 1
+ ASAN_OPTIONS: "detect_odr_violation=0:report_globals=0"
+ LSAN_OPTIONS: "suppressions=${{ github.workspace }}/lsan_suppressions.txt"
+
+ - name: Configure and build nginx and njs modules with quickjs, dynamic modules
+ run: |
+ cd nginx-source
+ $NGINX_CONFIGURE_CMD --with-debug --with-cc-opt="$CC_OPT -I${{ github.workspace }}/quickjs -fsanitize=address" --with-ld-opt="$LD_OPT -L${{ github.workspace }}/quickjs -fsanitize=address" --add-dynamic-module=../nginx || cat objs/autoconf.err
+ $MAKE_UTILITY -j$(nproc) modules
+ $MAKE_UTILITY -j$(nproc)
+
+ - name: Test njs modules, dynamic modules
+ run: |
+ ulimit -c unlimited
+ prove -v -j$(nproc) -Inginx-tests/lib --state=save nginx/t . || prove -v -Inginx-tests/lib --state=failed
+ env:
+ TEST_NGINX_BINARY: "${{ github.workspace }}/nginx-source/objs/nginx"
+ TEST_NGINX_GLOBALS: "load_module ${{ github.workspace }}/nginx-source/objs/ngx_http_js_module.so; load_module ${{ github.workspace }}/nginx-source/objs/ngx_stream_js_module.so;"
+ TEST_NGINX_VERBOSE: 1
+ ASAN_OPTIONS: "detect_odr_violation=0:report_globals=0:fast_unwind_on_malloc=0"
+ LSAN_OPTIONS: "suppressions=${{ github.workspace }}/lsan_suppressions.txt"
+
+ - name: Test njs modules (js_engine qjs), dynamic modules
+ run: |
+ ulimit -c unlimited
+ prove -v -j$(nproc) -Inginx-tests/lib --state=save nginx/t . || prove -v -Inginx-tests/lib --state=failed
+ env:
+ TEST_NGINX_BINARY: "${{ github.workspace }}/nginx-source/objs/nginx"
+ TEST_NGINX_GLOBALS: "load_module ${{ github.workspace }}/nginx-source/objs/ngx_stream_js_module.so; load_module ${{ github.workspace }}/nginx-source/objs/ngx_http_js_module.so;"
+ TEST_NGINX_GLOBALS_HTTP: "js_engine qjs;"
+ TEST_NGINX_GLOBALS_STREAM: "js_engine qjs;"
+ TEST_NGINX_VERBOSE: 1
+ ASAN_OPTIONS: "detect_odr_violation=0:report_globals=0:fast_unwind_on_malloc=0"
+ LSAN_OPTIONS: "suppressions=${{ github.workspace }}/lsan_suppressions.txt"
diff --git a/nginx/config b/nginx/config
index 700ae4ab..436f06cb 100644
--- a/nginx/config
+++ b/nginx/config
@@ -3,6 +3,7 @@ ngx_addon_name="ngx_js_module"
NJS_OPENSSL=${NJS_OPENSSL:-YES}
NJS_LIBXSLT=${NJS_LIBXSLT:-YES}
NJS_ZLIB=${NJS_ZLIB:-YES}
+NJS_QUICKJS=${NJS_QUICKJS:-YES}
NJS_DEPS="$ngx_addon_dir/ngx_js.h \
$ngx_addon_dir/ngx_js_fetch.h \
@@ -12,9 +13,78 @@ NJS_SRCS="$ngx_addon_dir/ngx_js.c \
$ngx_addon_dir/ngx_js_regex.c \
$ngx_addon_dir/ngx_js_shared_dict.c"
+QJS_DEPS=""
+QJS_SRCS=""
+
NJS_OPENSSL_LIB=
NJS_XSLT_LIB=
NJS_ZLIB_LIB=
+NJS_QUICKJS_LIB=
+NJS_QUICKJS_INC=
+NJS_HAVE_QUICKJS=
+
+if [ $NJS_QUICKJS != NO ]; then
+
+ ngx_feature="QuickJS library -lquickjs.lto"
+ ngx_feature_name=NJS_HAVE_QUICKJS
+ ngx_feature_run=yes
+ ngx_feature_incs="#if defined(__GNUC__) && (__GNUC__ >= 8)
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored \"-Wcast-function-type\"
+ #endif
+
+ #include <quickjs.h>"
+ ngx_feature_path=""
+ ngx_feature_libs="-lquickjs.lto -lm -ldl -lpthread"
+ ngx_feature_test="JSRuntime *rt;
+
+ rt = JS_NewRuntime();
+ (void) JS_GetClassID;
+ JS_FreeRuntime(rt);
+ return 0;"
+ . auto/feature
+
+ if [ $ngx_found = no ]; then
+ ngx_feature="QuickJS library -lquickjs"
+ ngx_feature_libs="-lquickjs -lm -ldl -lpthread"
+
+ . auto/feature
+ fi
+
+ if [ $ngx_found = no ]; then
+ ngx_feature="QuickJS library -I/usr/include/quickjs/ -L/usr/lib/quickjs/ -lquickjs.lto"
+ ngx_feature_path="/usr/include/quickjs/"
+ ngx_feature_libs="-L/usr/lib/quickjs/ -lquickjs.lto -lm -ldl -lpthread"
+
+ . auto/feature
+ fi
+
+ if [ $ngx_found = no ]; then
+ ngx_feature="QuickJS library -I/usr/include/quickjs/ -L/usr/lib/quickjs/ -lquickjs"
+ ngx_feature_libs="-L/usr/lib/quickjs/ -lquickjs -lm -ldl -lpthread"
+
+ . auto/feature
+ fi
+
+ if [ $ngx_found = yes ]; then
+
+ ngx_feature="QuickJS JS_NewTypedArray()"
+ ngx_feature_test="(void) JS_NewTypedArray;
+ return 0;"
+
+ . auto/feature
+
+ if [ $ngx_found = yes ]; then
+ have=NJS_HAVE_QUICKJS_NEW_TYPED_ARRAY . auto/have
+ fi
+
+ NJS_HAVE_QUICKJS=YES
+ NJS_QUICKJS_LIB="$ngx_feature_libs"
+ NJS_QUICKJS_INC="$ngx_feature_path"
+
+ echo " enabled QuickJS engine"
+ fi
+fi
if [ $NJS_OPENSSL != NO ]; then
NJS_OPENSSL_LIB=OPENSSL
@@ -37,17 +107,30 @@ if [ $NJS_ZLIB != NO ]; then
have=NJS_HAVE_ZLIB . auto/have
NJS_SRCS="$NJS_SRCS $ngx_addon_dir/../external/njs_zlib_module.c"
+ if [ "$NJS_HAVE_QUICKJS" = "YES" ]; then
+ NJS_SRCS="$NJS_SRCS $ngx_addon_dir/../external/qjs_zlib_module.c"
+ fi
+
echo " enabled zlib module"
fi
+
+NJS_ENGINE_DEP="$ngx_addon_dir/../build/libnjs.a"
+NJS_ENGINE_LIB="$ngx_addon_dir/../build/libnjs.a"
+if [ "$NJS_HAVE_QUICKJS" = "YES" ]; then
+ NJS_ENGINE_DEP="$ngx_addon_dir/../build/libqjs.a"
+ NJS_ENGINE_LIB="$ngx_addon_dir/../build/libnjs.a $ngx_addon_dir/../build/libqjs.a"
+fi
+
if [ $HTTP != NO ]; then
ngx_module_type=HTTP_AUX_FILTER
ngx_module_name=ngx_http_js_module
- ngx_module_incs="$ngx_addon_dir/../src $ngx_addon_dir/../build"
- ngx_module_deps="$ngx_addon_dir/../build/libnjs.a $NJS_DEPS"
- ngx_module_srcs="$ngx_addon_dir/ngx_http_js_module.c $NJS_SRCS"
+ ngx_module_incs="$ngx_addon_dir/../src $ngx_addon_dir/../build \
+ $NJS_QUICKJS_INC"
+ ngx_module_deps="$NJS_ENGINE_DEP $NJS_DEPS $QJS_DEPS"
+ ngx_module_srcs="$ngx_addon_dir/ngx_http_js_module.c $NJS_SRCS $QJS_SRCS"
ngx_module_libs="PCRE $NJS_OPENSSL_LIB $NJS_XSLT_LIB $NJS_ZLIB_LIB \
- $ngx_addon_dir/../build/libnjs.a -lm"
+ $NJS_QUICKJS_LIB $NJS_ENGINE_LIB -lm"
. auto/module
@@ -59,11 +142,12 @@ fi
if [ $STREAM != NO ]; then
ngx_module_type=STREAM
ngx_module_name=ngx_stream_js_module
- ngx_module_incs="$ngx_addon_dir/../src $ngx_addon_dir/../build"
- ngx_module_deps="$ngx_addon_dir/../build/libnjs.a $NJS_DEPS"
- ngx_module_srcs="$ngx_addon_dir/ngx_stream_js_module.c $NJS_SRCS"
+ ngx_module_incs="$ngx_addon_dir/../src $ngx_addon_dir/../build \
+ $NJS_QUICKJS_INC"
+ ngx_module_deps="$NJS_ENGINE_DEP $NJS_DEPS $QJS_DEPS"
+ ngx_module_srcs="$ngx_addon_dir/ngx_stream_js_module.c $NJS_SRCS $QJS_SRCS"
ngx_module_libs="PCRE $NJS_OPENSSL_LIB $NJS_XSLT_LIB $NJS_ZLIB_LIB \
- $ngx_addon_dir/../build/libnjs.a -lm"
+ $NJS_QUICKJS_LIB $NJS_ENGINE_LIB -lm"
. auto/module
fi
diff --git a/nginx/config.make b/nginx/config.make
index cf7859e9..2fa40063 100644
--- a/nginx/config.make
+++ b/nginx/config.make
@@ -3,7 +3,15 @@ cat << END >> $NGX_MAKEFILE
$ngx_addon_dir/../build/libnjs.a: $NGX_MAKEFILE
cd $ngx_addon_dir/.. \\
&& if [ -f build/Makefile ]; then \$(MAKE) clean; fi \\
- && CFLAGS="\$(CFLAGS)" CC="\$(CC)" ./configure --no-openssl --no-libxml2 --no-zlib --no-pcre \\
+ && CFLAGS="\$(CFLAGS)" CC="\$(CC)" ./configure --no-openssl \\
+ --no-libxml2 --no-zlib --no-pcre --no-quickjs \\
&& \$(MAKE) libnjs
+$ngx_addon_dir/../build/libqjs.a: $NGX_MAKEFILE
+ cd $ngx_addon_dir/.. \\
+ && if [ -f build/Makefile ]; then \$(MAKE) clean; fi \\
+ && CFLAGS="\$(CFLAGS)" CC="\$(CC)" ./configure --no-openssl \\
+ --no-libxml2 --no-zlib --no-pcre \\
+ && \$(MAKE) libnjs libqjs
+
END
diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c
index 35f988d0..4a50a949 100644
--- a/nginx/ngx_http_js_module.c
+++ b/nginx/ngx_http_js_module.c
@@ -46,6 +46,7 @@ typedef struct {
#define NJS_HEADER_SEMICOLON 0x1
#define NJS_HEADER_SINGLE 0x2
#define NJS_HEADER_ARRAY 0x4
+#define NJS_HEADER_GET 0x8
typedef struct ngx_http_js_ctx_s ngx_http_js_ctx_t;
@@ -86,6 +87,20 @@ typedef njs_int_t (*njs_http_js_header_handler_t)(njs_vm_t *vm,
typedef njs_int_t (*njs_http_js_header_handler122_t)(njs_vm_t *vm,
ngx_http_request_t *r, ngx_list_t *headers, njs_str_t *name,
njs_value_t *setval, njs_value_t *retval);
+#if (NJS_HAVE_QUICKJS)
+typedef int (*njs_http_qjs_header_handler_t)(JSContext *cx,
+ ngx_http_request_t *r, ngx_str_t *name, JSPropertyDescriptor *pdesc,
+ JSValue *value, unsigned flags);
+
+
+typedef struct {
+ ngx_http_request_t *request;
+ JSValue args;
+ JSValue request_body;
+ JSValue response_body;
+} ngx_http_qjs_request_t;
+
+#endif
typedef struct {
@@ -260,6 +275,88 @@ static njs_int_t ngx_http_js_server(njs_vm_t *vm, ngx_http_request_t *r,
unsigned flags, njs_str_t *name, njs_value_t *setval,
njs_value_t *retval);
+#if (NJS_HAVE_QUICKJS)
+static JSValue ngx_http_qjs_ext_to_string_tag(JSContext *cx,
+ JSValueConst this_val);
+static JSValue ngx_http_qjs_ext_args(JSContext *cx, JSValueConst this_val);
+static JSValue ngx_http_qjs_ext_done(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static JSValue ngx_http_qjs_ext_finish(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static JSValue ngx_http_qjs_ext_headers_in(JSContext *cx,
+ JSValueConst this_val);
+static JSValue ngx_http_qjs_ext_headers_out(JSContext *cx,
+ JSValueConst this_val);
+static JSValue ngx_http_qjs_ext_http_version(JSContext *cx,
+ JSValueConst this_val);
+static JSValue ngx_http_qjs_ext_internal(JSContext *cx, JSValueConst this_val);
+static JSValue ngx_http_qjs_ext_internal_redirect(JSContext *cx,
+ JSValueConst this_val, int argc, JSValueConst *argv);
+static JSValue ngx_http_qjs_ext_log(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv, int level);
+static JSValue ngx_http_qjs_ext_periodic_to_string_tag(JSContext *cx,
+ JSValueConst this_val);
+static JSValue ngx_http_qjs_ext_periodic_variables(JSContext *cx,
+ JSValueConst this_val, int type);
+static JSValue ngx_http_qjs_ext_parent(JSContext *cx, JSValueConst this_val);
+static JSValue ngx_http_qjs_ext_remote_address(JSContext *cx,
+ JSValueConst this_val);
+static JSValue ngx_http_qjs_ext_request_body(JSContext *cx,
+ JSValueConst this_val, int type);
+static JSValue ngx_http_qjs_ext_response_body(JSContext *cx,
+ JSValueConst this_val, int type);
+static JSValue ngx_http_qjs_ext_return(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static JSValue ngx_http_qjs_ext_send(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static JSValue ngx_http_qjs_ext_send_buffer(JSContext *cx,
+ JSValueConst this_val, int argc, JSValueConst *argv);
+static JSValue ngx_http_qjs_ext_send_header(JSContext *cx,
+ JSValueConst this_val, int argc, JSValueConst *argv);
+static JSValue ngx_http_qjs_ext_set_return_value(JSContext *cx,
+ JSValueConst this_val, int argc, JSValueConst *argv);
+static JSValue ngx_http_qjs_ext_status_get(JSContext *cx,
+ JSValueConst this_val);
+static JSValue ngx_http_qjs_ext_status_set(JSContext *cx, JSValueConst this_val,
+ JSValueConst value);
+static JSValue ngx_http_qjs_ext_string(JSContext *cx, JSValueConst this_val,
+ int offset);
+static JSValue ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static JSValue ngx_http_qjs_ext_raw_headers(JSContext *cx,
+ JSValueConst this_val, int out);
+static JSValue ngx_http_qjs_ext_variables(JSContext *cx,
+ JSValueConst this_val, int type);
+
+static int ngx_http_qjs_variables_own_property(JSContext *cx,
+ JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop);
+static int ngx_http_qjs_variables_set_property(JSContext *cx, JSValueConst obj,
+ JSAtom atom, JSValueConst value, JSValueConst receiver, int flags);
+
+static int ngx_http_qjs_headers_in_own_property(JSContext *cx,
+ JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop);
+static int ngx_http_qjs_headers_in_own_property_names(JSContext *ctx,
+ JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj);
+
+static int ngx_http_qjs_headers_out_own_property(JSContext *cx,
+ JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop);
+static int ngx_http_qjs_headers_out_own_property_names(JSContext *cx,
+ JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj);
+static int ngx_http_qjs_headers_out_set_property(JSContext *cx,
+ JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver,
+ int flags);
+static int ngx_http_qjs_headers_out_define_own_property(JSContext *cx,
+ JSValueConst this_obj, JSAtom prop, JSValueConst val, JSValueConst getter,
+ JSValueConst setter, int flags);
+static int ngx_http_qjs_headers_out_delete_property(JSContext *cx,
+ JSValueConst obj, JSAtom prop);
+
+static ngx_http_request_t *ngx_http_qjs_request(JSValueConst val);
+static JSValue ngx_http_qjs_request_make(JSContext *cx, ngx_int_t proto_id,
+ ngx_http_request_t *r);
+static void ngx_http_qjs_request_finalizer(JSRuntime *rt, JSValue val);
+#endif
+
static ngx_pool_t *ngx_http_js_pool(ngx_http_request_t *r);
static ngx_resolver_t *ngx_http_js_resolver(ngx_http_request_t *r);
static ngx_msec_t ngx_http_js_resolver_timeout(ngx_http_request_t *r);
@@ -304,6 +401,9 @@ static ngx_int_t ngx_http_js_parse_unsafe_uri(ngx_http_request_t *r,
static ngx_conf_bitmask_t ngx_http_js_engines[] = {
{ ngx_string("njs"), NGX_ENGINE_NJS },
+#if (NJS_HAVE_QUICKJS)
+ { ngx_string("qjs"), NGX_ENGINE_QJS },
+#endif
{ ngx_null_string, 0 }
};
@@ -328,6 +428,13 @@ static ngx_command_t ngx_http_js_commands[] = {
offsetof(ngx_http_js_loc_conf_t, type),
&ngx_http_js_engines },
+ { ngx_string("js_context_reuse"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_js_loc_conf_t, reuse),
+ NULL },
+
{ ngx_string("js_import"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE13,
ngx_js_import,
@@ -497,8 +604,8 @@ static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
-static njs_int_t ngx_http_js_request_proto_id;
-static njs_int_t ngx_http_js_periodic_session_proto_id;
+static njs_int_t ngx_http_js_request_proto_id = 1;
+static njs_int_t ngx_http_js_periodic_session_proto_id = 2;
static njs_external_t ngx_http_js_ext_request[] = {
@@ -924,6 +1031,125 @@ static ngx_http_js_entry_t ngx_http_methods[] = {
};
+#if (NJS_HAVE_QUICKJS)
+
+static const JSCFunctionListEntry ngx_http_qjs_ext_request[] = {
+ JS_CGETSET_DEF("[Symbol.toStringTag]", ngx_http_qjs_ext_to_string_tag,
+ NULL),
+ JS_CGETSET_DEF("args", ngx_http_qjs_ext_args, NULL),
+ JS_CFUNC_DEF("done", 0, ngx_http_qjs_ext_done),
+ JS_CFUNC_MAGIC_DEF("error", 1, ngx_http_qjs_ext_log, NGX_LOG_ERR),
+ JS_CFUNC_DEF("finish", 0, ngx_http_qjs_ext_finish),
+ JS_CGETSET_DEF("headersIn", ngx_http_qjs_ext_headers_in, NULL),
+ JS_CGETSET_DEF("headersOut", ngx_http_qjs_ext_headers_out, NULL),
+ JS_CGETSET_DEF("httpVersion", ngx_http_qjs_ext_http_version, NULL),
+ JS_CGETSET_DEF("internal", ngx_http_qjs_ext_internal, NULL),
+ JS_CFUNC_DEF("internalRedirect", 1, ngx_http_qjs_ext_internal_redirect),
+ JS_CFUNC_MAGIC_DEF("log", 1, ngx_http_qjs_ext_log, NGX_LOG_INFO),
+ JS_CGETSET_MAGIC_DEF("method", ngx_http_qjs_ext_string, NULL,
+ offsetof(ngx_http_request_t, method_name)),
+ JS_CGETSET_DEF("parent", ngx_http_qjs_ext_parent, NULL),
+ JS_CGETSET_MAGIC_DEF("rawHeadersIn", ngx_http_qjs_ext_raw_headers, NULL, 0),
+ JS_CGETSET_MAGIC_DEF("rawHeadersOut", ngx_http_qjs_ext_raw_headers, NULL,
+ 1),
+ JS_CGETSET_MAGIC_DEF("rawVariables", ngx_http_qjs_ext_variables,
+ NULL, NGX_JS_BUFFER),
+ JS_CGETSET_DEF("remoteAddress", ngx_http_qjs_ext_remote_address, NULL),
+ JS_CGETSET_MAGIC_DEF("requestBuffer", ngx_http_qjs_ext_request_body, NULL,
+ NGX_JS_BUFFER),
+ JS_CGETSET_MAGIC_DEF("requestText", ngx_http_qjs_ext_request_body, NULL,
+ NGX_JS_STRING),
+ JS_CGETSET_MAGIC_DEF("responseBuffer", ngx_http_qjs_ext_response_body, NULL,
+ NGX_JS_BUFFER),
+ JS_CGETSET_MAGIC_DEF("responseText", ngx_http_qjs_ext_response_body, NULL,
+ NGX_JS_STRING),
+ JS_CFUNC_DEF("return", 2, ngx_http_qjs_ext_return),
+ JS_CFUNC_DEF("send", 1, ngx_http_qjs_ext_send),
+ 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_CGETSET_DEF("status", ngx_http_qjs_ext_status_get,
+ ngx_http_qjs_ext_status_set),
+ 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("variables", ngx_http_qjs_ext_variables,
+ NULL, NGX_JS_STRING),
+ JS_CFUNC_MAGIC_DEF("warn", 1, ngx_http_qjs_ext_log, NGX_LOG_WARN),
+};
+
+
+static const JSCFunctionListEntry ngx_http_qjs_ext_periodic[] = {
+ JS_CGETSET_DEF("[Symbol.toStringTag]",
+ ngx_http_qjs_ext_periodic_to_string_tag, NULL),
+ JS_CGETSET_MAGIC_DEF("rawVariables", ngx_http_qjs_ext_periodic_variables,
+ NULL, NGX_JS_BUFFER),
+ JS_CGETSET_MAGIC_DEF("variables", ngx_http_qjs_ext_periodic_variables,
+ NULL, NGX_JS_STRING),
+};
+
+
+static JSClassDef ngx_http_qjs_request_class = {
+ "Request",
+ .finalizer = ngx_http_qjs_request_finalizer,
+};
+
+
+static JSClassDef ngx_http_qjs_periodic_class = {
+ "PeriodicSession",
+ .finalizer = NULL,
+};
+
+
+static JSClassDef ngx_http_qjs_variables_class = {
+ "Variables",
+ .finalizer = NULL,
+ .exotic = & (JSClassExoticMethods) {
+ .get_own_property = ngx_http_qjs_variables_own_property,
+ .set_property = ngx_http_qjs_variables_set_property,
+ },
+};
+
+
+static JSClassDef ngx_http_qjs_headers_in_class = {
+ "headersIn",
+ .finalizer = NULL,
+ .exotic = & (JSClassExoticMethods) {
+ .get_own_property = ngx_http_qjs_headers_in_own_property,
+ .get_own_property_names = ngx_http_qjs_headers_in_own_property_names,
+ },
+};
+
+
+static JSClassDef ngx_http_qjs_headers_out_class = {
+ "headersOut",
+ .finalizer = NULL,
+ .exotic = & (JSClassExoticMethods) {
+ .get_own_property = ngx_http_qjs_headers_out_own_property,
+ .get_own_property_names = ngx_http_qjs_headers_out_own_property_names,
+ .set_property = ngx_http_qjs_headers_out_set_property,
+ .define_own_property = ngx_http_qjs_headers_out_define_own_property,
+ .delete_property = ngx_http_qjs_headers_out_delete_property,
+ },
+};
+
+
+qjs_module_t *njs_http_qjs_addon_modules[] = {
+ &ngx_qjs_ngx_module,
+ /*
+ * Shared addons should be in the same order and the same positions
+ * in all nginx modules.
+ */
+#ifdef NJS_HAVE_ZLIB
+ &qjs_zlib_module,
+#endif
+ NULL,
+};
+
+
+#endif
+
+
static ngx_int_t
ngx_http_js_content_handler(ngx_http_request_t *r)
{
@@ -1426,6 +1652,14 @@ ngx_http_js_cleanup_ctx(void *data)
ctx->engine);
r = ngx_js_ctx_external(ctx);
+
+ /*
+ * Restoring the original module context, because it can be reset
+ * by internalRedirect() method. Proper ctx is required for
+ * ngx_http_qjs_request_finalizer() to work correctly.
+ */
+ ngx_http_set_ctx(r, ctx, ngx_http_js_module);
+
jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module);
ngx_js_ctx_destroy((ngx_js_ctx_t *) ctx, (ngx_js_loc_conf_t *) jlcf);
@@ -4447,25 +4681,2886 @@ ngx_engine_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf,
}
+#if (NJS_HAVE_QUICKJS)
+
static ngx_int_t
-ngx_http_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf)
+ngx_http_qjs_query_string_decode(njs_chb_t *chain, const u_char *start,
+ size_t size)
{
- ngx_engine_opts_t options;
- ngx_js_main_conf_t *jmcf;
+ u_char *dst;
+ uint32_t cp;
+ const u_char *p, *end;
+ njs_unicode_decode_t ctx;
- memset(&options, 0, sizeof(ngx_engine_opts_t));
+ static const int8_t hex[256]
+ njs_aligned(32) =
+ {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ };
- options.engine = conf->type;
+ njs_utf8_decode_init(&ctx);
- if (conf->type == NGX_ENGINE_NJS) {
- jmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_js_module);
- ngx_http_js_uptr[NGX_JS_MAIN_CONF_INDEX] = (uintptr_t) jmcf;
+ cp = 0;
- options.u.njs.metas = &ngx_http_js_metas;
- options.u.njs.addons = njs_http_js_addon_modules;
- options.clone = ngx_engine_njs_clone;
+ p = start;
+ end = p + size;
+
+ while (p < end) {
+ if (*p == '%' && end - p > 2 && hex[p[1]] >= 0 && hex[p[2]] >= 0) {
+ cp = njs_utf8_consume(&ctx, (hex[p[1]] << 4) | hex[p[2]]);
+ p += 3;
+
+ } else {
+ if (*p == '+') {
+ cp = ' ';
+ p++;
+
+ } else {
+ cp = njs_utf8_decode(&ctx, &p, end);
+ }
+ }
+
+ if (cp > NJS_UNICODE_MAX_CODEPOINT) {
+ if (cp == NJS_UNICODE_CONTINUE) {
+ continue;
+ }
+
+ cp = NJS_UNICODE_REPLACEMENT;
+ }
+
+ dst = njs_chb_reserve(chain, 4);
+ if (dst == NULL) {
+ return NGX_ERROR;
+ }
+
+ njs_chb_written(chain, njs_utf8_encode(dst, cp) - dst);
+ }
+
+ if (cp == NJS_UNICODE_CONTINUE) {
+ dst = njs_chb_reserve(chain, 3);
+ if (dst == NULL) {
+ return NGX_ERROR;
+ }
+
+ njs_chb_written(chain,
+ njs_utf8_encode(dst, NJS_UNICODE_REPLACEMENT) - dst);
+ }
+
+ return NGX_OK;
+}
+
+
+static JSValue
+ngx_http_qjs_ext_to_string_tag(JSContext *cx,
+ JSValueConst this_val)
+{
+ return JS_NewString(cx, "Request");
+}
+
+
+static JSValue
+ngx_http_qjs_ext_args(JSContext *cx, JSValueConst this_val)
+{
+ u_char *start, *end, *p, *v;
+ uint32_t len;
+ JSAtom key;
+ JSValue args, val, prev, length, arr;
+ njs_str_t decoded;
+ njs_int_t ret;
+ ngx_int_t rc;
+ njs_chb_t chain;
+ ngx_http_request_t *r;
+ ngx_http_qjs_request_t *req;
+
+ req = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_REQUEST);
+ if (req == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ if (!JS_IsUndefined(req->args)) {
+ return JS_DupValue(cx, req->args);
+ }
+
+ args = JS_NewObject(cx);
+ if (JS_IsException(args)) {
+ return JS_EXCEPTION;
+ }
+
+ NJS_CHB_CTX_INIT(&chain, cx);
+
+ r = req->request;
+
+ rc = ngx_http_qjs_query_string_decode(&chain, r->args.data, r->args.len);
+ if (rc != NGX_OK) {
+ njs_chb_destroy(&chain);
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ ret = njs_chb_join(&chain, &decoded);
+ njs_chb_destroy(&chain);
+
+ if (ret != NJS_OK) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ start = decoded.start;
+ end = start + decoded.length;
+
+ while (start < end) {
+ p = ngx_strlchr(start, end, '&');
+ if (p == NULL) {
+ p = end;
+ }
+
+ v = ngx_strlchr(start, p, '=');
+ if (v == NULL) {
+ v = p;
+ }
+
+ if (v == start) {
+ start = p + 1;
+ continue;
+ }
+
+ key = JS_NewAtomLen(cx, (const char *) start, v - start);
+ if (key == JS_ATOM_NULL) {
+ chain.free(cx, decoded.start);
+ return JS_EXCEPTION;
+ }
+
+ val = qjs_string_create(cx, v + 1, p - v - 1);
+ if (JS_IsException(val)) {
+ chain.free(cx, decoded.start);
+ JS_FreeAtom(cx, key);
+ return JS_EXCEPTION;
+ }
+
+ prev = JS_GetProperty(cx, args, key);
+ if (JS_IsException(prev)) {
+ chain.free(cx, decoded.start);
+ JS_FreeAtom(cx, key);
+ JS_FreeValue(cx, val);
+ return JS_EXCEPTION;
+ }
+
+ if (JS_IsUndefined(prev)) {
+ if (JS_SetProperty(cx, args, key, val) < 0) {
+ goto exception;
+ }
+
+ } else if (JS_IsArray(cx, prev)) {
+ length = JS_GetPropertyStr(cx, prev, "length");
+
+ if (JS_ToUint32(cx, &len, length)) {
+ goto exception;
+ }
+
+ JS_FreeValue(cx, length);
+
+ if (JS_SetPropertyUint32(cx, prev, len, val) < 0) {
+ goto exception;
+ }
+
+ JS_FreeValue(cx, prev);
+
+ } else {
+
+ arr = JS_NewArray(cx);
+ if (JS_IsException(arr)) {
+ goto exception;
+ }
+
+ if (JS_SetPropertyUint32(cx, arr, 0, prev) < 0) {
+ goto exception;
+ }
+
+ if (JS_SetPropertyUint32(cx, arr, 1, val) < 0) {
+ goto exception;
+ }
+
+ if (JS_SetProperty(cx, args, key, arr) < 0) {
+ goto exception;
+ }
+ }
+
+ JS_FreeAtom(cx, key);
+ start = p + 1;
+ }
+
+ chain.free(cx, decoded.start);
+ req->args = args;
+
+ return JS_DupValue(cx, args);
+
+exception:
+
+ chain.free(cx, decoded.start);
+ JS_FreeAtom(cx, key);
+ JS_FreeValue(cx, val);
+ JS_FreeValue(cx, prev);
+
+ return JS_EXCEPTION;
+}
+
+
+static JSValue
+ngx_http_qjs_ext_done(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv)
+{
+ ngx_http_js_ctx_t *ctx;
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+ if (!ctx->filter) {
+ return JS_ThrowTypeError(cx, "cannot set done while not filtering");
+ }
+
+ ctx->done = 1;
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_http_qjs_ext_finish(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv)
+{
+ ngx_http_js_ctx_t *ctx;
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ if (ngx_http_send_special(r, NGX_HTTP_LAST) == NGX_ERROR) {
+ return JS_ThrowInternalError(cx, "failed to send response");
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+ ctx->status = NGX_OK;
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_http_qjs_ext_headers_in(JSContext *cx, JSValueConst this_val)
+{
+ JSValue obj;
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_HTTP_HEADERS_IN);
+
+ JS_SetOpaque(obj, r);
+
+ return obj;
+}
+
+
+static JSValue
+ngx_http_qjs_ext_headers_out(JSContext *cx, JSValueConst this_val)
+{
+ JSValue obj;
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ obj = JS_NewObjectProtoClass(cx, JS_NULL,
+ NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT);
+
+ JS_SetOpaque(obj, r);
+
+ return obj;
+}
+
+
+static JSValue
+ngx_http_qjs_ext_http_version(JSContext *cx, JSValueConst this_val)
+{
+ ngx_str_t v;
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ switch (r->http_version) {
+ case NGX_HTTP_VERSION_9:
+ ngx_str_set(&v, "0.9");
+ break;
+
+ case NGX_HTTP_VERSION_10:
+ ngx_str_set(&v, "1.0");
+ break;
+
+ case NGX_HTTP_VERSION_11:
+ ngx_str_set(&v, "1.1");
+ break;
+
+ case NGX_HTTP_VERSION_20:
+ ngx_str_set(&v, "2.0");
+ break;
+
+#if (NGX_HTTP_VERSION_30)
+ case NGX_HTTP_VERSION_30:
+ ngx_str_set(&v, "3.0");
+ break;
+#endif
+
+ default:
+ ngx_str_set(&v, "");
+ break;
}
+ return qjs_string_create(cx, v.data, v.len);
+}
+
+
+static JSValue
+ngx_http_qjs_ext_internal(JSContext *cx, JSValueConst this_val)
+{
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ return JS_NewBool(cx, r->internal);
+}
+
+
+static JSValue
+ngx_http_qjs_ext_internal_redirect(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv)
+{
+ ngx_http_js_ctx_t *ctx;
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ if (r->parent != NULL) {
+ return JS_ThrowTypeError(cx,
+ "internalRedirect cannot be called from a subrequest");
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+ if (ctx->filter) {
+ return JS_ThrowTypeError(cx,
+ "internalRedirect cannot be called while filtering");
+ }
+
+ if (ngx_qjs_string(ctx->engine, argv[0], &ctx->redirect_uri) != NGX_OK) {
+ return JS_EXCEPTION;
+ }
+
+ ctx->status = NGX_DONE;
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_http_qjs_ext_log(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv, int level)
+{
+ int n;
+ const char *msg;
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ for (n = 0; n < argc; n++) {
+ msg = JS_ToCString(cx, argv[n]);
+
+ ngx_js_logger(r->connection, level, (u_char *) msg, ngx_strlen(msg));
+
+ JS_FreeCString(cx, msg);
+ }
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_http_qjs_ext_periodic_to_string_tag(JSContext *cx,
+ JSValueConst this_val)
+{
+ return JS_NewString(cx, "PeriodicSession");
+}
+
+
+static JSValue
+ngx_http_qjs_ext_periodic_variables(JSContext *cx,
+ JSValueConst this_val, int type)
+{
+ JSValue obj;
+ ngx_http_qjs_request_t *req;
+
+ req = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_PERIODIC);
+ if (req == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a periodic object");
+ }
+
+ obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_HTTP_VARS);
+
+ /*
+ * Using lowest bit of the pointer to store the buffer type.
+ */
+ type = (type == NGX_JS_BUFFER) ? 1 : 0;
+ JS_SetOpaque(obj, (void *) ((uintptr_t) req->request | (uintptr_t) type));
+
+ return obj;
+}
+
+
+static JSValue
+ngx_http_qjs_ext_parent(JSContext *cx, JSValueConst this_val)
+{
+ ngx_http_js_ctx_t *ctx;
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ ctx = r->parent ? ngx_http_get_module_ctx(r->parent, ngx_http_js_module)
+ : NULL;
+
+ if (ctx == NULL) {
+ return JS_UNDEFINED;
+ }
+
+ return JS_DupValue(cx, ngx_qjs_arg(ctx->args[0]));
+}
+
+
+static JSValue
+ngx_http_qjs_ext_remote_address(JSContext *cx, JSValueConst this_val)
+{
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ c = r->connection;
+
+ return qjs_string_create(cx, c->addr_text.data, c->addr_text.len);
+}
+
+
+static JSValue
+ngx_http_qjs_ext_response_body(JSContext *cx, JSValueConst this_val, int type)
+{
+ u_char *p;
+ size_t len;
+ uint32_t buffer_type;
+ ngx_buf_t *b;
+ JSValue body;
+ ngx_http_request_t *r;
+ ngx_http_qjs_request_t *req;
+
+ req = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_REQUEST);
+ if (req == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ buffer_type = ngx_js_buffer_type(type);
+
+ if (!JS_IsUndefined(req->response_body)) {
+ if ((buffer_type == NGX_JS_STRING) == JS_IsString(req->response_body)) {
+ return JS_DupValue(cx, req->response_body);
+ }
+ }
+
+ r = req->request;
+
+ b = r->out ? r->out->buf : NULL;
+
+ if (b == NULL) {
+ return JS_UNDEFINED;
+ }
+
+ len = b->last - b->pos;
+
+ p = ngx_pnalloc(r->pool, len);
+ if (p == NULL) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ if (len) {
+ ngx_memcpy(p, b->pos, len);
+ }
+
+ body = ngx_qjs_prop(cx, buffer_type, p, len);
+ if (JS_IsException(body)) {
+ return JS_EXCEPTION;
+ }
+
+ req->response_body = body;
+
+ return JS_DupValue(cx, req->response_body);
+}
+
+
+static JSValue
+ngx_http_qjs_ext_request_body(JSContext *cx, JSValueConst this_val, int type)
+{
+ u_char *p, *data;
+ size_t len;
+ JSValue body;
+ uint32_t buffer_type;
+ ngx_buf_t *buf;
+ ngx_chain_t *cl;
+ ngx_http_request_t *r;
+ ngx_http_qjs_request_t *req;
+
+ req = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_REQUEST);
+ if (req == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ buffer_type = ngx_js_buffer_type(type);
+
+ if (!JS_IsUndefined(req->request_body)) {
+ if ((buffer_type == NGX_JS_STRING) == JS_IsString(req->request_body)) {
+ return JS_DupValue(cx, req->request_body);
+ }
+
+ JS_FreeValue(cx, req->request_body);
+ }
+
+ r = req->request;
+
+ if (r->request_body == NULL || r->request_body->bufs == NULL) {
+ return JS_UNDEFINED;
+ }
+
+ if (r->request_body->temp_file) {
+ return JS_ThrowTypeError(cx, "request body is in a file");
+ }
+
+ cl = r->request_body->bufs;
+ buf = cl->buf;
+
+ if (cl->next == NULL) {
+ len = buf->last - buf->pos;
+ data = buf->pos;
+
+ goto done;
+ }
+
+ len = buf->last - buf->pos;
+ cl = cl->next;
+
+ for ( /* void */ ; cl; cl = cl->next) {
+ buf = cl->buf;
+ len += buf->last - buf->pos;
+ }
+
+ p = ngx_pnalloc(r->pool, len);
+ if (p == NULL) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ data = p;
+ cl = r->request_body->bufs;
+
+ for ( /* void */ ; cl; cl = cl->next) {
+ buf = cl->buf;
+ p = ngx_cpymem(p, buf->pos, buf->last - buf->pos);
+ }
+
+done:
+
+ body = ngx_qjs_prop(cx, buffer_type, data, len);
+ if (JS_IsException(body)) {
+ return JS_EXCEPTION;
+ }
+
+ req->request_body = body;
+
+ return JS_DupValue(cx, req->request_body);
+}
+
+
+static JSValue
+ngx_http_qjs_ext_return(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv)
+{
+ ngx_str_t body;
+ ngx_int_t status;
+ ngx_http_js_ctx_t *ctx;
+ ngx_http_request_t *r;
+ ngx_http_complex_value_t cv;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ if (ngx_qjs_integer(cx, argv[0], &status) != NGX_OK) {
+ return JS_EXCEPTION;
+ }
+
+ if (status < 0 || status > 999) {
+ return JS_ThrowRangeError(cx, "code is out of range");
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+ if (ngx_qjs_string(ctx->engine, argv[1], &body) != NGX_OK) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ if (status < NGX_HTTP_BAD_REQUEST || body.len) {
+ ngx_memzero(&cv, sizeof(ngx_http_complex_value_t));
+
+ cv.value.data = body.data;
+ cv.value.len = body.len;
+
+ ctx->status = ngx_http_send_response(r, status, NULL, &cv);
+
+ if (ctx->status == NGX_ERROR) {
+ return JS_ThrowTypeError(cx, "failed to send response");
+ }
+
+ } else {
+ ctx->status = status;
+ }
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_http_qjs_ext_status_get(JSContext *cx, JSValueConst this_val)
+{
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ return JS_NewInt32(cx, r->headers_out.status);
+}
+
+
+static JSValue
+ngx_http_qjs_ext_status_set(JSContext *cx, JSValueConst this_val,
+ JSValueConst value)
+{
+ ngx_int_t n;
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ if (ngx_qjs_integer(cx, value, &n) != NGX_OK) {
+ return JS_EXCEPTION;
+ }
+
+ r->headers_out.status = n;
+ r->headers_out.status_line.len = 0;
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_http_qjs_ext_string(JSContext *cx, JSValueConst this_val, int offset)
+{
+ ngx_str_t *field;
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ field = (ngx_str_t *) ((u_char *) r + offset);
+
+ return qjs_string_create(cx, field->data, field->len);
+}
+
+
+static JSValue
+ngx_http_qjs_ext_send(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv)
+{
+ ngx_str_t s;
+ ngx_buf_t *b;
+ ngx_uint_t n;
+ ngx_chain_t *out, *cl, **ll;
+ ngx_http_js_ctx_t *ctx;
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+ if (ctx->filter) {
+ return JS_ThrowTypeError(cx, "cannot send while in body filter");
+ }
+
+ out = NULL;
+ ll = &out;
+
+ for (n = 0; n < (ngx_uint_t) argc; n++) {
+ if (ngx_qjs_string(ctx->engine, argv[n], &s) != NGX_OK) {
+ return JS_ThrowTypeError(cx, "failed to convert arg");
+ }
+
+ if (s.len == 0) {
+ continue;
+ }
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return JS_ThrowInternalError(cx, "failed to allocate buffer");
+ }
+
+ b->start = s.data;
+ b->pos = b->start;
+ b->end = s.data + s.len;
+ b->last = b->end;
+ b->memory = 1;
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return JS_ThrowInternalError(cx, "failed to allocate chain link");
+ }
+
+ cl->buf = b;
+
+ *ll = cl;
+ ll = &cl->next;
+ }
+
+ *ll = NULL;
+
+ if (ngx_http_output_filter(r, out) == NGX_ERROR) {
+ return JS_ThrowInternalError(cx, "failed to send response");
+ }
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_http_qjs_ext_send_buffer(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv)
+{
+ unsigned last_buf, flush;
+ JSValue flags, value;
+ ngx_str_t buffer;
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+ ngx_http_js_ctx_t *ctx;
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+ if (!ctx->filter) {
+ return JS_ThrowTypeError(cx, "cannot send buffer while not filtering");
+ }
+
+ if (ngx_qjs_string(ctx->engine, argv[0], &buffer) != NGX_OK) {
+ return JS_ThrowTypeError(cx, "failed get buffer arg");
+ }
+
+ flush = ctx->buf->flush;
+ last_buf = ctx->buf->last_buf;
+
+ flags = argv[1];
+
+ if (JS_IsObject(flags)) {
+ value = JS_GetPropertyStr(cx, flags, "flush");
+ if (JS_IsException(value)) {
+ return JS_EXCEPTION;
+ }
+
+ flush = JS_ToBool(cx, value);
+ JS_FreeValue(cx, value);
+
+ value = JS_GetPropertyStr(cx, flags, "last");
+ if (JS_IsException(value)) {
+ return JS_EXCEPTION;
+ }
+
+ last_buf = JS_ToBool(cx, value);
+ JS_FreeValue(cx, value);
+ }
+
+ cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+ if (cl == NULL) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ b = cl->buf;
+
+ b->flush = flush;
+ b->last_buf = last_buf;
+
+ b->memory = (buffer.len ? 1 : 0);
+ b->sync = (buffer.len ? 0 : 1);
+ b->tag = (ngx_buf_tag_t) &ngx_http_js_module;
+
+ b->start = buffer.data;
+ b->end = buffer.data + buffer.len;
+ b->pos = b->start;
+ b->last = b->end;
+
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_http_qjs_ext_send_header(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv)
+{
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ if (ngx_http_set_content_type(r) != NGX_OK) {
+ return JS_ThrowInternalError(cx, "failed to set content type");
+ }
+
+ if (ngx_http_send_header(r) == NGX_ERROR) {
+ return JS_ThrowInternalError(cx, "failed to send header");
+ }
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_http_qjs_ext_set_return_value(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv)
+{
+ ngx_js_ctx_t *ctx;
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+ JS_FreeValue(cx, ngx_qjs_arg(ctx->retval));
+ ngx_qjs_arg(ctx->retval) = JS_DupValue(cx, argv[0]);
+
+ return JS_UNDEFINED;
+}
+
+
+static ngx_int_t
+ngx_http_qjs_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc)
+{
+ ngx_qjs_event_t *event = data;
+
+ JSValue reply;
+ JSContext *cx;
+ ngx_http_js_ctx_t *ctx, *sctx;
+
+ if (rc != NGX_OK || r->connection->error || r->buffered) {
+ return rc;
+ }
+
+ sctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+ if (sctx && sctx->done) {
+ return NGX_OK;
+ }
+
+ if (sctx == NULL) {
+ sctx = ngx_pcalloc(r->pool, sizeof(ngx_http_js_ctx_t));
+ if (sctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, sctx, ngx_http_js_module);
+
+ ngx_qjs_arg(sctx->response_body) = JS_UNDEFINED;
+ }
+
+ sctx->done = 1;
+
+ ctx = ngx_http_get_module_ctx(r->parent, ngx_http_js_module);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "js subrequest done s: %ui parent ctx: %p",
+ r->headers_out.status, ctx);
+
+ if (ctx == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "js subrequest: failed to get the parent context");
+
+ return NGX_ERROR;
+ }
+
+ cx = ctx->engine->u.qjs.ctx;
+
+ if (!JS_IsObject(ngx_qjs_arg(sctx->args[0]))) {
+ reply = ngx_http_qjs_request_make(cx, NGX_QJS_CLASS_ID_HTTP_REQUEST, r);
+ if (JS_IsException(reply)) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "js subrequest reply creation failed");
+ return NGX_ERROR;
+ }
+
+
+ } else {
+ reply = JS_DupValue(cx, ngx_qjs_arg(sctx->args[0]));
+ }
+
+ rc = ngx_qjs_call((ngx_js_ctx_t *) ctx, event->function, &reply, 1);
+
+ JS_FreeValue(cx, reply);
+ ngx_js_del_event(ctx, event);
+
+ ngx_http_js_event_finalize(r->parent, rc);
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_js_subrequest_event_destructor(ngx_qjs_event_t *event)
+{
+ JSContext *cx;
+
+ cx = event->ctx;
+
+ JS_FreeValue(cx, event->function);
+ JS_FreeValue(cx, event->args[0]);
+ JS_FreeValue(cx, event->args[1]);
+}
+
+
+static JSValue
+ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv)
+{
+ JSValue arg, options, callback, value, retval;
+ ngx_int_t rc;
+ ngx_str_t uri, args, method_name, body_arg;
+ ngx_uint_t method, methods_max, has_body, detached, flags,
+ promise;
+ ngx_qjs_event_t *event;
+ ngx_http_js_ctx_t *ctx;
+ ngx_http_request_t *r, *sr;
+ ngx_http_request_body_t *rb;
+ ngx_http_post_subrequest_t *ps;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+ if (r->main != r) {
+ return JS_ThrowTypeError(cx, "subrequest can only be created for "
+ "the primary request");
+ }
+
+ if (ngx_qjs_string(ctx->engine, argv[0], &uri) != NGX_OK) {
+ return JS_ThrowTypeError(cx, "failed to convert uri arg");
+ }
+
+ if (uri.len == 0) {
+ return JS_ThrowTypeError(cx, "uri is empty");
+ }
+
+ options = JS_UNDEFINED;
+ callback = JS_UNDEFINED;
+
+ method = 0;
+ methods_max = sizeof(ngx_http_methods) / sizeof(ngx_http_methods[0]);
+
+ args.len = 0;
+ args.data = NULL;
+
+ method_name.len = 0;
+ method_name.data = NULL;
+
+ has_body = 0;
+ detached = 0;
+
+ arg = argv[1];
+
+ if (JS_IsString(arg)) {
+ if (ngx_qjs_string(ctx->engine, arg, &args) != NGX_OK) {
+ return JS_ThrowTypeError(cx, "failed to convert args");
+ }
+
+ } else if (JS_IsFunction(cx, arg)) {
+ callback = arg;
+
+ } else if (JS_IsObject(arg)) {
+ options = arg;
+
+ } else if (!JS_IsNullOrUndefined(arg)) {
+ return JS_ThrowTypeError(cx, "failed to convert args");
+ }
+
+ if (!JS_IsUndefined(options)) {
+ value = JS_GetPropertyStr(cx, options, "args");
+ if (JS_IsException(value)) {
+ return JS_EXCEPTION;
+ }
+
+ if (!JS_IsUndefined(value)) {
+ rc = ngx_qjs_string(ctx->engine, value, &args);
+ JS_FreeValue(cx, value);
+
+ if (rc != NGX_OK) {
+ return JS_ThrowTypeError(cx, "failed to convert options.args");
+ }
+ }
+
+ value = JS_GetPropertyStr(cx, options, "detached");
+ if (JS_IsException(value)) {
+ return JS_EXCEPTION;
+ }
+
+ if (!JS_IsUndefined(value)) {
+ detached = JS_ToBool(cx, value);
+ JS_FreeValue(cx, value);
+ }
+
+ value = JS_GetPropertyStr(cx, options, "method");
+ if (JS_IsException(value)) {
+ return JS_EXCEPTION;
+ }
+
+ if (!JS_IsUndefined(value)) {
+ rc = ngx_qjs_string(ctx->engine, value, &method_name);
+ JS_FreeValue(cx, value);
+
+ if (rc != NGX_OK) {
+ return JS_ThrowTypeError(cx, "failed to convert option.method");
+ }
+
+ while (method < methods_max) {
+ if (method_name.len == ngx_http_methods[method].name.len
+ && ngx_memcmp(method_name.data,
+ ngx_http_methods[method].name.data,
+ method_name.len)
+ == 0)
+ {
+ break;
+ }
+
+ method++;
+ }
+ }
+
+ value = JS_GetPropertyStr(cx, options, "body");
+ if (JS_IsException(value)) {
+ return JS_EXCEPTION;
+ }
+
+ if (!JS_IsUndefined(value)) {
+ rc = ngx_qjs_string(ctx->engine, value, &body_arg);
+ JS_FreeValue(cx, value);
+
+ if (rc != NGX_OK) {
+ return JS_ThrowTypeError(cx, "failed to convert option.body");
+ }
+
+ has_body = 1;
+ }
+ }
+
+ flags = NGX_HTTP_LOG_UNSAFE;
+
+ if (ngx_http_parse_unsafe_uri(r, &uri, &args, &flags) != NGX_OK) {
+ return JS_ThrowTypeError(cx, "unsafe uri");
+ }
+
+ arg = argv[2];
+
+ if (JS_IsUndefined(callback) && !JS_IsNullOrUndefined(arg)) {
+ if (!JS_IsFunction(cx, arg)) {
+ return JS_ThrowTypeError(cx, "callback is not a function");
+ }
+
+ callback = arg;
+ }
+
+ if (detached && !JS_IsUndefined(callback)) {
+ return JS_ThrowTypeError(cx, "detached flag and callback are mutually "
+ "exclusive");
+ }
+
+ promise = 0;
+ retval = JS_UNDEFINED;
+ flags = NGX_HTTP_SUBREQUEST_BACKGROUND;
+
+ if (!detached) {
+ ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
+ if (ps == NULL) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ promise = !!JS_IsUndefined(callback);
+
+ event = ngx_pcalloc(r->pool, sizeof(ngx_qjs_event_t)
+ + sizeof(JSValue) * 2);
+ if (event == NULL) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ event->ctx = cx;
+ event->fd = ctx->event_id++;
+ event->args = (JSValue *) &event[1];
+ event->destructor = ngx_http_js_subrequest_event_destructor;
+
+ if (promise) {
+ retval = JS_NewPromiseCapability(cx, &event->args[0]);
+ if (JS_IsException(retval)) {
+ return JS_EXCEPTION;
+ }
+
+ callback = event->args[0];
+
+ } else {
+ event->args[0] = JS_UNDEFINED;
+ event->args[1] = JS_UNDEFINED;
+ }
+
+ event->function = JS_DupValue(cx, callback);
+
+ ps->handler = ngx_http_qjs_subrequest_done;
+ ps->data = event;
+
+ flags |= NGX_HTTP_SUBREQUEST_IN_MEMORY;
+
+ } else {
+ ps = NULL;
+ event = NULL;
+ }
+
+ if (ngx_http_subrequest(r, &uri, args.len ? &args : NULL, &sr, ps, flags)
+ != NGX_OK)
+ {
+ return JS_ThrowInternalError(cx, "subrequest creation failed");
+ }
+
+ if (event != NULL) {
+ ngx_js_add_event(ctx, event);
+ }
+
+ if (method != methods_max) {
+ sr->method = ngx_http_methods[method].value;
+ sr->method_name = ngx_http_methods[method].name;
+
+ } else {
+ sr->method = NGX_HTTP_UNKNOWN;
+ sr->method_name = method_name;
+ }
+
+ sr->header_only = (sr->method == NGX_HTTP_HEAD) || JS_IsUndefined(callback);
+
+ if (has_body) {
+ rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
+ if (rb == NULL) {
+ goto memory_error;
+ }
+
+ if (body_arg.len != 0) {
+ rb->bufs = ngx_alloc_chain_link(r->pool);
+ if (rb->bufs == NULL) {
+ goto memory_error;
+ }
+
+ rb->bufs->next = NULL;
+
+ rb->bufs->buf = ngx_calloc_buf(r->pool);
+ if (rb->bufs->buf == NULL) {
+ goto memory_error;
+ }
+
+ rb->bufs->buf->memory = 1;
+ rb->bufs->buf->last_buf = 1;
+
+ rb->bufs->buf->pos = body_arg.data;
+ rb->bufs->buf->last = body_arg.data + body_arg.len;
+ }
+
+ sr->request_body = rb;
+ sr->headers_in.content_length_n = body_arg.len;
+ sr->headers_in.chunked = 0;
+ }
+
+ return retval;
+
+memory_error:
+
+ return JS_ThrowOutOfMemory(cx);
+}
+
+
+static JSValue
+ngx_http_qjs_ext_raw_headers(JSContext *cx, JSValueConst this_val, int out)
+{
+ JSValue array, elem, key, val;
+ uint32_t idx;
+ ngx_uint_t i;
+ ngx_list_t *headers;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header, *h;
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ headers = (out) ? &r->headers_out.headers : &r->headers_in.headers;
+
+ array = JS_NewArray(cx);
+ if (JS_IsException(array)) {
+ return JS_EXCEPTION;
+ }
+
+ idx = 0;
+ 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) {
+ continue;
+ }
+
+ elem = JS_NewArray(cx);
+ if (JS_IsException(elem)) {
+ JS_FreeValue(cx, array);
+ return JS_EXCEPTION;
+ }
+
+ if (JS_DefinePropertyValueUint32(cx, array, idx++, elem,
+ JS_PROP_C_W_E) < 0)
+ {
+ JS_FreeValue(cx, elem);
+ JS_FreeValue(cx, array);
+ return JS_EXCEPTION;
+ }
+
+ key = qjs_string_create(cx, h->key.data, h->key.len);
+ if (JS_IsException(key)) {
+ JS_FreeValue(cx, array);
+ return JS_EXCEPTION;
+ }
+
+ if (JS_DefinePropertyValueUint32(cx, elem, 0, key, JS_PROP_C_W_E) < 0) {
+ JS_FreeValue(cx, key);
+ JS_FreeValue(cx, array);
+ return JS_EXCEPTION;
+ }
+
+ val = qjs_string_create(cx, h->value.data, h->value.len);
+ if (JS_IsException(val)) {
+ JS_FreeValue(cx, array);
+ return JS_EXCEPTION;
+ }
+
+ if (JS_DefinePropertyValueUint32(cx, elem, 1, val, JS_PROP_C_W_E) < 0) {
+ JS_FreeValue(cx, val);
+ JS_FreeValue(cx, array);
+ return JS_EXCEPTION;
+ }
+ }
+
+ return array;
+}
+
+
+static JSValue
+ngx_http_qjs_ext_variables(JSContext *cx, JSValueConst this_val, int type)
+{
+ JSValue obj;
+ ngx_http_request_t *r;
+
+ r = ngx_http_qjs_request(this_val);
+ if (r == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ }
+
+ obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_HTTP_VARS);
+
+ /*
+ * Using lowest bit of the pointer to store the buffer type.
+ */
+ type = (type == NGX_JS_BUFFER) ? 1 : 0;
+ JS_SetOpaque(obj, (void *) ((uintptr_t) r | (uintptr_t) type));
+
+ return obj;
+}
+
+
+static int
+ngx_http_qjs_variables_own_property(JSContext *cx, JSPropertyDescriptor *pdesc,
+ JSValueConst obj, JSAtom prop)
+{
+ uint32_t buffer_type;
+ ngx_str_t name;
+ ngx_uint_t i, key, start, length, is_capture;
+ ngx_http_request_t *r;
+ ngx_http_variable_value_t *vv;
+
+ r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_VARS);
+
+ buffer_type = ((uintptr_t) r & 1) ? NGX_JS_BUFFER : NGX_JS_STRING;
+ r = (ngx_http_request_t *) ((uintptr_t) r & ~(uintptr_t) 1);
+
+ if (r == NULL) {
+ (void) JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ return -1;
+ }
+
+ name.data = (u_char *) JS_AtomToCString(cx, prop);
+ if (name.data == NULL) {
+ return -1;
+ }
+
+ name.len = ngx_strlen(name.data);
+
+ is_capture = 1;
+ for (i = 0; i < name.len; i++) {
+ if (name.data[i] < '0' || name.data[i] > '9') {
+ is_capture = 0;
+ break;
+ }
+ }
+
+ if (is_capture) {
+ key = ngx_atoi(name.data, name.len) * 2;
+ JS_FreeCString(cx, (char *) name.data);
+ if (r->captures == NULL || r->captures_data == NULL
+ || r->ncaptures <= key)
+ {
+ return 0;
+ }
+
+
+ if (pdesc != NULL) {
+ pdesc->flags = JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE;
+ pdesc->getter = JS_UNDEFINED;
+ pdesc->setter = JS_UNDEFINED;
+
+ start = r->captures[key];
+ length = r->captures[key + 1] - start;
+ pdesc->value = ngx_qjs_prop(cx, buffer_type,
+ &r->captures_data[start], length);
+ }
+
+ return 1;
+ }
+
+ key = ngx_hash_strlow(name.data, name.data, name.len);
+
+ vv = ngx_http_get_variable(r, &name, key);
+ JS_FreeCString(cx, (char *) name.data);
+ if (vv == NULL || vv->not_found) {
+ return 0;
+ }
+
+ if (pdesc != NULL) {
+ pdesc->flags = JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE;
+ pdesc->getter = JS_UNDEFINED;
+ pdesc->setter = JS_UNDEFINED;
+ pdesc->value = ngx_qjs_prop(cx, buffer_type, vv->data, vv->len);
+ }
+
+ return 1;
+}
+
+
+static int
+ngx_http_qjs_variables_set_property(JSContext *cx, JSValueConst obj,
+ JSAtom prop, JSValueConst value, JSValueConst receiver, int flags)
+{
+ ngx_str_t name, s;
+ ngx_uint_t key;
+ ngx_http_js_ctx_t *ctx;
+ ngx_http_request_t *r;
+ ngx_http_variable_t *v;
+ ngx_http_variable_value_t *vv;
+ ngx_http_core_main_conf_t *cmcf;
+
+ r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_VARS);
+
+ r = (ngx_http_request_t *) ((uintptr_t) r & ~(uintptr_t) 1);
+
+ if (r == NULL) {
+ (void) JS_ThrowInternalError(cx, "\"this\" is not a request object");
+ return -1;
+ }
+
+ name.data = (u_char *) JS_AtomToCString(cx, prop);
+ if (name.data == NULL) {
+ return -1;
+ }
+
+ name.len = ngx_strlen(name.data);
+
+ key = ngx_hash_strlow(name.data, name.data, name.len);
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ v = ngx_hash_find(&cmcf->variables_hash, key, name.data, name.len);
+ JS_FreeCString(cx, (char *) name.data);
+
+ if (v == NULL) {
+ (void) JS_ThrowInternalError(cx, "variable not found");
+ return -1;
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+ if (ngx_qjs_string(ctx->engine, value, &s) != NGX_OK) {
+ return -1;
+ }
+
+ if (v->set_handler != NULL) {
+ vv = ngx_pcalloc(r->pool, sizeof(ngx_http_variable_value_t));
+ if (vv == NULL) {
+ (void) JS_ThrowOutOfMemory(cx);
+ return -1;
+ }
+
+ vv->valid = 1;
+ vv->not_found = 0;
+ vv->data = s.data;
+ vv->len = s.len;
+
+ v->set_handler(r, vv, v->data);
+
+ return 1;
+ }
+
+ if (!(v->flags & NGX_HTTP_VAR_INDEXED)) {
+ (void) JS_ThrowTypeError(cx, "variable is not writable");
+ return -1;
+ }
+
+ vv = &r->variables[v->index];
+
+ vv->valid = 1;
+ vv->not_found = 0;
+
+ vv->data = ngx_pnalloc(r->pool, s.len);
+ if (vv->data == NULL) {
+ vv->valid = 0;
+ (void) JS_ThrowOutOfMemory(cx);
+ return -1;
+ }
+
+ vv->len = s.len;
+ ngx_memcpy(vv->data, s.data, vv->len);
+
+ return 1;
+}
+
+
+static int
+ngx_http_qjs_ext_keys_header(JSContext *cx, ngx_list_t *headers, JSValue keys,
+ JSPropertyEnum **ptab, uint32_t *plen)
+{
+ JSAtom key;
+ ngx_uint_t item;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header, *h;
+
+ part = &headers->part;
+ item = 0;
+
+ while (part) {
+ if (item >= part->nelts) {
+ part = part->next;
+ item = 0;
+ continue;
+ }
+
+ header = part->elts;
+ h = &header[item++];
+
+ if (h->hash == 0) {
+ continue;
+ }
+
+ key = JS_NewAtomLen(cx, (const char *) h->key.data, h->key.len);
+ if (key == JS_ATOM_NULL) {
+ return -1;
+ }
+
+ if (JS_DefinePropertyValue(cx, keys, key, JS_UNDEFINED,
+ JS_PROP_ENUMERABLE) < 0)
+ {
+ JS_FreeAtom(cx, key);
+ return -1;
+ }
+
+ JS_FreeAtom(cx, key);
+ }
+
+ return JS_GetOwnPropertyNames(cx, ptab, plen, keys, JS_GPN_STRING_MASK);
+}
+
+
+static int
+ngx_http_qjs_headers_in_own_property_names(JSContext *cx,
+ JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj)
+{
+ int ret;
+ JSValue keys;
+ ngx_http_request_t *r;
+
+ r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_IN);
+ if (r == NULL) {
+ (void) JS_ThrowInternalError(cx, "\"this\" is not a headers_in object");
+ return -1;
+ }
+
+ keys = JS_NewObject(cx);
+ if (JS_IsException(keys)) {
+ return -1;
+ }
+
+ ret = ngx_http_qjs_ext_keys_header(cx, &r->headers_in.headers, keys, ptab,
+ plen);
+ JS_FreeValue(cx, keys);
+
+ return ret;
+}
+
+
+static njs_int_t
+ngx_http_qjs_header_generic(JSContext *cx, ngx_http_request_t *r,
+ ngx_list_t *headers, ngx_table_elt_t **ph, ngx_str_t *name,
+ JSPropertyDescriptor *pdesc, unsigned flags)
+{
+ int ret;
+ u_char sep;
+ njs_chb_t chain;
+ JSValue val;
+ ngx_uint_t i;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header, *h;
+
+ if (ph == NULL) {
+ /* iterate over all headers */
+
+ ph = &header;
+ part = &headers->part;
+ h = part->elts;
+
+ for (i = 0; /* void */ ; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ h = part->elts;
+ i = 0;
+ }
+
+ if (h[i].hash == 0
+ || name->len != h[i].key.len
+ || ngx_strncasecmp(name->data, h[i].key.data, name->len)
+ != 0)
+ {
+ continue;
+ }
+
+ *ph = &h[i];
+ ph = &h[i].next;
+ }
+
+ *ph = NULL;
+ ph = &header;
+ }
+
+ if (*ph == NULL) {
+ return 0;
+ }
+
+ if (flags & NJS_HEADER_ARRAY) {
+ if (pdesc == NULL) {
+ return 1;
+ }
+
+ pdesc->flags = JS_PROP_ENUMERABLE;
+ pdesc->getter = JS_UNDEFINED;
+ pdesc->setter = JS_UNDEFINED;
+ pdesc->value = JS_NewArray(cx);
+ if (JS_IsException(pdesc->value)) {
+ return -1;
+ }
+
+ for (h = *ph, i = 0; h; h = h->next, i++) {
+ val = qjs_string_create(cx, h->value.data, h->value.len);
+ if (JS_IsException(val)) {
+ JS_FreeValue(cx, pdesc->value);
+ return -1;
+ }
+
+ if (JS_DefinePropertyValueUint32(cx, pdesc->value, i, val,
+ JS_PROP_ENUMERABLE) < 0)
+ {
+ JS_FreeValue(cx, pdesc->value);
+ return -1;
+ }
+ }
+
+ return 1;
+ }
+
+ if ((*ph)->next == NULL || flags & NJS_HEADER_SINGLE) {
+ if (pdesc != NULL) {
+ pdesc->flags = JS_PROP_ENUMERABLE;
+ pdesc->getter = JS_UNDEFINED;
+ pdesc->setter = JS_UNDEFINED;
+ pdesc->value = qjs_string_create(cx, (*ph)->value.data,
+ (*ph)->value.len);
+ if (JS_IsException(pdesc->value)) {
+ return -1;
+ }
+ }
+
+ return 1;
+ }
+
+ NJS_CHB_CTX_INIT(&chain, cx);
+
+ sep = flags & NJS_HEADER_SEMICOLON ? ';' : ',';
+
+ for (h = *ph; h; h = h->next) {
+ njs_chb_append(&chain, h->value.data, h->value.len);
+ njs_chb_append(&chain, &sep, 1);
+ njs_chb_append_literal(&chain, " ");
+ }
+
+ ret = 1;
+
+ if (pdesc != NULL) {
+ pdesc->flags = JS_PROP_ENUMERABLE;
+ pdesc->getter = JS_UNDEFINED;
+ pdesc->setter = JS_UNDEFINED;
+ pdesc->value = qjs_string_create_chb(cx, &chain);
+ if (JS_IsException(pdesc->value)) {
+ ret = -1;
+ goto done;
+ }
+ }
+
+done:
+
+ njs_chb_destroy(&chain);
+
+ return ret;
+}
+
+
+static int
+ngx_http_qjs_header_in(JSContext *cx, ngx_http_request_t *r, unsigned flags,
+ ngx_str_t *name, JSPropertyDescriptor *pdesc)
+{
+ u_char *lowcase_key;
+ ngx_uint_t hash;
+ ngx_table_elt_t **ph;
+ ngx_http_header_t *hh;
+ ngx_http_core_main_conf_t *cmcf;
+
+ /* look up hashed headers */
+
+ lowcase_key = ngx_pnalloc(r->pool, name->len);
+ if (lowcase_key == NULL) {
+ (void) JS_ThrowOutOfMemory(cx);
+ return -1;
+ }
+
+ hash = ngx_hash_strlow(lowcase_key, name->data, name->len);
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ hh = ngx_hash_find(&cmcf->headers_in_hash, hash, lowcase_key,
+ name->len);
+
+ ph = NULL;
+
+ if (hh) {
+ if (hh->offset == offsetof(ngx_http_headers_in_t, cookie)) {
+ flags |= NJS_HEADER_SEMICOLON;
+ }
+
+ ph = (ngx_table_elt_t **) ((char *) &r->headers_in + hh->offset);
+ }
+
+ return ngx_http_qjs_header_generic(cx, r, &r->headers_in.headers, ph, name,
+ pdesc, flags);
+}
+
+
+static int
+ngx_http_qjs_headers_in_own_property(JSContext *cx, JSPropertyDescriptor *pdesc,
+ JSValueConst obj, JSAtom prop)
+{
+ int ret;
+ unsigned flags;
+ ngx_str_t name, *h;
+ ngx_http_request_t *r;
+
+ static ngx_str_t single_headers_in[] = {
+ ngx_string("Content-Type"),
+ ngx_string("ETag"),
+ ngx_string("From"),
+ ngx_string("Max-Forwards"),
+ ngx_string("Referer"),
+ ngx_string("Proxy-Authorization"),
+ ngx_string("User-Agent"),
+ ngx_string(""),
+ };
+
+ r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_IN);
+ if (r == NULL) {
+ (void) JS_ThrowInternalError(cx, "\"this\" is not a headers_in object");
+ return -1;
+ }
+
+ name.data = (u_char *) JS_AtomToCString(cx, prop);
+ if (name.data == NULL) {
+ return -1;
+ }
+
+ name.len = ngx_strlen(name.data);
+
+ flags = 0;
+
+ for (h = single_headers_in; h->len > 0; h++) {
+ if (h->len == name.len
+ && ngx_strncasecmp(h->data, name.data, name.len) == 0)
+ {
+ flags |= NJS_HEADER_SINGLE;
+ break;
+ }
+ }
+
+ ret = ngx_http_qjs_header_in(cx, r, flags, &name, pdesc);
+ JS_FreeCString(cx, (char *) name.data);
+
+ return ret;
+}
+
+
+static int
+ngx_http_qjs_headers_out_own_property_names(JSContext *cx,
+ JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj)
+{
+ int ret;
+ JSAtom key;
+ JSValue keys;
+ ngx_http_request_t *r;
+
+ r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT);
+ if (r == NULL) {
+ (void) JS_ThrowInternalError(cx, "\"this\" is not a headers_out"
+ " object");
+ return -1;
+ }
+
+ keys = JS_NewObject(cx);
+ if (JS_IsException(keys)) {
+ return -1;
+ }
+
+ if (r->headers_out.content_type.len) {
+ key = JS_NewAtomLen(cx, "Content-Type", njs_length("Content-Type"));
+ if (key == JS_ATOM_NULL) {
+ return -1;
+ }
+
+ if (JS_DefinePropertyValue(cx, keys, key, JS_UNDEFINED,
+ JS_PROP_ENUMERABLE) < 0)
+ {
+ JS_FreeAtom(cx, key);
+ return -1;
+ }
+
+ JS_FreeAtom(cx, key);
+ }
+
+ if (r->headers_out.content_length == NULL
+ && r->headers_out.content_length_n >= 0)
+ {
+ key = JS_NewAtomLen(cx, "Content-Length", njs_length("Content-Length"));
+ if (key == JS_ATOM_NULL) {
+ return -1;
+ }
+
+ if (JS_DefinePropertyValue(cx, keys, key, JS_UNDEFINED,
+ JS_PROP_ENUMERABLE) < 0)
+ {
+ JS_FreeAtom(cx, key);
+ return -1;
+ }
+
+ JS_FreeAtom(cx, key);
+ }
+
+ ret = ngx_http_qjs_ext_keys_header(cx, &r->headers_out.headers, keys, ptab,
+ plen);
+ JS_FreeValue(cx, keys);
+
+ return ret;
+}
+
+
+static int
+ngx_http_qjs_headers_out_handler(JSContext *cx, ngx_http_request_t *r,
+ ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value,
+ unsigned flags)
+{
+ u_char *p;
+ int64_t length;
+ uint32_t i;
+ ngx_int_t rc;
+ ngx_str_t s;
+ JSValue v;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header, *h, **ph;
+ ngx_http_js_ctx_t *ctx;
+
+ if (flags & NJS_HEADER_GET) {
+ return ngx_http_qjs_header_generic(cx, r, &r->headers_out.headers, NULL,
+ name, pdesc, flags);
+ }
+
+ part = &r->headers_out.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 != name->len
+ || ngx_strncasecmp(h->key.data, name->data, name->len) != 0)
+ {
+ continue;
+ }
+
+ h->hash = 0;
+ h->next = NULL;
+ }
+
+ if (value == NULL) {
+ return 1;
+ }
+
+ if (JS_IsArray(cx, *value)) {
+ v = JS_GetPropertyStr(cx, *value, "length");
+ if (JS_IsException(v)) {
+ return -1;
+ }
+
+ if (JS_ToInt64(cx, &length, v) < 0) {
+ JS_FreeValue(cx, v);
+ return -1;
+ }
+
+ JS_FreeValue(cx, v);
+
+ } else {
+ v = *value;
+ length = 1;
+ }
+
+ ph = &header;
+ ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+ for (i = 0; i < (uint32_t) length; i++) {
+ if (JS_IsArray(cx, *value)) {
+ v = JS_GetPropertyUint32(cx, *value, i);
+ if (JS_IsException(v)) {
+ return -1;
+ }
+ }
+
+ rc = ngx_qjs_string(ctx->engine, v, &s);
+
+ if (JS_IsArray(cx, *value)) {
+ JS_FreeValue(cx, v);
+ }
+
+ if (rc != NGX_OK) {
+ return -1;
+ }
+
+ if (s.len == 0) {
+ continue;
+ }
+
+ h = ngx_list_push(&r->headers_out.headers);
+ if (h == NULL) {
+ (void) JS_ThrowOutOfMemory(cx);
+ return -1;
+ }
+
+ p = ngx_pnalloc(r->pool, name->len);
+ if (p == NULL) {
+ h->hash = 0;
+ (void) JS_ThrowOutOfMemory(cx);
+ return -1;
+ }
+
+ ngx_memcpy(p, name->data, name->len);
+
+ h->key.data = p;
+ h->key.len = name->len;
+
+ p = ngx_pnalloc(r->pool, s.len);
+ if (p == NULL) {
+ h->hash = 0;
+ (void) JS_ThrowOutOfMemory(cx);
+ return -1;
+ }
+
+ ngx_memcpy(p, s.data, s.len);
+
+ h->value.data = p;
+ h->value.len = s.len;
+ h->hash = 1;
+
+ *ph = h;
+ ph = &h->next;
+ }
+
+ *ph = NULL;
+
+ return NJS_OK;
+}
+
+
+static int
+ngx_http_qjs_headers_out_special_handler(JSContext *cx, ngx_http_request_t *r,
+ ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value,
+ unsigned flags, ngx_table_elt_t **hh)
+{
+ u_char *p;
+ uint32_t length;
+ JSValue len, setval;
+ ngx_str_t s;
+ ngx_uint_t i, rc;
+ ngx_list_t *headers;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header, *h;
+ ngx_http_js_ctx_t *ctx;
+
+ if (flags & NJS_HEADER_GET) {
+ return ngx_http_qjs_headers_out_handler(cx, r, name, pdesc, NULL,
+ flags | NJS_HEADER_SINGLE);
+ }
+
+ if (value != NULL) {
+ if (JS_IsArray(cx, *value)) {
+ len = JS_GetPropertyStr(cx, *value, "length");
+ if (JS_IsException(len)) {
+ return -1;
+ }
+
+ if (JS_ToUint32(cx, &length, len) < 0) {
+ JS_FreeValue(cx, len);
+ return -1;
+ }
+
+ JS_FreeValue(cx, len);
+
+ setval = JS_GetPropertyUint32(cx, *value, length - 1);
+ if (JS_IsException(setval)) {
+ return -1;
+ }
+
+ } else {
+ setval = *value;
+ }
+
+ } else {
+ setval = JS_UNDEFINED;
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+ rc = ngx_qjs_string(ctx->engine, setval, &s);
+
+ if (value != NULL && JS_IsArray(cx, *value)) {
+ JS_FreeValue(cx, setval);
+ }
+
+ if (rc != NGX_OK) {
+ return -1;
+ }
+
+ headers = &r->headers_out.headers;
+ 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) {
+ continue;
+ }
+
+ if (h->key.len == name->len
+ && ngx_strncasecmp(h->key.data, name->data, name->len) == 0)
+ {
+ goto done;
+ }
+ }
+
+ h = NULL;
+
+done:
+
+ if (h != NULL && s.len == 0) {
+ h->hash = 0;
+ h = NULL;
+ }
+
+ if (h == NULL && s.len != 0) {
+ h = ngx_list_push(headers);
+ if (h == NULL) {
+ (void) JS_ThrowOutOfMemory(cx);
+ return -1;
+ }
+
+ p = ngx_pnalloc(r->pool, name->len);
+ if (p == NULL) {
+ h->hash = 0;
+ (void) JS_ThrowOutOfMemory(cx);
+ return -1;
+ }
+
+ ngx_memcpy(p, name->data, name->len);
+
+ h->key.data = p;
+ h->key.len = name->len;
+ }
+
+ if (h != NULL) {
+ p = ngx_pnalloc(r->pool, s.len);
+ if (p == NULL) {
+ h->hash = 0;
+ (void) JS_ThrowOutOfMemory(cx);
+ return -1;
+ }
+
+ ngx_memcpy(p, s.data, s.len);
+
+ h->value.data = p;
+ h->value.len = s.len;
+ h->hash = 1;
+ }
+
+ if (hh != NULL) {
+ *hh = h;
+ }
+
+ return 1;
+}
+
+
+static int
+ngx_http_qjs_headers_out_content_encoding(JSContext *cx, ngx_http_request_t *r,
+ ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value,
+ unsigned flags)
+{
+ int ret;
+ ngx_table_elt_t *h;
+
+ ret = ngx_http_qjs_headers_out_special_handler(cx, r, name, pdesc, value,
+ flags, &h);
+ if (ret < 0) {
+ return -1;
+ }
+
+ if (!(flags & NJS_HEADER_GET)) {
+ r->headers_out.content_encoding = h;
+ }
+
+ return ret;
+}
+
+
+static int
+ngx_http_qjs_headers_out_content_length(JSContext *cx, ngx_http_request_t *r,
+ ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value,
+ unsigned flags)
+{
+ int ret;
+ u_char *p;
+ ngx_int_t n;
+ ngx_table_elt_t *h;
+ u_char content_len[NGX_OFF_T_LEN];
+
+ if (flags & NJS_HEADER_GET) {
+ 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);
+
+ if (pdesc != NULL) {
+ pdesc->flags = JS_PROP_C_W_E;
+ pdesc->getter = JS_UNDEFINED;
+ pdesc->setter = JS_UNDEFINED;
+ pdesc->value = qjs_string_create(cx, content_len,
+ p - content_len);
+ if (JS_IsException(pdesc->value)) {
+ return -1;
+ }
+ }
+
+ return 1;
+ }
+ }
+
+ ret = ngx_http_qjs_headers_out_special_handler(cx, r, name, pdesc, value,
+ flags, &h);
+ if (ret < 0) {
+ return -1;
+ }
+
+ if (!(flags & NJS_HEADER_GET)) {
+ if (h != NULL) {
+ n = ngx_atoi(h->value.data, h->value.len);
+ if (n == NGX_ERROR) {
+ h->hash = 0;
+ (void) JS_ThrowInternalError(cx, "failed converting argument "
+ "to positive integer");
+ return -1;
+ }
+
+ r->headers_out.content_length = h;
+ r->headers_out.content_length_n = n;
+
+ } else {
+ ngx_http_clear_content_length(r);
+ }
+ }
+
+ return ret;
+}
+
+
+static int
+ngx_http_qjs_headers_out_content_type(JSContext *cx, ngx_http_request_t *r,
+ ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value,
+ unsigned flags)
+{
+ uint32_t length;
+ JSValue len, setval;
+ ngx_int_t rc;
+ ngx_str_t *hdr, s;
+ ngx_http_js_ctx_t *ctx;
+
+ if (flags & NJS_HEADER_GET) {
+ hdr = &r->headers_out.content_type;
+
+ if (pdesc != NULL) {
+ pdesc->flags = JS_PROP_C_W_E;
+ pdesc->getter = JS_UNDEFINED;
+ pdesc->setter = JS_UNDEFINED;
+
+ if (hdr->len == 0) {
+ pdesc->value = JS_UNDEFINED;
+ return 1;
+ }
+
+ pdesc->value = qjs_string_create(cx, hdr->data, hdr->len);
+ if (JS_IsException(pdesc->value)) {
+ return -1;
+ }
+ }
+
+ return 1;
+ }
+
+ if (value == NULL) {
+ r->headers_out.content_type.len = 0;
+ r->headers_out.content_type_len = 0;
+ r->headers_out.content_type.data = NULL;
+ r->headers_out.content_type_lowcase = NULL;
+ return 1;
+ }
+
+ if (JS_IsArray(cx, *value)) {
+ len = JS_GetPropertyStr(cx, *value, "length");
+ if (JS_IsException(len)) {
+ return -1;
+ }
+
+ if (JS_ToUint32(cx, &length, len) < 0) {
+ JS_FreeValue(cx, len);
+ return -1;
+ }
+
+ JS_FreeValue(cx, len);
+
+ setval = JS_GetPropertyUint32(cx, *value, length - 1);
+ if (JS_IsException(setval)) {
+ return -1;
+ }
+
+ } else {
+ setval = *value;
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+ rc = ngx_qjs_string(ctx->engine, setval, &s);
+
+ if (JS_IsArray(cx, *value)) {
+ JS_FreeValue(cx, setval);
+ }
+
+ if (rc != NGX_OK) {
+ return -1;
+ }
+
+ r->headers_out.content_type.len = s.len;
+ r->headers_out.content_type_len = r->headers_out.content_type.len;
+ r->headers_out.content_type.data = s.data;
+ r->headers_out.content_type_lowcase = NULL;
+
+ return 1;
+}
+
+
+static int
+ngx_http_qjs_headers_out_date(JSContext *cx, ngx_http_request_t *r,
+ ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value,
+ unsigned flags)
+{
+ int ret;
+ ngx_table_elt_t *h;
+
+ ret = ngx_http_qjs_headers_out_special_handler(cx, r, name, pdesc, value,
+ flags, &h);
+ if (ret < 0) {
+ return -1;
+ }
+
+ if (!(flags & NJS_HEADER_GET)) {
+ r->headers_out.date = h;
+ }
+
+ return ret;
+}
+
+
+static int
+ngx_http_qjs_headers_out_last_modified(JSContext *cx, ngx_http_request_t *r,
+ ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value,
+ unsigned flags)
+{
+ int ret;
+ ngx_table_elt_t *h;
+
+ ret = ngx_http_qjs_headers_out_special_handler(cx, r, name, pdesc, value,
+ flags, &h);
+ if (ret < 0) {
+ return -1;
+ }
+
+ if (!(flags & NJS_HEADER_GET)) {
+ r->headers_out.last_modified = h;
+ }
+
+ return ret;
+}
+
+
+static int
+ngx_http_qjs_headers_out_location(JSContext *cx, ngx_http_request_t *r,
+ ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value,
+ unsigned flags)
+{
+ int ret;
+ ngx_table_elt_t *h;
+
+ ret = ngx_http_qjs_headers_out_special_handler(cx, r, name, pdesc, value,
+ flags, &h);
+ if (ret < 0) {
+ return -1;
+ }
+
+ if (!(flags & NJS_HEADER_GET)) {
+ r->headers_out.location = h;
+ }
+
+ return ret;
+}
+
+
+static int
+ngx_http_qjs_headers_out_server(JSContext *cx, ngx_http_request_t *r,
+ ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value,
+ unsigned flags)
+{
+ int ret;
+ ngx_table_elt_t *h;
+
+ ret = ngx_http_qjs_headers_out_special_handler(cx, r, name, pdesc, value,
+ flags, &h);
+ if (ret < 0) {
+ return -1;
+ }
+
+ if (!(flags & NJS_HEADER_GET)) {
+ r->headers_out.server = h;
+ }
+
+ return ret;
+}
+
+
+static int
+ngx_http_qjs_headers_out(JSContext *cx, ngx_http_request_t *r,
+ ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value,
+ unsigned flags)
+{
+ ngx_http_js_header_t *h;
+
+ static ngx_http_js_header_t headers_out[] = {
+#define header(name, fl, h) { njs_str(name), fl, (uintptr_t) h }
+ header("Age", NJS_HEADER_SINGLE, ngx_http_qjs_headers_out_handler),
+ header("Content-Encoding", 0, ngx_http_qjs_headers_out_content_encoding),
+ header("Content-Length", 0, ngx_http_qjs_headers_out_content_length),
+ header("Content-Type", 0, ngx_http_qjs_headers_out_content_type),
+ header("Date", 0, ngx_http_qjs_headers_out_date),
+ header("Etag", NJS_HEADER_SINGLE, ngx_http_qjs_headers_out_handler),
+ header("Expires", NJS_HEADER_SINGLE, ngx_http_qjs_headers_out_handler),
+ header("Last-Modified", 0, ngx_http_qjs_headers_out_last_modified),
+ header("Location", 0, ngx_http_qjs_headers_out_location),
+ header("Server", 0, ngx_http_qjs_headers_out_server),
+ header("Set-Cookie", NJS_HEADER_ARRAY,
+ ngx_http_qjs_headers_out_handler),
+ header("Retry-After", NJS_HEADER_SINGLE,
+ ngx_http_qjs_headers_out_handler),
+ header("", 0, ngx_http_qjs_headers_out_handler),
+#undef header
+ };
+
+ for (h = headers_out; h->name.len > 0; h++) {
+ if (h->name.len == name->len
+ && ngx_strncasecmp(h->name.data, name->data, name->len) == 0)
+ {
+ break;
+ }
+ }
+
+ return ((njs_http_qjs_header_handler_t) h->handler)(cx,
+ r, name, pdesc, value, h->flags | flags);
+}
+
+
+static int
+ngx_http_qjs_headers_out_own_property(JSContext *cx,
+ JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop)
+{
+ int ret;
+ ngx_str_t name;
+ ngx_http_request_t *r;
+
+ r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT);
+ if (r == NULL) {
+ (void) JS_ThrowInternalError(cx, "\"this\" is not a headers_out"
+ " object");
+ return -1;
+ }
+
+ name.data = (u_char *) JS_AtomToCString(cx, prop);
+ if (name.data == NULL) {
+ return -1;
+ }
+
+ name.len = ngx_strlen(name.data);
+
+ ret = ngx_http_qjs_headers_out(cx, r, &name, pdesc, NULL, NJS_HEADER_GET);
+ JS_FreeCString(cx, (char *) name.data);
+
+ return ret;
+}
+
+
+static int
+ngx_http_qjs_headers_out_set_property(JSContext *cx,
+ JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver,
+ int flags)
+{
+ return ngx_http_qjs_headers_out_define_own_property(cx, obj, atom, value,
+ JS_UNDEFINED, JS_UNDEFINED, flags);
+}
+
+
+static int
+ngx_http_qjs_headers_out_define_own_property(JSContext *cx,
+ JSValueConst obj, JSAtom prop, JSValueConst value, JSValueConst getter,
+ JSValueConst setter, int flags)
+{
+ int ret;
+ ngx_str_t name;
+ ngx_http_request_t *r;
+
+ r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT);
+ if (r == NULL) {
+ (void) JS_ThrowInternalError(cx, "\"this\" is not a headers_out"
+ " object");
+ return -1;
+ }
+
+ if (!JS_IsUndefined(setter) || !JS_IsUndefined(getter)) {
+ (void) JS_ThrowTypeError(cx, "cannot define getter or setter");
+ return -1;
+ }
+
+ name.data = (u_char *) JS_AtomToCString(cx, prop);
+ if (name.data == NULL) {
+ return -1;
+ }
+
+ name.len = ngx_strlen(name.data);
+
+ if (r->header_sent) {
+ ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+ "ignored setting of response header \"%V\" because"
+ " headers were already sent", &name);
+ }
+
+ ret = ngx_http_qjs_headers_out(cx, r, &name, NULL, &value, 0);
+ JS_FreeCString(cx, (char *) name.data);
+
+ return ret;
+}
+
+
+static int
+ngx_http_qjs_headers_out_delete_property(JSContext *cx,
+ JSValueConst obj, JSAtom prop)
+{
+ int ret;
+ ngx_str_t name;
+ ngx_http_request_t *r;
+
+ r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT);
+ if (r == NULL) {
+ (void) JS_ThrowInternalError(cx, "\"this\" is not a headers_out"
+ " object");
+ return -1;
+ }
+
+ name.data = (u_char *) JS_AtomToCString(cx, prop);
+ if (name.data == NULL) {
+ return -1;
+ }
+
+ name.len = ngx_strlen(name.data);
+
+ ret = ngx_http_qjs_headers_out(cx, r, &name, NULL, NULL, 0);
+ JS_FreeCString(cx, (char *) name.data);
+
+ return ret;
+}
+
+
+static ngx_int_t
+ngx_http_qjs_body_filter(ngx_http_request_t *r, ngx_http_js_loc_conf_t *jlcf,
+ ngx_http_js_ctx_t *ctx, ngx_chain_t *in)
+{
+ size_t len;
+ u_char *p;
+ JSAtom last_key;
+ JSValue arguments[3], last;
+ ngx_int_t rc;
+ njs_int_t pending;
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+ JSContext *cx;
+ ngx_connection_t *c;
+
+ c = r->connection;
+ cx = ctx->engine->u.qjs.ctx;
+
+ arguments[0] = ngx_qjs_arg(ctx->args[0]);
+
+ last_key = JS_NewAtom(cx, "last");
+ if (last_key == JS_ATOM_NULL) {
+ return NGX_ERROR;
+ }
+
+ while (in != NULL) {
+ ctx->buf = in->buf;
+ b = ctx->buf;
+
+ if (!ctx->done) {
+ len = b->last - b->pos;
+
+ p = ngx_pnalloc(r->pool, len);
+ if (p == NULL) {
+ return NJS_ERROR;
+ }
+
+ if (len) {
+ ngx_memcpy(p, b->pos, len);
+ }
+
+ arguments[1] = ngx_qjs_prop(cx, jlcf->buffer_type, p, len);
+ if (JS_IsException(arguments[1])) {
+ JS_FreeAtom(cx, last_key);
+ return NGX_ERROR;
+ }
+
+ last = JS_NewBool(cx, b->last_buf);
+
+ arguments[2] = JS_NewObject(cx);
+ if (JS_IsException(arguments[2])) {
+ JS_FreeAtom(cx, last_key);
+ JS_FreeValue(cx, arguments[1]);
+ return NGX_ERROR;
+ }
+
+ if (JS_SetProperty(cx, arguments[2], last_key, last) < 0) {
+ JS_FreeAtom(cx, last_key);
+ JS_FreeValue(cx, arguments[1]);
+ JS_FreeValue(cx, arguments[2]);
+ return NGX_ERROR;
+ }
+
+ pending = ngx_js_ctx_pending(ctx);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http js body call \"%V\"", &jlcf->body_filter);
+
+ rc = ctx->engine->call((ngx_js_ctx_t *) ctx, &jlcf->body_filter,
+ (njs_opaque_value_t *) &arguments[0], 3);
+
+ JS_FreeAtom(cx, last_key);
+ JS_FreeValue(cx, arguments[1]);
+ JS_FreeValue(cx, arguments[2]);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (!pending && rc == NGX_AGAIN) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "async operation inside \"%V\" body filter",
+ &jlcf->body_filter);
+ return NGX_ERROR;
+ }
+
+ ctx->buf->pos = ctx->buf->last;
+
+ } else {
+ cl = ngx_alloc_chain_link(c->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+ }
+
+ in = in->next;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_http_request_t *
+ngx_http_qjs_request(JSValueConst val)
+{
+ ngx_http_qjs_request_t *req;
+
+ req = JS_GetOpaque(val, NGX_QJS_CLASS_ID_HTTP_REQUEST);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ return req->request;
+}
+
+
+static JSValue
+ngx_http_qjs_request_make(JSContext *cx, ngx_int_t proto_id,
+ ngx_http_request_t *r)
+{
+ JSValue request;
+ ngx_http_qjs_request_t *req;
+
+ request = JS_NewObjectClass(cx, proto_id);
+ if (JS_IsException(request)) {
+ return JS_EXCEPTION;
+ }
+
+ req = js_malloc(cx, sizeof(ngx_http_qjs_request_t));
+ if (req == NULL) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ req->request = r;
+ req->args = JS_UNDEFINED;
+ req->request_body = JS_UNDEFINED;
+ req->response_body = JS_UNDEFINED;
+
+ JS_SetOpaque(request, req);
+
+ return request;
+}
+
+
+static void
+ngx_http_qjs_request_finalizer(JSRuntime *rt, JSValue val)
+{
+ ngx_http_qjs_request_t *req;
+
+ req = JS_GetOpaque(val, NGX_QJS_CLASS_ID_HTTP_REQUEST);
+ if (req == NULL) {
+ return;
+ }
+
+ JS_FreeValueRT(rt, req->args);
+ JS_FreeValueRT(rt, req->request_body);
+ JS_FreeValueRT(rt, req->response_body);
+
+ js_free_rt(rt, req);
+}
+
+
+static ngx_engine_t *
+ngx_engine_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf,
+ njs_int_t proto_id, void *external)
+{
+ JSValue proto;
+ JSContext *cx;
+ ngx_engine_t *engine;
+ ngx_http_js_ctx_t *hctx;
+
+ engine = ngx_qjs_clone(ctx, cf, external);
+ if (engine == NULL) {
+ return NULL;
+ }
+
+ cx = engine->u.qjs.ctx;
+
+ if (!JS_IsRegisteredClass(JS_GetRuntime(cx),
+ NGX_QJS_CLASS_ID_HTTP_REQUEST))
+ {
+ if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_HTTP_REQUEST,
+ &ngx_http_qjs_request_class) < 0)
+ {
+ return NULL;
+ }
+
+ proto = JS_NewObject(cx);
+ if (JS_IsException(proto)) {
+ return NULL;
+ }
+
+ JS_SetPropertyFunctionList(cx, proto, ngx_http_qjs_ext_request,
+ njs_nitems(ngx_http_qjs_ext_request));
+
+ JS_SetClassProto(cx, NGX_QJS_CLASS_ID_HTTP_REQUEST, proto);
+
+ if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_HTTP_PERIODIC,
+ &ngx_http_qjs_periodic_class) < 0)
+ {
+ return NULL;
+ }
+
+ proto = JS_NewObject(cx);
+ if (JS_IsException(proto)) {
+ return NULL;
+ }
+
+ JS_SetPropertyFunctionList(cx, proto, ngx_http_qjs_ext_periodic,
+ njs_nitems(ngx_http_qjs_ext_periodic));
+
+ JS_SetClassProto(cx, NGX_QJS_CLASS_ID_HTTP_PERIODIC, proto);
+
+ if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_HTTP_VARS,
+ &ngx_http_qjs_variables_class) < 0)
+ {
+ return NULL;
+ }
+
+ if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_HTTP_HEADERS_IN,
+ &ngx_http_qjs_headers_in_class) < 0)
+ {
+ return NULL;
+ }
+
+ if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT,
+ &ngx_http_qjs_headers_out_class) < 0)
+ {
+ return NULL;
+ }
+ }
+
+ hctx = (ngx_http_js_ctx_t *) ctx;
+ hctx->body_filter = ngx_http_qjs_body_filter;
+
+ if (proto_id == ngx_http_js_request_proto_id) {
+ proto_id = NGX_QJS_CLASS_ID_HTTP_REQUEST;
+
+ } else if (proto_id == ngx_http_js_periodic_session_proto_id) {
+ proto_id = NGX_QJS_CLASS_ID_HTTP_PERIODIC;
+ }
+
+ ngx_qjs_arg(hctx->args[0]) = ngx_http_qjs_request_make(cx, proto_id,
+ external);
+ if (JS_IsException(ngx_qjs_arg(hctx->args[0]))) {
+ return NULL;
+ }
+
+ return engine;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf)
+{
+ ngx_engine_opts_t options;
+ ngx_js_main_conf_t *jmcf;
+
+ memset(&options, 0, sizeof(ngx_engine_opts_t));
+
+ options.engine = conf->type;
+
+ jmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_js_module);
+ ngx_http_js_uptr[NGX_JS_MAIN_CONF_INDEX] = (uintptr_t) jmcf;
+
+ if (conf->type == NGX_ENGINE_NJS) {
+ options.u.njs.metas = &ngx_http_js_metas;
+ options.u.njs.addons = njs_http_js_addon_modules;
+ options.clone = ngx_engine_njs_clone;
+ }
+
+#if (NJS_HAVE_QUICKJS)
+ else if (conf->type == NGX_ENGINE_QJS) {
+ options.u.qjs.metas = ngx_http_js_uptr;
+ options.u.qjs.addons = njs_http_qjs_addon_modules;
+ options.clone = ngx_engine_qjs_clone;
+ }
+#endif
+
return ngx_js_init_conf_vm(cf, conf, &options);
}
diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c
index ce4988f9..5fe3dc84 100644
--- a/nginx/ngx_js.c
+++ b/nginx/ngx_js.c
@@ -8,6 +8,7 @@
#include <ngx_config.h>
#include <ngx_core.h>
+#include <math.h>
#include "ngx_js.h"
@@ -57,6 +58,53 @@ static ngx_int_t ngx_engine_njs_string(ngx_engine_t *e,
static void ngx_engine_njs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx,
ngx_js_loc_conf_t *conf);
+#if (NJS_HAVE_QUICKJS)
+static ngx_int_t ngx_engine_qjs_init(ngx_engine_t *engine,
+ ngx_engine_opts_t *opts);
+static ngx_int_t ngx_engine_qjs_compile(ngx_js_loc_conf_t *conf, ngx_log_t *log,
+ u_char *start, size_t size);
+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);
+static void *ngx_engine_qjs_external(ngx_engine_t *engine);
+static ngx_int_t ngx_engine_qjs_pending(ngx_engine_t *engine);
+static ngx_int_t ngx_engine_qjs_string(ngx_engine_t *e,
+ njs_opaque_value_t *value, ngx_str_t *str);
+
+static JSValue ngx_qjs_ext_set_timeout(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv, int immediate);
+static JSValue ngx_qjs_ext_clear_timeout(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+
+static JSValue ngx_qjs_ext_build(JSContext *cx, JSValueConst this_val);
+static JSValue ngx_qjs_ext_conf_file_path(JSContext *cx, JSValueConst this_val);
+static JSValue ngx_qjs_ext_conf_prefix(JSContext *cx, JSValueConst this_val);
+static JSValue ngx_qjs_ext_constant_integer(JSContext *cx,
+ JSValueConst this_val, int magic);
+static JSValue ngx_qjs_ext_error_log_path(JSContext *cx, JSValueConst this_val);
+static JSValue ngx_qjs_ext_log(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv, int level);
+static JSValue ngx_qjs_ext_console_time(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static JSValue ngx_qjs_ext_console_time_end(JSContext *cx,
+ JSValueConst this_val, int argc, JSValueConst *argv);
+static JSValue ngx_qjs_ext_prefix(JSContext *cx, JSValueConst this_val);
+static JSValue ngx_qjs_ext_worker_id(JSContext *cx, JSValueConst this_val);
+
+static void ngx_qjs_console_finalizer(JSRuntime *rt, JSValue val);
+
+static JSModuleDef *ngx_qjs_module_loader(JSContext *ctx,
+ const char *module_name, void *opaque);
+static int ngx_qjs_unhandled_rejection(ngx_js_ctx_t *ctx);
+static void ngx_qjs_rejection_tracker(JSContext *ctx, JSValueConst promise,
+ JSValueConst reason, JS_BOOL is_handled, void *opaque);
+
+static JSValue ngx_qjs_value(JSContext *cx, const ngx_str_t *path);
+static ngx_int_t ngx_qjs_dump_obj(ngx_engine_t *e, JSValueConst val,
+ ngx_str_t *dst);
+
+static JSModuleDef *ngx_qjs_core_init(JSContext *cx, const char *name);
+#endif
+
static njs_int_t ngx_js_ext_build(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_js_ext_conf_file_path(njs_vm_t *vm,
@@ -377,6 +425,57 @@ njs_module_t *njs_js_addon_modules_shared[] = {
static njs_int_t ngx_js_console_proto_id;
+#if (NJS_HAVE_QUICKJS)
+
+static const JSCFunctionListEntry ngx_qjs_ext_ngx[] = {
+ JS_CGETSET_DEF("build", ngx_qjs_ext_build, NULL),
+ JS_CGETSET_DEF("conf_prefix", ngx_qjs_ext_conf_prefix, NULL),
+ JS_CGETSET_DEF("conf_file_path", ngx_qjs_ext_conf_file_path, NULL),
+ JS_CGETSET_MAGIC_DEF("ERR", ngx_qjs_ext_constant_integer, NULL,
+ NGX_LOG_ERR),
+ JS_CGETSET_DEF("error_log_path", ngx_qjs_ext_error_log_path, NULL),
+ JS_CGETSET_MAGIC_DEF("INFO", ngx_qjs_ext_constant_integer, NULL,
+ NGX_LOG_INFO),
+ JS_CFUNC_MAGIC_DEF("log", 1, ngx_qjs_ext_log, 0),
+ JS_CGETSET_DEF("prefix", ngx_qjs_ext_prefix, NULL),
+ JS_PROP_STRING_DEF("version", NGINX_VERSION, JS_PROP_C_W_E),
+ JS_PROP_INT32_DEF("version_number", nginx_version, JS_PROP_C_W_E),
+ JS_CGETSET_MAGIC_DEF("WARN", ngx_qjs_ext_constant_integer, NULL,
+ NGX_LOG_WARN),
+ JS_CGETSET_DEF("worker_id", ngx_qjs_ext_worker_id, NULL),
+};
+
+
+static const JSCFunctionListEntry ngx_qjs_ext_console[] = {
+ JS_CFUNC_MAGIC_DEF("error", 1, ngx_qjs_ext_log, NGX_LOG_ERR),
+ JS_CFUNC_MAGIC_DEF("info", 1, ngx_qjs_ext_log, NGX_LOG_INFO),
+ JS_CFUNC_MAGIC_DEF("log", 1, ngx_qjs_ext_log, NGX_LOG_INFO),
+ JS_CFUNC_DEF("time", 1, ngx_qjs_ext_console_time),
+ JS_CFUNC_DEF("timeEnd", 1, ngx_qjs_ext_console_time_end),
+ JS_CFUNC_MAGIC_DEF("warn", 1, ngx_qjs_ext_log, NGX_LOG_WARN),
+};
+
+
+static const JSCFunctionListEntry ngx_qjs_ext_global[] = {
+ JS_CFUNC_MAGIC_DEF("setTimeout", 1, ngx_qjs_ext_set_timeout, 0),
+ JS_CFUNC_MAGIC_DEF("setImmediate", 1, ngx_qjs_ext_set_timeout, 1),
+ JS_CFUNC_DEF("clearTimeout", 1, ngx_qjs_ext_clear_timeout),
+};
+
+
+static JSClassDef ngx_qjs_console_class = {
+ "Console",
+ .finalizer = ngx_qjs_console_finalizer,
+};
+
+
+qjs_module_t ngx_qjs_ngx_module = {
+ .name = "ngx",
+ .init = ngx_qjs_core_init,
+};
+
+#endif
+
static ngx_engine_t *
ngx_create_engine(ngx_engine_opts_t *opts)
{
@@ -415,6 +514,25 @@ ngx_create_engine(ngx_engine_opts_t *opts)
: ngx_engine_njs_destroy;
break;
+#if (NJS_HAVE_QUICKJS)
+ case NGX_ENGINE_QJS:
+ rc = ngx_engine_qjs_init(engine, opts);
+ if (rc != NGX_OK) {
+ return NULL;
+ }
+
+ engine->name = "QuickJS";
+ engine->type = NGX_ENGINE_QJS;
+ engine->compile = ngx_engine_qjs_compile;
+ engine->call = ngx_engine_qjs_call;
+ engine->external = ngx_engine_qjs_external;
+ engine->pending = ngx_engine_qjs_pending;
+ engine->string = ngx_engine_qjs_string;
+ engine->destroy = opts->destroy ? opts->destroy
+ : ngx_engine_qjs_destroy;
+ break;
+#endif
+
default:
return NULL;
}
@@ -493,212 +611,1592 @@ ngx_engine_njs_compile(ngx_js_loc_conf_t *conf, ngx_log_t *log, u_char *start,
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;
- }
- }
- }
+ 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;
+ }
+ }
+ }
+
+ ngx_log_error(NGX_LOG_EMERG, log, 0, "%*s", text.length, text.start);
+ return NGX_ERROR;
+ }
+
+ if (start != end) {
+ ngx_log_error(NGX_LOG_EMERG, log, 0,
+ "extra characters in js script: \"%*s\"",
+ end - start, start);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_engine_t *
+ngx_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external)
+{
+ njs_vm_t *vm;
+ njs_int_t rc;
+ njs_str_t key;
+ ngx_str_t exception;
+ ngx_uint_t i;
+ ngx_engine_t *engine;
+ njs_opaque_value_t retval;
+ ngx_js_named_path_t *preload;
+
+ vm = njs_vm_clone(cf->engine->u.njs.vm, external);
+ if (vm == NULL) {
+ return NULL;
+ }
+
+ engine = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(ngx_engine_t));
+ if (engine == NULL) {
+ return NULL;
+ }
+
+ memcpy(engine, cf->engine, sizeof(ngx_engine_t));
+ engine->pool = njs_vm_memory_pool(vm);
+ engine->u.njs.vm = vm;
+
+ /* bind objects from preload vm */
+
+ if (cf->preload_objects != NGX_CONF_UNSET_PTR) {
+ preload = cf->preload_objects->elts;
+
+ for (i = 0; i < cf->preload_objects->nelts; i++) {
+ key.start = preload[i].name.data;
+ key.length = preload[i].name.len;
+
+ rc = njs_vm_value(cf->preload_vm, &key, njs_value_arg(&retval));
+ if (rc != NJS_OK) {
+ return NULL;
+ }
+
+ rc = njs_vm_bind(vm, &key, njs_value_arg(&retval), 0);
+ if (rc != NJS_OK) {
+ return NULL;
+ }
+ }
+ }
+
+ if (njs_vm_start(vm, njs_value_arg(&retval)) == NJS_ERROR) {
+ ngx_js_exception(vm, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception);
+
+ return NULL;
+ }
+
+ return engine;
+}
+
+
+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;
+ ngx_str_t exception;
+ njs_function_t *func;
+
+ name.start = fname->data;
+ name.length = fname->len;
+
+ vm = ctx->engine->u.njs.vm;
+
+ func = njs_vm_function(vm, &name);
+ if (func == NULL) {
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+ "js function \"%V\" not found", fname);
+ return NGX_ERROR;
+ }
+
+ ret = njs_vm_invoke(vm, func, njs_value_arg(args), nargs,
+ njs_value_arg(&ctx->retval));
+ if (ret == NJS_ERROR) {
+ ngx_js_exception(vm, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+ "js exception: %V", &exception);
+
+ return NGX_ERROR;
+ }
+
+ for ( ;; ) {
+ ret = njs_vm_execute_pending_job(vm);
+ if (ret <= NJS_OK) {
+ if (ret == NJS_ERROR) {
+ ngx_js_exception(vm, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+ "js job exception: %V", &exception);
+ return NGX_ERROR;
+ }
+
+ break;
+ }
+ }
+
+ if (ngx_js_unhandled_rejection(ctx)) {
+ ngx_js_exception(vm, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception);
+ return NGX_ERROR;
+ }
+
+ return njs_rbtree_is_empty(&ctx->waiting_events) ? NGX_OK : NGX_AGAIN;
+}
+
+
+static void *
+ngx_engine_njs_external(ngx_engine_t *engine)
+{
+ return njs_vm_external_ptr(engine->u.njs.vm);
+}
+
+static ngx_int_t
+ngx_engine_njs_pending(ngx_engine_t *e)
+{
+ return njs_vm_pending(e->u.njs.vm);
+}
+
+
+static ngx_int_t
+ngx_engine_njs_string(ngx_engine_t *e, njs_opaque_value_t *value,
+ ngx_str_t *str)
+{
+ ngx_int_t rc;
+ njs_str_t s;
+
+ rc = ngx_js_string(e->u.njs.vm, njs_value_arg(value), &s);
+
+ str->data = s.start;
+ str->len = s.length;
+
+ return rc;
+}
+
+
+static void
+ngx_engine_njs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx,
+ ngx_js_loc_conf_t *conf)
+{
+ ngx_js_event_t *event;
+ njs_rbtree_node_t *node;
+
+ if (ctx != NULL) {
+ node = njs_rbtree_min(&ctx->waiting_events);
+
+ while (njs_rbtree_is_there_successor(&ctx->waiting_events, node)) {
+ event = (ngx_js_event_t *) ((u_char *) node
+ - offsetof(ngx_js_event_t, node));
+
+ if (event->destructor != NULL) {
+ event->destructor(event);
+ }
+
+ node = njs_rbtree_node_successor(&ctx->waiting_events, node);
+ }
+ }
+
+ njs_vm_destroy(e->u.njs.vm);
+
+ /*
+ * when ctx !=NULL e->pool is vm pool, in such case it is destroyed
+ * by njs_vm_destroy().
+ */
+
+ if (ctx == NULL) {
+ njs_mp_destroy(e->pool);
+ }
+}
+
+
+#if (NJS_HAVE_QUICKJS)
+
+static ngx_int_t
+ngx_engine_qjs_init(ngx_engine_t *engine, ngx_engine_opts_t *opts)
+{
+ JSRuntime *rt;
+
+ rt = JS_NewRuntime();
+ if (rt == NULL) {
+ return NGX_ERROR;
+ }
+
+ engine->u.qjs.ctx = qjs_new_context(rt, opts->u.qjs.addons);
+ if (engine->u.qjs.ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ JS_SetRuntimeOpaque(rt, opts->u.qjs.metas);
+ JS_SetContextOpaque(engine->u.qjs.ctx, opts->u.qjs.addons);
+
+ JS_SetModuleLoaderFunc(rt, NULL, ngx_qjs_module_loader, opts->conf);
+
+ return NGX_OK;
+}
+
+
+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;
+ JSContext *cx;
+ ngx_engine_t *engine;
+ ngx_js_code_entry_t *pc;
+
+ engine = conf->engine;
+ cx = engine->u.qjs.ctx;
+
+ code = JS_Eval(cx, (char *) start, size, "<main>",
+ JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
+
+ if (JS_IsException(code)) {
+ ngx_qjs_exception(engine, &text);
+ ngx_log_error(NGX_LOG_EMERG, log, 0, "js compile %V", &text);
+ return NGX_ERROR;
+ }
+
+ pc = njs_arr_add(engine->precompiled);
+ if (pc == NULL) {
+ JS_FreeValue(cx, code);
+ ngx_log_error(NGX_LOG_EMERG, log, 0, "njs_arr_add() failed");
+ return NGX_ERROR;
+ }
+
+ pc->code = JS_WriteObject(cx, &pc->code_size, code, JS_WRITE_OBJ_BYTECODE);
+ if (pc->code == NULL) {
+ JS_FreeValue(cx, code);
+ ngx_log_error(NGX_LOG_EMERG, log, 0, "JS_WriteObject() failed");
+ return NGX_ERROR;
+ }
+
+ JS_FreeValue(cx, code);
+
+ return NGX_OK;
+}
+
+
+static JSValue
+js_std_await(JSContext *ctx, JSValue obj)
+{
+ int state, err;
+ JSValue ret;
+ JSContext *ctx1;
+
+ for (;;) {
+ state = JS_PromiseState(ctx, obj);
+ if (state == JS_PROMISE_FULFILLED) {
+ ret = JS_PromiseResult(ctx, obj);
+ JS_FreeValue(ctx, obj);
+ break;
+
+ } else if (state == JS_PROMISE_REJECTED) {
+ ret = JS_Throw(ctx, JS_PromiseResult(ctx, obj));
+ JS_FreeValue(ctx, obj);
+ break;
+
+ } else if (state == JS_PROMISE_PENDING) {
+ err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
+ if (err < 0) {
+ /* js_std_dump_error(ctx1); */
+ }
+
+ } else {
+ /* not a promise */
+ ret = obj;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+
+ngx_engine_t *
+ngx_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external)
+{
+ JSValue rv;
+ njs_mp_t *mp;
+ uint32_t i, length;
+ JSRuntime *rt;
+ ngx_str_t exception;
+ JSContext *cx;
+ ngx_engine_t *engine;
+ ngx_js_code_entry_t *pc;
+
+ mp = njs_mp_fast_create(2 * getpagesize(), 128, 512, 16);
+ if (mp == NULL) {
+ return NULL;
+ }
+
+ engine = njs_mp_alloc(mp, sizeof(ngx_engine_t));
+ if (engine == NULL) {
+ return NULL;
+ }
+
+ memcpy(engine, cf->engine, sizeof(ngx_engine_t));
+ engine->pool = mp;
+
+ if (cf->reuse_queue != NULL) {
+ engine->u.qjs.ctx = ngx_js_queue_pop(cf->reuse_queue);
+ if (engine->u.qjs.ctx != NULL) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "js reused context: %p", engine->u.qjs.ctx);
+ JS_SetContextOpaque(engine->u.qjs.ctx, external);
+ return engine;
+ }
+ }
+
+ rt = JS_NewRuntime();
+ if (rt == NULL) {
+ return NULL;
+ }
+
+ JS_SetRuntimeOpaque(rt, JS_GetRuntimeOpaque(
+ JS_GetRuntime(cf->engine->u.qjs.ctx)));
+
+ cx = qjs_new_context(rt, JS_GetContextOpaque(cf->engine->u.qjs.ctx));
+ if (cx == NULL) {
+ JS_FreeRuntime(rt);
+ return NULL;
+ }
+
+ engine->u.qjs.ctx = cx;
+ JS_SetContextOpaque(cx, external);
+
+ JS_SetHostPromiseRejectionTracker(rt, ngx_qjs_rejection_tracker, ctx);
+
+
+ /* TODO: bind objects from preload vm */
+
+ rv = JS_UNDEFINED;
+ pc = engine->precompiled->start;
+ length = engine->precompiled->items;
+
+ for (i = 0; i < length; i++) {
+ rv = JS_ReadObject(cx, pc[i].code, pc[i].code_size,
+ JS_READ_OBJ_BYTECODE);
+ if (JS_IsException(rv)) {
+ ngx_qjs_exception(engine, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+ "js load module exception: %V", &exception);
+ goto destroy;
+ }
+ }
+
+ if (JS_ResolveModule(cx, rv) < 0) {
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js resolve module failed");
+ goto destroy;
+ }
+
+ rv = JS_EvalFunction(cx, rv);
+
+ if (JS_IsException(rv)) {
+ ngx_qjs_exception(engine, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js eval exception: %V",
+ &exception);
+ goto destroy;
+ }
+
+ rv = js_std_await(cx, rv);
+ if (JS_IsException(rv)) {
+ ngx_qjs_exception(engine, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js eval exception: %V",
+ &exception);
+ goto destroy;
+ }
+
+ JS_FreeValue(cx, rv);
+
+ return engine;
+
+destroy:
+
+ JS_FreeContext(cx);
+ JS_FreeRuntime(rt);
+ njs_mp_destroy(mp);
+
+ return NULL;
+}
+
+
+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)
+{
+ int rc;
+ JSValue fn, val;
+ ngx_str_t exception;
+ JSRuntime *rt;
+ JSContext *cx, *cx1;
+
+ cx = ctx->engine->u.qjs.ctx;
+
+ fn = ngx_qjs_value(cx, fname);
+ if (!JS_IsFunction(cx, fn)) {
+ JS_FreeValue(cx, fn);
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js function \"%V\" not found",
+ fname);
+
+ return NGX_ERROR;
+ }
+
+ val = JS_Call(cx, fn, JS_UNDEFINED, nargs, &ngx_qjs_arg(args[0]));
+ JS_FreeValue(cx, fn);
+ if (JS_IsException(val)) {
+ ngx_qjs_exception(ctx->engine, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+ "js call exception: %V", &exception);
+
+ return NGX_ERROR;
+ }
+
+ JS_FreeValue(cx, ngx_qjs_arg(ctx->retval));
+ ngx_qjs_arg(ctx->retval) = val;
+
+ rt = JS_GetRuntime(cx);
+
+ for ( ;; ) {
+ rc = JS_ExecutePendingJob(rt, &cx1);
+ if (rc <= 0) {
+ if (rc == -1) {
+ ngx_qjs_exception(ctx->engine, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+ "js job exception: %V", &exception);
+
+ return NGX_ERROR;
+ }
+
+ break;
+ }
+ }
+
+ if (ngx_qjs_unhandled_rejection(ctx)) {
+ ngx_qjs_exception(ctx->engine, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception);
+ return NGX_ERROR;
+ }
+
+ return njs_rbtree_is_empty(&ctx->waiting_events) ? NGX_OK : NGX_AGAIN;
+}
+
+
+static void *
+ngx_engine_qjs_external(ngx_engine_t *e)
+{
+ return JS_GetContextOpaque(e->u.qjs.ctx);
+}
+
+
+static ngx_int_t
+ngx_engine_qjs_pending(ngx_engine_t *e)
+{
+ return JS_IsJobPending(JS_GetRuntime(e->u.qjs.ctx));
+}
+
+
+static ngx_int_t
+ngx_engine_qjs_string(ngx_engine_t *e, njs_opaque_value_t *value,
+ ngx_str_t *str)
+{
+ return ngx_qjs_dump_obj(e, ngx_qjs_arg(*value), str);
+}
+
+
+static void
+ngx_js_cleanup_reuse_ctx(void *data)
+{
+ JSRuntime *rt;
+ JSContext *cx;
+
+ ngx_js_queue_t *reuse = data;
+
+ for ( ;; ) {
+ cx = ngx_js_queue_pop(reuse);
+ if (cx == NULL) {
+ break;
+ }
+
+ rt = JS_GetRuntime(cx);
+ JS_FreeContext(cx);
+ JS_FreeRuntime(rt);
+ }
+}
+
+
+void
+ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx,
+ ngx_js_loc_conf_t *conf)
+{
+ uint32_t i, length;
+ JSRuntime *rt;
+ JSContext *cx;
+ JSClassID class_id;
+ ngx_qjs_event_t *event;
+ ngx_js_opaque_t *opaque;
+ njs_rbtree_node_t *node;
+ ngx_pool_cleanup_t *cln;
+ ngx_js_code_entry_t *pc;
+ ngx_js_rejected_promise_t *rejected_promise;
+
+ cx = e->u.qjs.ctx;
+
+ if (ctx != NULL) {
+ node = njs_rbtree_min(&ctx->waiting_events);
+
+ while (njs_rbtree_is_there_successor(&ctx->waiting_events, node)) {
+ event = (ngx_qjs_event_t *) ((u_char *) node
+ - offsetof(ngx_qjs_event_t, node));
+
+ if (event->destructor != NULL) {
+ event->destructor(event);
+ }
+
+ node = njs_rbtree_node_successor(&ctx->waiting_events, node);
+ }
+
+ if (ctx->rejected_promises != NULL) {
+ rejected_promise = ctx->rejected_promises->start;
+
+ for (i = 0; i < ctx->rejected_promises->items; i++) {
+ JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].promise));
+ JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].message));
+ }
+ }
+
+ class_id = JS_GetClassID(ngx_qjs_arg(ctx->args[0]));
+ opaque = JS_GetOpaque(ngx_qjs_arg(ctx->args[0]), class_id);
+ opaque->external = NULL;
+
+ JS_FreeValue(cx, ngx_qjs_arg(ctx->args[0]));
+ JS_FreeValue(cx, ngx_qjs_arg(ctx->retval));
+
+ } else if (e->precompiled != NULL) {
+ pc = e->precompiled->start;
+ length = e->precompiled->items;
+
+ for (i = 0; i < length; i++) {
+ js_free(cx, pc[i].code);
+ }
+ }
+
+ njs_mp_destroy(e->pool);
+
+ if (conf != NULL && conf->reuse != 0) {
+ if (conf->reuse_queue == NULL) {
+ conf->reuse_queue = ngx_js_queue_create(ngx_cycle->pool,
+ conf->reuse);
+ if (conf->reuse_queue == NULL) {
+ goto free_ctx;
+ }
+
+ cln = ngx_pool_cleanup_add(ngx_cycle->pool, 0);
+ if (cln == NULL) {
+ goto free_ctx;
+ }
+
+ cln->handler = ngx_js_cleanup_reuse_ctx;
+ cln->data = conf->reuse_queue;
+ }
+
+ if (ngx_js_queue_push(conf->reuse_queue, cx) != NGX_OK) {
+ goto free_ctx;
+ }
+
+ return;
+ }
+
+free_ctx:
+
+ rt = JS_GetRuntime(cx);
+ JS_FreeContext(cx);
+ JS_FreeRuntime(rt);
+}
+
+
+static JSValue
+ngx_qjs_value(JSContext *cx, const ngx_str_t *path)
+{
+ u_char *start, *p, *end;
+ JSAtom key;
+ size_t size;
+ JSValue value, rv;
+
+ start = path->data;
+ end = start + path->len;
+
+ value = JS_GetGlobalObject(cx);
+
+ for ( ;; ) {
+ p = njs_strlchr(start, end, '.');
+
+ size = ((p != NULL) ? p : end) - start;
+ if (size == 0) {
+ JS_FreeValue(cx, value);
+ return JS_ThrowTypeError(cx, "empty path element");
+ }
+
+ key = JS_NewAtomLen(cx, (char *) start, size);
+ if (key == JS_ATOM_NULL) {
+ JS_FreeValue(cx, value);
+ return JS_ThrowInternalError(cx, "could not create atom");
+ }
+
+ rv = JS_GetProperty(cx, value, key);
+ JS_FreeAtom(cx, key);
+ if (JS_IsException(rv)) {
+ JS_FreeValue(cx, value);
+ return JS_EXCEPTION;
+ }
+
+ JS_FreeValue(cx, value);
+
+ if (p == NULL) {
+ break;
+ }
+
+ start = p + 1;
+ value = rv;
+ }
+
+ return rv;
+}
+
+
+static ngx_int_t
+ngx_qjs_dump_obj(ngx_engine_t *e, JSValueConst val, ngx_str_t *dst)
+{
+ size_t len, byte_offset, byte_length;
+ u_char *start, *p;
+ JSValue buffer, stack;
+ ngx_str_t str, stack_str;
+ JSContext *cx;
+
+ if (JS_IsNullOrUndefined(val)) {
+ dst->data = NULL;
+ dst->len = 0;
+ return NGX_OK;
+ }
+
+ cx = e->u.qjs.ctx;
+
+ buffer = JS_GetTypedArrayBuffer(cx, val, &byte_offset, &byte_length, NULL);
+ if (!JS_IsException(buffer)) {
+ start = JS_GetArrayBuffer(cx, &dst->len, buffer);
+
+ JS_FreeValue(cx, buffer);
+
+ if (start != NULL) {
+ start += byte_offset;
+ dst->len = byte_length;
+
+ dst->data = njs_mp_alloc(e->pool, dst->len);
+ if (dst->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ memcpy(dst->data, start, dst->len);
+ return NGX_OK;
+ }
+ }
+
+ str.data = (u_char *) JS_ToCString(cx, val);
+ if (str.data != NULL) {
+ str.len = ngx_strlen(str.data);
+
+ stack = JS_GetPropertyStr(cx, val, "stack");
+
+ stack_str.len = 0;
+ stack_str.data = NULL;
+
+ if (!JS_IsException(stack) && !JS_IsUndefined(stack)) {
+ stack_str.data = (u_char *) JS_ToCString(cx, stack);
+ if (stack_str.data != NULL) {
+ stack_str.len = ngx_strlen(stack_str.data);
+ }
+ }
+
+ len = str.len;
+
+ if (stack_str.len != 0) {
+ len += stack_str.len + njs_length("\n");
+ }
+
+ start = njs_mp_alloc(e->pool, len);
+ if (start == NULL) {
+ JS_FreeCString(cx, (char *) str.data);
+ JS_FreeValue(cx, stack);
+ return NGX_ERROR;
+ }
+
+ p = ngx_cpymem(start, str.data, str.len);
+
+ if (stack_str.len != 0) {
+ *p++ = '\n';
+ (void) ngx_cpymem(p, stack_str.data, stack_str.len);
+ JS_FreeCString(cx, (char *) stack_str.data);
+ }
+
+ JS_FreeCString(cx, (char *) str.data);
+ JS_FreeValue(cx, stack);
+
+ } else {
+ len = njs_length("[exception]");
+
+ start = njs_mp_alloc(e->pool, len);
+ if (start == NULL) {
+ return NGX_ERROR;
+ }
+
+ memcpy(start, "[exception]", len);
+ }
+
+ dst->data = start;
+ dst->len = len;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_qjs_call(ngx_js_ctx_t *ctx, JSValue fn, JSValue *argv, int argc)
+{
+ int rc;
+ JSValue ret;
+ ngx_str_t exception;
+ JSRuntime *rt;
+ JSContext *cx, *cx1;
+
+ cx = ctx->engine->u.qjs.ctx;
+
+ ret = JS_Call(cx, fn, JS_UNDEFINED, argc, argv);
+ if (JS_IsException(ret)) {
+ ngx_qjs_exception(ctx->engine, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+ "js call exception: %V", &exception);
+
+ return NGX_ERROR;
+ }
+
+ JS_FreeValue(cx, ret);
+
+ rt = JS_GetRuntime(cx);
+
+ for ( ;; ) {
+ rc = JS_ExecutePendingJob(rt, &cx1);
+ if (rc <= 0) {
+ if (rc == -1) {
+ ngx_qjs_exception(ctx->engine, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+ "js job exception: %V", &exception);
+
+ return NGX_ERROR;
+ }
+
+ break;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_qjs_exception(ngx_engine_t *e, ngx_str_t *s)
+{
+ JSValue exception;
+
+ exception = JS_GetException(e->u.qjs.ctx);
+ if (ngx_qjs_dump_obj(e, exception, s) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ JS_FreeValue(e->u.qjs.ctx, exception);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_qjs_integer(JSContext *cx, JSValueConst val, ngx_int_t *n)
+{
+ double num;
+
+ if (JS_ToFloat64(cx, &num, val)) {
+ return NGX_ERROR;
+ }
+
+ if (isinf(num) || isnan(num)) {
+ (void) JS_ThrowTypeError(cx, "invalid number");
+ return NGX_ERROR;
+ }
+
+ *n = num;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_qjs_string(ngx_engine_t *e, JSValueConst val, ngx_str_t *dst)
+{
+ size_t len, byte_offset, byte_length;
+ u_char *start;
+ JSValue buffer;
+ JSContext *cx;
+ const char *str;
+
+ if (JS_IsNullOrUndefined(val)) {
+ dst->data = NULL;
+ dst->len = 0;
+ return NGX_OK;
+ }
+
+ cx = e->u.qjs.ctx;
+
+ buffer = JS_GetTypedArrayBuffer(cx, val, &byte_offset, &byte_length, NULL);
+ if (!JS_IsException(buffer)) {
+ start = JS_GetArrayBuffer(cx, &dst->len, buffer);
+
+ JS_FreeValue(cx, buffer);
+
+ if (start != NULL) {
+ start += byte_offset;
+ dst->len = byte_length;
+
+ dst->data = njs_mp_alloc(e->pool, dst->len);
+ if (dst->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ memcpy(dst->data, start, dst->len);
+ return NGX_OK;
+ }
+ }
+
+ str = JS_ToCString(cx, val);
+ if (str == NULL) {
+ return NGX_ERROR;
+ }
+
+ len = strlen(str);
+
+ start = njs_mp_alloc(e->pool, len);
+ if (start == NULL) {
+ JS_FreeCString(cx, str);
+ return NGX_ERROR;
+ }
+
+ memcpy(start, str, len);
+
+ JS_FreeCString(cx, str);
+
+ dst->data = start;
+ dst->len = len;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_qjs_timer_handler(ngx_event_t *ev)
+{
+ void *external;
+ JSContext *cx;
+ ngx_int_t rc;
+ ngx_js_ctx_t *ctx;
+ ngx_qjs_event_t *event;
+
+ event = (ngx_qjs_event_t *) ((u_char *) ev - offsetof(ngx_qjs_event_t, ev));
+
+ cx = event->ctx;
+ external = JS_GetContextOpaque(cx);
+ ctx = ngx_qjs_external_ctx(cx, external);
+
+ rc = ngx_qjs_call((ngx_js_ctx_t *) ctx, event->function, event->args,
+ event->nargs);
+
+ ngx_js_del_event(ctx, event);
+
+ ngx_qjs_external_event_finalize(cx)(external, rc);
+}
+
+
+static void
+ngx_qjs_clear_timer(ngx_qjs_event_t *event)
+{
+ int i;
+ JSContext *cx;
+
+ cx = event->ctx;
+
+ if (event->ev.timer_set) {
+ ngx_del_timer(&event->ev);
+ }
+
+ JS_FreeValue(cx, event->function);
+
+ for (i = 0; i < (int) event->nargs; i++) {
+ JS_FreeValue(cx, event->args[i]);
+ }
+}
+
+
+static JSValue
+ngx_qjs_ext_set_timeout(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv, int immediate)
+{
+ int i, n;
+ void *external;
+ uint32_t delay;
+ ngx_js_ctx_t *ctx;
+ ngx_qjs_event_t *event;
+ ngx_connection_t *c;
+
+ if (!JS_IsFunction(cx, argv[0])) {
+ return JS_ThrowTypeError(cx, "first arg must be a function");
+ }
+
+ delay = 0;
+
+ if (!immediate && argc >= 2) {
+ if (JS_ToUint32(cx, &delay, argv[1]) < 0) {
+ return JS_EXCEPTION;
+ }
+ }
+
+ n = immediate ? 1 : 2;
+ argc = (argc >= n) ? argc - n : 0;
+ external = JS_GetContextOpaque(cx);
+ ctx = ngx_qjs_external_ctx(cx, external);
+
+ event = ngx_pcalloc(ngx_qjs_external_pool(cx, external),
+ sizeof(ngx_qjs_event_t) + sizeof(JSValue) * argc);
+ if (event == NULL) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ event->ctx = cx;
+ event->function = JS_DupValue(cx, argv[0]);
+ event->nargs = argc;
+ event->args = (JSValue *) &event[1];
+ event->destructor = ngx_qjs_clear_timer;
+ event->fd = ctx->event_id++;
+
+ c = ngx_qjs_external_connection(cx, external);
+
+ event->ev.log = c->log;
+ event->ev.data = event;
+ event->ev.handler = ngx_qjs_timer_handler;
+
+ if (event->nargs != 0) {
+ for (i = 0; i < argc; i++) {
+ event->args[i] = JS_DupValue(cx, argv[n + i]);
+ }
+ }
+
+ ngx_js_add_event(ctx, event);
+
+ ngx_add_timer(&event->ev, delay);
+
+ return JS_NewInt32(cx, event->fd);
+}
+
+
+static JSValue
+ngx_qjs_ext_clear_timeout(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv)
+{
+ uint32_t id;
+ ngx_js_ctx_t *ctx;
+ ngx_qjs_event_t event_lookup, *event;
+ njs_rbtree_node_t *rb;
+
+ if (JS_ToUint32(cx, &id, argv[0]) < 0) {
+ return JS_EXCEPTION;
+ }
+
+ ctx = ngx_qjs_external_ctx(cx, JS_GetContextOpaque(cx));
+ event_lookup.fd = id;
+
+ rb = njs_rbtree_find(&ctx->waiting_events, &event_lookup.node);
+ if (rb == NULL) {
+ return JS_ThrowReferenceError(cx, "failed to find timer");
+ }
+
+ event = (ngx_qjs_event_t *) ((u_char *) rb
+ - offsetof(ngx_qjs_event_t, node));
+
+ ngx_js_del_event(ctx, event);
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_qjs_ext_build(JSContext *cx, JSValueConst this_val)
+{
+ return JS_NewStringLen(cx,
+#ifdef NGX_BUILD
+ (char *) NGX_BUILD,
+ njs_strlen(NGX_BUILD)
+#else
+ (char *) "",
+ 0
+#endif
+ );
+}
+
+
+static JSValue
+ngx_qjs_ext_conf_prefix(JSContext *cx, JSValueConst this_val)
+{
+ return JS_NewStringLen(cx, (char *) ngx_cycle->prefix.data,
+ ngx_cycle->prefix.len);
+}
+
+
+static JSValue
+ngx_qjs_ext_conf_file_path(JSContext *cx, JSValueConst this_val)
+{
+ return JS_NewStringLen(cx, (char *) ngx_cycle->conf_file.data,
+ ngx_cycle->conf_file.len);
+}
+
+
+static JSValue
+ngx_qjs_ext_constant_integer(JSContext *cx, JSValueConst this_val, int magic)
+{
+ return JS_NewInt32(cx, magic);
+}
+
+
+static JSValue
+ngx_qjs_ext_error_log_path(JSContext *cx, JSValueConst this_val)
+{
+ return JS_NewStringLen(cx, (char *) ngx_cycle->error_log.data,
+ ngx_cycle->error_log.len);
+}
+
+
+static JSValue
+ngx_qjs_ext_prefix(JSContext *cx, JSValueConst this_val)
+{
+ return JS_NewStringLen(cx, (char *) ngx_cycle->prefix.data,
+ ngx_cycle->prefix.len);
+}
+
+
+static JSValue
+ngx_qjs_ext_worker_id(JSContext *cx, JSValueConst this_val)
+{
+ return JS_NewInt32(cx, ngx_worker);
+}
+
+
+static void
+ngx_qjs_console_finalizer(JSRuntime *rt, JSValue val)
+{
+ ngx_queue_t *labels, *q, *next;
+ ngx_js_console_t *console;
+ ngx_js_timelabel_t *label;
+
+ console = JS_GetOpaque(val, NGX_QJS_CLASS_ID_CONSOLE);
+ if (console == (void *) 1) {
+ return;
+ }
+
+ labels = &console->labels;
+ q = ngx_queue_head(labels);
+
+ for ( ;; ) {
+ if (q == ngx_queue_sentinel(labels)) {
+ break;
+ }
+
+ next = ngx_queue_next(q);
+
+ label = ngx_queue_data(q, ngx_js_timelabel_t, queue);
+ ngx_queue_remove(&label->queue);
+ js_free_rt(rt, label);
+
+ q = next;
+ }
+
+ js_free_rt(rt, console);
+}
+
+
+static JSValue
+ngx_qjs_ext_log(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv, int magic)
+{
+ char *p;
+ uint32_t level;
+ ngx_str_t msg;
+ ngx_js_ctx_t *ctx;
+ ngx_connection_t *c;
+
+ p = JS_GetContextOpaque(cx);
+ if (p == NULL) {
+ return JS_ThrowInternalError(cx, "external is not set");
+ }
+
+ level = magic & NGX_JS_LOG_MASK;
+
+ if (level == 0) {
+ if (JS_ToUint32(cx, &level, argv[0]) < 0) {
+ return JS_EXCEPTION;
+ }
+
+ argc--;
+ argv++;
+ }
+
+ ctx = ngx_qjs_external_ctx(cx, p);
+ c = ngx_qjs_external_connection(cx, p);
+
+ for ( ; argc > 0; argc--, argv++) {
+ if (ngx_qjs_dump_obj(ctx->engine, argv[0], &msg) != NGX_OK) {
+ return JS_EXCEPTION;
+ }
+
+ ngx_js_logger(c, level, (u_char *) msg.data, msg.len);
+ }
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_qjs_ext_console_time(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ ngx_str_t name;
+ ngx_queue_t *labels, *q;
+ ngx_js_console_t *console;
+ ngx_connection_t *c;
+ ngx_js_timelabel_t *label;
+
+ static const ngx_str_t default_label = ngx_string("default");
+
+ console = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_CONSOLE);
+ if (console == NULL) {
+ return JS_ThrowInternalError(cx, "this is not a console object");
+ }
+
+ if (console == (void *) 1) {
+ console = js_malloc(cx, sizeof(ngx_js_console_t));
+ if (console == NULL) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ ngx_queue_init(&console->labels);
+
+ JS_SetOpaque(this_val, console);
+ }
+
+ if (!JS_IsUndefined(argv[0])) {
+ name.data = (u_char *) JS_ToCStringLen(cx, &name.len, argv[0]);
+ if (name.data == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ } else {
+ name = default_label;
+ }
+
+ labels = &console->labels;
+
+ for (q = ngx_queue_head(labels);
+ q != ngx_queue_sentinel(labels);
+ q = ngx_queue_next(q))
+ {
+ label = ngx_queue_data(q, ngx_js_timelabel_t, queue);
+
+ if (name.len == label->name.length
+ && ngx_strncmp(name.data, label->name.start, name.len) == 0)
+ {
+ c = ngx_qjs_external_connection(cx, JS_GetContextOpaque(cx));
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "js: Timer \"%V\" already"
+ " exists", &name);
+
+ goto done;
+ }
+ }
+
+ label = js_malloc(cx, sizeof(ngx_js_timelabel_t) + name.len);
+ if (label == NULL) {
+ if (name.data != default_label.data) {
+ JS_FreeCString(cx, (char *) name.data);
+ }
+
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ label->name.length = name.len;
+ label->name.start = (u_char *) label + sizeof(ngx_js_timelabel_t);
+ memcpy(label->name.start, name.data, name.len);
+
+ label->time = ngx_js_monotonic_time();
+
+ ngx_queue_insert_tail(&console->labels, &label->queue);
+
+done:
+
+ if (name.data != default_label.data) {
+ JS_FreeCString(cx, (char *) name.data);
+ }
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_qjs_ext_console_time_end(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ uint64_t ns, ms;
+ ngx_str_t name;
+ ngx_queue_t *labels, *q;
+ ngx_js_console_t *console;
+ ngx_connection_t *c;
+ ngx_js_timelabel_t *label;
+
+ static const ngx_str_t default_label = ngx_string("default");
+
+ ns = ngx_js_monotonic_time();
+
+ console = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_CONSOLE);
+ if (console == NULL) {
+ return JS_ThrowInternalError(cx, "this is not a console object");
+ }
+
+ if (!JS_IsUndefined(argv[0])) {
+ name.data = (u_char *) JS_ToCStringLen(cx, &name.len, argv[0]);
+ if (name.data == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ } else {
+ name = default_label;
+ }
+
+ if (console == (void *) 1) {
+ goto not_found;
+ }
+
+ labels = &console->labels;
+ q = ngx_queue_head(labels);
+
+ for ( ;; ) {
+ if (q == ngx_queue_sentinel(labels)) {
+ goto not_found;
+ }
+
+ label = ngx_queue_data(q, ngx_js_timelabel_t, queue);
+
+ if (name.len == label->name.length
+ && ngx_strncmp(name.data, label->name.start, name.len) == 0)
+ {
+ ngx_queue_remove(&label->queue);
+ break;
+ }
+
+ q = ngx_queue_next(q);
+ }
+
+ ns = ns - label->time;
+
+ js_free(cx, label);
+
+ ms = ns / 1000000;
+ ns = ns % 1000000;
+
+ c = ngx_qjs_external_connection(cx, JS_GetContextOpaque(cx));
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "js: %V: %uL.%06uLms",
+ &name, ms, ns);
- ngx_log_error(NGX_LOG_EMERG, log, 0, "%*s", text.length, text.start);
- return NGX_ERROR;
+ if (name.data != default_label.data) {
+ JS_FreeCString(cx, (char *) name.data);
}
- if (start != end) {
- ngx_log_error(NGX_LOG_EMERG, log, 0,
- "extra characters in js script: \"%*s\"",
- end - start, start);
- return NGX_ERROR;
+ return JS_UNDEFINED;
+
+not_found:
+
+ c = ngx_qjs_external_connection(cx, JS_GetContextOpaque(cx));
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "js: Timer \"%V\" doesn't exist",
+ &name);
+
+ if (name.data != default_label.data) {
+ JS_FreeCString(cx, (char *) name.data);
}
- return NGX_OK;
+ return JS_UNDEFINED;
}
-ngx_engine_t *
-ngx_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external)
+static JSModuleDef *
+ngx_qjs_module_loader(JSContext *cx, const char *module_name, void *opaque)
{
- njs_vm_t *vm;
- njs_int_t rc;
- njs_str_t key;
- ngx_str_t exception;
- ngx_uint_t i;
- ngx_engine_t *engine;
- njs_opaque_value_t retval;
- ngx_js_named_path_t *preload;
+ JSValue func_val;
+ njs_int_t ret;
+ njs_str_t text;
+ JSModuleDef *m;
+ njs_module_info_t info;
+ ngx_js_loc_conf_t *conf;
+ ngx_js_code_entry_t *pc;
- vm = njs_vm_clone(cf->engine->u.njs.vm, external);
- if (vm == NULL) {
- return NULL;
- }
+ conf = opaque;
- engine = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(ngx_engine_t));
- if (engine == NULL) {
+ njs_memzero(&info, sizeof(njs_module_info_t));
+
+ info.name.start = (u_char *) module_name;
+ info.name.length = njs_strlen(module_name);
+
+ ret = ngx_js_module_lookup(conf, &info);
+ if (ret != NJS_OK) {
+ JS_ThrowReferenceError(cx, "could not load module filename '%s'",
+ module_name);
return NULL;
}
- memcpy(engine, cf->engine, sizeof(ngx_engine_t));
- engine->pool = njs_vm_memory_pool(vm);
- engine->u.njs.vm = vm;
+ ret = ngx_js_module_read(conf->engine->pool, info.fd, &text);
- /* bind objects from preload vm */
+ (void) close(info.fd);
- if (cf->preload_objects != NGX_CONF_UNSET_PTR) {
- preload = cf->preload_objects->elts;
+ if (ret != NJS_OK) {
+ JS_ThrowInternalError(cx, "while reading \"%*s\" module",
+ (int) info.file.length, info.file.start);
+ return NULL;
+ }
- for (i = 0; i < cf->preload_objects->nelts; i++) {
- key.start = preload[i].name.data;
- key.length = preload[i].name.len;
+ func_val = JS_Eval(cx, (char *) text.start, text.length, module_name,
+ JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
- rc = njs_vm_value(cf->preload_vm, &key, njs_value_arg(&retval));
- if (rc != NJS_OK) {
- return NULL;
- }
+ njs_mp_free(conf->engine->pool, text.start);
- rc = njs_vm_bind(vm, &key, njs_value_arg(&retval), 0);
- if (rc != NJS_OK) {
- return NULL;
- }
- }
+ if (JS_IsException(func_val)) {
+ return NULL;
}
- if (njs_vm_start(vm, njs_value_arg(&retval)) == NJS_ERROR) {
- ngx_js_exception(vm, &exception);
+ if (conf->engine->precompiled == NULL) {
+ conf->engine->precompiled = njs_arr_create(conf->engine->pool, 4,
+ sizeof(ngx_js_code_entry_t));
+ if (conf->engine->precompiled == NULL) {
+ JS_FreeValue(cx, func_val);
+ JS_ThrowOutOfMemory(cx);
+ return NULL;
+ }
+ }
- ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception);
+ pc = njs_arr_add(conf->engine->precompiled);
+ if (pc == NULL) {
+ JS_FreeValue(cx, func_val);
+ JS_ThrowOutOfMemory(cx);
+ return NULL;
+ }
+ pc->code = JS_WriteObject(cx, &pc->code_size, func_val,
+ JS_WRITE_OBJ_BYTECODE);
+ if (pc->code == NULL) {
+ JS_FreeValue(cx, func_val);
+ JS_ThrowInternalError(cx, "could not write module bytecode");
return NULL;
}
- return engine;
+ m = JS_VALUE_GET_PTR(func_val);
+ JS_FreeValue(cx, func_val);
+
+ return m;
}
-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)
+static int
+ngx_qjs_unhandled_rejection(ngx_js_ctx_t *ctx)
{
- njs_vm_t *vm;
- njs_int_t ret;
- njs_str_t name;
- ngx_str_t exception;
- njs_function_t *func;
+ size_t len;
+ uint32_t i;
+ JSContext *cx;
+ const char *str;
+ ngx_js_rejected_promise_t *rejected_promise;
- name.start = fname->data;
- name.length = fname->len;
+ if (ctx->rejected_promises == NULL
+ || ctx->rejected_promises->items == 0)
+ {
+ return 0;
+ }
- vm = ctx->engine->u.njs.vm;
+ cx = ctx->engine->u.qjs.ctx;
+ rejected_promise = ctx->rejected_promises->start;
- func = njs_vm_function(vm, &name);
- if (func == NULL) {
- ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
- "js function \"%V\" not found", fname);
- return NGX_ERROR;
+ str = JS_ToCStringLen(cx, &len, ngx_qjs_arg(rejected_promise->message));
+ if (njs_slow_path(str == NULL)) {
+ return -1;
}
- ret = njs_vm_invoke(vm, func, njs_value_arg(args), nargs,
- njs_value_arg(&ctx->retval));
- if (ret == NJS_ERROR) {
- ngx_js_exception(vm, &exception);
-
- ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
- "js exception: %V", &exception);
+ JS_ThrowTypeError(cx, "unhandled promise rejection: %*s", (int) len, str);
+ JS_FreeCString(cx, str);
- return NGX_ERROR;
+ for (i = 0; i < ctx->rejected_promises->items; i++) {
+ JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].promise));
+ JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].message));
}
- for ( ;; ) {
- ret = njs_vm_execute_pending_job(vm);
- if (ret <= NJS_OK) {
- if (ret == NJS_ERROR) {
- ngx_js_exception(vm, &exception);
+ njs_arr_destroy(ctx->rejected_promises);
+ ctx->rejected_promises = NULL;
- ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
- "js job exception: %V", &exception);
- return NGX_ERROR;
- }
+ return 1;
+}
- break;
+
+static void
+ngx_qjs_rejection_tracker(JSContext *cx, JSValueConst promise,
+ JSValueConst reason, JS_BOOL is_handled, void *opaque)
+{
+ void *promise_obj;
+ uint32_t i, length;
+ ngx_js_ctx_t *ctx;
+ ngx_js_rejected_promise_t *rejected_promise;
+
+ ctx = opaque;
+
+ if (is_handled && ctx->rejected_promises != NULL) {
+ rejected_promise = ctx->rejected_promises->start;
+ length = ctx->rejected_promises->items;
+
+ promise_obj = JS_VALUE_GET_PTR(promise);
+
+ for (i = 0; i < length; i++) {
+ if (JS_VALUE_GET_PTR(ngx_qjs_arg(rejected_promise[i].promise))
+ == promise_obj)
+ {
+ JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].promise));
+ JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].message));
+ njs_arr_remove(ctx->rejected_promises, &rejected_promise[i]);
+
+ break;
+ }
}
+
+ return;
}
- if (ngx_js_unhandled_rejection(ctx)) {
- ngx_js_exception(vm, &exception);
+ if (ctx->rejected_promises == NULL) {
+ if (ctx->engine == NULL) {
+ /* Do not track rejections during eval stage. The exception
+ * is lifted by the ngx_qjs_clone() function manually. */
+ return;
+ }
- ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception);
- return NGX_ERROR;
+ ctx->rejected_promises = njs_arr_create(ctx->engine->pool, 4,
+ sizeof(ngx_js_rejected_promise_t));
+ if (ctx->rejected_promises == NULL) {
+ return;
+ }
}
- return njs_rbtree_is_empty(&ctx->waiting_events) ? NGX_OK : NGX_AGAIN;
+ rejected_promise = njs_arr_add(ctx->rejected_promises);
+ if (rejected_promise == NULL) {
+ return;
+ }
+
+ ngx_qjs_arg(rejected_promise->promise) = JS_DupValue(cx, promise);
+ ngx_qjs_arg(rejected_promise->message) = JS_DupValue(cx, reason);
}
-static void *
-ngx_engine_njs_external(ngx_engine_t *engine)
+static JSModuleDef *
+ngx_qjs_core_init(JSContext *cx, const char *name)
{
- return njs_vm_external_ptr(engine->u.njs.vm);
-}
+ int ret;
+ JSValue global_obj, proto, obj;
+ JSModuleDef *m;
-static ngx_int_t
-ngx_engine_njs_pending(ngx_engine_t *e)
-{
- return njs_vm_pending(e->u.njs.vm);
-}
+ if (!JS_IsRegisteredClass(JS_GetRuntime(cx),
+ NGX_QJS_CLASS_ID_CONSOLE))
+ {
+ if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_CONSOLE,
+ &ngx_qjs_console_class) < 0)
+ {
+ return NULL;
+ }
+ proto = JS_NewObject(cx);
+ if (JS_IsException(proto)) {
+ return NULL;
+ }
-static ngx_int_t
-ngx_engine_njs_string(ngx_engine_t *e, njs_opaque_value_t *value,
- ngx_str_t *str)
-{
- ngx_int_t rc;
- njs_str_t s;
+ JS_SetPropertyFunctionList(cx, proto, ngx_qjs_ext_console,
+ njs_nitems(ngx_qjs_ext_console));
- rc = ngx_js_string(e->u.njs.vm, njs_value_arg(value), &s);
+ JS_SetClassProto(cx, NGX_QJS_CLASS_ID_CONSOLE, proto);
+ }
- str->data = s.start;
- str->len = s.length;
+ obj = JS_NewObject(cx);
+ if (JS_IsException(obj)) {
+ return NULL;
+ }
- return rc;
-}
+ JS_SetPropertyFunctionList(cx, obj, ngx_qjs_ext_ngx,
+ njs_nitems(ngx_qjs_ext_ngx));
+ global_obj = JS_GetGlobalObject(cx);
-static void
-ngx_engine_njs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx,
- ngx_js_loc_conf_t *conf)
-{
- ngx_js_event_t *event;
- njs_rbtree_node_t *node;
+ JS_SetPropertyFunctionList(cx, global_obj, ngx_qjs_ext_global,
+ njs_nitems(ngx_qjs_ext_global));
- if (ctx != NULL) {
- node = njs_rbtree_min(&ctx->waiting_events);
+ ret = JS_SetPropertyStr(cx, global_obj, "ngx", obj);
+ if (ret < 0) {
+ JS_FreeValue(cx, global_obj);
+ return NULL;
+ }
- while (njs_rbtree_is_there_successor(&ctx->waiting_events, node)) {
- event = (ngx_js_event_t *) ((u_char *) node
- - offsetof(ngx_js_event_t, node));
+ obj = JS_NewObjectClass(cx, NGX_QJS_CLASS_ID_CONSOLE);
+ if (JS_IsException(obj)) {
+ JS_FreeValue(cx, global_obj);
+ return NULL;
+ }
- if (event->destructor != NULL) {
- event->destructor(event);
- }
+ JS_SetOpaque(obj, (void *) 1);
- node = njs_rbtree_node_successor(&ctx->waiting_events, node);
- }
+ ret = JS_SetPropertyStr(cx, global_obj, "console", obj);
+ if (ret < 0) {
+ JS_FreeValue(cx, global_obj);
+ return NULL;
}
- njs_vm_destroy(e->u.njs.vm);
-
- /*
- * when ctx !=NULL e->pool is vm pool, in such case it is destroyed
- * by njs_vm_destroy().
- */
+ JS_FreeValue(cx, global_obj);
- if (ctx == NULL) {
- njs_mp_destroy(e->pool);
+ m = JS_NewCModule(cx, name, NULL);
+ if (m == NULL) {
+ return NULL;
}
+
+ return m;
}
+#endif
+
ngx_int_t
ngx_js_call(njs_vm_t *vm, njs_function_t *func, njs_opaque_value_t *args,
@@ -2439,11 +3937,18 @@ ngx_js_create_conf(ngx_conf_t *cf, size_t size)
return NULL;
}
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->reuse_queue = NULL;
+ */
+
conf->paths = NGX_CONF_UNSET_PTR;
conf->type = NGX_CONF_UNSET_UINT;
conf->imports = NGX_CONF_UNSET_PTR;
conf->preload_objects = NGX_CONF_UNSET_PTR;
+ conf->reuse = NGX_CONF_UNSET_SIZE;
conf->buffer_size = NGX_CONF_UNSET_SIZE;
conf->max_response_body_size = NGX_CONF_UNSET_SIZE;
conf->timeout = NGX_CONF_UNSET_MSEC;
@@ -2507,6 +4012,7 @@ ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child,
ngx_conf_merge_uint_value(conf->type, prev->type, NGX_ENGINE_NJS);
ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000);
+ ngx_conf_merge_size_value(conf->reuse, prev->reuse, 128);
ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 16384);
ngx_conf_merge_size_value(conf->max_response_body_size,
prev->max_response_body_size, 1048576);
@@ -2562,3 +4068,59 @@ ngx_js_monotonic_time(void)
return (uint64_t) tv.tv_sec * 1000000000 + tv.tv_usec * 1000;
#endif
}
+
+
+ngx_js_queue_t *
+ngx_js_queue_create(ngx_pool_t *pool, ngx_uint_t capacity)
+{
+ ngx_js_queue_t *queue;
+
+ queue = ngx_pcalloc(pool, sizeof(ngx_js_queue_t));
+ if (queue == NULL) {
+ return NULL;
+ }
+
+ queue->data = ngx_pcalloc(pool, sizeof(void *) * capacity);
+ if (queue->data == NULL) {
+ return NULL;
+ }
+
+ queue->head = 0;
+ queue->tail = 0;
+ queue->size = 0;
+ queue->capacity = capacity;
+
+ return queue;
+}
+
+
+ngx_int_t
+ngx_js_queue_push(ngx_js_queue_t *queue, void *item)
+{
+ if (queue->size >= queue->capacity) {
+ return NGX_ERROR;
+ }
+
+ queue->data[queue->tail] = item;
+ queue->tail = (queue->tail + 1) % queue->capacity;
+ queue->size++;
+
+ return NGX_OK;
+}
+
+
+void *
+ngx_js_queue_pop(ngx_js_queue_t *queue)
+{
+ void *item;
+
+ if (queue->size == 0) {
+ return NULL;
+ }
+
+ item = queue->data[queue->head];
+ queue->head = (queue->head + 1) % queue->capacity;
+ queue->size--;
+
+ return item;
+}
diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h
index a3bbd541..8b6fbc85 100644
--- a/nginx/ngx_js.h
+++ b/nginx/ngx_js.h
@@ -19,8 +19,12 @@
#include "ngx_js_fetch.h"
#include "ngx_js_shared_dict.h"
+#if (NJS_HAVE_QUICKJS)
+#include <qjs.h>
+#endif
#define NGX_ENGINE_NJS 1
+#define NGX_ENGINE_QJS 2
#define NGX_JS_UNSET 0
#define NGX_JS_DEPRECATED 1
@@ -37,6 +41,26 @@
#define ngx_js_buffer_type(btype) ((btype) & ~NGX_JS_DEPRECATED)
+/*
+ * This static table solves the problem of a native QuickJS approach
+ * which uses a static variables of type JSClassID and JS_NewClassID() to
+ * allocate class ids for custom classes. The static variables approach
+ * causes a problem when two modules linked with -Wl,-Bsymbolic-functions flag
+ * are loaded dynamically.
+ */
+
+#define NGX_QJS_CLASS_ID_OFFSET (QJS_CORE_CLASS_ID_LAST)
+#define NGX_QJS_CLASS_ID_CONSOLE (NGX_QJS_CLASS_ID_OFFSET + 1)
+#define NGX_QJS_CLASS_ID_HTTP_REQUEST (NGX_QJS_CLASS_ID_OFFSET + 2)
+#define NGX_QJS_CLASS_ID_HTTP_PERIODIC (NGX_QJS_CLASS_ID_OFFSET + 3)
+#define NGX_QJS_CLASS_ID_HTTP_VARS (NGX_QJS_CLASS_ID_OFFSET + 4)
+#define NGX_QJS_CLASS_ID_HTTP_HEADERS_IN (NGX_QJS_CLASS_ID_OFFSET + 5)
+#define NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT (NGX_QJS_CLASS_ID_OFFSET + 6)
+#define NGX_QJS_CLASS_ID_STREAM_SESSION (NGX_QJS_CLASS_ID_OFFSET + 7)
+#define NGX_QJS_CLASS_ID_STREAM_PERIODIC (NGX_QJS_CLASS_ID_OFFSET + 8)
+#define NGX_QJS_CLASS_ID_STREAM_FLAGS (NGX_QJS_CLASS_ID_OFFSET + 9)
+#define NGX_QJS_CLASS_ID_STREAM_VARS (NGX_QJS_CLASS_ID_OFFSET + 10)
+
typedef struct ngx_js_loc_conf_s ngx_js_loc_conf_t;
typedef struct ngx_js_event_s ngx_js_event_t;
@@ -76,6 +100,15 @@ struct ngx_js_event_s {
};
+typedef struct {
+ void **data;
+ ngx_uint_t head;
+ ngx_uint_t tail;
+ ngx_uint_t size;
+ ngx_uint_t capacity;
+} ngx_js_queue_t;
+
+
#define NGX_JS_COMMON_MAIN_CONF \
ngx_js_dict_t *dicts; \
ngx_array_t *periodics \
@@ -84,6 +117,8 @@ struct ngx_js_event_s {
#define _NGX_JS_COMMON_LOC_CONF \
ngx_uint_t type; \
ngx_engine_t *engine; \
+ ngx_uint_t reuse; \
+ ngx_js_queue_t *reuse_queue; \
ngx_str_t cwd; \
ngx_array_t *imports; \
ngx_array_t *paths; \
@@ -157,6 +192,11 @@ struct ngx_js_ctx_s {
};
+typedef struct {
+ void *external;
+} ngx_js_opaque_t;
+
+
typedef struct ngx_engine_opts_s {
unsigned engine;
union {
@@ -164,6 +204,12 @@ typedef struct ngx_engine_opts_s {
njs_vm_meta_t *metas;
njs_module_t **addons;
} njs;
+#if (NJS_HAVE_QUICKJS)
+ struct {
+ uintptr_t *metas;
+ qjs_module_t **addons;
+ } qjs;
+#endif
} u;
njs_str_t file;
@@ -176,11 +222,22 @@ typedef struct ngx_engine_opts_s {
} ngx_engine_opts_t;
+typedef struct {
+ u_char *code;
+ size_t code_size;
+} ngx_js_code_entry_t;
+
+
struct ngx_engine_s {
union {
struct {
njs_vm_t *vm;
} njs;
+#if (NJS_HAVE_QUICKJS)
+ struct {
+ JSContext *ctx;
+ } qjs;
+#endif
} u;
ngx_int_t (*compile)(ngx_js_loc_conf_t *conf, ngx_log_t *lg,
@@ -202,6 +259,7 @@ struct ngx_engine_s {
unsigned type;
const char *name;
njs_mp_t *pool;
+ njs_arr_t *precompiled;
};
@@ -246,6 +304,7 @@ void ngx_js_ctx_init(ngx_js_ctx_t *ctx, ngx_log_t *log);
#define ngx_js_ctx_external(ctx) \
((ctx)->engine->external(ctx->engine))
+
void ngx_js_ctx_destroy(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *conf);
ngx_int_t ngx_js_call(njs_vm_t *vm, njs_function_t *func,
njs_opaque_value_t *args, njs_uint_t nargs);
@@ -253,6 +312,74 @@ ngx_int_t ngx_js_exception(njs_vm_t *vm, ngx_str_t *s);
ngx_engine_t *ngx_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf,
void *external);
+#if (NJS_HAVE_QUICKJS)
+
+typedef struct ngx_qjs_event_s ngx_qjs_event_t;
+
+typedef union {
+ njs_opaque_value_t opaque;
+ JSValue value;
+} ngx_qjs_value_t;
+
+struct ngx_qjs_event_s {
+ void *ctx;
+ JSValue function;
+ JSValue *args;
+ ngx_socket_t fd;
+ NJS_RBTREE_NODE (node);
+ njs_uint_t nargs;
+ void (*destructor)(ngx_qjs_event_t *event);
+ ngx_event_t ev;
+ void *data;
+};
+
+#define ngx_qjs_arg(val) (((ngx_qjs_value_t *) &(val))->value)
+ngx_engine_t *ngx_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf,
+ void *external);
+void ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx,
+ ngx_js_loc_conf_t *conf);
+ngx_int_t ngx_qjs_call(ngx_js_ctx_t *ctx, JSValue function,
+ JSValue *argv, int argc);
+ngx_int_t ngx_qjs_exception(ngx_engine_t *e, ngx_str_t *s);
+ngx_int_t ngx_qjs_integer(JSContext *cx, JSValueConst val, ngx_int_t *n);
+ngx_int_t ngx_qjs_string(ngx_engine_t *e, JSValueConst val, ngx_str_t *str);
+
+#define ngx_qjs_prop(cx, type, start, len) \
+ ((type == NGX_JS_STRING) ? qjs_string_create(cx, start, len) \
+ : qjs_buffer_create(cx, (u_char *) start, len))
+
+#define ngx_qjs_meta(cx, i) \
+ ((uintptr_t *) JS_GetRuntimeOpaque(JS_GetRuntime(cx)))[i]
+#define ngx_qjs_external_connection(cx, e) \
+ (*((ngx_connection_t **) ((u_char *) (e) + ngx_qjs_meta(cx, 0))))
+#define ngx_qjs_external_pool(cx, e) \
+ ((ngx_external_pool_pt) ngx_qjs_meta(cx, 1))(e)
+#define ngx_qjs_external_resolver(cx, e) \
+ ((ngx_external_resolver_pt) ngx_qjs_meta(vm, 2))(e)
+#define ngx_qjs_external_resolver_timeout(cx, e) \
+ ((ngx_external_timeout_pt) ngx_qjs_meta(cx, 3))(e)
+#define ngx_qjs_external_event_finalize(cx) \
+ ((ngx_js_event_finalize_pt) ngx_qjs_meta(cx, 4))
+#define ngx_qjs_external_ssl(cx, e) \
+ ((ngx_external_ssl_pt) ngx_qjs_meta(cx, 5))(e)
+#define ngx_qjs_external_ssl_verify(cx, e) \
+ ((ngx_external_flag_pt) ngx_qjs_meta(cx, 6))(e)
+#define ngx_qjs_external_fetch_timeout(cx, e) \
+ ((ngx_external_timeout_pt) ngx_qjs_meta(cx, 7))(e)
+#define ngx_qjs_external_buffer_size(cx, e) \
+ ((ngx_external_size_pt) ngx_qjs_meta(cx, 8))(e)
+#define ngx_qjs_external_max_response_buffer_size(cx, e) \
+ ((ngx_external_size_pt) ngx_qjs_meta(cx, 9))(e)
+#define ngx_qjs_main_conf(cx) \
+ ((ngx_js_main_conf_t *) ngx_qjs_meta(cx, NGX_JS_MAIN_CONF_INDEX))
+#define ngx_qjs_external_ctx(cx, e) \
+ ((ngx_js_external_ctx_pt) ngx_qjs_meta(cx, 11))(e)
+
+extern qjs_module_t qjs_zlib_module;
+extern qjs_module_t ngx_qjs_ngx_module;
+
+#endif
+
njs_int_t ngx_js_ext_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t level, njs_value_t *retval);
void ngx_js_log(njs_vm_t *vm, njs_external_ptr_t external,
@@ -286,6 +413,10 @@ njs_int_t ngx_js_ext_flags(njs_vm_t *vm, njs_object_prop_t *prop,
ngx_int_t ngx_js_string(njs_vm_t *vm, njs_value_t *value, njs_str_t *str);
ngx_int_t ngx_js_integer(njs_vm_t *vm, njs_value_t *value, ngx_int_t *n);
+ngx_js_queue_t *ngx_js_queue_create(ngx_pool_t *pool, ngx_uint_t capacity);
+ngx_int_t ngx_js_queue_push(ngx_js_queue_t *queue, void *item);
+void *ngx_js_queue_pop(ngx_js_queue_t *queue);
+
extern njs_module_t ngx_js_ngx_module;
extern njs_module_t njs_webcrypto_module;
diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c
index 565f4e66..98427aae 100644
--- a/nginx/ngx_stream_js_module.c
+++ b/nginx/ngx_stream_js_module.c
@@ -75,6 +75,21 @@ struct ngx_stream_js_ctx_s {
};
+#if (NJS_HAVE_QUICKJS)
+
+typedef struct {
+ ngx_str_t name;
+ ngx_uint_t data_type;
+ ngx_uint_t id;
+} ngx_stream_qjs_event_t;
+
+typedef struct {
+ ngx_stream_session_t *session;
+ JSValue callbacks[NGX_JS_EVENT_MAX];
+} ngx_stream_qjs_session_t;
+
+#endif
+
#define ngx_stream_pending(ctx) \
(ngx_js_ctx_pending(ctx) || ngx_stream_js_pending_events(ctx))
@@ -128,6 +143,57 @@ static njs_int_t ngx_stream_js_periodic_variables(njs_vm_t *vm,
njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval,
njs_value_t *retval);
+#if (NJS_HAVE_QUICKJS)
+
+static JSValue ngx_stream_qjs_ext_to_string_tag(JSContext *cx,
+ JSValueConst this_val);
+static JSValue ngx_stream_qjs_ext_done(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv, int magic);
+static JSValue ngx_stream_qjs_ext_log(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv, int level);
+static JSValue ngx_stream_qjs_ext_on(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static JSValue ngx_stream_qjs_ext_off(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static JSValue ngx_stream_qjs_ext_periodic_to_string_tag(JSContext *cx,
+ JSValueConst this_val);
+static JSValue ngx_stream_qjs_ext_periodic_variables(JSContext *cx,
+ JSValueConst this_val, int type);
+static JSValue ngx_stream_qjs_ext_remote_address(JSContext *cx,
+ JSValueConst this_val);
+static JSValue ngx_stream_qjs_ext_send(JSContext *cx, JSValueConst this_val,
+ 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_variables(JSContext *cx,
+ JSValueConst this_val, int type);
+static JSValue ngx_stream_qjs_ext_uint(JSContext *cx, JSValueConst this_val,
+ int offset);
+static JSValue ngx_stream_qjs_ext_flag(JSContext *cx, JSValueConst this_val,
+ int mask);
+
+static int ngx_stream_qjs_variables_own_property(JSContext *cx,
+ JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop);
+static int ngx_stream_qjs_variables_set_property(JSContext *cx,
+ JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver,
+ int flags);
+static int ngx_stream_qjs_variables_define_own_property(JSContext *cx,
+ JSValueConst obj, JSAtom prop, JSValueConst value, JSValueConst getter,
+ JSValueConst setter, int flags);
+
+static ngx_int_t ngx_stream_qjs_run_event(ngx_stream_session_t *s,
+ ngx_stream_js_ctx_t *ctx, ngx_stream_js_ev_t *event,
+ ngx_uint_t from_upstream);
+static ngx_int_t ngx_stream_qjs_body_filter(ngx_stream_session_t *s,
+ ngx_stream_js_ctx_t *ctx, ngx_chain_t *in, ngx_uint_t from_upstream);
+
+static ngx_stream_session_t *ngx_stream_qjs_session(JSValueConst val);
+static JSValue ngx_stream_qjs_session_make(JSContext *cx, ngx_int_t proto_id,
+ ngx_stream_session_t *s);
+static void ngx_stream_qjs_session_finalizer(JSRuntime *rt, JSValue val);
+
+#endif
+
static ngx_pool_t *ngx_stream_js_pool(ngx_stream_session_t *s);
static ngx_resolver_t *ngx_stream_js_resolver(ngx_stream_session_t *s);
static ngx_msec_t ngx_stream_js_resolver_timeout(ngx_stream_session_t *s);
@@ -167,6 +233,9 @@ static ngx_flag_t ngx_stream_js_ssl_verify(ngx_stream_session_t *s);
static ngx_conf_bitmask_t ngx_stream_js_engines[] = {
{ ngx_string("njs"), NGX_ENGINE_NJS },
+#if (NJS_HAVE_QUICKJS)
+ { ngx_string("qjs"), NGX_ENGINE_QJS },
+#endif
{ ngx_null_string, 0 }
};
@@ -191,6 +260,13 @@ static ngx_command_t ngx_stream_js_commands[] = {
offsetof(ngx_stream_js_srv_conf_t, type),
&ngx_stream_js_engines },
+ { ngx_string("js_context_reuse"),
+ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_STREAM_SRV_CONF_OFFSET,
+ offsetof(ngx_stream_js_srv_conf_t, reuse),
+ NULL },
+
{ ngx_string("js_import"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE13,
ngx_js_import,
@@ -649,9 +725,9 @@ static njs_vm_meta_t ngx_stream_js_metas = {
static ngx_stream_filter_pt ngx_stream_next_filter;
-static njs_int_t ngx_stream_js_session_proto_id;
-static njs_int_t ngx_stream_js_periodic_session_proto_id;
-static njs_int_t ngx_stream_js_session_flags_proto_id;
+static njs_int_t ngx_stream_js_session_proto_id = 1;
+static njs_int_t ngx_stream_js_periodic_session_proto_id = 2;
+static njs_int_t ngx_stream_js_session_flags_proto_id = 3;
njs_module_t ngx_js_stream_module = {
@@ -682,6 +758,96 @@ njs_module_t *njs_stream_js_addon_modules[] = {
NULL,
};
+#if (NJS_HAVE_QUICKJS)
+
+static const JSCFunctionListEntry ngx_stream_qjs_ext_session[] = {
+ JS_CGETSET_DEF("[Symbol.toStringTag]", ngx_stream_qjs_ext_to_string_tag,
+ NULL),
+ JS_CFUNC_MAGIC_DEF("allow", 1, ngx_stream_qjs_ext_done, NGX_OK),
+ JS_CFUNC_MAGIC_DEF("decline", 1, ngx_stream_qjs_ext_done, -NGX_DECLINED),
+ JS_CFUNC_MAGIC_DEF("deny", 1, ngx_stream_qjs_ext_done, -NGX_DONE),
+ JS_CFUNC_MAGIC_DEF("done", 1, ngx_stream_qjs_ext_done, NGX_OK),
+ JS_CFUNC_MAGIC_DEF("error", 1, ngx_stream_qjs_ext_log, NGX_LOG_ERR),
+ JS_CFUNC_MAGIC_DEF("log", 1, ngx_stream_qjs_ext_log, NGX_LOG_INFO),
+ JS_CFUNC_DEF("on", 2, ngx_stream_qjs_ext_on),
+ JS_CFUNC_DEF("off", 1, ngx_stream_qjs_ext_off),
+ JS_CGETSET_MAGIC_DEF("rawVariables", ngx_stream_qjs_ext_variables,
+ NULL, NGX_JS_BUFFER),
+ JS_CGETSET_DEF("remoteAddress", ngx_stream_qjs_ext_remote_address, NULL),
+ JS_CFUNC_MAGIC_DEF("send", 2, ngx_stream_qjs_ext_send, NGX_JS_BOOL_UNSET),
+ JS_CFUNC_MAGIC_DEF("sendDownstream", 1, ngx_stream_qjs_ext_send,
+ NGX_JS_BOOL_TRUE),
+ 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_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,
+ NULL, NGX_JS_STRING),
+ JS_CFUNC_MAGIC_DEF("warn", 1, ngx_stream_qjs_ext_log, NGX_LOG_WARN),
+};
+
+
+static const JSCFunctionListEntry ngx_stream_qjs_ext_periodic[] = {
+ JS_CGETSET_DEF("[Symbol.toStringTag]",
+ ngx_stream_qjs_ext_periodic_to_string_tag, NULL),
+ JS_CGETSET_MAGIC_DEF("rawVariables", ngx_stream_qjs_ext_periodic_variables,
+ NULL, NGX_JS_BUFFER),
+ JS_CGETSET_MAGIC_DEF("variables", ngx_stream_qjs_ext_periodic_variables,
+ NULL, NGX_JS_STRING),
+};
+
+
+static const JSCFunctionListEntry ngx_stream_qjs_ext_flags[] = {
+ JS_CGETSET_MAGIC_DEF("from_upstream", ngx_stream_qjs_ext_flag, NULL,
+ 2),
+ JS_CGETSET_MAGIC_DEF("last", ngx_stream_qjs_ext_flag, NULL, 1),
+};
+
+
+static JSClassDef ngx_stream_qjs_session_class = {
+ "Session",
+ .finalizer = ngx_stream_qjs_session_finalizer,
+};
+
+
+static JSClassDef ngx_stream_qjs_periodic_class = {
+ "Periodic",
+ .finalizer = NULL,
+};
+
+
+static JSClassDef ngx_stream_qjs_flags_class = {
+ "Stream Flags",
+ .finalizer = NULL,
+};
+
+
+static JSClassDef ngx_stream_qjs_variables_class = {
+ "Variables",
+ .finalizer = NULL,
+ .exotic = & (JSClassExoticMethods) {
+ .get_own_property = ngx_stream_qjs_variables_own_property,
+ .set_property = ngx_stream_qjs_variables_set_property,
+ .define_own_property = ngx_stream_qjs_variables_define_own_property,
+ },
+};
+
+
+qjs_module_t *njs_stream_qjs_addon_modules[] = {
+ &ngx_qjs_ngx_module,
+ /*
+ * Shared addons should be in the same order and the same positions
+ * in all nginx modules.
+ */
+#ifdef NJS_HAVE_ZLIB
+ &qjs_zlib_module,
+#endif
+ NULL,
+};
+
+#endif
+
static ngx_int_t
ngx_stream_js_access_handler(ngx_stream_session_t *s)
@@ -783,7 +949,6 @@ ngx_stream_js_body_filter(ngx_stream_session_t *s, ngx_chain_t *in,
{
ngx_int_t rc;
ngx_chain_t *out;
- ngx_connection_t *c;
ngx_stream_js_ctx_t *ctx;
ngx_stream_js_srv_conf_t *jscf;
@@ -792,10 +957,8 @@ ngx_stream_js_body_filter(ngx_stream_session_t *s, ngx_chain_t *in,
return ngx_stream_next_filter(s, in, from_upstream);
}
- c = s->connection;
-
- ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream js filter u:%ui",
- from_upstream);
+ ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+ "stream js filter u:%ui", from_upstream);
rc = ngx_stream_js_init_vm(s, ngx_stream_js_session_proto_id);
@@ -810,7 +973,7 @@ ngx_stream_js_body_filter(ngx_stream_session_t *s, ngx_chain_t *in,
ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
if (!ctx->filter) {
- ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0,
+ ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
"stream js filter call \"%V\"" , &jscf->filter);
rc = ctx->engine->call((ngx_js_ctx_t *) ctx, &jscf->filter,
@@ -990,9 +1153,9 @@ ngx_stream_js_init_vm(ngx_stream_session_t *s, njs_int_t proto_id)
return NGX_ERROR;
}
- ngx_log_debug2(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
- "stream js vm clone: %p from: %p", ctx->engine,
- jscf->engine);
+ ngx_log_debug3(NGX_LOG_DEBUG_STREAM, ctx->log, 0,
+ "stream js vm clone %s: %p from: %p", jscf->engine->name,
+ ctx->engine, jscf->engine);
cln = ngx_pool_cleanup_add(s->connection->pool, 0);
if (cln == NULL) {
@@ -1806,25 +1969,950 @@ ngx_engine_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf,
}
-static ngx_int_t
-ngx_stream_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf)
+#if (NJS_HAVE_QUICKJS)
+
+static JSValue
+ngx_stream_qjs_ext_to_string_tag(JSContext *cx, JSValueConst this_val)
{
- ngx_engine_opts_t options;
- ngx_js_main_conf_t *jmcf;
+ return JS_NewString(cx, "Stream Session");
+}
- memset(&options, 0, sizeof(ngx_engine_opts_t));
- options.engine = conf->type;
+static JSValue
+ngx_stream_qjs_ext_done(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv, int magic)
+{
+ ngx_int_t status;
+ ngx_stream_js_ctx_t *ctx;
+ ngx_stream_session_t *s;
- if (conf->type == NGX_ENGINE_NJS) {
- jmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_js_module);
- ngx_stream_js_uptr[NGX_JS_MAIN_CONF_INDEX] = (uintptr_t) jmcf;
+ s = ngx_stream_qjs_session(this_val);
+ if (s == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a session object");
+ }
- options.u.njs.metas = &ngx_stream_js_metas;
- options.u.njs.addons = njs_stream_js_addon_modules;
- options.clone = ngx_engine_njs_clone;
+ status = (ngx_int_t) magic;
+ status = -status;
+
+ if (status == NGX_DONE) {
+ status = NGX_STREAM_FORBIDDEN;
+ }
+
+ if (!JS_IsUndefined(argv[0])) {
+ if (ngx_qjs_integer(cx, argv[0], &status) != NGX_OK) {
+ return JS_EXCEPTION;
+ }
+
+ if (status < NGX_ABORT || status > NGX_STREAM_SERVICE_UNAVAILABLE) {
+ return JS_ThrowInternalError(cx, "code is out of range");
+ }
+ }
+
+ ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
+
+ if (ctx->filter) {
+ return JS_ThrowInternalError(cx, "should not be called while "
+ "filtering");
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+ "stream js set status: %i", status);
+
+ ctx->status = status;
+
+ ngx_stream_js_drop_events(ctx);
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_stream_qjs_ext_log(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv, int level)
+{
+ int n;
+ const char *msg;
+ ngx_stream_session_t *s;
+
+ s = ngx_stream_qjs_session(this_val);
+ if (s == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a session object");
+ }
+
+ for (n = 0; n < argc; n++) {
+ msg = JS_ToCString(cx, argv[n]);
+
+ ngx_js_logger(s->connection, level, (u_char *) msg, ngx_strlen(msg));
+
+ JS_FreeCString(cx, msg);
+ }
+
+ return JS_UNDEFINED;
+}
+
+
+static const ngx_stream_qjs_event_t *
+ngx_stream_qjs_event(ngx_stream_session_t *s, JSContext *cx, ngx_str_t *event)
+{
+ ngx_uint_t i, n, type;
+ ngx_stream_js_ctx_t *ctx;
+
+ static const ngx_stream_qjs_event_t events[] = {
+ {
+ ngx_string("upload"),
+ NGX_JS_STRING,
+ NGX_JS_EVENT_UPLOAD,
+ },
+
+ {
+ ngx_string("download"),
+ NGX_JS_STRING,
+ NGX_JS_EVENT_DOWNLOAD,
+ },
+
+ {
+ ngx_string("upstream"),
+ NGX_JS_BUFFER,
+ NGX_JS_EVENT_UPLOAD,
+ },
+
+ {
+ ngx_string("downstream"),
+ NGX_JS_BUFFER,
+ NGX_JS_EVENT_DOWNLOAD,
+ },
+ };
+
+ ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
+
+ i = 0;
+ n = sizeof(events) / sizeof(events[0]);
+
+ while (i < n) {
+ if (event->len == events[i].name.len
+ && ngx_memcmp(event->data, events[i].name.data, event->len)
+ == 0)
+ {
+ break;
+ }
+
+ i++;
+ }
+
+ if (i == n) {
+ (void) JS_ThrowInternalError(cx, "unknown event \"%*s\"",
+ (int) event->len, event->data);
+ return NULL;
+ }
+
+ ctx->events[events[i].id].data_type = events[i].data_type;
+
+ for (n = 0; n < NGX_JS_EVENT_MAX; n++) {
+ type = ctx->events[n].data_type;
+ if (type != NGX_JS_UNSET && type != events[i].data_type) {
+ (void) JS_ThrowInternalError(cx, "mixing string and buffer"
+ " events is not allowed");
+ return NULL;
+ }
+ }
+
+ return &events[i];
+}
+
+
+static JSValue
+ngx_stream_qjs_ext_on(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ ngx_str_t name;
+ ngx_stream_js_ctx_t *ctx;
+ ngx_stream_qjs_session_t *ses;
+ const ngx_stream_qjs_event_t *e;
+
+ ses = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_STREAM_SESSION);
+ if (ses == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a session object");
+ }
+
+ ctx = ngx_stream_get_module_ctx(ses->session, ngx_stream_js_module);
+
+ if (ngx_qjs_string(ctx->engine, argv[0], &name) != NGX_OK) {
+ return JS_EXCEPTION;
+ }
+
+ e = ngx_stream_qjs_event(ses->session, cx, &name);
+ if (e == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ if (JS_IsFunction(cx, ngx_qjs_arg(ctx->events[e->id].function))) {
+ return JS_ThrowInternalError(cx, "event handler \"%s\" is already set",
+ name.data);
+ }
+
+ if (!JS_IsFunction(cx, argv[1])) {
+ return JS_ThrowTypeError(cx, "callback is not a function");
+ }
+
+ ngx_qjs_arg(ctx->events[e->id].function) = argv[1];
+
+ JS_FreeValue(cx, ses->callbacks[e->id]);
+ ses->callbacks[e->id] = JS_DupValue(cx, argv[1]);
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_stream_qjs_ext_off(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ ngx_str_t name;
+ ngx_stream_js_ctx_t *ctx;
+ ngx_stream_session_t *s;
+ const ngx_stream_qjs_event_t *e;
+
+ s = ngx_stream_qjs_session(this_val);
+ if (s == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a session object");
+ }
+
+ ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
+
+ if (ngx_qjs_string(ctx->engine, argv[0], &name) != NGX_OK) {
+ return JS_EXCEPTION;
+ }
+
+ e = ngx_stream_qjs_event(s, cx, &name);
+ if (e == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ ngx_qjs_arg(ctx->events[e->id].function) = JS_NULL;
+ ctx->events[e->id].data_type = NGX_JS_UNSET;
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_stream_qjs_ext_periodic_to_string_tag(JSContext *cx,
+ JSValueConst this_val)
+{
+ return JS_NewString(cx, "PeriodicSession");
+}
+
+
+static JSValue
+ngx_stream_qjs_ext_periodic_variables(JSContext *cx,
+ JSValueConst this_val, int type)
+{
+ JSValue obj;
+ ngx_stream_qjs_session_t *ses;
+
+ ses = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_STREAM_PERIODIC);
+ if (ses == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a periodic object");
+ }
+
+ obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_STREAM_VARS);
+
+ /*
+ * Using lowest bit of the pointer to store the buffer type.
+ */
+ type = (type == NGX_JS_BUFFER) ? 1 : 0;
+ JS_SetOpaque(obj, (void *) ((uintptr_t) ses->session | (uintptr_t) type));
+
+ return obj;
+}
+
+
+static JSValue
+ngx_stream_qjs_ext_remote_address(JSContext *cx, JSValueConst this_val)
+{
+ ngx_connection_t *c;
+ ngx_stream_session_t *s;
+
+ s = ngx_stream_qjs_session(this_val);
+ if (s == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a session object");
+ }
+
+ c = s->connection;
+
+ return qjs_string_create(cx, c->addr_text.data, c->addr_text.len);
+}
+
+
+static JSValue
+ngx_stream_qjs_ext_send(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv, int from_upstream)
+{
+ JSValue val;
+ unsigned last_buf, flush;
+ ngx_str_t buffer;
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+ ngx_connection_t *c;
+ ngx_stream_js_ctx_t *ctx;
+ ngx_stream_session_t *s;
+
+ s = ngx_stream_qjs_session(this_val);
+ if (s == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a session object");
+ }
+
+ c = s->connection;
+
+ ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
+
+ if (!ctx->filter) {
+ return JS_ThrowInternalError(cx, "cannot send buffer in this handler");
+ }
+
+ if (ngx_qjs_string(ctx->engine, argv[0], &buffer) != NGX_OK) {
+ return JS_EXCEPTION;
}
+ /*
+ * ctx->buf != NULL when s.send() is called while processing incoming
+ * data chunks, otherwise s.send() is called asynchronously
+ */
+
+ if (ctx->buf != NULL) {
+ flush = ctx->buf->flush;
+ last_buf = ctx->buf->last_buf;
+
+ } else {
+ flush = 0;
+ last_buf = 0;
+ }
+
+ if (JS_IsObject(argv[1])) {
+ val = JS_GetPropertyStr(cx, argv[1], "flush");
+ if (JS_IsException(val)) {
+ return JS_EXCEPTION;
+ }
+
+ if (!JS_IsUndefined(val)) {
+ flush = JS_ToBool(cx, val);
+ JS_FreeValue(cx, val);
+ }
+
+ val = JS_GetPropertyStr(cx, argv[1], "last");
+ if (JS_IsException(val)) {
+ return JS_EXCEPTION;
+ }
+
+ if (!JS_IsUndefined(val)) {
+ last_buf = JS_ToBool(cx, val);
+ JS_FreeValue(cx, val);
+ }
+
+ if (from_upstream == NGX_JS_BOOL_UNSET) {
+ val = JS_GetPropertyStr(cx, argv[1], "from_upstream");
+ if (JS_IsException(val)) {
+ return JS_EXCEPTION;
+ }
+
+ if (!JS_IsUndefined(val)) {
+ from_upstream = JS_ToBool(cx, val);
+ JS_FreeValue(cx, val);
+ }
+
+ if (from_upstream == NGX_JS_BOOL_UNSET && ctx->buf == NULL) {
+ return JS_ThrowInternalError(cx, "from_upstream flag is "
+ "expected when called "
+ "asynchronously");
+ }
+ }
+ }
+
+ cl = ngx_chain_get_free_buf(c->pool, &ctx->free);
+ if (cl == NULL) {
+ return JS_ThrowInternalError(cx, "memory error");
+ }
+
+ b = cl->buf;
+
+ b->flush = flush;
+ b->last_buf = last_buf;
+
+ b->memory = (buffer.len ? 1 : 0);
+ b->sync = (buffer.len ? 0 : 1);
+ b->tag = (ngx_buf_tag_t) &ngx_stream_js_module;
+
+ b->start = buffer.data;
+ b->end = buffer.data + buffer.len;
+
+ b->pos = b->start;
+ b->last = b->end;
+
+ if (from_upstream == NGX_JS_BOOL_UNSET) {
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ } else {
+
+ if (ngx_stream_js_next_filter(s, ctx, cl, from_upstream) == NGX_ERROR) {
+ return JS_ThrowInternalError(cx, "ngx_stream_js_next_filter() "
+ "failed");
+ }
+ }
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_stream_qjs_ext_set_return_value(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv)
+{
+ ngx_js_ctx_t *ctx;
+ ngx_stream_session_t *s;
+
+ s = ngx_stream_qjs_session(this_val);
+ if (s == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a session object");
+ }
+
+ ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
+
+ JS_FreeValue(cx, ngx_qjs_arg(ctx->retval));
+ ngx_qjs_arg(ctx->retval) = JS_DupValue(cx, argv[0]);
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_stream_qjs_ext_variables(JSContext *cx, JSValueConst this_val, int type)
+{
+ JSValue obj;
+ ngx_stream_session_t *s;
+
+ s = ngx_stream_qjs_session(this_val);
+ if (s == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a session object");
+ }
+
+ obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_STREAM_VARS);
+
+ /*
+ * Using lowest bit of the pointer to store the buffer type.
+ */
+ type = (type == NGX_JS_BUFFER) ? 1 : 0;
+ JS_SetOpaque(obj, (void *) ((uintptr_t) s | (uintptr_t) type));
+
+ return obj;
+}
+
+
+static JSValue
+ngx_stream_qjs_ext_uint(JSContext *cx, JSValueConst this_val, int offset)
+{
+ ngx_uint_t *field;
+ ngx_stream_session_t *s;
+
+ s = ngx_stream_qjs_session(this_val);
+ if (s == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a session object");
+ }
+
+ field = (ngx_uint_t *) ((u_char *) s + offset);
+
+ return JS_NewUint32(cx, *field);
+}
+
+
+static JSValue
+ngx_stream_qjs_ext_flag(JSContext *cx, JSValueConst this_val, int mask)
+{
+ uintptr_t flags;
+
+ flags = (uintptr_t) JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_STREAM_FLAGS);
+
+ return JS_NewBool(cx, flags & mask);
+}
+
+
+static int
+ngx_stream_qjs_variables_own_property(JSContext *cx,
+ JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop)
+{
+ uint32_t buffer_type;
+ ngx_str_t name;
+ ngx_uint_t key;
+ ngx_stream_session_t *s;
+ ngx_stream_variable_value_t *vv;
+
+ s = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_STREAM_VARS);
+
+ buffer_type = ((uintptr_t) s & 1) ? NGX_JS_BUFFER : NGX_JS_STRING;
+ s = (ngx_stream_session_t *) ((uintptr_t) s & ~(uintptr_t) 1);
+
+ if (s == NULL) {
+ (void) JS_ThrowInternalError(cx, "\"this\" is not a session object");
+ return -1;
+ }
+
+ name.data = (u_char *) JS_AtomToCString(cx, prop);
+ if (name.data == NULL) {
+ return -1;
+ }
+
+ name.len = ngx_strlen(name.data);
+
+ key = ngx_hash_strlow(name.data, name.data, name.len);
+
+ vv = ngx_stream_get_variable(s, &name, key);
+ JS_FreeCString(cx, (char *) name.data);
+ if (vv == NULL || vv->not_found) {
+ return 0;
+ }
+
+ if (pdesc != NULL) {
+ pdesc->flags = JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE;
+ pdesc->getter = JS_UNDEFINED;
+ pdesc->setter = JS_UNDEFINED;
+ pdesc->value = ngx_qjs_prop(cx, buffer_type, vv->data, vv->len);
+ }
+
+ return 1;
+}
+
+
+static int
+ngx_stream_qjs_variables_set_property(JSContext *cx, JSValueConst obj,
+ JSAtom prop, JSValueConst value, JSValueConst receiver, int flags)
+{
+ ngx_str_t name, val;
+ ngx_uint_t key;
+ ngx_js_ctx_t *ctx;
+ ngx_stream_session_t *s;
+ ngx_stream_variable_t *v;
+ ngx_stream_variable_value_t *vv;
+ ngx_stream_core_main_conf_t *cmcf;
+
+ s = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_STREAM_VARS);
+
+ s = (ngx_stream_session_t *) ((uintptr_t) s & ~(uintptr_t) 1);
+
+ if (s == NULL) {
+ (void) JS_ThrowInternalError(cx, "\"this\" is not a session object");
+ return -1;
+ }
+
+ name.data = (u_char *) JS_AtomToCString(cx, prop);
+ if (name.data == NULL) {
+ return -1;
+ }
+
+ name.len = ngx_strlen(name.data);
+
+ key = ngx_hash_strlow(name.data, name.data, name.len);
+
+ cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
+
+ v = ngx_hash_find(&cmcf->variables_hash, key, name.data, name.len);
+ JS_FreeCString(cx, (char *) name.data);
+
+ if (v == NULL) {
+ (void) JS_ThrowInternalError(cx, "variable not found");
+ return -1;
+ }
+
+ ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
+
+ if (ngx_qjs_string(ctx->engine, value, &val) != NGX_OK) {
+ return -1;
+ }
+
+ if (v->set_handler != NULL) {
+ vv = ngx_pcalloc(s->connection->pool,
+ sizeof(ngx_stream_variable_value_t));
+ if (vv == NULL) {
+ (void) JS_ThrowOutOfMemory(cx);
+ return -1;
+ }
+
+ vv->valid = 1;
+ vv->not_found = 0;
+ vv->data = val.data;
+ vv->len = val.len;
+
+ v->set_handler(s, vv, v->data);
+
+ return 1;
+ }
+
+ if (!(v->flags & NGX_STREAM_VAR_INDEXED)) {
+ (void) JS_ThrowTypeError(cx, "variable is not writable");
+ return -1;
+ }
+
+ vv = &s->variables[v->index];
+
+ vv->valid = 1;
+ vv->not_found = 0;
+
+ vv->data = ngx_pnalloc(s->connection->pool, val.len);
+ if (vv->data == NULL) {
+ vv->valid = 0;
+ (void) JS_ThrowOutOfMemory(cx);
+ return -1;
+ }
+
+ vv->len = val.len;
+ ngx_memcpy(vv->data, val.data, vv->len);
+
+ return 1;
+}
+
+
+static int
+ngx_stream_qjs_variables_define_own_property(JSContext *cx,
+ JSValueConst obj, JSAtom prop, JSValueConst value, JSValueConst getter,
+ JSValueConst setter, int flags)
+{
+ if (!JS_IsUndefined(setter) || !JS_IsUndefined(getter)) {
+ (void) JS_ThrowTypeError(cx, "cannot define getter or setter");
+ return -1;
+ }
+
+ return ngx_stream_qjs_variables_set_property(cx, obj, prop, value, obj,
+ flags);
+}
+
+
+static ngx_int_t
+ngx_stream_qjs_run_event(ngx_stream_session_t *s, ngx_stream_js_ctx_t *ctx,
+ ngx_stream_js_ev_t *event, ngx_uint_t from_upstream)
+{
+ size_t len;
+ u_char *p;
+ JSContext *cx;
+ ngx_int_t rc;
+ ngx_str_t exception;
+ ngx_buf_t *b;
+ uintptr_t flags;
+ ngx_connection_t *c;
+ JSValue argv[2];
+
+ cx = ctx->engine->u.qjs.ctx;
+
+ if (!JS_IsFunction(cx, ngx_qjs_arg(event->function))) {
+ return NGX_OK;
+ }
+
+ c = s->connection;
+ b = ctx->filter ? ctx->buf : c->buffer;
+
+ len = b ? b->last - b->pos : 0;
+
+ p = ngx_pnalloc(c->pool, len);
+ if (p == NULL) {
+ (void) JS_ThrowOutOfMemory(cx);
+ goto error;
+ }
+
+ if (len) {
+ ngx_memcpy(p, b->pos, len);
+ }
+
+ argv[0] = ngx_qjs_prop(cx, event->data_type, p, len);
+ if (JS_IsException(argv[0])) {
+ goto error;
+ }
+
+ argv[1] = JS_NewObjectClass(cx, NGX_QJS_CLASS_ID_STREAM_FLAGS);
+ if (JS_IsException(argv[1])) {
+ JS_FreeValue(cx, argv[0]);
+ goto error;
+ }
+
+ flags = from_upstream << 1 | (uintptr_t) (b && b->last_buf);
+
+ JS_SetOpaque(argv[1], (void *) flags);
+
+ rc = ngx_qjs_call((ngx_js_ctx_t *) ctx, ngx_qjs_arg(event->function),
+ &argv[0], 2);
+ JS_FreeValue(cx, argv[0]);
+ JS_FreeValue(cx, argv[1]);
+
+ if (rc == NGX_ERROR) {
+error:
+ ngx_qjs_exception(ctx->engine, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %V",
+ &exception);
+
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_qjs_body_filter(ngx_stream_session_t *s, ngx_stream_js_ctx_t *ctx,
+ ngx_chain_t *in, ngx_uint_t from_upstream)
+{
+ ngx_int_t rc;
+ JSContext *cx;
+ ngx_chain_t *cl;
+ ngx_stream_js_ev_t *event;
+
+ cx = ctx->engine->u.qjs.ctx;
+
+ while (in != NULL) {
+ ctx->buf = in->buf;
+
+ event = ngx_stream_event(from_upstream);
+
+ if (JS_IsFunction(cx, ngx_qjs_arg(event->function))) {
+ rc = ngx_stream_qjs_run_event(s, ctx, event, from_upstream);
+ if (rc != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ctx->buf->pos = ctx->buf->last;
+
+ } else {
+ cl = ngx_alloc_chain_link(s->connection->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = ctx->buf;
+
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+ }
+
+ in = in->next;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_stream_session_t *
+ngx_stream_qjs_session(JSValueConst val)
+{
+ ngx_stream_qjs_session_t *ses;
+
+ ses = JS_GetOpaque(val, NGX_QJS_CLASS_ID_STREAM_SESSION);
+ if (ses == NULL) {
+ return NULL;
+ }
+
+ return ses->session;
+}
+
+
+static JSValue
+ngx_stream_qjs_session_make(JSContext *cx, ngx_int_t proto_id,
+ ngx_stream_session_t *s)
+{
+ JSValue session;
+ ngx_uint_t i;
+ ngx_stream_qjs_session_t *ses;
+
+ session = JS_NewObjectClass(cx, proto_id);
+ if (JS_IsException(session)) {
+ return JS_EXCEPTION;
+ }
+
+ ses = js_malloc(cx, sizeof(ngx_stream_qjs_session_t));
+ if (ses == NULL) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ ses->session = s;
+
+ for (i = 0; i < NGX_JS_EVENT_MAX; i++) {
+ ses->callbacks[i] = JS_UNDEFINED;
+ }
+
+ JS_SetOpaque(session, ses);
+
+ return session;
+}
+
+
+static void
+ngx_stream_qjs_session_finalizer(JSRuntime *rt, JSValue val)
+{
+ ngx_uint_t i;
+ ngx_stream_qjs_session_t *ses;
+
+ ses = JS_GetOpaque(val, NGX_QJS_CLASS_ID_STREAM_SESSION);
+ if (ses == NULL) {
+ return;
+ }
+
+ for (i = 0; i < NGX_JS_EVENT_MAX; i++) {
+ JS_FreeValueRT(rt, ses->callbacks[i]);
+ }
+
+ js_free_rt(rt, ses);
+}
+
+
+static ngx_engine_t *
+ngx_engine_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf,
+ njs_int_t proto_id, void *external)
+{
+ JSValue proto;
+ JSContext *cx;
+ ngx_engine_t *engine;
+ ngx_stream_js_ctx_t *sctx;
+
+ engine = ngx_qjs_clone(ctx, cf, external);
+ if (engine == NULL) {
+ return NULL;
+ }
+
+ cx = engine->u.qjs.ctx;
+
+ if (!JS_IsRegisteredClass(JS_GetRuntime(cx),
+ NGX_QJS_CLASS_ID_STREAM_SESSION))
+ {
+ if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_STREAM_SESSION,
+ &ngx_stream_qjs_session_class) < 0)
+ {
+ return NULL;
+ }
+
+ proto = JS_NewObject(cx);
+ if (JS_IsException(proto)) {
+ return NULL;
+ }
+
+ JS_SetPropertyFunctionList(cx, proto, ngx_stream_qjs_ext_session,
+ njs_nitems(ngx_stream_qjs_ext_session));
+
+ JS_SetClassProto(cx, NGX_QJS_CLASS_ID_STREAM_SESSION, proto);
+
+ if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_STREAM_PERIODIC,
+ &ngx_stream_qjs_periodic_class) < 0)
+ {
+ return NULL;
+ }
+
+ proto = JS_NewObject(cx);
+ if (JS_IsException(proto)) {
+ return NULL;
+ }
+
+ JS_SetPropertyFunctionList(cx, proto, ngx_stream_qjs_ext_periodic,
+ njs_nitems(ngx_stream_qjs_ext_periodic));
+
+ JS_SetClassProto(cx, NGX_QJS_CLASS_ID_STREAM_PERIODIC, proto);
+
+ if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_STREAM_FLAGS,
+ &ngx_stream_qjs_flags_class) < 0)
+ {
+ return NULL;
+ }
+
+ proto = JS_NewObject(cx);
+ if (JS_IsException(proto)) {
+ return NULL;
+ }
+
+ JS_SetPropertyFunctionList(cx, proto, ngx_stream_qjs_ext_flags,
+ njs_nitems(ngx_stream_qjs_ext_flags));
+
+ JS_SetClassProto(cx, NGX_QJS_CLASS_ID_STREAM_FLAGS, proto);
+
+ if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_STREAM_VARS,
+ &ngx_stream_qjs_variables_class) < 0)
+ {
+ return NULL;
+ }
+ }
+
+ sctx = (ngx_stream_js_ctx_t *) ctx;
+ sctx->run_event = ngx_stream_qjs_run_event;
+ sctx->body_filter = ngx_stream_qjs_body_filter;
+
+ if (proto_id == ngx_stream_js_session_proto_id) {
+ proto_id = NGX_QJS_CLASS_ID_STREAM_SESSION;
+
+ } else if (proto_id == ngx_stream_js_periodic_session_proto_id) {
+ proto_id = NGX_QJS_CLASS_ID_STREAM_PERIODIC;
+ }
+
+ ngx_qjs_arg(ctx->args[0]) = ngx_stream_qjs_session_make(cx, proto_id,
+ external);
+ if (JS_IsException(ngx_qjs_arg(ctx->args[0]))) {
+ return NULL;
+ }
+
+ return engine;
+}
+
+
+static void
+ngx_stream_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx,
+ ngx_js_loc_conf_t *conf)
+{
+ ngx_uint_t i;
+ JSValue cb;
+ ngx_stream_qjs_session_t *ses;
+
+ if (ctx != NULL) {
+ /*
+ * explicitly freeing the callback functions
+ * to avoid circular references with the session object.
+ */
+ ses = JS_GetOpaque(ngx_qjs_arg(ctx->args[0]),
+ NGX_QJS_CLASS_ID_STREAM_SESSION);
+ if (ses != NULL) {
+ for (i = 0; i < NGX_JS_EVENT_MAX; i++) {
+ cb = ses->callbacks[i];
+ ses->callbacks[i] = JS_UNDEFINED;
+ JS_FreeValue(e->u.qjs.ctx, cb);
+ }
+ }
+ }
+
+ ngx_engine_qjs_destroy(e, ctx, conf);
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_stream_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf)
+{
+ ngx_engine_opts_t options;
+ ngx_js_main_conf_t *jmcf;
+
+ memset(&options, 0, sizeof(ngx_engine_opts_t));
+
+ options.engine = conf->type;
+
+ jmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_js_module);
+ ngx_stream_js_uptr[NGX_JS_MAIN_CONF_INDEX] = (uintptr_t) jmcf;
+
+ if (conf->type == NGX_ENGINE_NJS) {
+ options.u.njs.metas = &ngx_stream_js_metas;
+ options.u.njs.addons = njs_stream_js_addon_modules;
+ options.clone = ngx_engine_njs_clone;
+ }
+
+#if (NJS_HAVE_QUICKJS)
+ else if (conf->type == NGX_ENGINE_QJS) {
+ options.u.qjs.metas = ngx_stream_js_uptr;
+ options.u.qjs.addons = njs_stream_qjs_addon_modules;
+ options.clone = ngx_engine_qjs_clone;
+ options.destroy = ngx_stream_qjs_destroy;
+ }
+#endif
+
return ngx_js_init_conf_vm(cf, conf, &options);
}
@@ -2392,7 +3480,6 @@ ngx_stream_js_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
ngx_stream_js_srv_conf_t *prev = parent;
ngx_stream_js_srv_conf_t *conf = child;
- ngx_conf_merge_uint_value(conf->type, prev->type, NGX_ENGINE_NJS);
ngx_conf_merge_str_value(conf->access, prev->access, "");
ngx_conf_merge_str_value(conf->preread, prev->preread, "");
ngx_conf_merge_str_value(conf->filter, prev->filter, "");
diff --git a/nginx/t/js_console.t b/nginx/t/js_console.t
index fcaac3a6..c9499169 100644
--- a/nginx/t/js_console.t
+++ b/nginx/t/js_console.t
@@ -41,6 +41,10 @@ http {
listen 127.0.0.1:8080;
server_name localhost;
+ location /engine {
+ js_content test.engine;
+ }
+
location /dump {
js_content test.dump;
}
@@ -74,6 +78,10 @@ http {
EOF
$t->write_file('test.js', <<EOF);
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
function l(r, method) {
const data = Buffer.from(r.args.data, 'base64');
const object = JSON.parse(data);
@@ -116,7 +124,7 @@ $t->write_file('test.js', <<EOF);
l(r, 'warn');
}
- export default {dump, error, info, log, time, time_test, warn};
+ export default {engine, dump, error, info, log, time, time_test, warn};
EOF
@@ -124,6 +132,8 @@ $t->try_run('no njs console')->plan(7);
###############################################################################
+my $engine = http_get('/engine');
+
http_get('/dump?data=eyJhIjpbMiwzXX0');
http_get('/error?data=IldBS0Ei');
http_get('/info?data=IkJBUiI');
@@ -136,8 +146,16 @@ $t->stop();
like($t->read_file('error.log'), qr/\[error\].*js: WAKA/, 'console.error');
like($t->read_file('error.log'), qr/\[info\].*js: BAR/, 'console.info');
+
+SKIP: {
+ skip "QuickJS has no console.dump() method.", 1
+ if $engine =~ /QuickJS$/m;
+
like($t->read_file('error.log'), qr/\[info\].*js: \{a:\['B','C'\]\}/,
'console.log with object');
+
+}
+
like($t->read_file('error.log'), qr/\[warn\].*js: FOO/, 'console.warn');
like($t->read_file('error.log'), qr/\[info\].*js: foo: \d+\.\d\d\d\d\d\dms/,
'console.time foo');
diff --git a/nginx/t/js_dump.t b/nginx/t/js_dump.t
index c00a53a2..a96c2cd4 100644
--- a/nginx/t/js_dump.t
+++ b/nginx/t/js_dump.t
@@ -42,6 +42,10 @@ http {
listen 127.0.0.1:8080;
server_name localhost;
+ location /engine {
+ js_content test.engine;
+ }
+
location /dump {
js_content test.dump;
}
@@ -63,6 +67,10 @@ http {
EOF
$t->write_file('test.js', <<EOF);
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
function dump(r) {
r.headersOut.baz = 'bar';
r.return(200, njs.dump(r));
@@ -80,7 +88,7 @@ $t->write_file('test.js', <<EOF);
});
}
- export default {dump, stringify, stringify_subrequest};
+ export default {engine, dump, stringify, stringify_subrequest};
EOF
@@ -88,6 +96,10 @@ $t->try_run('no njs dump')->plan(3);
###############################################################################
+SKIP: {
+ skip "QuickJS has no njs.dump() method.", 1
+ if http_get('/engine') =~ /QuickJS$/m;
+
like(http(
'GET /dump?v=1&t=x HTTP/1.0' . CRLF
. 'Foo: bar' . CRLF
@@ -95,6 +107,12 @@ like(http(
. 'Host: localhost' . CRLF . CRLF
), qr/method:'GET'/, 'njs.dump(r)');
+}
+
+TODO: {
+ local $TODO = 'in QuickJS these are non-enumerable getter/setter props'
+ if http_get('/engine') =~ /^(QuickJS)$/m;
+
like(http(
'GET /stringify?v=1&t=x HTTP/1.0' . CRLF
. 'Foo: bar' . CRLF
@@ -107,4 +125,6 @@ like(http(
. 'Host: localhost' . CRLF . CRLF
), qr/"status":201/, 'JSON.stringify(reply)');
+}
+
###############################################################################
diff --git a/nginx/t/js_engine.t b/nginx/t/js_engine.t
new file mode 100644
index 00000000..e188ccea
--- /dev/null
+++ b/nginx/t/js_engine.t
@@ -0,0 +1,140 @@
+#!/usr/bin/perl
+
+# (C) Dmitry Volyntsev
+# (C) Nginx, Inc.
+
+# Tests for http njs module, js_engine directive.
+
+###############################################################################
+
+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 proxy/)
+ ->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+ %%TEST_GLOBALS_HTTP%%
+
+ js_import test.js;
+
+ server {
+ listen 127.0.0.1:8080;
+ server_name localhost;
+
+ location /njs {
+ js_content test.njs;
+ }
+
+ location /njs/ {
+ proxy_pass http://127.0.0.1:8081/;
+ }
+
+ location /qjs/ {
+ proxy_pass http://127.0.0.1:8082/;
+ }
+ }
+
+ server {
+ listen 127.0.0.1:8081;
+ server_name localhost;
+
+ js_engine njs;
+
+ location /test {
+ js_content test.test;
+ }
+
+ location /override {
+ js_engine qjs;
+ js_content test.test;
+ }
+ }
+
+ server {
+ listen 127.0.0.1:8082;
+ server_name localhost;
+
+ js_engine qjs;
+
+ location /test {
+ js_content test.test;
+ }
+
+ location /override {
+ js_engine njs;
+ js_content test.test;
+ }
+ }
+}
+
+EOF
+
+$t->write_file('test.js', <<EOF);
+ function test_njs(r) {
+ r.return(200, njs.version);
+ }
+
+ function test(r) {
+ r.return(200, njs.engine);
+ }
+
+ export default {njs: test_njs, test};
+
+EOF
+
+$t->try_run('no njs js_engine')->plan(4);
+
+###############################################################################
+
+TODO: {
+local $TODO = 'not yet' unless has_version('0.8.6');
+
+like(http_get('/njs/test'), qr/njs/, 'js_engine njs server');
+like(http_get('/njs/override'), qr/QuickJS/, 'js_engine override');
+like(http_get('/qjs/test'), qr/QuickJS/, 'js_engine qjs server');
+like(http_get('/qjs/override'), qr/njs/, 'js_engine override');
+
+}
+
+$t->stop();
+
+###############################################################################
+
+sub has_version {
+ my $need = shift;
+
+ http_get('/njs') =~ /^([.0-9]+)$/m;
+
+ my @v = split(/\./, $1);
+ my ($n, $v);
+
+ for $n (split(/\./, $need)) {
+ $v = shift @v || 0;
+ return 0 if $n > $v;
+ return 1 if $v > $n;
+ }
+
+ return 1;
+}
+
+###############################################################################
diff --git a/nginx/t/js_fetch.t b/nginx/t/js_fetch.t
index e0763a6a..320e06f5 100644
--- a/nginx/t/js_fetch.t
+++ b/nginx/t/js_fetch.t
@@ -52,6 +52,10 @@ http {
js_content test.njs;
}
+ location /engine {
+ js_content test.engine;
+ }
+
location /broken {
js_content test.broken;
}
@@ -134,6 +138,10 @@ $t->write_file('test.js', <<EOF);
r.return(200, njs.version);
}
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
function body(r) {
var loc = r.args.loc;
var getter = r.args.getter;
@@ -398,10 +406,14 @@ $t->write_file('test.js', <<EOF);
export default {njs: test_njs, body, broken, broken_response, body_special,
chain, chunked_ok, chunked_fail, header, header_iter,
- host_header, multi, loc, property};
+ host_header, multi, loc, property, engine};
EOF
-$t->try_run('no njs.fetch')->plan(36);
+$t->try_run('no njs.fetch');
+
+plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m;
+
+$t->plan(36);
$t->run_daemon(\&http_daemon, port(8082));
$t->waitforsocket('127.0.0.1:' . port(8082));
diff --git a/nginx/t/js_fetch_https.t b/nginx/t/js_fetch_https.t
index 9d4ebb0a..9a44a339 100644
--- a/nginx/t/js_fetch_https.t
+++ b/nginx/t/js_fetch_https.t
@@ -48,6 +48,10 @@ http {
js_content test.njs;
}
+ location /engine {
+ js_content test.engine;
+ }
+
location /https {
js_content test.https;
}
@@ -102,6 +106,10 @@ $t->write_file('test.js', <<EOF);
r.return(200, njs.version);
}
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
function https(r) {
var url = `https://\${r.args.domain}:$p1/loc`;
var opt = {};
@@ -116,7 +124,7 @@ $t->write_file('test.js', <<EOF);
.catch(e => r.return(501, e.message))
}
- export default {njs: test_njs, https};
+ export default {njs: test_njs, https, engine};
EOF
my $d = $t->testdir();
@@ -186,7 +194,11 @@ foreach my $name ('default.example.com', '1.example.com') {
. $t->read_file('intermediate.crt'));
}
-$t->try_run('no njs.fetch')->plan(7);
+$t->try_run('no njs.fetch');
+
+plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m;
+
+$t->plan(7);
$t->run_daemon(\&dns_daemon, port(8981), $t);
$t->waitforfile($t->testdir . '/' . port(8981));
diff --git a/nginx/t/js_fetch_objects.t b/nginx/t/js_fetch_objects.t
index d0f47630..1bc88a3d 100644
--- a/nginx/t/js_fetch_objects.t
+++ b/nginx/t/js_fetch_objects.t
@@ -45,6 +45,10 @@ http {
js_content test.njs;
}
+ location /engine {
+ js_content test.engine;
+ }
+
location /headers {
js_content test.headers;
}
@@ -88,6 +92,10 @@ $t->write_file('test.js', <<EOF);
r.return(200, njs.version);
}
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
function header(r) {
r.return(200, r.headersIn.a);
}
@@ -501,11 +509,15 @@ $t->write_file('test.js', <<EOF);
run(r, tests);
}
- export default {njs: test_njs, body, headers, request, response, fetch,
- fetch_multi_header};
+ export default {njs: test_njs, engine, body, headers, request, response,
+ fetch, fetch_multi_header};
EOF
-$t->try_run('no njs')->plan(5);
+$t->try_run('no njs');
+
+plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m;
+
+$t->plan(5);
###############################################################################
diff --git a/nginx/t/js_fetch_resolver.t b/nginx/t/js_fetch_resolver.t
index 8fb6b66f..7cea3386 100644
--- a/nginx/t/js_fetch_resolver.t
+++ b/nginx/t/js_fetch_resolver.t
@@ -50,6 +50,10 @@ http {
js_content test.njs;
}
+ location /engine {
+ js_content test.engine;
+ }
+
location /dns {
js_content test.dns;
@@ -104,6 +108,10 @@ $t->write_file('test.js', <<EOF);
r.return(200, njs.version);
}
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
const p0 = $p0;
const p1 = $p1;
@@ -133,10 +141,14 @@ $t->write_file('test.js', <<EOF);
r.return(c, `\${v.host}:\${v.request_method}:\${foo}:\${bar}:\${body}`);
}
- export default {njs: test_njs, dns, loc};
+ export default {njs: test_njs, dns, loc, engine};
EOF
-$t->try_run('no njs.fetch')->plan(5);
+$t->try_run('no njs.fetch');
+
+plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m;
+
+$t->plan(5);
$t->run_daemon(\&dns_daemon, port(8981), $t);
$t->waitforfile($t->testdir . '/' . port(8981));
diff --git a/nginx/t/js_fetch_timeout.t b/nginx/t/js_fetch_timeout.t
index 486656d6..1ac1c7aa 100644
--- a/nginx/t/js_fetch_timeout.t
+++ b/nginx/t/js_fetch_timeout.t
@@ -47,6 +47,10 @@ http {
js_content test.njs;
}
+ location /engine {
+ js_content test.engine;
+ }
+
location /normal_timeout {
js_content test.timeout_test;
}
@@ -80,6 +84,10 @@ $t->write_file('test.js', <<EOF);
r.return(200, njs.version);
}
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
async function timeout_test(r) {
let rs = await Promise.allSettled([
'http://127.0.0.1:$p1/normal_reply',
@@ -102,10 +110,15 @@ $t->write_file('test.js', <<EOF);
setTimeout((r) => { r.return(200); }, 250, r, 0);
}
- export default {njs: test_njs, timeout_test, normal_reply, delayed_reply};
+ export default {njs: test_njs, engine, timeout_test, normal_reply,
+ delayed_reply};
EOF
-$t->try_run('no js_fetch_timeout')->plan(2);
+$t->try_run('no js_fetch_timeout');
+
+plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m;
+
+$t->plan(2);
###############################################################################
diff --git a/nginx/t/js_fetch_verify.t b/nginx/t/js_fetch_verify.t
index d6bb1d9e..4c97e04d 100644
--- a/nginx/t/js_fetch_verify.t
+++ b/nginx/t/js_fetch_verify.t
@@ -48,6 +48,10 @@ http {
js_content test.njs;
}
+ location /engine {
+ js_content test.engine;
+ }
+
location /https {
js_content test.https;
}
@@ -76,6 +80,10 @@ $t->write_file('test.js', <<EOF);
r.return(200, njs.version);
}
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
function https(r) {
ngx.fetch(`https://example.com:$p1/loc`)
.then(reply => reply.text())
@@ -83,7 +91,7 @@ $t->write_file('test.js', <<EOF);
.catch(e => r.return(501, e.message));
}
- export default {njs: test_njs, https};
+ export default {njs: test_njs, engine, https};
EOF
$t->write_file('openssl.conf', <<EOF);
@@ -104,7 +112,11 @@ foreach my $name ('localhost') {
or die "Can't create certificate for $name: $!\n";
}
-$t->try_run('no js_fetch_verify')->plan(2);
+$t->try_run('no js_fetch_verify');
+
+plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m;
+
+$t->plan(2);
$t->run_daemon(\&dns_daemon, port(8981), $t);
$t->waitforfile($t->testdir . '/' . port(8981));
diff --git a/nginx/t/js_object.t b/nginx/t/js_object.t
index 97e778a2..8c154010 100644
--- a/nginx/t/js_object.t
+++ b/nginx/t/js_object.t
@@ -42,6 +42,10 @@ http {
listen 127.0.0.1:8080;
server_name localhost;
+ location /engine {
+ js_content test.engine;
+ }
+
location /to_string {
js_content test.to_string;
}
@@ -75,6 +79,10 @@ http {
EOF
$t->write_file('test.js', <<EOF);
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
function to_string(r) {
r.return(200, r.toString());
}
@@ -113,7 +121,7 @@ $t->write_file('test.js', <<EOF);
Object.getOwnPropertyDescriptors(r)['log'].value === r.log);
}
- export default {to_string, define_prop, in_operator, redefine_bind,
+ export default {engine, to_string, define_prop, in_operator, redefine_bind,
redefine_proxy, redefine_proto, get_own_prop_descs};
EOF
@@ -132,6 +140,12 @@ like(http(
like(http_get('/redefine_bind'), qr/redefine_bind/, 'redefine_bind');
like(http_get('/redefine_proxy'), qr/redefine_proxy/, 'redefine_proxy');
like(http_get('/redefine_proto'), qr/a|b/, 'redefine_proto');
-like(http_get('/get_own_prop_descs'), qr/true/, 'get_own_prop_descs');
+
+SKIP: {
+ skip "In QuickJS methods are in the prototype", 1
+ if http_get('/engine') =~ /QuickJS$/m;
+
+ like(http_get('/get_own_prop_descs'), qr/true/, 'get_own_prop_descs');
+}
###############################################################################
diff --git a/nginx/t/js_periodic.t b/nginx/t/js_periodic.t
index 8451ce53..63afe379 100644
--- a/nginx/t/js_periodic.t
+++ b/nginx/t/js_periodic.t
@@ -71,6 +71,10 @@ http {
js_periodic test.timeout_exception interval=30ms;
}
+ location /engine {
+ js_content test.engine;
+ }
+
location /fetch_ok {
return 200 'ok';
}
@@ -120,6 +124,10 @@ my $p0 = port(8080);
$t->write_file('test.js', <<EOF);
import fs from 'fs';
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
function affinity() {
ngx.shared.workers.set(ngx.worker_id, 1);
}
@@ -241,10 +249,14 @@ $t->write_file('test.js', <<EOF);
test_file, test_multiple_fetches, test_tick,
test_timeout_exception, test_timer, test_vars, tick,
tick_exception, timer, timer_exception,
- timeout_exception };
+ timeout_exception, engine };
EOF
-$t->try_run('no js_periodic')->plan(9);
+$t->try_run('no js_periodic');
+
+plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m;
+
+$t->plan(9);
###############################################################################
diff --git a/nginx/t/js_preload_object.t b/nginx/t/js_preload_object.t
index 407e97fe..49befd66 100644
--- a/nginx/t/js_preload_object.t
+++ b/nginx/t/js_preload_object.t
@@ -45,6 +45,10 @@ http {
js_import lib.js;
js_preload_object lx from l.json;
+ location /engine {
+ js_content lib.engine;
+ }
+
location /test {
js_content lib.test;
}
@@ -83,6 +87,10 @@ $t->write_file('lib.js', <<EOF);
r.return(200, ga + ' ' + g1.c.prop[0].a + ' ' + lx);
}
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
function test_var(r) {
return g1.b[2];
}
@@ -123,7 +131,7 @@ $t->write_file('lib.js', <<EOF);
r.return(200, gg);
}
- export default {test, test_var, mutate, suffix};
+ export default {engine, test, test_var, mutate, suffix};
EOF
@@ -151,7 +159,11 @@ $t->write_file('ga.json', '"ga loaded"');
$t->write_file('l.json', '"l loaded"');
$t->write_file('no_suffix', '"no_suffix loaded"');
-$t->try_run('no js_preload_object available')->plan(12);
+$t->try_run('no js_preload_object available');
+
+plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m;
+
+$t->plan(12);
###############################################################################
diff --git a/nginx/t/js_shared_dict.t b/nginx/t/js_shared_dict.t
index ffc286e2..16128225 100644
--- a/nginx/t/js_shared_dict.t
+++ b/nginx/t/js_shared_dict.t
@@ -51,6 +51,10 @@ http {
js_content test.njs;
}
+ location /engine {
+ js_content test.engine;
+ }
+
location /add {
js_content test.add;
}
@@ -132,6 +136,10 @@ $t->write_file('test.js', <<'EOF');
r.return(200, njs.version);
}
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
function convertToValue(dict, v) {
if (dict.type == 'number') {
return parseInt(v);
@@ -257,7 +265,7 @@ $t->write_file('test.js', <<'EOF');
function pop(r) {
var dict = ngx.shared[r.args.dict];
- var val = dict.pop(r.args.key);
+ var val = dict.pop(r.args.key);
if (val == '') {
val = 'empty';
@@ -302,10 +310,14 @@ $t->write_file('test.js', <<'EOF');
export default { add, capacity, chain, clear, del, free_space, get, has,
incr, items, keys, name, njs: test_njs, pop, replace, set,
- set_clear, size, zones };
+ set_clear, size, zones, engine };
EOF
-$t->try_run('no js_shared_dict_zone')->plan(51);
+$t->try_run('no js_shared_dict_zone');
+
+plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m;
+
+$t->plan(51);
###############################################################################
diff --git a/nginx/t/stream_js_console.t b/nginx/t/stream_js_console.t
index c3c22800..0c253289 100644
--- a/nginx/t/stream_js_console.t
+++ b/nginx/t/stream_js_console.t
@@ -34,6 +34,21 @@ daemon off;
events {
}
+http {
+ %%TEST_GLOBALS_HTTP%%
+
+ js_import test.js;
+
+ server {
+ listen 127.0.0.1:8080;
+ server_name localhost;
+
+ location /engine {
+ js_content test.engine;
+ }
+ }
+}
+
stream {
%%TEST_GLOBALS_STREAM%%
@@ -41,7 +56,7 @@ stream {
server {
- listen 127.0.0.1:8080;
+ listen 127.0.0.1:8081;
js_preread test.log;
@@ -49,7 +64,7 @@ stream {
}
server {
- listen 127.0.0.1:8081;
+ listen 127.0.0.1:8082;
js_preread test.timer;
@@ -60,6 +75,10 @@ stream {
EOF
$t->write_file('test.js', <<EOF);
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
function log(s) {
s.on('upload', function (data) {
if (data.length > 0) {
@@ -85,7 +104,7 @@ $t->write_file('test.js', <<EOF);
});
}
- export default { log, timer };
+ export default { engine, log, timer };
EOF
$t->run_daemon(\&stream_daemon, port(8090));
@@ -94,14 +113,23 @@ $t->waitforsocket('127.0.0.1:' . port(8090));
###############################################################################
-is(stream('127.0.0.1:' . port(8080))->io('eyJhIjpbIkIiLCJDIl19'),
+my $engine = http_get('/engine');
+
+is(stream('127.0.0.1:' . port(8081))->io('eyJhIjpbIkIiLCJDIl19'),
'eyJhIjpbIkIiLCJDIl19', 'log test');
-is(stream('127.0.0.1:' . port(8081))->io('timer'), 'timer', 'timer test');
+is(stream('127.0.0.1:' . port(8082))->io('timer'), 'timer', 'timer test');
$t->stop();
+SKIP: {
+ skip "QuickJS has no console.dump() method.", 1
+ if $engine =~ /QuickJS$/m;
+
like($t->read_file('error.log'), qr/\[info\].*js: \{a:\['B','C'\]\}/,
'console.log with object');
+
+}
+
like($t->read_file('error.log'), qr/\[info\].*js: foo: \d+\.\d\d\d\d\d\dms/,
'console.time foo');
diff --git a/nginx/t/stream_js_exit.t b/nginx/t/stream_js_exit.t
index a8bc34ae..01778f0f 100644
--- a/nginx/t/stream_js_exit.t
+++ b/nginx/t/stream_js_exit.t
@@ -45,6 +45,10 @@ http {
location /njs {
js_content test.njs;
}
+
+ location /engine {
+ js_content test.engine;
+ }
}
}
@@ -74,6 +78,10 @@ $t->write_file('test.js', <<EOF);
r.return(200, njs.version);
}
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
function access(s) {
njs.on('exit', () => {
var v = s.variables;
@@ -95,10 +103,14 @@ $t->write_file('test.js', <<EOF);
});
}
- export default {njs: test_njs, access, filter};
+ export default {njs: test_njs, engine, access, filter};
EOF
-$t->try_run('no stream njs available')->plan(2);
+$t->try_run('no stream njs available');
+
+plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m;
+
+$t->plan(2);
$t->run_daemon(\&stream_daemon, port(8090));
$t->waitforsocket('127.0.0.1:' . port(8090));
diff --git a/nginx/t/stream_js_fetch.t b/nginx/t/stream_js_fetch.t
index 106702dc..c57128a8 100644
--- a/nginx/t/stream_js_fetch.t
+++ b/nginx/t/stream_js_fetch.t
@@ -46,6 +46,10 @@ http {
js_content test.njs;
}
+ location /engine {
+ js_content test.engine;
+ }
+
location /validate {
js_content test.validate;
}
@@ -99,6 +103,10 @@ $t->write_file('test.js', <<EOF);
r.return(200, njs.version);
}
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
function validate(r) {
r.return((r.requestText == 'QZ') ? 200 : 403);
}
@@ -158,10 +166,14 @@ $t->write_file('test.js', <<EOF);
}
export default {njs: test_njs, validate, preread_verify, filter_verify,
- access_ok, access_nok};
+ access_ok, access_nok, engine};
EOF
-$t->try_run('no stream njs available')->plan(9);
+$t->try_run('no stream njs available');
+
+plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m;
+
+$t->plan(9);
$t->run_daemon(\&stream_daemon, port(8090), port(8091));
$t->waitforsocket('127.0.0.1:' . port(8090));
diff --git a/nginx/t/stream_js_fetch_https.t b/nginx/t/stream_js_fetch_https.t
index c49b833b..5d7c5c20 100644
--- a/nginx/t/stream_js_fetch_https.t
+++ b/nginx/t/stream_js_fetch_https.t
@@ -47,6 +47,10 @@ http {
location /njs {
js_content test.njs;
}
+
+ location /engine {
+ js_content test.engine;
+ }
}
server {
@@ -159,6 +163,10 @@ $t->write_file('test.js', <<EOF);
r.return(200, njs.version);
}
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
function preread(s) {
s.on('upload', function (data, flags) {
if (data.startsWith('GO')) {
@@ -193,7 +201,7 @@ $t->write_file('test.js', <<EOF);
(r.status == 200) ? s.allow(): s.deny();
}
- export default {njs: test_njs, preread, access_ok, access_nok};
+ export default {njs: test_njs, engine, preread, access_ok, access_nok};
EOF
my $d = $t->testdir();
@@ -263,7 +271,11 @@ foreach my $name ('default.example.com', '1.example.com') {
. $t->read_file('intermediate.crt'));
}
-$t->try_run('no njs.fetch')->plan(6);
+$t->try_run('no njs.fetch');
+
+plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m;
+
+$t->plan(6);
$t->run_daemon(\&dns_daemon, port(8981), $t);
$t->waitforfile($t->testdir . '/' . port(8981));
diff --git a/nginx/t/stream_js_fetch_init.t b/nginx/t/stream_js_fetch_init.t
index 6de487da..3f6d7262 100644
--- a/nginx/t/stream_js_fetch_init.t
+++ b/nginx/t/stream_js_fetch_init.t
@@ -58,6 +58,10 @@ http {
js_content test.njs;
}
+ location /engine {
+ js_content test.engine;
+ }
+
location /success {
return 200;
}
@@ -73,16 +77,24 @@ $t->write_file('test.js', <<EOF);
r.return(200, njs.version);
}
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
async function access_ok(s) {
let reply = await ngx.fetch('http://127.0.0.1:$p/success');
(reply.status == 200) ? s.allow(): s.deny();
}
- export default {njs: test_njs, access_ok};
+ export default {njs: test_njs, engine, access_ok};
EOF
-$t->try_run('no stream njs available')->plan(1);
+$t->try_run('no stream njs available');
+
+plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m;
+
+$t->plan(1);
$t->run_daemon(\&stream_daemon, port(8090));
$t->waitforsocket('127.0.0.1:' . port(8090));
diff --git a/nginx/t/stream_js_object.t b/nginx/t/stream_js_object.t
index 504b9348..571d0a87 100644
--- a/nginx/t/stream_js_object.t
+++ b/nginx/t/stream_js_object.t
@@ -33,35 +33,76 @@ daemon off;
events {
}
+http {
+ %%TEST_GLOBALS_HTTP%%
+
+ js_import test.js;
+
+ server {
+ listen 127.0.0.1:8080;
+ server_name localhost;
+
+ location /engine {
+ js_content test.engine;
+ }
+ }
+}
+
stream {
%%TEST_GLOBALS_STREAM%%
- js_set $test test.test;
-
js_import test.js;
+ js_set $to_string test.to_string;
+ js_set $define_prop test.define_prop;
+ js_set $in_operator test.in_operator;
+ js_set $redefine_proto test.redefine_proto;
+ js_set $get_own_prop_descs test.get_own_prop_descs;
+
server {
listen 127.0.0.1:8081;
- return $test$status;
+ return $to_string;
+ }
+
+ server {
+ listen 127.0.0.1:8082;
+ return $define_prop$status;
+ }
+
+ server {
+ listen 127.0.0.1:8083;
+ return $in_operator;
+ }
+
+ server {
+ listen 127.0.0.1:8084;
+ return $redefine_proto;
+ }
+
+ server {
+ listen 127.0.0.1:8085;
+ return $get_own_prop_descs;
}
}
EOF
$t->write_file('test.js', <<EOF);
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
function to_string(s) {
- return s.toString() === '[object Stream Session]';
+ return s.toString();
}
function define_prop(s) {
Object.defineProperty(s.variables, 'status', {value:400});
- return s.variables.status == 400;
+ return s.variables.status;
}
function in_operator(s) {
- return ['status', 'unknown']
- .map(v=>v in s.variables)
- .toString() === 'true,false';
+ return ['status', 'unknown'].map(v=>v in s.variables).toString();
}
function redefine_proto(s) {
@@ -76,23 +117,27 @@ $t->write_file('test.js', <<EOF);
return Object.getOwnPropertyDescriptors(s)['on'].value === s.on;
}
- function test(s) {
- return [ to_string,
- define_prop,
- in_operator,
- redefine_proto,
- get_own_prop_descs,
- ].every(v=>v(s));
- }
-
- export default {test};
+ export default { engine, to_string, define_prop, in_operator,
+ redefine_proto, get_own_prop_descs };
EOF
-$t->try_run('no njs stream session object')->plan(1);
+$t->try_run('no njs stream session object')->plan(5);
###############################################################################
-is(stream('127.0.0.1:' . port(8081))->read(), 'true400', 'var set');
+is(stream('127.0.0.1:' . port(8081))->read(), '[object Stream Session]',
+ 'to_string');
+is(stream('127.0.0.1:' . port(8082))->read(), '400400', 'define_prop');
+is(stream('127.0.0.1:' . port(8083))->read(), 'true,false', 'in_operator');
+is(stream('127.0.0.1:' . port(8084))->read(), 'true', 'redefine_proto');
+
+SKIP: {
+ skip "In QuickJS methods are in the prototype", 1
+ if http_get('/engine') =~ /QuickJS$/m;
+
+is(stream('127.0.0.1:' . port(8085))->read(), 'true', 'get_own_prop_descs');
+
+}
###############################################################################
diff --git a/nginx/t/stream_js_preload_object.t b/nginx/t/stream_js_preload_object.t
index 3c27098d..34ffae2b 100644
--- a/nginx/t/stream_js_preload_object.t
+++ b/nginx/t/stream_js_preload_object.t
@@ -33,6 +33,21 @@ daemon off;
events {
}
+http {
+ %%TEST_GLOBALS_HTTP%%
+
+ js_import main.js;
+
+ server {
+ listen 127.0.0.1:8080;
+ server_name localhost;
+
+ location /engine {
+ js_content main.engine;
+ }
+ }
+}
+
stream {
%%TEST_GLOBALS_STREAM%%
@@ -104,14 +119,22 @@ $t->write_file('lib.js', <<EOF);
EOF
$t->write_file('main.js', <<EOF);
- export default {bar: {p(s) {return g1.b[2]}}};
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
+ export default {engine, bar: {p(s) {return g1.b[2]}}};
EOF
$t->write_file('g.json',
'{"a":1, "b":[1,2,"element",4,5], "c":{"prop":[{"a":3}]}}');
-$t->try_run('no js_preload_object available')->plan(2);
+$t->try_run('no js_preload_object available');
+
+plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m;
+
+$t->plan(2);
###############################################################################
diff --git a/nginx/t/stream_js_shared_dict.t b/nginx/t/stream_js_shared_dict.t
index e8e482f4..0bdfaeb7 100644
--- a/nginx/t/stream_js_shared_dict.t
+++ b/nginx/t/stream_js_shared_dict.t
@@ -43,6 +43,10 @@ http {
location / {
return 200;
}
+
+ location /engine {
+ js_content test.engine;
+ }
}
}
@@ -71,6 +75,10 @@ EOF
$t->write_file('test.js', <<EOF);
import qs from 'querystring';
+ function engine(r) {
+ r.return(200, 'engine');
+ }
+
function preread_verify(s) {
var collect = Buffer.from([]);
@@ -113,11 +121,15 @@ $t->write_file('test.js', <<EOF);
});
}
- export default { preread_verify, control_access };
+ export default { engine, preread_verify, control_access };
EOF
-$t->try_run('no js_shared_dict_zone')->plan(9);
+$t->try_run('no js_shared_dict_zone');
+
+plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m;
+
+$t->plan(9);
$t->run_daemon(\&stream_daemon, port(8090));
$t->waitforsocket('127.0.0.1:' . port(8090));
diff --git a/src/qjs.h b/src/qjs.h
index dff5919b..2418e6cd 100644
--- a/src/qjs.h
+++ b/src/qjs.h
@@ -33,6 +33,12 @@
#include <pthread.h>
+#define QJS_CORE_CLASS_ID_OFFSET 64
+#define QJS_CORE_CLASS_ID_BUFFER (QJS_CORE_CLASS_ID_OFFSET)
+#define QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR (QJS_CORE_CLASS_ID_OFFSET + 1)
+#define QJS_CORE_CLASS_ID_LAST (QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR)
+
+
typedef JSModuleDef *(*qjs_addon_init_pt)(JSContext *ctx, const char *name);
typedef struct {
diff --git a/src/qjs_buffer.c b/src/qjs_buffer.c
index 06574110..2487c633 100644
--- a/src/qjs_buffer.c
+++ b/src/qjs_buffer.c
@@ -262,15 +262,12 @@ static JSClassDef qjs_buffer_class = {
};
-static JSClassID qjs_buffer_class_id;
-
#ifndef NJS_HAVE_QUICKJS_NEW_TYPED_ARRAY
static JSClassDef qjs_uint8_array_ctor_class = {
"Uint8ArrayConstructor",
.finalizer = NULL,
};
-static JSClassID qjs_uint8_array_ctor_id;
#endif
@@ -354,7 +351,7 @@ qjs_buffer_ctor(JSContext *ctx, JSValueConst this_val, int argc,
return ret;
}
- proto = JS_GetClassProto(ctx, qjs_buffer_class_id);
+ proto = JS_GetClassProto(ctx, QJS_CORE_CLASS_ID_BUFFER);
JS_SetPrototype(ctx, ret, proto);
JS_FreeValue(ctx, proto);
@@ -725,7 +722,7 @@ qjs_buffer_is_buffer(JSContext *ctx, JSValueConst this_val,
JSValue proto, buffer_proto, ret;
proto = JS_GetPrototype(ctx, argv[0]);
- buffer_proto = JS_GetClassProto(ctx, qjs_buffer_class_id);
+ buffer_proto = JS_GetClassProto(ctx, QJS_CORE_CLASS_ID_BUFFER);
ret = JS_NewBool(ctx, JS_VALUE_GET_TAG(argv[0]) == JS_TAG_OBJECT &&
JS_VALUE_GET_OBJ(buffer_proto) == JS_VALUE_GET_OBJ(proto));
@@ -2426,7 +2423,7 @@ qjs_buffer_alloc(JSContext *ctx, size_t size)
return ret;
}
- proto = JS_GetClassProto(ctx, qjs_buffer_class_id);
+ proto = JS_GetClassProto(ctx, QJS_CORE_CLASS_ID_BUFFER);
JS_SetPrototype(ctx, ret, proto);
JS_FreeValue(ctx, proto);
@@ -2494,7 +2491,7 @@ qjs_new_uint8_array(JSContext *ctx, int argc, JSValueConst *argv)
#else
JSValue ctor;
- ctor = JS_GetClassProto(ctx, qjs_uint8_array_ctor_id);
+ ctor = JS_GetClassProto(ctx, QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR);
ret = JS_CallConstructor(ctx, ctor, argc, argv);
JS_FreeValue(ctx, ctor);
#endif
@@ -2511,8 +2508,8 @@ qjs_buffer_builtin_init(JSContext *ctx)
JSValue global_obj, buffer, proto, ctor, ta, ta_proto, symbol, species;
JSClassID u8_ta_class_id;
- JS_NewClassID(&qjs_buffer_class_id);
- JS_NewClass(JS_GetRuntime(ctx), qjs_buffer_class_id, &qjs_buffer_class);
+ JS_NewClass(JS_GetRuntime(ctx), QJS_CORE_CLASS_ID_BUFFER,
+ &qjs_buffer_class);
global_obj = JS_GetGlobalObject(ctx);
@@ -2528,10 +2525,10 @@ qjs_buffer_builtin_init(JSContext *ctx)
* We use JS_SetClassProto()/JS_GetClassProto() as a key-value store
* for fast value query by class ID without querying the global object.
*/
- JS_NewClassID(&qjs_uint8_array_ctor_id);
- JS_NewClass(JS_GetRuntime(ctx), qjs_uint8_array_ctor_id,
+ JS_NewClass(JS_GetRuntime(ctx), QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR,
&qjs_uint8_array_ctor_class);
- JS_SetClassProto(ctx, qjs_uint8_array_ctor_id, JS_DupValue(ctx, ctor));
+ JS_SetClassProto(ctx, QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR,
+ JS_DupValue(ctx, ctor));
#endif
ta = JS_CallConstructor(ctx, ctor, 0, NULL);
@@ -2543,7 +2540,7 @@ qjs_buffer_builtin_init(JSContext *ctx)
JS_SetPrototype(ctx, proto, ta_proto);
JS_FreeValue(ctx, ta_proto);
- JS_SetClassProto(ctx, qjs_buffer_class_id, proto);
+ JS_SetClassProto(ctx, QJS_CORE_CLASS_ID_BUFFER, proto);
buffer = JS_NewCFunction2(ctx, qjs_buffer, "Buffer", 3,
JS_CFUNC_constructor, 0);
More information about the nginx-devel
mailing list