Skip to content

Commit 7d54d27

Browse files
committed
QuickJS: added njs.on('exit') API t support.
1 parent 2ca3b42 commit 7d54d27

File tree

7 files changed

+270
-19
lines changed

7 files changed

+270
-19
lines changed

external/njs_shell.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2702,6 +2702,8 @@ njs_engine_qjs_destroy(njs_engine_t *engine)
27022702
njs_queue_link_t *link;
27032703
njs_rejected_promise_t *rejected_promise;
27042704

2705+
qjs_call_exit_hook(engine->u.qjs.ctx);
2706+
27052707
console = JS_GetRuntimeOpaque(engine->u.qjs.rt);
27062708

27072709
if (console->rejected_promises != NULL) {

nginx/ngx_http_js_module.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1609,7 +1609,7 @@ static ngx_int_t
16091609
ngx_http_js_init_vm(ngx_http_request_t *r, njs_int_t proto_id)
16101610
{
16111611
ngx_http_js_ctx_t *ctx;
1612-
ngx_pool_cleanup_t *cln;
1612+
ngx_http_cleanup_t *cln;
16131613
ngx_http_js_loc_conf_t *jlcf;
16141614

16151615
jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module);
@@ -1644,7 +1644,7 @@ ngx_http_js_init_vm(ngx_http_request_t *r, njs_int_t proto_id)
16441644
"http js vm clone %s: %p from: %p", jlcf->engine->name,
16451645
ctx->engine, jlcf->engine);
16461646

1647-
cln = ngx_pool_cleanup_add(r->pool, 0);
1647+
cln = ngx_http_cleanup_add(r, 0);
16481648
if (cln == NULL) {
16491649
return NGX_ERROR;
16501650
}

nginx/ngx_js.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,6 +1130,7 @@ ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx,
11301130
uint32_t i, length;
11311131
ngx_str_t exception;
11321132
JSRuntime *rt;
1133+
JSValue ret;
11331134
JSContext *cx;
11341135
JSClassID class_id;
11351136
JSMemoryUsage stats;
@@ -1142,6 +1143,13 @@ ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx,
11421143
cx = e->u.qjs.ctx;
11431144

11441145
if (ctx != NULL) {
1146+
ret = qjs_call_exit_hook(cx);
1147+
if (JS_IsException(ret)) {
1148+
ngx_qjs_exception(e, &exception);
1149+
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
1150+
"js exit hook exception: %V", &exception);
1151+
}
1152+
11451153
node = njs_rbtree_min(&ctx->waiting_events);
11461154

11471155
while (njs_rbtree_is_there_successor(&ctx->waiting_events, node)) {

nginx/t/js_exit.t

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/perl
2+
3+
# (C) Dmitry Volyntsev
4+
# (C) F5, Inc.
5+
6+
# Tests for http njs module, njs.on('exit', ...).
7+
8+
###############################################################################
9+
10+
use warnings;
11+
use strict;
12+
13+
use Test::More;
14+
use Socket qw/ CRLF /;
15+
16+
BEGIN { use FindBin; chdir($FindBin::Bin); }
17+
18+
use lib 'lib';
19+
use Test::Nginx;
20+
21+
###############################################################################
22+
23+
select STDERR; $| = 1;
24+
select STDOUT; $| = 1;
25+
26+
my $t = Test::Nginx->new()->has(qw/http/)
27+
->write_file_expand('nginx.conf', <<'EOF');
28+
29+
%%TEST_GLOBALS%%
30+
31+
daemon off;
32+
33+
events {
34+
}
35+
36+
http {
37+
%%TEST_GLOBALS_HTTP%%
38+
39+
js_import test.js;
40+
41+
server {
42+
listen 127.0.0.1:8080;
43+
server_name localhost;
44+
45+
location /test {
46+
js_content test.test;
47+
}
48+
}
49+
}
50+
51+
EOF
52+
53+
$t->write_file('test.js', <<EOF);
54+
function test(r) {
55+
njs.on('exit', function() {
56+
ngx.log(ngx.WARN, `exit hook: bs: \${r.variables.bytes_sent}`);
57+
});
58+
59+
r.return(200, `bs: \${r.variables.bytes_sent}`);
60+
}
61+
62+
export default { test };
63+
64+
EOF
65+
66+
$t->try_run('no njs')->plan(2);
67+
68+
###############################################################################
69+
70+
like(http_get('/test'), qr/bs: 0/, 'response');
71+
72+
$t->stop();
73+
74+
like($t->read_file('error.log'), qr/\[warn\].*exit hook: bs: \d+/, 'exit hook logged');
75+
76+
###############################################################################

nginx/t/stream_js_exit.t

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,6 @@ EOF
108108

109109
$t->try_run('no stream njs available');
110110

111-
plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m;
112-
113111
$t->plan(2);
114112

115113
$t->run_daemon(\&stream_daemon, port(8090));

src/qjs.c

Lines changed: 180 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ typedef struct {
1919
} qjs_signal_entry_t;
2020

2121

22+
typedef struct {
23+
#define QJS_NJS_HOOK_EXIT 0
24+
JSValue hooks[1];
25+
} qjs_njs_t;
26+
27+
2228
typedef enum {
2329
QJS_ENCODING_UTF8,
2430
} qjs_encoding_t;
@@ -42,7 +48,13 @@ typedef struct {
4248
extern char **environ;
4349

4450

45-
static JSValue qjs_njs_getter(JSContext *ctx, JSValueConst this_val);
51+
static int qjs_add_intrinsic_njs(JSContext *cx, JSValueConst global);
52+
static JSValue qjs_njs_on(JSContext *ctx, JSValueConst this_val, int argc,
53+
JSValueConst *argv);
54+
static void qjs_njs_mark(JSRuntime *rt, JSValueConst val,
55+
JS_MarkFunc *mark_func);
56+
static void qjs_njs_finalizer(JSRuntime *rt, JSValue val);
57+
4658
static JSValue qjs_process_env(JSContext *ctx, JSValueConst this_val);
4759
static JSValue qjs_process_kill(JSContext *ctx, JSValueConst this_val,
4860
int argc, JSValueConst *argv);
@@ -99,10 +111,6 @@ static qjs_encoding_label_t qjs_encoding_labels[] =
99111
};
100112

101113

102-
static const JSCFunctionListEntry qjs_global_proto[] = {
103-
JS_CGETSET_DEF("njs", qjs_njs_getter, NULL),
104-
};
105-
106114
static const JSCFunctionListEntry qjs_text_decoder_proto[] = {
107115
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "TextDecoder",
108116
JS_PROP_CONFIGURABLE),
@@ -126,6 +134,7 @@ static const JSCFunctionListEntry qjs_njs_proto[] = {
126134
JS_PROP_INT32_DEF("version_number", NJS_VERSION_NUMBER,
127135
JS_PROP_C_W_E),
128136
JS_PROP_STRING_DEF("engine", "QuickJS", JS_PROP_C_W_E),
137+
JS_CFUNC_DEF("on", 2, qjs_njs_on),
129138
};
130139

131140
static const JSCFunctionListEntry qjs_process_proto[] = {
@@ -137,6 +146,13 @@ static const JSCFunctionListEntry qjs_process_proto[] = {
137146
};
138147

139148

149+
static JSClassDef qjs_njs_class = {
150+
"njs",
151+
.finalizer = qjs_njs_finalizer,
152+
.gc_mark = qjs_njs_mark,
153+
};
154+
155+
140156
static JSClassDef qjs_text_decoder_class = {
141157
"TextDecoder",
142158
.finalizer = qjs_text_decoder_finalizer,
@@ -186,6 +202,10 @@ qjs_new_context(JSRuntime *rt, qjs_module_t **addons)
186202

187203
global_obj = JS_GetGlobalObject(ctx);
188204

205+
if (qjs_add_intrinsic_njs(ctx, global_obj) < 0) {
206+
return NULL;
207+
}
208+
189209
if (qjs_add_intrinsic_text_decoder(ctx, global_obj) < 0) {
190210
return NULL;
191211
}
@@ -194,9 +214,6 @@ qjs_new_context(JSRuntime *rt, qjs_module_t **addons)
194214
return NULL;
195215
}
196216

197-
JS_SetPropertyFunctionList(ctx, global_obj, qjs_global_proto,
198-
njs_nitems(qjs_global_proto));
199-
200217
prop = JS_NewAtom(ctx, "eval");
201218
if (prop == JS_ATOM_NULL) {
202219
return NULL;
@@ -225,20 +242,168 @@ qjs_new_context(JSRuntime *rt, qjs_module_t **addons)
225242
}
226243

227244

228-
static JSValue
229-
qjs_njs_getter(JSContext *ctx, JSValueConst this_val)
245+
JSValue
246+
qjs_call_exit_hook(JSContext *ctx)
230247
{
231-
JSValue obj;
248+
JSValue global, obj, ret;
249+
qjs_njs_t *njs;
232250

233-
obj = JS_NewObject(ctx);
251+
global = JS_GetGlobalObject(ctx);
252+
253+
obj = JS_GetPropertyStr(ctx, global, "njs");
254+
if (JS_IsException(obj) || JS_IsUndefined(obj)) {
255+
goto done;
256+
}
257+
258+
njs = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_NJS);
259+
if (njs != NULL && JS_IsFunction(ctx, njs->hooks[QJS_NJS_HOOK_EXIT])) {
260+
ret = JS_Call(ctx, njs->hooks[QJS_NJS_HOOK_EXIT], JS_UNDEFINED,
261+
0, NULL);
262+
263+
JS_FreeValue(ctx, njs->hooks[QJS_NJS_HOOK_EXIT]);
264+
njs->hooks[QJS_NJS_HOOK_EXIT] = JS_UNDEFINED;
265+
266+
if (JS_IsException(ret)) {
267+
JS_FreeValue(ctx, obj);
268+
JS_FreeValue(ctx, global);
269+
return ret;
270+
}
271+
272+
JS_FreeValue(ctx, ret);
273+
}
274+
275+
done:
276+
277+
JS_FreeValue(ctx, obj);
278+
JS_FreeValue(ctx, global);
279+
280+
return JS_UNDEFINED;
281+
}
282+
283+
284+
static int
285+
qjs_add_intrinsic_njs(JSContext *cx, JSValueConst global)
286+
{
287+
JSValue obj, proto;
288+
289+
if (JS_NewClass(JS_GetRuntime(cx), QJS_CORE_CLASS_ID_NJS,
290+
&qjs_njs_class) < 0)
291+
{
292+
return -1;
293+
}
294+
295+
proto = JS_NewObject(cx);
296+
if (JS_IsException(proto)) {
297+
return -1;
298+
}
299+
300+
JS_SetPropertyFunctionList(cx, proto, qjs_njs_proto,
301+
njs_nitems(qjs_njs_proto));
302+
303+
JS_SetClassProto(cx, QJS_CORE_CLASS_ID_NJS, proto);
304+
305+
obj = JS_NewObjectClass(cx, QJS_CORE_CLASS_ID_NJS);
234306
if (JS_IsException(obj)) {
307+
return -1;
308+
}
309+
310+
if (JS_SetPropertyStr(cx, global, "njs", obj) < 0) {
311+
JS_FreeValue(cx, obj);
312+
return -1;
313+
}
314+
315+
return 0;
316+
}
317+
318+
319+
static void
320+
qjs_njs_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
321+
{
322+
unsigned i;
323+
qjs_njs_t *njs;
324+
325+
njs = JS_GetOpaque(val, QJS_CORE_CLASS_ID_NJS);
326+
if (njs != NULL) {
327+
for (i = 0; i < njs_nitems(njs->hooks); i++) {
328+
JS_MarkValue(rt, njs->hooks[i], mark_func);
329+
}
330+
}
331+
}
332+
333+
334+
static void
335+
qjs_njs_finalizer(JSRuntime *rt, JSValue val)
336+
{
337+
unsigned i;
338+
qjs_njs_t *njs;
339+
340+
njs = JS_GetOpaque(val, QJS_CORE_CLASS_ID_NJS);
341+
if (njs != NULL) {
342+
for (i = 0; i < njs_nitems(njs->hooks); i++) {
343+
JS_FreeValueRT(rt, njs->hooks[i]);
344+
}
345+
346+
js_free_rt(rt, njs);
347+
}
348+
}
349+
350+
351+
352+
static JSValue
353+
qjs_njs_on(JSContext *ctx, JSValueConst this_val, int argc,
354+
JSValueConst *argv)
355+
{
356+
unsigned i, n;
357+
qjs_njs_t *njs;
358+
njs_str_t name;
359+
360+
static const njs_str_t hooks[] = {
361+
njs_str("exit"),
362+
};
363+
364+
njs = JS_GetOpaque(this_val, QJS_CORE_CLASS_ID_NJS);
365+
if (njs == NULL) {
366+
njs = js_mallocz(ctx, sizeof(qjs_njs_t));
367+
if (njs == NULL) {
368+
return JS_ThrowOutOfMemory(ctx);
369+
}
370+
371+
JS_SetOpaque(this_val, njs);
372+
}
373+
374+
name.start = (u_char *) JS_ToCStringLen(ctx, &name.length, argv[0]);
375+
if (name.start == NULL) {
235376
return JS_EXCEPTION;
236377
}
237378

238-
JS_SetPropertyFunctionList(ctx, obj, qjs_njs_proto,
239-
njs_nitems(qjs_njs_proto));
379+
i = 0;
380+
n = njs_nitems(hooks);
240381

241-
return obj;
382+
while (i < n) {
383+
if (njs_strstr_eq(&name, &hooks[i])) {
384+
break;
385+
}
386+
387+
i++;
388+
}
389+
390+
if (i == n) {
391+
JS_ThrowTypeError(ctx, "unknown hook \"%s\"", name.start);
392+
JS_FreeCString(ctx, (const char *) name.start);
393+
return JS_EXCEPTION;
394+
}
395+
396+
JS_FreeCString(ctx, (const char *) name.start);
397+
398+
if (!JS_IsFunction(ctx, argv[1]) && !JS_IsNull(argv[1])) {
399+
JS_ThrowTypeError(ctx, "callback is not a function or null");
400+
return JS_EXCEPTION;
401+
}
402+
403+
JS_FreeValue(ctx, njs->hooks[i]);
404+
njs->hooks[i] = JS_DupValue(ctx, argv[1]);
405+
406+
return JS_UNDEFINED;
242407
}
243408

244409

0 commit comments

Comments
 (0)