location /test {
js_content test.test;
}
+
+ location /replaced {
+ js_context_reuse 0;
+ js_content test.replaced;
+ }
}
}
r.return(200, `bs: \${r.variables.bytes_sent}`);
}
+ function replaced(r) {
+ njs.on('exit', () => ngx.log(ngx.WARN, "replaced exit hook"));
+
+ globalThis.njs = {};
+
+ r.return(200, 'replaced');
+ }
+
function bytes_sent(r) {
return r.variables.bytes_sent;
}
return r.uri;
}
- export default { test, bytes_sent, foo_header, url };
+ export default { test, replaced, bytes_sent, foo_header, url };
EOF
-$t->try_run('no njs')->plan(4);
+$t->try_run('no njs')->plan(6);
###############################################################################
. 'Foo: bar' . CRLF
. 'Host: localhost' . CRLF . CRLF
), qr/bs: 0/, 'response');
+like(http_get('/replaced'), qr/replaced$/, 'replaced njs response');
$t->stop();
like($error_log, qr/\[warn\].*exit hook: bs: \d+/, 'exit hook logged');
like($error_log, qr/\[warn\].*exit hook promise/, 'exit hook promise logged');
+like($error_log, qr/\[warn\].*replaced exit hook/,
+ 'exit hook with replaced njs');
like($t->read_file('access.log'), qr/\[var:\d+ header:626172 url:\/test\]/,
'access log has bytes_sent');
JSValueConst *argv);
static int qjs_add_intrinsic_njs(JSContext *cx, JSValueConst global);
+static qjs_njs_t *qjs_njs(JSContext *ctx);
static JSValue qjs_njs_on(JSContext *ctx, JSValueConst this_val, int argc,
JSValueConst *argv);
static void qjs_njs_mark(JSRuntime *rt, JSValueConst val,
};
+static JSClassDef qjs_njs_object_class = {
+ "njsObject",
+ .finalizer = NULL,
+};
+
+
static JSClassDef qjs_text_decoder_class = {
"TextDecoder",
.finalizer = qjs_text_decoder_finalizer,
}
+static qjs_njs_t *
+qjs_njs(JSContext *ctx)
+{
+ JSValue obj;
+ qjs_njs_t *njs;
+
+ obj = JS_GetClassProto(ctx, QJS_CORE_CLASS_ID_NJS_OBJECT);
+ njs = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_NJS);
+ JS_FreeValue(ctx, obj);
+
+ return njs;
+}
+
+
JSValue
qjs_call_exit_hook(JSContext *ctx)
{
- JSValue global, obj, hook, ret;
+ JSValue hook, ret;
qjs_njs_t *njs;
- global = JS_GetGlobalObject(ctx);
-
- obj = JS_GetPropertyStr(ctx, global, "njs");
- if (JS_IsException(obj) || JS_IsUndefined(obj)) {
- goto done;
+ njs = qjs_njs(ctx);
+ if (njs == NULL || !JS_IsFunction(ctx, njs->hooks[QJS_NJS_HOOK_EXIT])) {
+ return JS_UNDEFINED;
}
- njs = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_NJS);
- if (njs != NULL && JS_IsFunction(ctx, njs->hooks[QJS_NJS_HOOK_EXIT])) {
- hook = njs->hooks[QJS_NJS_HOOK_EXIT];
- njs->hooks[QJS_NJS_HOOK_EXIT] = JS_UNDEFINED;
-
- ret = JS_Call(ctx, hook, JS_UNDEFINED, 0, NULL);
+ hook = njs->hooks[QJS_NJS_HOOK_EXIT];
+ njs->hooks[QJS_NJS_HOOK_EXIT] = JS_UNDEFINED;
- JS_FreeValue(ctx, hook);
+ ret = JS_Call(ctx, hook, JS_UNDEFINED, 0, NULL);
- if (JS_IsException(ret)) {
- JS_FreeValue(ctx, obj);
- JS_FreeValue(ctx, global);
- return ret;
- }
+ JS_FreeValue(ctx, hook);
- JS_FreeValue(ctx, ret);
+ if (JS_IsException(ret)) {
+ return ret;
}
-done:
-
- JS_FreeValue(ctx, obj);
- JS_FreeValue(ctx, global);
+ JS_FreeValue(ctx, ret);
return JS_UNDEFINED;
}
return -1;
}
+ /*
+ * The class is used only to keep the njs object in its prototype
+ * slot, so the exit hook does not depend on the "njs" global property.
+ */
+
+ if (JS_NewClass(JS_GetRuntime(cx), QJS_CORE_CLASS_ID_NJS_OBJECT,
+ &qjs_njs_object_class) < 0)
+ {
+ return -1;
+ }
+
proto = JS_NewObject(cx);
if (JS_IsException(proto)) {
return -1;
JS_SetOpaque(obj, njs);
+ JS_SetClassProto(cx, QJS_CORE_CLASS_ID_NJS_OBJECT, JS_DupValue(cx, obj));
+
if (JS_SetPropertyStr(cx, global, "njs", obj) < 0) {
return -1;
}