aboutsummaryrefslogtreecommitdiff
path: root/nginx/ngx_stream_js_module.c
diff options
context:
space:
mode:
authorDmitry Volyntsev <xeioex@nginx.com>2024-06-14 20:54:28 -0700
committerDmitry Volyntsev <xeioexception@gmail.com>2024-09-17 18:05:57 -0700
commit201b127679e27fe63eff5c1b4356ec4ed9ec4611 (patch)
tree2477939f07f646ee6137a7d30ccc8e807206da61 /nginx/ngx_stream_js_module.c
parent9b6744129ce4c18e74b5e41cd1da66f2cbadcf36 (diff)
downloadnjs-201b127679e27fe63eff5c1b4356ec4ed9ec4611.tar.gz
njs-201b127679e27fe63eff5c1b4356ec4ed9ec4611.zip
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.
Diffstat (limited to 'nginx/ngx_stream_js_module.c')
-rw-r--r--nginx/ngx_stream_js_module.c1119
1 files changed, 1103 insertions, 16 deletions
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,6 +1969,922 @@ ngx_engine_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf,
}
+#if (NJS_HAVE_QUICKJS)
+
+static JSValue
+ngx_stream_qjs_ext_to_string_tag(JSContext *cx, JSValueConst this_val)
+{
+ return JS_NewString(cx, "Stream Session");
+}
+
+
+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;
+
+ s = ngx_stream_qjs_session(this_val);
+ if (s == NULL) {
+ return JS_ThrowInternalError(cx, "\"this\" is not a session object");
+ }
+
+ 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)
{
@@ -1816,15 +2895,24 @@ ngx_stream_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf)
options.engine = conf->type;
- 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;
+ 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, "");