From noreply at nginx.com Wed Sep 3 16:29:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 3 Sep 2025 16:29:02 +0000 (UTC) Subject: [njs] QuickJS: fix nginx configure with not configured njs module. Message-ID: <20250903162902.8064346BD7@pubserv1.nginx> details: https://github.com/nginx/njs/commit/99aac0dca38c0b3dd247e02b15164b8bf73849f7 branches: master commit: 99aac0dca38c0b3dd247e02b15164b8bf73849f7 user: Vadim Zhestikov date: Fri, 29 Aug 2025 09:42:18 -0700 description: QuickJS: fix nginx configure with not configured njs module. This fixes issue #960 which was introduced in 8259f9a. --- .github/workflows/check-pr.yml | 2 ++ auto/quickjs | 14 +++++++------- external/njs_shell.c | 1 + nginx/config | 2 +- src/qjs.h | 23 +---------------------- src/quickjs_compat.h | 24 ++++++++++++++++++++++++ 6 files changed, 36 insertions(+), 30 deletions(-) diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index 2be02e7b..c75f3cbe 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -134,7 +134,9 @@ jobs: - name: Configure and build nginx and njs modules with quickjs, asan, static modules run: | + $MAKE_UTILITY clean cd nginx-source + $MAKE_UTILITY clean $NGINX_CONFIGURE_CMD --with-cc-opt="$CC_OPT -I${{ github.workspace }}/quickjs -fsanitize=address -DNJS_DEBUG_MEMORY -DNGX_DEBUG_PALLOC -DNGX_DEBUG_MALLOC" --with-ld-opt="$LD_OPT -L${{ github.workspace }}/quickjs -fsanitize=address" --add-module=../nginx || cat objs/autoconf.err $MAKE_UTILITY -j$(nproc) diff --git a/auto/quickjs b/auto/quickjs index cbf860a2..8d6b544f 100644 --- a/auto/quickjs +++ b/auto/quickjs @@ -15,7 +15,7 @@ if [ $NJS_TRY_QUICKJS = YES ]; then njs_feature_run=yes njs_feature_incs="$NJS_QUICKJS_DEFAULT_INCS" njs_feature_libs="-lquickjs.lto -lm -ldl -lpthread" - njs_feature_test="#include + njs_feature_test="#include int main() { JSRuntime *rt; @@ -62,7 +62,7 @@ if [ $NJS_TRY_QUICKJS = YES ]; then njs_feature="QuickJS JS_GetClassID()" njs_feature_name=NJS_HAVE_QUICKJS_GET_CLASS_ID - njs_feature_test="#include + njs_feature_test="#include int main() { (void) JS_GetClassID(JS_UNDEFINED); @@ -80,7 +80,7 @@ if [ $NJS_TRY_QUICKJS = YES ]; then njs_feature="QuickJS JS_NewTypedArray()" njs_feature_name=NJS_HAVE_QUICKJS_NEW_TYPED_ARRAY - njs_feature_test="#include + njs_feature_test="#include int main() { JSValue ta, argv; @@ -102,7 +102,7 @@ if [ $NJS_TRY_QUICKJS = YES ]; then njs_feature="QuickJS JS_IsSameValue()" njs_feature_name=NJS_HAVE_QUICKJS_IS_SAME_VALUE - njs_feature_test="#include + njs_feature_test="#include int main() { JSRuntime *rt; @@ -120,7 +120,7 @@ if [ $NJS_TRY_QUICKJS = YES ]; then njs_feature="QuickJS JS_IsArray()" njs_feature_name=NJS_HAVE_QUICKJS_IS_ARRAY_SINGLE_ARG - njs_feature_test="#include + njs_feature_test="#include int main() { JSRuntime *rt; @@ -138,7 +138,7 @@ if [ $NJS_TRY_QUICKJS = YES ]; then njs_feature="QuickJS JS_AddIntrinsicBigInt()" njs_feature_name=NJS_HAVE_QUICKJS_ADD_INTRINSIC_BIG_INT - njs_feature_test="#include + njs_feature_test="#include int main() { JSRuntime *rt; @@ -157,7 +157,7 @@ if [ $NJS_TRY_QUICKJS = YES ]; then njs_feature="QuickJS version" njs_feature_name=NJS_QUICKJS_VERSION njs_feature_run=value - njs_feature_test="#include + njs_feature_test="#include int main() { #if defined(QJS_VERSION_MAJOR) diff --git a/external/njs_shell.c b/external/njs_shell.c index 75316339..c2294f75 100644 --- a/external/njs_shell.c +++ b/external/njs_shell.c @@ -13,6 +13,7 @@ #if (NJS_HAVE_QUICKJS) #include +#include #endif #if (!defined NJS_FUZZER_TARGET && defined NJS_HAVE_READLINE) diff --git a/nginx/config b/nginx/config index 474be8f3..3386dac0 100644 --- a/nginx/config +++ b/nginx/config @@ -31,7 +31,7 @@ if [ $NJS_QUICKJS != NO ]; then ngx_feature="QuickJS library -lquickjs.lto" ngx_feature_name=NJS_HAVE_QUICKJS ngx_feature_run=yes - ngx_feature_incs="#include " + ngx_feature_incs="#include " ngx_feature_path="$NJS_QUICKJS_DEFAULT_INCS" ngx_feature_libs="-lquickjs.lto -lm -ldl -lpthread" ngx_feature_test="JSRuntime *rt; diff --git a/src/qjs.h b/src/qjs.h index 954cc23c..cd5c2521 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -19,28 +19,7 @@ #include #include #include - -#ifndef __has_warning -# define __has_warning(x) 0 -#endif - -#if (defined(__GNUC__) && (__GNUC__ >= 8)) \ - || (defined(__clang__) && __has_warning("-Wcast-function-type")) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-function-type" -#endif - -#include - -#ifndef JS_BOOL -#define JS_BOOL bool -#endif - -#if (defined(__GNUC__) && (__GNUC__ >= 8)) \ - || (defined(__clang__) && __has_warning("-Wcast-function-type")) -#pragma GCC diagnostic pop -#endif -#include +#include #define QJS_CORE_CLASS_ID_OFFSET 64 diff --git a/src/quickjs_compat.h b/src/quickjs_compat.h new file mode 100644 index 00000000..3b7a618c --- /dev/null +++ b/src/quickjs_compat.h @@ -0,0 +1,24 @@ + +/* + * Copyright (C) F5, Inc. + */ + +#ifndef __has_warning + #define __has_warning(x) 0 +#endif + +#if (defined(__GNUC__) && (__GNUC__ >= 8)) \ + || (defined(__clang__) && __has_warning("-Wcast-function-type")) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wcast-function-type" + + #include + + #pragma GCC diagnostic pop +#else + #include +#endif + +#ifndef JS_BOOL + #define JS_BOOL bool +#endif From noreply at nginx.com Mon Sep 8 23:00:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 8 Sep 2025 23:00:02 +0000 (UTC) Subject: [njs] QuickJS: using enum instead of manual opaque id table. Message-ID: <20250908230002.BC9A247904@pubserv1.nginx> details: https://github.com/nginx/njs/commit/d029662fcb74e69ff5b46711b34ff08a9e822aa8 branches: master commit: d029662fcb74e69ff5b46711b34ff08a9e822aa8 user: Dmitry Volyntsev date: Mon, 25 Aug 2025 22:26:24 -0700 description: QuickJS: using enum instead of manual opaque id table. --- nginx/ngx_js.h | 37 ++++++++++++++++++++----------------- src/qjs.h | 31 ++++++++++++++++--------------- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index 5a5a79b3..0e217e90 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -39,6 +39,7 @@ #define ngx_js_buffer_type(btype) ((btype) & ~NGX_JS_DEPRECATED) +#if (NJS_HAVE_QUICKJS) /* * This static table solves the problem of a native QuickJS approach * which uses a static variables of type JSClassID and JS_NewClassID() to @@ -47,23 +48,25 @@ * 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) -#define NGX_QJS_CLASS_ID_SHARED (NGX_QJS_CLASS_ID_OFFSET + 11) -#define NGX_QJS_CLASS_ID_SHARED_DICT (NGX_QJS_CLASS_ID_OFFSET + 12) -#define NGX_QJS_CLASS_ID_SHARED_DICT_ERROR (NGX_QJS_CLASS_ID_OFFSET + 13) -#define NGX_QJS_CLASS_ID_FETCH_HEADERS (NGX_QJS_CLASS_ID_OFFSET + 14) -#define NGX_QJS_CLASS_ID_FETCH_REQUEST (NGX_QJS_CLASS_ID_OFFSET + 15) -#define NGX_QJS_CLASS_ID_FETCH_RESPONSE (NGX_QJS_CLASS_ID_OFFSET + 16) +enum { + NGX_QJS_CLASS_ID_CONSOLE = QJS_CORE_CLASS_ID_LAST, + NGX_QJS_CLASS_ID_HTTP_REQUEST, + NGX_QJS_CLASS_ID_HTTP_PERIODIC, + NGX_QJS_CLASS_ID_HTTP_VARS, + NGX_QJS_CLASS_ID_HTTP_HEADERS_IN, + NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT, + NGX_QJS_CLASS_ID_STREAM_SESSION, + NGX_QJS_CLASS_ID_STREAM_PERIODIC, + NGX_QJS_CLASS_ID_STREAM_FLAGS, + NGX_QJS_CLASS_ID_STREAM_VARS, + NGX_QJS_CLASS_ID_SHARED, + NGX_QJS_CLASS_ID_SHARED_DICT, + NGX_QJS_CLASS_ID_SHARED_DICT_ERROR, + NGX_QJS_CLASS_ID_FETCH_HEADERS, + NGX_QJS_CLASS_ID_FETCH_REQUEST, + NGX_QJS_CLASS_ID_FETCH_RESPONSE, +}; +#endif typedef struct ngx_js_loc_conf_s ngx_js_loc_conf_t; diff --git a/src/qjs.h b/src/qjs.h index cd5c2521..3b50143e 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -22,21 +22,22 @@ #include -#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_TEXT_DECODER (QJS_CORE_CLASS_ID_OFFSET + 2) -#define QJS_CORE_CLASS_ID_TEXT_ENCODER (QJS_CORE_CLASS_ID_OFFSET + 3) -#define QJS_CORE_CLASS_ID_FS_STATS (QJS_CORE_CLASS_ID_OFFSET + 4) -#define QJS_CORE_CLASS_ID_FS_DIRENT (QJS_CORE_CLASS_ID_OFFSET + 5) -#define QJS_CORE_CLASS_ID_FS_FILEHANDLE (QJS_CORE_CLASS_ID_OFFSET + 6) -#define QJS_CORE_CLASS_ID_WEBCRYPTO_KEY (QJS_CORE_CLASS_ID_OFFSET + 7) -#define QJS_CORE_CLASS_CRYPTO_HASH (QJS_CORE_CLASS_ID_OFFSET + 8) -#define QJS_CORE_CLASS_CRYPTO_HMAC (QJS_CORE_CLASS_ID_OFFSET + 9) -#define QJS_CORE_CLASS_ID_XML_DOC (QJS_CORE_CLASS_ID_OFFSET + 10) -#define QJS_CORE_CLASS_ID_XML_NODE (QJS_CORE_CLASS_ID_OFFSET + 11) -#define QJS_CORE_CLASS_ID_XML_ATTR (QJS_CORE_CLASS_ID_OFFSET + 12) -#define QJS_CORE_CLASS_ID_LAST (QJS_CORE_CLASS_ID_OFFSET + 13) +enum { + QJS_CORE_CLASS_ID_BUFFER = 64, + QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR, + QJS_CORE_CLASS_ID_TEXT_DECODER, + QJS_CORE_CLASS_ID_TEXT_ENCODER, + QJS_CORE_CLASS_ID_FS_STATS, + QJS_CORE_CLASS_ID_FS_DIRENT, + QJS_CORE_CLASS_ID_FS_FILEHANDLE, + QJS_CORE_CLASS_ID_WEBCRYPTO_KEY, + QJS_CORE_CLASS_CRYPTO_HASH, + QJS_CORE_CLASS_CRYPTO_HMAC, + QJS_CORE_CLASS_ID_XML_DOC, + QJS_CORE_CLASS_ID_XML_NODE, + QJS_CORE_CLASS_ID_XML_ATTR, + QJS_CORE_CLASS_ID_LAST, +}; typedef JSModuleDef *(*qjs_addon_init_pt)(JSContext *ctx, const char *name); From noreply at nginx.com Mon Sep 8 23:00:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 8 Sep 2025 23:00:02 +0000 (UTC) Subject: [njs] QuickJS: added njs.on('exit') API support. Message-ID: <20250908230002.DD6774913C@pubserv1.nginx> details: https://github.com/nginx/njs/commit/04f6dfb91991a855095c41df760149d9e3847bd9 branches: master commit: 04f6dfb91991a855095c41df760149d9e3847bd9 user: Dmitry Volyntsev date: Tue, 26 Aug 2025 14:22:19 -0700 description: QuickJS: added njs.on('exit') API support. This closes #955 issue on Github. --- external/njs_shell.c | 2 + nginx/ngx_http_js_module.c | 4 +- nginx/ngx_js.c | 8 ++ nginx/t/js_exit.t | 76 ++++++++++++++++++ nginx/t/stream_js_exit.t | 2 - src/qjs.c | 194 +++++++++++++++++++++++++++++++++++++++++---- src/qjs.h | 2 + 7 files changed, 269 insertions(+), 19 deletions(-) diff --git a/external/njs_shell.c b/external/njs_shell.c index c2294f75..b29ccee2 100644 --- a/external/njs_shell.c +++ b/external/njs_shell.c @@ -2703,6 +2703,8 @@ njs_engine_qjs_destroy(njs_engine_t *engine) njs_queue_link_t *link; njs_rejected_promise_t *rejected_promise; + qjs_call_exit_hook(engine->u.qjs.ctx); + console = JS_GetRuntimeOpaque(engine->u.qjs.rt); if (console->rejected_promises != NULL) { diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index c73171b8..286a0a78 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -1609,7 +1609,7 @@ static ngx_int_t ngx_http_js_init_vm(ngx_http_request_t *r, njs_int_t proto_id) { ngx_http_js_ctx_t *ctx; - ngx_pool_cleanup_t *cln; + ngx_http_cleanup_t *cln; ngx_http_js_loc_conf_t *jlcf; 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) "http js vm clone %s: %p from: %p", jlcf->engine->name, ctx->engine, jlcf->engine); - cln = ngx_pool_cleanup_add(r->pool, 0); + cln = ngx_http_cleanup_add(r, 0); if (cln == NULL) { return NGX_ERROR; } diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index d2bb781c..c385e16e 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -1130,6 +1130,7 @@ ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, uint32_t i, length; ngx_str_t exception; JSRuntime *rt; + JSValue ret; JSContext *cx; JSClassID class_id; JSMemoryUsage stats; @@ -1142,6 +1143,13 @@ ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, cx = e->u.qjs.ctx; if (ctx != NULL) { + ret = qjs_call_exit_hook(cx); + if (JS_IsException(ret)) { + ngx_qjs_exception(e, &exception); + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "js exit hook exception: %V", &exception); + } + node = njs_rbtree_min(&ctx->waiting_events); while (njs_rbtree_is_there_successor(&ctx->waiting_events, node)) { diff --git a/nginx/t/js_exit.t b/nginx/t/js_exit.t new file mode 100644 index 00000000..c0ea90ee --- /dev/null +++ b/nginx/t/js_exit.t @@ -0,0 +1,76 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) F5, Inc. + +# Tests for http njs module, njs.on('exit', ...). + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +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/) + ->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 /test { + js_content test.test; + } + } +} + +EOF + +$t->write_file('test.js', <try_run('no njs')->plan(2); + +############################################################################### + +like(http_get('/test'), qr/bs: 0/, 'response'); + +$t->stop(); + +like($t->read_file('error.log'), qr/\[warn\].*exit hook: bs: \d+/, 'exit hook logged'); + +############################################################################### diff --git a/nginx/t/stream_js_exit.t b/nginx/t/stream_js_exit.t index 01778f0f..41fbe1b3 100644 --- a/nginx/t/stream_js_exit.t +++ b/nginx/t/stream_js_exit.t @@ -108,8 +108,6 @@ EOF $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)); diff --git a/src/qjs.c b/src/qjs.c index 9c0fcdb4..b7899158 100644 --- a/src/qjs.c +++ b/src/qjs.c @@ -19,6 +19,12 @@ typedef struct { } qjs_signal_entry_t; +typedef struct { +#define QJS_NJS_HOOK_EXIT 0 + JSValue hooks[1]; +} qjs_njs_t; + + typedef enum { QJS_ENCODING_UTF8, } qjs_encoding_t; @@ -42,7 +48,13 @@ typedef struct { extern char **environ; -static JSValue qjs_njs_getter(JSContext *ctx, JSValueConst this_val); +static int qjs_add_intrinsic_njs(JSContext *cx, JSValueConst global); +static JSValue qjs_njs_on(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv); +static void qjs_njs_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void qjs_njs_finalizer(JSRuntime *rt, JSValue val); + static JSValue qjs_process_env(JSContext *ctx, JSValueConst this_val); static JSValue qjs_process_kill(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); @@ -99,10 +111,6 @@ static qjs_encoding_label_t qjs_encoding_labels[] = }; -static const JSCFunctionListEntry qjs_global_proto[] = { - JS_CGETSET_DEF("njs", qjs_njs_getter, NULL), -}; - static const JSCFunctionListEntry qjs_text_decoder_proto[] = { JS_PROP_STRING_DEF("[Symbol.toStringTag]", "TextDecoder", JS_PROP_CONFIGURABLE), @@ -126,6 +134,7 @@ static const JSCFunctionListEntry qjs_njs_proto[] = { JS_PROP_INT32_DEF("version_number", NJS_VERSION_NUMBER, JS_PROP_C_W_E), JS_PROP_STRING_DEF("engine", "QuickJS", JS_PROP_C_W_E), + JS_CFUNC_DEF("on", 2, qjs_njs_on), }; static const JSCFunctionListEntry qjs_process_proto[] = { @@ -137,6 +146,13 @@ static const JSCFunctionListEntry qjs_process_proto[] = { }; +static JSClassDef qjs_njs_class = { + "njs", + .finalizer = qjs_njs_finalizer, + .gc_mark = qjs_njs_mark, +}; + + static JSClassDef qjs_text_decoder_class = { "TextDecoder", .finalizer = qjs_text_decoder_finalizer, @@ -186,6 +202,10 @@ qjs_new_context(JSRuntime *rt, qjs_module_t **addons) global_obj = JS_GetGlobalObject(ctx); + if (qjs_add_intrinsic_njs(ctx, global_obj) < 0) { + return NULL; + } + if (qjs_add_intrinsic_text_decoder(ctx, global_obj) < 0) { return NULL; } @@ -194,9 +214,6 @@ qjs_new_context(JSRuntime *rt, qjs_module_t **addons) return NULL; } - JS_SetPropertyFunctionList(ctx, global_obj, qjs_global_proto, - njs_nitems(qjs_global_proto)); - prop = JS_NewAtom(ctx, "eval"); if (prop == JS_ATOM_NULL) { return NULL; @@ -225,20 +242,167 @@ qjs_new_context(JSRuntime *rt, qjs_module_t **addons) } -static JSValue -qjs_njs_getter(JSContext *ctx, JSValueConst this_val) +JSValue +qjs_call_exit_hook(JSContext *ctx) { - JSValue obj; + JSValue global, obj, ret; + qjs_njs_t *njs; - obj = JS_NewObject(ctx); + global = JS_GetGlobalObject(ctx); + + obj = JS_GetPropertyStr(ctx, global, "njs"); + if (JS_IsException(obj) || JS_IsUndefined(obj)) { + goto done; + } + + njs = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_NJS); + if (njs != NULL && JS_IsFunction(ctx, njs->hooks[QJS_NJS_HOOK_EXIT])) { + ret = JS_Call(ctx, njs->hooks[QJS_NJS_HOOK_EXIT], JS_UNDEFINED, + 0, NULL); + + JS_FreeValue(ctx, njs->hooks[QJS_NJS_HOOK_EXIT]); + njs->hooks[QJS_NJS_HOOK_EXIT] = JS_UNDEFINED; + + if (JS_IsException(ret)) { + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, global); + return ret; + } + + JS_FreeValue(ctx, ret); + } + +done: + + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, global); + + return JS_UNDEFINED; +} + + +static int +qjs_add_intrinsic_njs(JSContext *cx, JSValueConst global) +{ + JSValue obj, proto; + + if (JS_NewClass(JS_GetRuntime(cx), QJS_CORE_CLASS_ID_NJS, + &qjs_njs_class) < 0) + { + return -1; + } + + proto = JS_NewObject(cx); + if (JS_IsException(proto)) { + return -1; + } + + JS_SetPropertyFunctionList(cx, proto, qjs_njs_proto, + njs_nitems(qjs_njs_proto)); + + JS_SetClassProto(cx, QJS_CORE_CLASS_ID_NJS, proto); + + obj = JS_NewObjectClass(cx, QJS_CORE_CLASS_ID_NJS); if (JS_IsException(obj)) { + return -1; + } + + if (JS_SetPropertyStr(cx, global, "njs", obj) < 0) { + JS_FreeValue(cx, obj); + return -1; + } + + return 0; +} + + +static void +qjs_njs_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) +{ + unsigned i; + qjs_njs_t *njs; + + njs = JS_GetOpaque(val, QJS_CORE_CLASS_ID_NJS); + if (njs != NULL) { + for (i = 0; i < njs_nitems(njs->hooks); i++) { + JS_MarkValue(rt, njs->hooks[i], mark_func); + } + } +} + + +static void +qjs_njs_finalizer(JSRuntime *rt, JSValue val) +{ + unsigned i; + qjs_njs_t *njs; + + njs = JS_GetOpaque(val, QJS_CORE_CLASS_ID_NJS); + if (njs != NULL) { + for (i = 0; i < njs_nitems(njs->hooks); i++) { + JS_FreeValueRT(rt, njs->hooks[i]); + } + + js_free_rt(rt, njs); + } +} + + +static JSValue +qjs_njs_on(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + unsigned i, n; + qjs_njs_t *njs; + njs_str_t name; + + static const njs_str_t hooks[] = { + njs_str("exit"), + }; + + njs = JS_GetOpaque(this_val, QJS_CORE_CLASS_ID_NJS); + if (njs == NULL) { + njs = js_mallocz(ctx, sizeof(qjs_njs_t)); + if (njs == NULL) { + return JS_ThrowOutOfMemory(ctx); + } + + JS_SetOpaque(this_val, njs); + } + + name.start = (u_char *) JS_ToCStringLen(ctx, &name.length, argv[0]); + if (name.start == NULL) { return JS_EXCEPTION; } - JS_SetPropertyFunctionList(ctx, obj, qjs_njs_proto, - njs_nitems(qjs_njs_proto)); + i = 0; + n = njs_nitems(hooks); - return obj; + while (i < n) { + if (njs_strstr_eq(&name, &hooks[i])) { + break; + } + + i++; + } + + if (i == n) { + JS_ThrowTypeError(ctx, "unknown hook \"%s\"", name.start); + JS_FreeCString(ctx, (const char *) name.start); + return JS_EXCEPTION; + } + + JS_FreeCString(ctx, (const char *) name.start); + + if (!JS_IsFunction(ctx, argv[1]) && !JS_IsNull(argv[1])) { + JS_ThrowTypeError(ctx, "callback is not a function or null"); + return JS_EXCEPTION; + } + + JS_FreeValue(ctx, njs->hooks[i]); + njs->hooks[i] = JS_DupValue(ctx, argv[1]); + + return JS_UNDEFINED; } diff --git a/src/qjs.h b/src/qjs.h index 3b50143e..df5bfd11 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -27,6 +27,7 @@ enum { QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR, QJS_CORE_CLASS_ID_TEXT_DECODER, QJS_CORE_CLASS_ID_TEXT_ENCODER, + QJS_CORE_CLASS_ID_NJS, QJS_CORE_CLASS_ID_FS_STATS, QJS_CORE_CLASS_ID_FS_DIRENT, QJS_CORE_CLASS_ID_FS_FILEHANDLE, @@ -49,6 +50,7 @@ typedef struct { JSContext *qjs_new_context(JSRuntime *rt, qjs_module_t **addons); +JSValue qjs_call_exit_hook(JSContext *ctx); JSValue qjs_new_uint8_array(JSContext *ctx, int argc, JSValueConst *argv); From noreply at nginx.com Mon Sep 8 23:00:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 8 Sep 2025 23:00:02 +0000 (UTC) Subject: [njs] QuickJS: freeing temporary strings. Message-ID: <20250908230002.C346749062@pubserv1.nginx> details: https://github.com/nginx/njs/commit/a9dabca11201f5e56196c876a6b4791e858c0358 branches: master commit: a9dabca11201f5e56196c876a6b4791e858c0358 user: Dmitry Volyntsev date: Thu, 28 Aug 2025 16:29:22 -0700 description: QuickJS: freeing temporary strings. --- nginx/ngx_js_shared_dict.c | 73 ++++++++++++++++++++++++++++++++------------ nginx/ngx_qjs_fetch.c | 46 +++++++++++++++++----------- nginx/ngx_stream_js_module.c | 33 ++++++++++---------- 3 files changed, 99 insertions(+), 53 deletions(-) diff --git a/nginx/ngx_js_shared_dict.c b/nginx/ngx_js_shared_dict.c index 5445b4f2..28eed0da 100644 --- a/nginx/ngx_js_shared_dict.c +++ b/nginx/ngx_js_shared_dict.c @@ -3202,6 +3202,7 @@ static JSValue ngx_qjs_ext_shared_dict_delete(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { + JSValue ret; ngx_str_t key; ngx_shm_zone_t *shm_zone; @@ -3210,11 +3211,16 @@ ngx_qjs_ext_shared_dict_delete(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "\"this\" is not a shared dict"); } - if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { + key.data = (u_char *) JS_ToCStringLen(cx, &key.len, argv[0]); + if (key.data == NULL) { return JS_EXCEPTION; } - return ngx_qjs_dict_delete(cx, shm_zone->data, &key, 0); + ret = ngx_qjs_dict_delete(cx, shm_zone->data, &key, 0); + + JS_FreeCString(cx, (char *) key.data); + + return ret; } @@ -3245,6 +3251,7 @@ static JSValue ngx_qjs_ext_shared_dict_get(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { + JSValue ret; ngx_str_t key; ngx_shm_zone_t *shm_zone; @@ -3253,11 +3260,16 @@ ngx_qjs_ext_shared_dict_get(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "\"this\" is not a shared dict"); } - if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { + key.data = (u_char *) JS_ToCStringLen(cx, &key.len, argv[0]); + if (key.data == NULL) { return JS_EXCEPTION; } - return ngx_qjs_dict_get(cx, shm_zone->data, &key); + ret = ngx_qjs_dict_get(cx, shm_zone->data, &key); + + JS_FreeCString(cx, (char *) key.data); + + return ret; } @@ -3277,7 +3289,8 @@ ngx_qjs_ext_shared_dict_has(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "\"this\" is not a shared dict"); } - if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { + key.data = (u_char *) JS_ToCStringLen(cx, &key.len, argv[0]); + if (key.data == NULL) { return JS_EXCEPTION; } @@ -3298,6 +3311,8 @@ ngx_qjs_ext_shared_dict_has(JSContext *cx, JSValueConst this_val, ngx_rwlock_unlock(&dict->sh->rwlock); + JS_FreeCString(cx, (char *) key.data); + return JS_NewBool(cx, node != NULL); } @@ -3306,6 +3321,7 @@ static JSValue ngx_qjs_ext_shared_dict_incr(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { + JSValue ret; double delta, init; uint32_t timeout; ngx_str_t key; @@ -3323,10 +3339,6 @@ ngx_qjs_ext_shared_dict_incr(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "shared dict is not a number dict"); } - if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { - return JS_EXCEPTION; - } - if (JS_ToFloat64(cx, &delta, argv[1]) < 0) { return JS_EXCEPTION; } @@ -3357,7 +3369,16 @@ ngx_qjs_ext_shared_dict_incr(JSContext *cx, JSValueConst this_val, timeout = dict->timeout; } - return ngx_qjs_dict_incr(cx, dict, &key, delta, init, timeout); + key.data = (u_char *) JS_ToCStringLen(cx, &key.len, argv[0]); + if (key.data == NULL) { + return JS_EXCEPTION; + } + + ret = ngx_qjs_dict_incr(cx, dict, &key, delta, init, timeout); + + JS_FreeCString(cx, (char *) key.data); + + return ret; } @@ -3576,6 +3597,7 @@ static JSValue ngx_qjs_ext_shared_dict_pop(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { + JSValue ret; ngx_str_t key; ngx_shm_zone_t *shm_zone; @@ -3584,11 +3606,16 @@ ngx_qjs_ext_shared_dict_pop(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "\"this\" is not a shared dict"); } - if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { + key.data = (u_char *) JS_ToCStringLen(cx, &key.len, argv[0]); + if (key.data == NULL) { return JS_EXCEPTION; } - return ngx_qjs_dict_delete(cx, shm_zone->data, &key, 1); + ret = ngx_qjs_dict_delete(cx, shm_zone->data, &key, 1); + + JS_FreeCString(cx, (char *) key.data); + + return ret; } @@ -3607,10 +3634,6 @@ ngx_qjs_ext_shared_dict_set(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "\"this\" is not a shared dict"); } - if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { - return JS_EXCEPTION; - } - dict = shm_zone->data; if (dict->type == NGX_JS_DICT_TYPE_STRING) { @@ -3647,17 +3670,29 @@ ngx_qjs_ext_shared_dict_set(JSContext *cx, JSValueConst this_val, timeout = dict->timeout; } + key.data = (u_char *) JS_ToCStringLen(cx, &key.len, argv[0]); + if (key.data == NULL) { + return JS_EXCEPTION; + } + ret = ngx_qjs_dict_set(cx, shm_zone->data, &key, argv[1], timeout, flags); if (JS_IsException(ret)) { - return JS_EXCEPTION; + goto done; } if (flags) { /* add() or replace(). */ - return ret; + goto done; } - return JS_DupValue(cx, this_val); + JS_FreeValue(cx, ret); + ret = JS_DupValue(cx, this_val); + +done: + + JS_FreeCString(cx, (char *) key.data); + + return ret; } diff --git a/nginx/ngx_qjs_fetch.c b/nginx/ngx_qjs_fetch.c index 5ed8fc30..d043bd5d 100644 --- a/nginx/ngx_qjs_fetch.c +++ b/nginx/ngx_qjs_fetch.c @@ -1768,7 +1768,6 @@ static JSValue ngx_qjs_ext_fetch_headers_delete(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { - ngx_int_t rc; ngx_str_t name; ngx_uint_t i; ngx_list_part_t *part; @@ -1781,8 +1780,8 @@ ngx_qjs_ext_fetch_headers_delete(JSContext *cx, JSValueConst this_val, "\"this\" is not fetch headers object"); } - rc = ngx_qjs_string(cx, argv[0], &name); - if (rc != NGX_OK) { + name.data = (u_char *) JS_ToCStringLen(cx, &name.len, argv[0]); + if (name.data == NULL) { return JS_EXCEPTION; } @@ -1819,6 +1818,8 @@ ngx_qjs_ext_fetch_headers_delete(JSContext *cx, JSValueConst this_val, headers->content_type = NULL; } + JS_FreeCString(cx, (const char *) name.data); + return JS_UNDEFINED; } @@ -1827,7 +1828,6 @@ static JSValue ngx_qjs_ext_fetch_headers_foreach(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { - int ret; JSValue callback, keys, key; JSValue header, retval, arguments[2]; uint32_t length;; @@ -1862,13 +1862,14 @@ ngx_qjs_ext_fetch_headers_foreach(JSContext *cx, JSValueConst this_val, goto fail; } - ret = ngx_qjs_string(cx, key, &name); - if (ret != NGX_OK) { + name.data = (u_char *) JS_ToCStringLen(cx, &name.len, key); + if (name.data == NULL) { JS_FreeValue(cx, key); goto fail; } header = ngx_qjs_headers_get(cx, this_val, &name, 0); + JS_FreeCString(cx, (char *) name.data); if (JS_IsException(header)) { JS_FreeValue(cx, key); goto fail; @@ -1900,15 +1901,19 @@ static JSValue ngx_qjs_ext_fetch_headers_get(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { - ngx_int_t rc; + JSValue ret; ngx_str_t name; - rc = ngx_qjs_string(cx, argv[0], &name); - if (rc != NGX_OK) { + name.data = (u_char *) JS_ToCStringLen(cx, &name.len, argv[0]); + if (name.data == NULL) { return JS_EXCEPTION; } - return ngx_qjs_headers_get(cx, this_val, &name, magic); + ret = ngx_qjs_headers_get(cx, this_val, &name, magic); + + JS_FreeCString(cx, (char *) name.data); + + return ret; } @@ -1920,12 +1925,13 @@ ngx_qjs_ext_fetch_headers_has(JSContext *cx, JSValueConst this_val, ngx_int_t rc; ngx_str_t name; - rc = ngx_qjs_string(cx, argv[0], &name); - if (rc != NGX_OK) { + name.data = (u_char *) JS_ToCStringLen(cx, &name.len, argv[0]); + if (name.data == NULL) { return JS_EXCEPTION; } retval = ngx_qjs_headers_get(cx, this_val, &name, 0); + JS_FreeCString(cx, (char *) name.data); if (JS_IsException(retval)) { return JS_EXCEPTION; } @@ -1954,13 +1960,14 @@ ngx_qjs_ext_fetch_headers_set(JSContext *cx, JSValueConst this_val, "\"this\" is not fetch headers object"); } - rc = ngx_qjs_string(cx, argv[0], &name); - if (rc != NGX_OK) { + name.data = (u_char *) JS_ToCStringLen(cx, &name.len, argv[0]); + if (name.data == NULL) { return JS_EXCEPTION; } rc = ngx_qjs_string(cx, argv[1], &value); if (rc != NGX_OK) { + JS_FreeCString(cx, (const char *) name.data); return JS_EXCEPTION; } @@ -1997,12 +2004,15 @@ ngx_qjs_ext_fetch_headers_set(JSContext *cx, JSValueConst this_val, *pp = NULL; } + JS_FreeCString(cx, (const char *) name.data); + return JS_UNDEFINED; } } rc = ngx_qjs_headers_append(cx, headers, name.data, name.len, value.data, value.len); + JS_FreeCString(cx, (const char *) name.data); if (rc != NGX_OK) { return JS_EXCEPTION; } @@ -2426,7 +2436,6 @@ ngx_qjs_fetch_flag_set(JSContext *cx, const ngx_qjs_entry_t *entries, JSValue object, const char *prop) { JSValue value; - ngx_int_t rc; ngx_str_t flag; const ngx_qjs_entry_t *e; @@ -2440,9 +2449,9 @@ ngx_qjs_fetch_flag_set(JSContext *cx, const ngx_qjs_entry_t *entries, return entries[0].value; } - rc = ngx_qjs_string(cx, value, &flag); + flag.data = (u_char *) JS_ToCStringLen(cx, &flag.len, value); JS_FreeValue(cx, value); - if (rc != NGX_OK) { + if (flag.data == NULL) { return NGX_ERROR; } @@ -2450,6 +2459,7 @@ ngx_qjs_fetch_flag_set(JSContext *cx, const ngx_qjs_entry_t *entries, if (flag.len == e->name.len && ngx_strncasecmp(e->name.data, flag.data, flag.len) == 0) { + JS_FreeCString(cx, (const char *) flag.data); return e->value; } } @@ -2457,6 +2467,8 @@ ngx_qjs_fetch_flag_set(JSContext *cx, const ngx_qjs_entry_t *entries, JS_ThrowInternalError(cx, "unknown %s type: %.*s", prop, (int) flag.len, flag.data); + JS_FreeCString(cx, (const char *) flag.data); + return NGX_ERROR; } diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index 13b685e5..cb44aa1f 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -2085,9 +2085,10 @@ ngx_stream_qjs_ext_log(JSContext *cx, JSValueConst this_val, int argc, static const ngx_stream_qjs_event_t * -ngx_stream_qjs_event(ngx_stream_session_t *s, JSContext *cx, ngx_str_t *event) +ngx_stream_qjs_event(ngx_stream_session_t *s, JSContext *cx, JSValue name) { ngx_uint_t i, n, type; + ngx_str_t event; ngx_stream_js_ctx_t *ctx; static const ngx_stream_qjs_event_t events[] = { @@ -2116,14 +2117,19 @@ ngx_stream_qjs_event(ngx_stream_session_t *s, JSContext *cx, ngx_str_t *event) }, }; + event.data = (u_char *) JS_ToCStringLen(cx, &event.len, name); + if (event.data == NULL) { + return NULL; + } + 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) + if (event.len == events[i].name.len + && ngx_memcmp(event.data, events[i].name.data, event.len) == 0) { break; @@ -2134,10 +2140,13 @@ ngx_stream_qjs_event(ngx_stream_session_t *s, JSContext *cx, ngx_str_t *event) if (i == n) { (void) JS_ThrowInternalError(cx, "unknown event \"%.*s\"", - (int) event->len, event->data); + (int) event.len, event.data); + JS_FreeCString(cx, (char *) event.data); return NULL; } + JS_FreeCString(cx, (char *) event.data); + ctx->events[events[i].id].data_type = events[i].data_type; for (n = 0; n < NGX_JS_EVENT_MAX; n++) { @@ -2157,7 +2166,6 @@ 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; @@ -2169,18 +2177,14 @@ ngx_stream_qjs_ext_on(JSContext *cx, JSValueConst this_val, int argc, ctx = ngx_stream_get_module_ctx(ses->session, ngx_stream_js_module); - if (ngx_qjs_string(cx, argv[0], &name) != NGX_OK) { - return JS_EXCEPTION; - } - - e = ngx_stream_qjs_event(ses->session, cx, &name); + e = ngx_stream_qjs_event(ses->session, cx, argv[0]); 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); + e->name.data); } if (!JS_IsFunction(cx, argv[1])) { @@ -2200,7 +2204,6 @@ 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; @@ -2212,11 +2215,7 @@ ngx_stream_qjs_ext_off(JSContext *cx, JSValueConst this_val, int argc, ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); - if (ngx_qjs_string(cx, argv[0], &name) != NGX_OK) { - return JS_EXCEPTION; - } - - e = ngx_stream_qjs_event(s, cx, &name); + e = ngx_stream_qjs_event(s, cx, argv[0]); if (e == NULL) { return JS_EXCEPTION; } From noreply at nginx.com Mon Sep 8 23:00:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 8 Sep 2025 23:00:02 +0000 (UTC) Subject: [njs] QuickJS: removed extra copy of body argument in ngx.fetch(). Message-ID: <20250908230002.D27C04913B@pubserv1.nginx> details: https://github.com/nginx/njs/commit/8cfb0b960be469107ce47c4b84be6c7e0f4056e6 branches: master commit: 8cfb0b960be469107ce47c4b84be6c7e0f4056e6 user: Dmitry Volyntsev date: Thu, 28 Aug 2025 19:15:01 -0700 description: QuickJS: removed extra copy of body argument in ngx.fetch(). --- nginx/ngx_qjs_fetch.c | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/nginx/ngx_qjs_fetch.c b/nginx/ngx_qjs_fetch.c index 7130a0c2..46b54eab 100644 --- a/nginx/ngx_qjs_fetch.c +++ b/nginx/ngx_qjs_fetch.c @@ -805,10 +805,12 @@ ngx_qjs_fetch_response_ctor(JSContext *cx, JSValueConst new_target, int argc, JSValueConst *argv) { int ret; + size_t byte_offset, byte_length; u_char *p, *end; - JSValue init, value, body, proto, obj; + JSValue init, value, body, proto, obj, buf; ngx_str_t bd; ngx_int_t rc; + const char *str; ngx_pool_t *pool; ngx_js_ctx_t *ctx; ngx_js_response_t *response; @@ -913,12 +915,39 @@ ngx_qjs_fetch_response_ctor(JSContext *cx, JSValueConst new_target, int argc, body = argv[0]; if (!JS_IsNullOrUndefined(body)) { - if (ngx_qjs_string(cx, pool, body, &bd) != NGX_OK) { - return JS_ThrowInternalError(cx, "invalid Response body"); + str = NULL; + if (JS_IsString(body)) { + goto string; + } + + buf = JS_GetTypedArrayBuffer(cx, body, &byte_offset, &byte_length, NULL); + if (!JS_IsException(buf)) { + bd.data = JS_GetArrayBuffer(cx, &bd.len, buf); + + JS_FreeValue(cx, buf); + + if (bd.data != NULL) { + bd.data += byte_offset; + bd.len = byte_length; + } + + } else { + +string: + str = JS_ToCStringLen(cx, &bd.len, body); + if (str == NULL) { + return JS_EXCEPTION; + } + + bd.data = (u_char *) str; } njs_chb_append(&response->chain, bd.data, bd.len); + if (str != NULL) { + JS_FreeCString(cx, str); + } + if (JS_IsString(body)) { rc = ngx_qjs_headers_append(cx, &response->headers, (u_char *) "Content-Type", From noreply at nginx.com Mon Sep 8 23:00:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 8 Sep 2025 23:00:02 +0000 (UTC) Subject: [njs] QuickJS: fixed potential heap-use-after-free. Message-ID: <20250908230002.CD3EC49132@pubserv1.nginx> details: https://github.com/nginx/njs/commit/4eeb4c527c8031b676c720c7ebb49ad356233a31 branches: master commit: 4eeb4c527c8031b676c720c7ebb49ad356233a31 user: Dmitry Volyntsev date: Thu, 28 Aug 2025 15:20:31 -0700 description: QuickJS: fixed potential heap-use-after-free. Previously in QuickJS engine, fields allocated from memory pool linked to QuickJS engine lifetime were stored in nginx data structs. This causes a heap-use-after-free if QuickJS engine is destroyed earlier than a last access from nginx. For example, it becomes visible when moving NJS cleanup handler from pool->cleanup to r->cleanup. The fix is to only store references in nginx objects allocated from nginx memory pool. --- nginx/ngx_http_js_module.c | 46 ++++++++++++++------------------------------ nginx/ngx_js.c | 7 ++++--- nginx/ngx_js.h | 3 ++- nginx/ngx_qjs_fetch.c | 26 +++++++++++++++---------- nginx/ngx_stream_js_module.c | 2 +- 5 files changed, 37 insertions(+), 47 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 3f38f86f..c73171b8 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -5173,7 +5173,7 @@ ngx_http_qjs_ext_internal_redirect(JSContext *cx, JSValueConst this_val, "internalRedirect cannot be called while filtering"); } - if (ngx_qjs_string(cx, argv[0], &ctx->redirect_uri) != NGX_OK) { + if (ngx_qjs_string(cx, r->pool, argv[0], &ctx->redirect_uri) != NGX_OK) { return JS_EXCEPTION; } @@ -5452,7 +5452,7 @@ ngx_http_qjs_ext_return(JSContext *cx, JSValueConst this_val, ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); if (status < NGX_HTTP_BAD_REQUEST || !JS_IsNullOrUndefined(argv[1])) { - if (ngx_qjs_string(cx, argv[1], &body) != NGX_OK) { + if (ngx_qjs_string(cx, r->pool, argv[1], &body) != NGX_OK) { return JS_ThrowOutOfMemory(cx); } @@ -5557,7 +5557,7 @@ ngx_http_qjs_ext_send(JSContext *cx, JSValueConst this_val, ll = &out; for (n = 0; n < (ngx_uint_t) argc; n++) { - if (ngx_qjs_string(cx, argv[n], &s) != NGX_OK) { + if (ngx_qjs_string(cx, r->pool, argv[n], &s) != NGX_OK) { return JS_ThrowTypeError(cx, "failed to convert arg"); } @@ -5905,7 +5905,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, "the primary request"); } - if (ngx_qjs_string(cx, argv[0], &uri) != NGX_OK) { + if (ngx_qjs_string(cx, r->pool, argv[0], &uri) != NGX_OK) { return JS_ThrowTypeError(cx, "failed to convert uri arg"); } @@ -5931,7 +5931,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, arg = argv[1]; if (JS_IsString(arg)) { - if (ngx_qjs_string(cx, arg, &args) != NGX_OK) { + if (ngx_qjs_string(cx, r->pool, arg, &args) != NGX_OK) { return JS_ThrowTypeError(cx, "failed to convert args"); } @@ -5952,7 +5952,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, } if (!JS_IsUndefined(value)) { - rc = ngx_qjs_string(cx, value, &args); + rc = ngx_qjs_string(cx, r->pool, value, &args); JS_FreeValue(cx, value); if (rc != NGX_OK) { @@ -5976,7 +5976,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, } if (!JS_IsUndefined(value)) { - rc = ngx_qjs_string(cx, value, &method_name); + rc = ngx_qjs_string(cx, r->pool, value, &method_name); JS_FreeValue(cx, value); if (rc != NGX_OK) { @@ -6003,7 +6003,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, } if (!JS_IsUndefined(value)) { - rc = ngx_qjs_string(cx, value, &body_arg); + rc = ngx_qjs_string(cx, r->pool, value, &body_arg); JS_FreeValue(cx, value); if (rc != NGX_OK) { @@ -6397,7 +6397,7 @@ ngx_http_qjs_variables_set_property(JSContext *cx, JSValueConst obj, return -1; } - if (ngx_qjs_string(cx, value, &s) != NGX_OK) { + if (ngx_qjs_string(cx, r->pool, value, &s) != NGX_OK) { return -1; } @@ -6876,7 +6876,7 @@ ngx_http_qjs_headers_out_handler(JSContext *cx, ngx_http_request_t *r, } } - rc = ngx_qjs_string(cx, v, &s); + rc = ngx_qjs_string(cx, r->pool, v, &s); if (qjs_is_array(cx, *value)) { JS_FreeValue(cx, v); @@ -6908,16 +6908,7 @@ ngx_http_qjs_headers_out_handler(JSContext *cx, ngx_http_request_t *r, 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.data = s.data; h->value.len = s.len; h->hash = 1; @@ -6977,7 +6968,7 @@ ngx_http_qjs_headers_out_special_handler(JSContext *cx, ngx_http_request_t *r, setval = JS_UNDEFINED; } - rc = ngx_qjs_string(cx, setval, &s); + rc = ngx_qjs_string(cx, r->pool, setval, &s); if (value != NULL && qjs_is_array(cx, *value)) { JS_FreeValue(cx, setval); @@ -7046,16 +7037,7 @@ done: } 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.data = s.data; h->value.len = s.len; h->hash = 1; } @@ -7212,7 +7194,7 @@ ngx_http_qjs_headers_out_content_type(JSContext *cx, ngx_http_request_t *r, setval = *value; } - rc = ngx_qjs_string(cx, setval, &s); + rc = ngx_qjs_string(cx, r->pool, setval, &s); if (qjs_is_array(cx, *value)) { JS_FreeValue(cx, setval); diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index 0678c3e6..d2bb781c 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -1462,7 +1462,8 @@ ngx_qjs_integer(JSContext *cx, JSValueConst val, ngx_int_t *n) ngx_int_t -ngx_qjs_string(JSContext *cx, JSValueConst val, ngx_str_t *dst) +ngx_qjs_string(JSContext *cx, ngx_pool_t *pool, JSValueConst val, + ngx_str_t *dst) { size_t len, byte_offset, byte_length; u_char *start; @@ -1496,7 +1497,7 @@ ngx_qjs_string(JSContext *cx, JSValueConst val, ngx_str_t *dst) start += byte_offset; dst->len = byte_length; - dst->data = njs_mp_alloc(e->pool, dst->len); + dst->data = ngx_pnalloc(pool, dst->len); if (dst->data == NULL) { return NGX_ERROR; } @@ -1513,7 +1514,7 @@ string: return NGX_ERROR; } - start = njs_mp_alloc(e->pool, len); + start = ngx_pnalloc(pool, len); if (start == NULL) { JS_FreeCString(cx, str); return NGX_ERROR; diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index 0e217e90..032bd415 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -354,7 +354,8 @@ ngx_int_t ngx_qjs_call(JSContext *cx, 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(JSContext *cx, JSValueConst val, ngx_str_t *str); +ngx_int_t ngx_qjs_string(JSContext *cx, ngx_pool_t *pool, JSValueConst val, + ngx_str_t *dst); JSValue ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); diff --git a/nginx/ngx_qjs_fetch.c b/nginx/ngx_qjs_fetch.c index d043bd5d..7130a0c2 100644 --- a/nginx/ngx_qjs_fetch.c +++ b/nginx/ngx_qjs_fetch.c @@ -622,7 +622,7 @@ ngx_qjs_request_ctor(JSContext *cx, ngx_js_request_t *request, } if (JS_IsString(input)) { - rc = ngx_qjs_string(cx, input, &request->url); + rc = ngx_qjs_string(cx, pool, input, &request->url); if (rc != NGX_OK) { JS_ThrowInternalError(cx, "failed to convert url arg"); return NGX_ERROR; @@ -694,7 +694,7 @@ ngx_qjs_request_ctor(JSContext *cx, ngx_js_request_t *request, } if (!JS_IsUndefined(value)) { - rc = ngx_qjs_string(cx, value, &request->method); + rc = ngx_qjs_string(cx, pool, value, &request->method); JS_FreeValue(cx, value); if (rc != NGX_OK) { @@ -774,7 +774,7 @@ ngx_qjs_request_ctor(JSContext *cx, ngx_js_request_t *request, } if (!JS_IsUndefined(value)) { - if (ngx_qjs_string(cx, value, &request->body) != NGX_OK) { + if (ngx_qjs_string(cx, pool, value, &request->body) != NGX_OK) { JS_FreeValue(cx, value); JS_ThrowInternalError(cx, "invalid Request body"); return NGX_ERROR; @@ -866,7 +866,7 @@ ngx_qjs_fetch_response_ctor(JSContext *cx, JSValueConst new_target, int argc, } if (!JS_IsUndefined(value)) { - ret = ngx_qjs_string(cx, value, &response->status_text); + ret = ngx_qjs_string(cx, pool, value, &response->status_text); JS_FreeValue(cx, value); if (ret < 0) { @@ -913,7 +913,7 @@ ngx_qjs_fetch_response_ctor(JSContext *cx, JSValueConst new_target, int argc, body = argv[0]; if (!JS_IsNullOrUndefined(body)) { - if (ngx_qjs_string(cx, body, &bd) != NGX_OK) { + if (ngx_qjs_string(cx, pool, body, &bd) != NGX_OK) { return JS_ThrowInternalError(cx, "invalid Response body"); } @@ -1054,16 +1054,19 @@ static ngx_int_t ngx_qjs_headers_fill_header_free(JSContext *cx, ngx_js_headers_t *headers, JSValue prop_name, JSValue prop_value) { - ngx_int_t rc; - ngx_str_t name, value; + ngx_int_t rc; + ngx_str_t name, value; + ngx_pool_t *pool; + + pool = ngx_qjs_external_pool(cx, JS_GetContextOpaque(cx)); - if (ngx_qjs_string(cx, prop_name, &name) != NGX_OK) { + if (ngx_qjs_string(cx, pool, prop_name, &name) != NGX_OK) { JS_FreeValue(cx, prop_name); JS_FreeValue(cx, prop_value); return NGX_ERROR; } - if (ngx_qjs_string(cx, prop_value, &value) != NGX_OK) { + if (ngx_qjs_string(cx, pool, prop_value, &value) != NGX_OK) { JS_FreeValue(cx, prop_name); JS_FreeValue(cx, prop_value); return NGX_ERROR; @@ -1950,6 +1953,7 @@ ngx_qjs_ext_fetch_headers_set(JSContext *cx, JSValueConst this_val, ngx_int_t rc; ngx_str_t name, value; ngx_uint_t i; + ngx_pool_t *pool; ngx_list_part_t *part; ngx_js_tb_elt_t *h, **ph, **pp; ngx_js_headers_t *headers; @@ -1965,7 +1969,9 @@ ngx_qjs_ext_fetch_headers_set(JSContext *cx, JSValueConst this_val, return JS_EXCEPTION; } - rc = ngx_qjs_string(cx, argv[1], &value); + pool = ngx_qjs_external_pool(cx, JS_GetContextOpaque(cx)); + + rc = ngx_qjs_string(cx, pool, argv[1], &value); if (rc != NGX_OK) { JS_FreeCString(cx, (const char *) name.data); return JS_EXCEPTION; diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index cb44aa1f..5ccc08a2 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -2651,7 +2651,7 @@ ngx_stream_qjs_variables_set_property(JSContext *cx, JSValueConst obj, return -1; } - if (ngx_qjs_string(cx, value, &val) != NGX_OK) { + if (ngx_qjs_string(cx, s->connection->pool, value, &val) != NGX_OK) { return -1; } From pluknet at nginx.com Wed Sep 10 14:15:06 2025 From: pluknet at nginx.com (Sergey Kandaurov) Date: Wed, 10 Sep 2025 18:15:06 +0400 Subject: stale SSL errors in quic In-Reply-To: References: Message-ID: > On 22 Aug 2025, at 11:49, Vladimir Homutov wrote: > > On Thu, Aug 21, 2025 at 06:25:25PM +0400, Sergey Kandaurov wrote: >> On Fri, Aug 15, 2025 at 10:56:40AM +0300, Vladimir Homutov wrote: >>> Hello, >>> >>> Commits 7468a10b62276be4adee0fcd6aaf6244270984ab >>> "QUIC: adjusted handling of callback errors." >>> >>> and 47f96993f669543c6cb4979dd3f680ad01314ee5 >>> "QUIC: logging of SSL library errors." >>> >>> lead to the situation when you may get spurious >>> "ignoring stale global SSL error" errors in unrelated connections. >>> >>> This happens due to the fact that openssl error queue is (thread) global, >>> and quic handshake handler does not read out the error after the failed >>> handshake and relies on qc->error set by callback. >>> >>> So the error stays in queue and may show itself in unrelated quic connection, >>> typically on SSL shutdown. >>> >>> config below may be used to reproduce the issue: >>> >>>>>> >>> daemon off; >>> error_log logs/error.log debug; >>> events { >>> } >>> >>> http { >>> ssl_certificate_key localhost.key; >>> ssl_certificate localhost.crt; >>> server { >>> error_log logs/good.log debug; >>> listen 127.0.0.1:8080 quic; >>> location / { return 200 OK; } >>> } >>> server { >>> error_log logs/reject.log debug; >>> listen 127.0.0.1:8081 quic; >>> ssl_reject_handshake on; >>> location / { return 200 OK; } >>> } >>> } >>> <<< >>> >>> start the server and run: >>> >>> $ curl -k --http3 https://127.0.0.1:8080/ >>> $ curl -k --http3 https://127.0.0.1:8081/ >>> >>> The result is alert in good.log: >>> >>> 2025/08/15 09:58:19 [alert] 1154786#1154786: *1 ignoring stale global SSL error (SSL: error:10000084:SSL routines:OPENSSL_internal:CLIENTHELLO_TLSEXT error:100000be:SSL routines:OPENSSL_internal:PARSE_TLSEXT) while preparing ack, client: 127.0.0.1, server: 127.0.0.1:8080 >>> >>> caused by failed handshake in server 2; >> >> Thanks for reporting. >> I was able to reproduce this with Test::Nginx reliably: >> >> my $s = Test::Nginx::HTTP3->new(8980); >> my $s2 = Test::Nginx::HTTP3->new(8981, probe => 1); >> >> select undef, undef, undef, 0.1; >> $s = undef; >> >> select undef, undef, undef, 0.2; >> >> $t->stop(); >> >> So the error queue remains non-empty until the 1st connection SSL shutdown. >> The following happens as seen in debug (filtered): >> >> 2025/08/21 15:52:02 [debug] 73543#1490328: *1 quic run >> 2025/08/21 15:52:02 [debug] 73543#1490328: *1 SSL_do_handshake: -1 >> 2025/08/21 15:52:02 [debug] 73543#1490328: *1 SSL_do_handshake: 1 >> 2025/08/21 15:52:02 [debug] 73543#1490328: *6 quic run >> 2025/08/21 15:52:02 [debug] 73543#1490328: *6 quic ngx_quic_send_alert() level:0 alert:112 >> 2025/08/21 15:52:02 [debug] 73543#1490328: *6 SSL_do_handshake: -1 >> 2025/08/21 15:52:02 [alert] 73543#1490328: *1 ignoring stale global SSL error (SSL: error:10000084:SSL routines:OPENSSL_internal:CLIENTHELLO_TLSEXT error:100000be:SSL routines:OPENSSL_internal:PARSE_TLSEXT) while preparing ack, client: 127.0.0.1, server: 127.0.0.1:8980 >> 2025/08/21 15:52:02 [debug] 73543#1490328: *1 quic ngx_quic_send_alert() level:3 alert:0 >> 2025/08/21 15:52:02 [debug] 73543#1490328: *1 SSL_shutdown: 1 >> >> The above is using BoringSSL to match the report. >> This is equivalently broken with all other libraries >> with a slightly different error message: >> >> 2025/08/21 16:03:40 [alert] 78666#1507465: *1 ignoring stale global SSL error (SSL: error:0A0000EA:SSL routines::callback failed) while preparing ack, client: 127.0.0.1, server: 127.0.0.1:8980 >> >> I don't think 47f96993f669543c6cb4979dd3f680ad01314ee5 is related. >> Although it formally changes reproduction surface by ngx_ssl_error(), >> this touches errors, which should not normally happen in practice. >> 7468a10b62276be4adee0fcd6aaf6244270984ab is a real culprit. >> >>> >>> when the ngx_http_ssl_servername() callback returns error, >>> the ngx_quic_send_alert() sets qc->error, and the ngx_quic_ssl_handshake() >>> checks qc->error after SSL_do_handshake() and closes connection. >>> But the error stays, and manifests itself later in another connection >>> in the same process. >>> >>> The quic fix is probably something like this: >>> >>> diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c >>> index e961c80cd..dc0a030ff 100644 >>> --- a/src/event/quic/ngx_event_quic_ssl.c >>> +++ b/src/event/quic/ngx_event_quic_ssl.c >>> @@ -696,6 +696,7 @@ ngx_quic_handshake(ngx_connection_t *c) >>> ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); >>> >>> if (qc->error) { >>> + ERR_clear_error(); >>> return NGX_ERROR; >>> } >>> >>> Or may be it makes sense to clear error in the moment of setting qc->error; >>> and do not clear everything, but try to log using ngx_ssl_error() with some >>> non-alert level. >> >> What happens here is the added early check for qc->error broke the >> check for handshake_rejected, resulting in a missing diagnostics >> for the SNI-rejected handshake, and in a missing ERR_clear_error(). >> >> This can be fixed by moving the qc->error check to later such that >> handshake_rejected is checked first to make the error queue cleared. >> Although not practicably visible as needed, this can be also accompanied >> by clearing the error queue under the qc->error case as well, to be safe. >> >> Fixing this is complicated by different codes returned from SSL_get_error() >> for handshake errors happened in QUIC callbacks in various SSL libraries: >> >> # define SSL_ERROR_SSL 1 >> # define SSL_ERROR_WANT_READ 2 >> # define SSL_ERROR_WANT_WRITE 3 >> >> compat 3.5 bssl qtls lssl >> tp 2 3* 2 2 2 >> sni 1 1 1 1 1 >> >> In this regard, checking c->ssl->handshake_rejected is moved out of >> "sslerr != SSL_ERROR_WANT_READ". It is a set only flag, so this should >> be safe to do. This also somewhat simplifies and streamlines code. >> >> Patch also reconstructs a missing "SSL_do_handshake() failed" diagnostics >> for the qc->error case. It is made this way to avoid logging at the crit >> log level because qc->error is expected to have an empty error queue. >> >> Please test if it works for you. >> >> diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c >> index e961c80cd..1de896291 100644 >> --- a/src/event/quic/ngx_event_quic_ssl.c >> +++ b/src/event/quic/ngx_event_quic_ssl.c >> @@ -695,25 +695,25 @@ ngx_quic_handshake(ngx_connection_t *c) >> >> ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); >> >> - if (qc->error) { >> - return NGX_ERROR; >> - } >> - >> if (n <= 0) { >> sslerr = SSL_get_error(ssl_conn, n); >> >> ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", >> sslerr); >> >> - if (sslerr != SSL_ERROR_WANT_READ) { >> - >> - if (c->ssl->handshake_rejected) { >> - ngx_connection_error(c, 0, "handshake rejected"); >> - ERR_clear_error(); >> + if (c->ssl->handshake_rejected) { >> + ngx_connection_error(c, 0, "handshake rejected"); >> + ERR_clear_error(); >> + return NGX_ERROR; >> + } >> >> - return NGX_ERROR; >> - } >> + if (qc->error) { >> + ngx_connection_error(c, 0, "SSL_do_handshake() failed"); >> + ERR_clear_error(); >> + return NGX_ERROR; >> + } >> >> + if (sslerr != SSL_ERROR_WANT_READ) { >> ngx_ssl_connection_error(c, sslerr, 0, "SSL_do_handshake() failed"); >> return NGX_ERROR; >> } > > yes, this works and totally makes sense. > > thank you! Thanks for testing! Just for the record, submitted as https://github.com/nginx/nginx/pull/889 -- Sergey Kandaurov From noreply at nginx.com Fri Sep 12 13:58:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 12 Sep 2025 13:58:02 +0000 (UTC) Subject: [nginx] QUIC: fixed ssl_reject_handshake error handling. Message-ID: <20250912135802.86C9A47871@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/eb5ebbbed74c8ce72465bb079bde0ad29966d170 branches: master commit: eb5ebbbed74c8ce72465bb079bde0ad29966d170 user: Sergey Kandaurov date: Wed, 10 Sep 2025 17:25:36 +0400 description: QUIC: fixed ssl_reject_handshake error handling. This was broken in 7468a10b6 (1.29.0), resulting in a missing diagnostics and SSL error queue not cleared for SSL handshakes rejected by SNI, seen as "ignoring stale global SSL error" alerts, for instance, when doing SSL shutdown of a long standing connection after rejecting another one by SNI. The fix is to move the qc->error check after c->ssl->handshake_rejected is handled first, to make the error queue cleared. Although not practicably visible as needed, this is accompanied by clearing the error queue under the qc->error case as well, to be on the safe side. As an implementation note, due to the way of handling invalid transport parameters for OpenSSL 3.5 and above, which leaves a passed pointer not advanced on error, SSL_get_error() may return either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE depending on a library. To cope with that, both qc->error and c->ssl->handshake_rejected checks were moved out of "sslerr != SSL_ERROR_WANT_READ". Also, this reconstructs a missing "SSL_do_handshake() failed" diagnostics for the qc->error case, replacing using ngx_ssl_connection_error() with ngx_connection_error(). It is made this way to avoid logging at the crit log level because qc->error set is expected to have an empty error queue. Reported and tested by Vladimir Homutov. --- src/event/quic/ngx_event_quic_ssl.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index e961c80cd..355348406 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -695,30 +695,35 @@ ngx_quic_handshake(ngx_connection_t *c) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); - if (qc->error) { - return NGX_ERROR; - } - if (n <= 0) { sslerr = SSL_get_error(ssl_conn, n); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); - if (sslerr != SSL_ERROR_WANT_READ) { - - if (c->ssl->handshake_rejected) { - ngx_connection_error(c, 0, "handshake rejected"); - ERR_clear_error(); + if (c->ssl->handshake_rejected) { + ngx_connection_error(c, 0, "handshake rejected"); + ERR_clear_error(); + return NGX_ERROR; + } - return NGX_ERROR; - } + if (qc->error) { + ngx_connection_error(c, 0, "SSL_do_handshake() failed"); + ERR_clear_error(); + return NGX_ERROR; + } + if (sslerr != SSL_ERROR_WANT_READ) { ngx_ssl_connection_error(c, sslerr, 0, "SSL_do_handshake() failed"); return NGX_ERROR; } } + if (qc->error) { + ngx_connection_error(c, 0, "SSL_do_handshake() failed"); + return NGX_ERROR; + } + if (!SSL_is_init_finished(ssl_conn)) { if (ngx_quic_keys_available(qc->keys, NGX_QUIC_ENCRYPTION_EARLY_DATA, 0) && qc->client_tp_done) From noreply at nginx.com Mon Sep 15 18:14:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 15 Sep 2025 18:14:02 +0000 (UTC) Subject: [nginx] Updated link to xslscript. Message-ID: <20250915181402.5C24D48F8B@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/417c87b78dd9e69dbea3ec41313c9950b4721f8e branches: master commit: 417c87b78dd9e69dbea3ec41313c9950b4721f8e user: Sergey Kandaurov date: Mon, 18 Aug 2025 22:10:25 +0400 description: Updated link to xslscript. --- misc/README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/README b/misc/README index 3f7b3236d..a5a88f2ba 100644 --- a/misc/README +++ b/misc/README @@ -3,7 +3,7 @@ make -f misc/GNUmakefile release the required tools: *) xsltproc to build CHANGES, -*) xslscript.pl ( http://hg.nginx.org/xslscript ) to build XSLTs +*) xslscript.pl ( https://github.com/nginx/xslscript ) to build XSLTs from XSLScript sources. From noreply at nginx.com Tue Sep 16 23:08:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 16 Sep 2025 23:08:02 +0000 (UTC) Subject: [njs] Modules: simplified access to nginx values from JS engines. Message-ID: <20250916230802.93A9748854@pubserv1.nginx> details: https://github.com/nginx/njs/commit/51326759b5b5bebc960d7b89ab92a24c55639ffc branches: master commit: 51326759b5b5bebc960d7b89ab92a24c55639ffc user: Dmitry Volyntsev date: Wed, 3 Sep 2025 22:44:04 -0700 description: Modules: simplified access to nginx values from JS engines. --- nginx/ngx_http_js_module.c | 87 ++++++-------------------------------------- nginx/ngx_js.h | 76 ++++++++++++++++++++------------------ nginx/ngx_js_fetch.c | 21 +++++------ nginx/ngx_js_http.c | 6 +-- nginx/ngx_js_http.h | 2 +- nginx/ngx_qjs_fetch.c | 11 +++--- nginx/ngx_stream_js_module.c | 86 ++++++------------------------------------- 7 files changed, 83 insertions(+), 206 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 286a0a78..736635cd 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -358,10 +358,8 @@ static void ngx_http_qjs_periodic_finalizer(JSRuntime *rt, JSValue val); 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); -static ngx_msec_t ngx_http_js_fetch_timeout(ngx_http_request_t *r); -static size_t ngx_http_js_buffer_size(ngx_http_request_t *r); -static size_t ngx_http_js_max_response_buffer_size(ngx_http_request_t *r); static void ngx_http_js_event_finalize(ngx_http_request_t *r, ngx_int_t rc); +static ngx_js_loc_conf_t *ngx_http_js_loc_conf(ngx_http_request_t *r); static ngx_js_ctx_t *ngx_http_js_ctx(ngx_http_request_t *r); static void ngx_http_js_periodic_handler(ngx_event_t *ev); @@ -391,9 +389,6 @@ static void *ngx_http_js_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_js_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); -static ngx_ssl_t *ngx_http_js_ssl(ngx_http_request_t *r); -static ngx_flag_t ngx_http_js_ssl_verify(ngx_http_request_t *r); - static ngx_int_t ngx_http_js_parse_unsafe_uri(ngx_http_request_t *r, njs_str_t *uri, njs_str_t *args); @@ -972,13 +967,9 @@ static uintptr_t ngx_http_js_uptr[] = { (uintptr_t) ngx_http_js_resolver, (uintptr_t) ngx_http_js_resolver_timeout, (uintptr_t) ngx_http_js_event_finalize, - (uintptr_t) ngx_http_js_ssl, - (uintptr_t) ngx_http_js_ssl_verify, - (uintptr_t) ngx_http_js_fetch_timeout, - (uintptr_t) ngx_http_js_buffer_size, - (uintptr_t) ngx_http_js_max_response_buffer_size, - (uintptr_t) 0 /* main_conf ptr */, + (uintptr_t) ngx_http_js_loc_conf, (uintptr_t) ngx_http_js_ctx, + (uintptr_t) 0 /* main_conf ptr */, }; @@ -4666,39 +4657,6 @@ ngx_http_js_resolver_timeout(ngx_http_request_t *r) } -static ngx_msec_t -ngx_http_js_fetch_timeout(ngx_http_request_t *r) -{ - ngx_http_js_loc_conf_t *jlcf; - - jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); - - return jlcf->timeout; -} - - -static size_t -ngx_http_js_buffer_size(ngx_http_request_t *r) -{ - ngx_http_js_loc_conf_t *jlcf; - - jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); - - return jlcf->buffer_size; -} - - -static size_t -ngx_http_js_max_response_buffer_size(ngx_http_request_t *r) -{ - ngx_http_js_loc_conf_t *jlcf; - - jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); - - return jlcf->max_response_body_size; -} - - static void ngx_http_js_event_finalize(ngx_http_request_t *r, ngx_int_t rc) { @@ -4721,6 +4679,13 @@ ngx_http_js_event_finalize(ngx_http_request_t *r, ngx_int_t rc) } +static ngx_js_loc_conf_t * +ngx_http_js_loc_conf(ngx_http_request_t *r) +{ + return ngx_http_get_module_loc_conf(r, ngx_http_js_module); +} + + static ngx_js_ctx_t * ngx_http_js_ctx(ngx_http_request_t *r) { @@ -7724,7 +7689,7 @@ ngx_http_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf) 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; + ngx_http_js_uptr[NGX_JS_EXTERNAL_MAIN_CONF] = (uintptr_t) jmcf; if (conf->type == NGX_ENGINE_NJS) { options.u.njs.metas = &ngx_http_js_metas; @@ -8236,36 +8201,6 @@ ngx_http_js_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) } -static ngx_ssl_t * -ngx_http_js_ssl(ngx_http_request_t *r) -{ -#if (NGX_HTTP_SSL) - ngx_http_js_loc_conf_t *jlcf; - - jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); - - return jlcf->ssl; -#else - return NULL; -#endif -} - - -static ngx_flag_t -ngx_http_js_ssl_verify(ngx_http_request_t *r) -{ -#if (NGX_HTTP_SSL) - ngx_http_js_loc_conf_t *jlcf; - - jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); - - return jlcf->ssl_verify; -#else - return 0; -#endif -} - - static ngx_int_t ngx_http_js_parse_unsafe_uri(ngx_http_request_t *r, njs_str_t *uri, njs_str_t *args) diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index 032bd415..257227e5 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -80,9 +80,7 @@ typedef ngx_pool_t *(*ngx_external_pool_pt)(njs_external_ptr_t e); typedef void (*ngx_js_event_finalize_pt)(njs_external_ptr_t e, ngx_int_t rc); typedef ngx_resolver_t *(*ngx_external_resolver_pt)(njs_external_ptr_t e); typedef ngx_msec_t (*ngx_external_timeout_pt)(njs_external_ptr_t e); -typedef ngx_flag_t (*ngx_external_flag_pt)(njs_external_ptr_t e); -typedef ngx_flag_t (*ngx_external_size_pt)(njs_external_ptr_t e); -typedef ngx_ssl_t *(*ngx_external_ssl_pt)(njs_external_ptr_t e); +typedef ngx_js_loc_conf_t *(*ngx_js_external_loc_conf_pt)(njs_external_ptr_t e); typedef ngx_js_ctx_t *(*ngx_js_external_ctx_pt)(njs_external_ptr_t e); @@ -272,31 +270,35 @@ struct ngx_engine_s { }; +enum { + NGX_JS_EXTERNAL_CONNECTION = 0, + NGX_JS_EXTERNAL_POOL, + NGX_JS_EXTERNAL_RESOLVER, + NGX_JS_EXTERNAL_RESOLVER_TIMEOUT, + NGX_JS_EXTERNAL_EVENT_FINALIZE, + NGX_JS_EXTERNAL_LOC_CONF, + NGX_JS_EXTERNAL_CTX, + NGX_JS_EXTERNAL_MAIN_CONF, +}; + #define ngx_external_connection(vm, e) \ - (*((ngx_connection_t **) ((u_char *) (e) + njs_vm_meta(vm, 0)))) + (*((ngx_connection_t **) \ + ((u_char *) (e) + njs_vm_meta(vm, NGX_JS_EXTERNAL_CONNECTION)))) #define ngx_external_pool(vm, e) \ - ((ngx_external_pool_pt) njs_vm_meta(vm, 1))(e) + ((ngx_external_pool_pt) njs_vm_meta(vm, NGX_JS_EXTERNAL_POOL))(e) #define ngx_external_resolver(vm, e) \ - ((ngx_external_resolver_pt) njs_vm_meta(vm, 2))(e) + ((ngx_external_resolver_pt) njs_vm_meta(vm, NGX_JS_EXTERNAL_RESOLVER))(e) #define ngx_external_resolver_timeout(vm, e) \ - ((ngx_external_timeout_pt) njs_vm_meta(vm, 3))(e) + ((ngx_external_timeout_pt) \ + njs_vm_meta(vm, NGX_JS_EXTERNAL_RESOLVER_TIMEOUT))(e) #define ngx_external_event_finalize(vm) \ - ((ngx_js_event_finalize_pt) njs_vm_meta(vm, 4)) -#define ngx_external_ssl(vm, e) \ - ((ngx_external_ssl_pt) njs_vm_meta(vm, 5))(e) -#define ngx_external_ssl_verify(vm, e) \ - ((ngx_external_flag_pt) njs_vm_meta(vm, 6))(e) -#define ngx_external_fetch_timeout(vm, e) \ - ((ngx_external_timeout_pt) njs_vm_meta(vm, 7))(e) -#define ngx_external_buffer_size(vm, e) \ - ((ngx_external_size_pt) njs_vm_meta(vm, 8))(e) -#define ngx_external_max_response_buffer_size(vm, e) \ - ((ngx_external_size_pt) njs_vm_meta(vm, 9))(e) -#define NGX_JS_MAIN_CONF_INDEX 10 -#define ngx_main_conf(vm) \ - ((ngx_js_main_conf_t *) njs_vm_meta(vm, NGX_JS_MAIN_CONF_INDEX)) + ((ngx_js_event_finalize_pt) njs_vm_meta(vm, NGX_JS_EXTERNAL_EVENT_FINALIZE)) +#define ngx_external_loc_conf(vm, e) \ + ((ngx_js_external_loc_conf_pt) njs_vm_meta(vm, NGX_JS_EXTERNAL_LOC_CONF))(e) #define ngx_external_ctx(vm, e) \ - ((ngx_js_external_ctx_pt) njs_vm_meta(vm, 11))(e) + ((ngx_js_external_ctx_pt) njs_vm_meta(vm, NGX_JS_EXTERNAL_CTX))(e) +#define ngx_main_conf(vm) \ + ((ngx_js_main_conf_t *) njs_vm_meta(vm, NGX_JS_EXTERNAL_MAIN_CONF)) #define ngx_js_prop(vm, type, value, start, len) \ @@ -367,29 +369,33 @@ JSValue ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, #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)))) + (*((ngx_connection_t **) \ + ((u_char *) (e) + ngx_qjs_meta(cx, NGX_JS_EXTERNAL_CONNECTION)))) #define ngx_qjs_external_pool(cx, e) \ - ((ngx_external_pool_pt) ngx_qjs_meta(cx, 1))(e) + ((ngx_external_pool_pt) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_POOL))(e) #define ngx_qjs_external_resolver(cx, e) \ - ((ngx_external_resolver_pt) ngx_qjs_meta(cx, 2))(e) + ((ngx_external_resolver_pt) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_RESOLVER))(e) #define ngx_qjs_external_resolver_timeout(cx, e) \ - ((ngx_external_timeout_pt) ngx_qjs_meta(cx, 3))(e) + ((ngx_external_timeout_pt) \ + ngx_qjs_meta(cx, NGX_JS_EXTERNAL_RESOLVER_TIMEOUT))(e) #define ngx_qjs_external_event_finalize(cx) \ - ((ngx_js_event_finalize_pt) ngx_qjs_meta(cx, 4)) + ((ngx_js_event_finalize_pt) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_EVENT_FINALIZE)) +#define ngx_qjs_external_loc_conf(cx, e) \ + ((ngx_js_external_loc_conf_pt) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_LOC_CONF))(e) #define ngx_qjs_external_ssl(cx, e) \ - ((ngx_external_ssl_pt) ngx_qjs_meta(cx, 5))(e) + (ngx_qjs_external_loc_conf(cx, e)->ssl) #define ngx_qjs_external_ssl_verify(cx, e) \ - ((ngx_external_flag_pt) ngx_qjs_meta(cx, 6))(e) + (ngx_qjs_external_loc_conf(cx, e)->ssl_verify) #define ngx_qjs_external_fetch_timeout(cx, e) \ - ((ngx_external_timeout_pt) ngx_qjs_meta(cx, 7))(e) + (ngx_qjs_external_loc_conf(cx, e)->timeout) #define ngx_qjs_external_buffer_size(cx, e) \ - ((ngx_external_size_pt) ngx_qjs_meta(cx, 8))(e) + (ngx_qjs_external_loc_conf(cx, e)->buffer_size) #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)) + (ngx_qjs_external_loc_conf(cx, e)->max_response_body_size) #define ngx_qjs_external_ctx(cx, e) \ - ((ngx_js_external_ctx_pt) ngx_qjs_meta(cx, 11))(e) + ((ngx_js_external_ctx_pt) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_CTX))(e) +#define ngx_qjs_main_conf(cx) \ + ((ngx_js_main_conf_t *) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_MAIN_CONF)) extern qjs_module_t qjs_webcrypto_module; extern qjs_module_t qjs_xml_module; diff --git a/nginx/ngx_js_fetch.c b/nginx/ngx_js_fetch.c index faa38aab..1c74bde6 100644 --- a/nginx/ngx_js_fetch.c +++ b/nginx/ngx_js_fetch.c @@ -40,7 +40,7 @@ static njs_int_t ngx_js_headers_inherit(njs_vm_t *vm, ngx_js_headers_t *headers, static njs_int_t ngx_js_headers_fill(njs_vm_t *vm, ngx_js_headers_t *headers, njs_value_t *init); static ngx_js_fetch_t *ngx_js_fetch_alloc(njs_vm_t *vm, ngx_pool_t *pool, - ngx_log_t *log); + ngx_log_t *log, ngx_js_loc_conf_t *conf); static void ngx_js_fetch_error(ngx_js_http_t *http, const char *err); static void ngx_js_fetch_destructor(ngx_js_event_t *event); static njs_int_t ngx_js_fetch_promissified_result(njs_vm_t *vm, @@ -537,7 +537,8 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, c = ngx_external_connection(vm, external); pool = ngx_external_pool(vm, external); - fetch = ngx_js_fetch_alloc(vm, pool, c->log); + fetch = ngx_js_fetch_alloc(vm, pool, c->log, + ngx_external_loc_conf(vm, external)); if (fetch == NULL) { return NJS_ERROR; } @@ -550,15 +551,13 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } http->response.url = request.url; - http->timeout = ngx_external_fetch_timeout(vm, external); - http->buffer_size = ngx_external_buffer_size(vm, external); - http->max_response_body_size = - ngx_external_max_response_buffer_size(vm, external); + http->buffer_size = http->conf->buffer_size; + http->max_response_body_size = http->conf->max_response_body_size; #if (NGX_SSL) if (u.default_port == 443) { - http->ssl = ngx_external_ssl(vm, external); - http->ssl_verify = ngx_external_ssl_verify(vm, external); + http->ssl = http->conf->ssl; + http->ssl_verify = http->conf->ssl_verify; } #endif @@ -1134,7 +1133,8 @@ ngx_js_headers_fill(njs_vm_t *vm, ngx_js_headers_t *headers, njs_value_t *init) static ngx_js_fetch_t * -ngx_js_fetch_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log) +ngx_js_fetch_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log, + ngx_js_loc_conf_t *conf) { njs_int_t ret; ngx_js_ctx_t *ctx; @@ -1152,8 +1152,7 @@ ngx_js_fetch_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log) http->pool = pool; http->log = log; - - http->timeout = 10000; + http->conf = conf; http->http_parse.content_length_n = -1; diff --git a/nginx/ngx_js_http.c b/nginx/ngx_js_http.c index 4555a7ac..92e7b94b 100644 --- a/nginx/ngx_js_http.c +++ b/nginx/ngx_js_http.c @@ -255,8 +255,8 @@ ngx_js_http_connect(ngx_js_http_t *http) http->process = ngx_js_http_process_status_line; - ngx_add_timer(http->peer.connection->read, http->timeout); - ngx_add_timer(http->peer.connection->write, http->timeout); + ngx_add_timer(http->peer.connection->read, http->conf->timeout); + ngx_add_timer(http->peer.connection->write, http->conf->timeout); #if (NGX_SSL) if (http->ssl != NULL && http->peer.connection->ssl == NULL) { @@ -522,7 +522,7 @@ ngx_js_http_write_handler(ngx_event_t *wev) } if (!wev->timer_set) { - ngx_add_timer(wev, http->timeout); + ngx_add_timer(wev, http->conf->timeout); } } diff --git a/nginx/ngx_js_http.h b/nginx/ngx_js_http.h index 63d0f035..027d0b21 100644 --- a/nginx/ngx_js_http.h +++ b/nginx/ngx_js_http.h @@ -114,8 +114,8 @@ struct ngx_js_http_s { in_port_t port; ngx_peer_connection_t peer; - ngx_msec_t timeout; + ngx_js_loc_conf_t *conf; ngx_int_t buffer_size; ngx_int_t max_response_body_size; diff --git a/nginx/ngx_qjs_fetch.c b/nginx/ngx_qjs_fetch.c index 46b54eab..f855e099 100644 --- a/nginx/ngx_qjs_fetch.c +++ b/nginx/ngx_qjs_fetch.c @@ -39,7 +39,7 @@ static ngx_int_t ngx_qjs_headers_inherit(JSContext *cx, static ngx_int_t ngx_qjs_headers_fill(JSContext *cx, ngx_js_headers_t *headers, JSValue init); static ngx_qjs_fetch_t *ngx_qjs_fetch_alloc(JSContext *cx, ngx_pool_t *pool, - ngx_log_t *log); + ngx_log_t *log, ngx_js_loc_conf_t *conf); static void ngx_qjs_fetch_error(ngx_js_http_t *http, const char *err); static void ngx_qjs_fetch_destructor(ngx_qjs_event_t *event); static void ngx_qjs_fetch_done(ngx_qjs_fetch_t *fetch, JSValue retval, @@ -257,7 +257,8 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, c = ngx_qjs_external_connection(cx, external); pool = ngx_qjs_external_pool(cx, external); - fetch = ngx_qjs_fetch_alloc(cx, pool, c->log); + fetch = ngx_qjs_fetch_alloc(cx, pool, c->log, + ngx_qjs_external_loc_conf(cx, external)); if (fetch == NULL) { return JS_ThrowOutOfMemory(cx); } @@ -271,7 +272,6 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, http = &fetch->http; http->response.url = request.url; - http->timeout = ngx_qjs_external_fetch_timeout(cx, external); http->buffer_size = ngx_qjs_external_buffer_size(cx, external); http->max_response_body_size = ngx_qjs_external_max_response_buffer_size(cx, external); @@ -1193,7 +1193,8 @@ fail: static ngx_qjs_fetch_t * -ngx_qjs_fetch_alloc(JSContext *cx, ngx_pool_t *pool, ngx_log_t *log) +ngx_qjs_fetch_alloc(JSContext *cx, ngx_pool_t *pool, ngx_log_t *log, + ngx_js_loc_conf_t *conf) { ngx_js_ctx_t *ctx; ngx_js_http_t *http; @@ -1210,7 +1211,7 @@ ngx_qjs_fetch_alloc(JSContext *cx, ngx_pool_t *pool, ngx_log_t *log) http->pool = pool; http->log = log; - http->timeout = 10000; + http->conf = conf; http->http_parse.content_length_n = -1; diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index 5ccc08a2..1257abb5 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -194,10 +194,8 @@ static void ngx_stream_qjs_periodic_finalizer(JSRuntime *rt, JSValue val); 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); -static ngx_msec_t ngx_stream_js_fetch_timeout(ngx_stream_session_t *s); -static size_t ngx_stream_js_buffer_size(ngx_stream_session_t *s); -static size_t ngx_stream_js_max_response_buffer_size(ngx_stream_session_t *s); static void ngx_stream_js_event_finalize(ngx_stream_session_t *s, ngx_int_t rc); +static ngx_js_loc_conf_t *ngx_stream_js_srv_conf(ngx_stream_session_t *s); static ngx_js_ctx_t *ngx_stream_js_ctx(ngx_stream_session_t *s); static void ngx_stream_js_periodic_handler(ngx_event_t *ev); @@ -225,8 +223,6 @@ static char *ngx_stream_js_merge_srv_conf(ngx_conf_t *cf, void *parent, static char *ngx_stream_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); -static ngx_ssl_t *ngx_stream_js_ssl(ngx_stream_session_t *s); -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 }, @@ -710,13 +706,9 @@ static uintptr_t ngx_stream_js_uptr[] = { (uintptr_t) ngx_stream_js_resolver, (uintptr_t) ngx_stream_js_resolver_timeout, (uintptr_t) ngx_stream_js_event_finalize, - (uintptr_t) ngx_stream_js_ssl, - (uintptr_t) ngx_stream_js_ssl_verify, - (uintptr_t) ngx_stream_js_fetch_timeout, - (uintptr_t) ngx_stream_js_buffer_size, - (uintptr_t) ngx_stream_js_max_response_buffer_size, - (uintptr_t) 0 /* main_conf ptr */, + (uintptr_t) ngx_stream_js_srv_conf, (uintptr_t) ngx_stream_js_ctx, + (uintptr_t) 0 /* main_conf ptr */, }; @@ -1891,39 +1883,6 @@ ngx_stream_js_resolver_timeout(ngx_stream_session_t *s) } -static ngx_msec_t -ngx_stream_js_fetch_timeout(ngx_stream_session_t *s) -{ - ngx_stream_js_srv_conf_t *jscf; - - jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module); - - return jscf->timeout; -} - - -static size_t -ngx_stream_js_buffer_size(ngx_stream_session_t *s) -{ - ngx_stream_js_srv_conf_t *jscf; - - jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module); - - return jscf->buffer_size; -} - - -static size_t -ngx_stream_js_max_response_buffer_size(ngx_stream_session_t *s) -{ - ngx_stream_js_srv_conf_t *jscf; - - jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module); - - return jscf->max_response_body_size; -} - - static void ngx_stream_js_event_finalize(ngx_stream_session_t *s, ngx_int_t rc) { @@ -1946,6 +1905,13 @@ ngx_stream_js_event_finalize(ngx_stream_session_t *s, ngx_int_t rc) } +static ngx_js_loc_conf_t * +ngx_stream_js_srv_conf(ngx_stream_session_t *s) +{ + return ngx_stream_get_module_srv_conf(s, ngx_stream_js_module); +} + + static ngx_js_ctx_t * ngx_stream_js_ctx(ngx_stream_session_t *s) { @@ -3025,7 +2991,7 @@ ngx_stream_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf) 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; + ngx_stream_js_uptr[NGX_JS_EXTERNAL_MAIN_CONF] = (uintptr_t) jmcf; if (conf->type == NGX_ENGINE_NJS) { options.u.njs.metas = &ngx_stream_js_metas; @@ -3716,33 +3682,3 @@ ngx_stream_js_init(ngx_conf_t *cf) return NGX_OK; } - - -static ngx_ssl_t * -ngx_stream_js_ssl(ngx_stream_session_t *s) -{ -#if (NGX_STREAM_SSL) - ngx_stream_js_srv_conf_t *jscf; - - jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module); - - return jscf->ssl; -#else - return NULL; -#endif -} - - -static ngx_flag_t -ngx_stream_js_ssl_verify(ngx_stream_session_t *s) -{ -#if (NGX_STREAM_SSL) - ngx_stream_js_srv_conf_t *jscf; - - jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module); - - return jscf->ssl_verify; -#else - return 0; -#endif -} From noreply at nginx.com Tue Sep 16 23:08:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 16 Sep 2025 23:08:02 +0000 (UTC) Subject: [njs] Fetch: added parsing of HTTP version. Message-ID: <20250916230802.9733048896@pubserv1.nginx> details: https://github.com/nginx/njs/commit/8fcffdc3090985f49e9c2ecb4043d246514bde50 branches: master commit: 8fcffdc3090985f49e9c2ecb4043d246514bde50 user: Dmitry Volyntsev date: Fri, 12 Sep 2025 19:10:32 -0700 description: Fetch: added parsing of HTTP version. --- nginx/ngx_js_http.c | 21 +++++++++++++++++++++ nginx/ngx_js_http.h | 3 +++ 2 files changed, 24 insertions(+) diff --git a/nginx/ngx_js_http.c b/nginx/ngx_js_http.c index 92e7b94b..3f52868e 100644 --- a/nginx/ngx_js_http.c +++ b/nginx/ngx_js_http.c @@ -942,6 +942,12 @@ ngx_js_http_parse_status_line(ngx_js_http_parse_t *hp, ngx_buf_t *b) return NGX_ERROR; } + hp->http_major = ch - '0'; + + if (hp->http_major > 1) { + return NGX_ERROR; + } + state = sw_major_digit; break; @@ -956,6 +962,12 @@ ngx_js_http_parse_status_line(ngx_js_http_parse_t *hp, ngx_buf_t *b) return NGX_ERROR; } + hp->http_major = hp->http_major * 10 + (ch - '0'); + + if (hp->http_major > 1) { + return NGX_ERROR; + } + break; /* the first digit of minor HTTP version */ @@ -964,6 +976,7 @@ ngx_js_http_parse_status_line(ngx_js_http_parse_t *hp, ngx_buf_t *b) return NGX_ERROR; } + hp->http_minor = ch - '0'; state = sw_minor_digit; break; @@ -978,6 +991,12 @@ ngx_js_http_parse_status_line(ngx_js_http_parse_t *hp, ngx_buf_t *b) return NGX_ERROR; } + if (hp->http_minor > 99) { + return NGX_ERROR; + } + + hp->http_minor = hp->http_minor * 10 + (ch - '0'); + break; /* HTTP status code */ @@ -1055,6 +1074,8 @@ done: b->pos = p + 1; hp->state = sw_start; + hp->http_version = hp->http_major * 1000 + hp->http_minor; + return NGX_OK; } diff --git a/nginx/ngx_js_http.h b/nginx/ngx_js_http.h index 027d0b21..7adcc130 100644 --- a/nginx/ngx_js_http.h +++ b/nginx/ngx_js_http.h @@ -16,6 +16,9 @@ typedef struct ngx_js_http_s ngx_js_http_t; typedef struct { ngx_uint_t state; + unsigned http_major:16; + unsigned http_minor:16; + ngx_uint_t http_version; ngx_uint_t code; u_char *status_text; u_char *status_text_end; From noreply at nginx.com Tue Sep 16 23:08:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 16 Sep 2025 23:08:02 +0000 (UTC) Subject: [njs] Fetch: added keepalive support for ngx.fetch() API. Message-ID: <20250916230802.A6193492AF@pubserv1.nginx> details: https://github.com/nginx/njs/commit/7b3c8a66879dc0d5250bfeb53b2fbc85d15da637 branches: master commit: 7b3c8a66879dc0d5250bfeb53b2fbc85d15da637 user: Dmitry Volyntsev date: Wed, 3 Sep 2025 20:27:16 -0700 description: Fetch: added keepalive support for ngx.fetch() API. This closes #957 feature request on Github. --- nginx/ngx_http_js_module.c | 28 +++ nginx/ngx_js.c | 17 ++ nginx/ngx_js.h | 10 +- nginx/ngx_js_fetch.c | 30 ++- nginx/ngx_js_http.c | 390 ++++++++++++++++++++++++++++++++---- nginx/ngx_js_http.h | 12 +- nginx/ngx_qjs_fetch.c | 30 ++- nginx/ngx_stream_js_module.c | 28 +++ nginx/t/js_fetch_https_keepalive.t | 345 +++++++++++++++++++++++++++++++ nginx/t/js_fetch_keepalive.t | 289 ++++++++++++++++++++++++++ nginx/t/stream_js_fetch_keepalive.t | 200 ++++++++++++++++++ 11 files changed, 1309 insertions(+), 70 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 736635cd..f9d9721a 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -565,6 +565,34 @@ static ngx_command_t ngx_http_js_commands[] = { 0, NULL }, + { ngx_string("js_fetch_keepalive"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_js_loc_conf_t, fetch_keepalive), + NULL }, + + { ngx_string("js_fetch_keepalive_requests"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_js_loc_conf_t, fetch_keepalive_requests), + NULL }, + + { ngx_string("js_fetch_keepalive_time"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_js_loc_conf_t, fetch_keepalive_time), + NULL }, + + { ngx_string("js_fetch_keepalive_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_js_loc_conf_t, fetch_keepalive_timeout), + NULL }, + ngx_null_command }; diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index c385e16e..efef3467 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -10,6 +10,7 @@ #include #include #include "ngx_js.h" +#include "ngx_js_http.h" typedef struct { @@ -3986,6 +3987,11 @@ ngx_js_create_conf(ngx_conf_t *cf, size_t size) conf->max_response_body_size = NGX_CONF_UNSET_SIZE; conf->timeout = NGX_CONF_UNSET_MSEC; + conf->fetch_keepalive = NGX_CONF_UNSET_UINT; + conf->fetch_keepalive_requests = NGX_CONF_UNSET_UINT; + conf->fetch_keepalive_time = NGX_CONF_UNSET_MSEC; + conf->fetch_keepalive_timeout = NGX_CONF_UNSET_MSEC; + return conf; } @@ -4097,6 +4103,17 @@ ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child, ngx_conf_merge_size_value(conf->max_response_body_size, prev->max_response_body_size, 1048576); + ngx_conf_merge_uint_value(conf->fetch_keepalive, prev->fetch_keepalive, 0); + ngx_conf_merge_uint_value(conf->fetch_keepalive_requests, + prev->fetch_keepalive_requests, 1000); + ngx_conf_merge_msec_value(conf->fetch_keepalive_time, + prev->fetch_keepalive_time, 3600000); + ngx_conf_merge_msec_value(conf->fetch_keepalive_timeout, + prev->fetch_keepalive_timeout, 60000); + + ngx_queue_init(&conf->fetch_keepalive_cache); + ngx_queue_init(&conf->fetch_keepalive_free); + if (ngx_js_merge_vm(cf, (ngx_js_loc_conf_t *) conf, (ngx_js_loc_conf_t *) prev, init_vm) diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index 257227e5..af19e007 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -133,7 +134,14 @@ typedef struct { \ size_t buffer_size; \ size_t max_response_body_size; \ - ngx_msec_t timeout + ngx_msec_t timeout; \ + \ + ngx_uint_t fetch_keepalive; \ + ngx_uint_t fetch_keepalive_requests; \ + ngx_msec_t fetch_keepalive_time; \ + ngx_msec_t fetch_keepalive_timeout; \ + ngx_queue_t fetch_keepalive_cache; \ + ngx_queue_t fetch_keepalive_free #if defined(NGX_HTTP_SSL) || defined(NGX_STREAM_SSL) diff --git a/nginx/ngx_js_fetch.c b/nginx/ngx_js_fetch.c index 1c74bde6..ac1c1a27 100644 --- a/nginx/ngx_js_fetch.c +++ b/nginx/ngx_js_fetch.c @@ -7,10 +7,6 @@ */ -#include -#include -#include -#include #include "ngx_js.h" #include "ngx_js_http.h" @@ -550,6 +546,13 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, goto fail; } + if (u.host.len >= NGX_JS_HOST_MAX_LEN) { + njs_vm_error(vm, "Host name too long"); + goto fail; + } + + http->host = u.host; + http->port = u.port; http->response.url = request.url; http->buffer_size = http->conf->buffer_size; http->max_response_body_size = http->conf->max_response_body_size; @@ -681,18 +684,22 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, continue; } + if (h[i].key.len == 10 + && ngx_strncasecmp(h[i].key.data, (u_char *) "Connection", 10) + == 0) + { + continue; + } + njs_chb_append(&http->chain, h[i].key.data, h[i].key.len); njs_chb_append_literal(&http->chain, ": "); njs_chb_append(&http->chain, h[i].value.data, h[i].value.len); njs_chb_append_literal(&http->chain, CRLF); } - njs_chb_append_literal(&http->chain, "Connection: close" CRLF); - -#if (NGX_SSL) - http->tls_name.data = u.host.data; - http->tls_name.len = u.host.len; -#endif + if (!http->keepalive) { + njs_chb_append_literal(&http->chain, "Connection: close" CRLF); + } if (request.body.len != 0) { njs_chb_sprintf(&http->chain, 32, "Content-Length: %uz" CRLF CRLF, @@ -1154,7 +1161,8 @@ ngx_js_fetch_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log, http->log = log; http->conf = conf; - http->http_parse.content_length_n = -1; + http->content_length_n = -1; + http->keepalive = (conf->fetch_keepalive > 0); http->append_headers = ngx_js_fetch_append_headers; http->ready_handler = ngx_js_fetch_process_done; diff --git a/nginx/ngx_js_http.c b/nginx/ngx_js_http.c index 3f52868e..f07ccceb 100644 --- a/nginx/ngx_js_http.c +++ b/nginx/ngx_js_http.c @@ -7,19 +7,35 @@ */ -#include -#include -#include -#include #include "ngx_js.h" #include "ngx_js_http.h" +typedef struct { + ngx_js_loc_conf_t *conf; + ngx_queue_t queue; + ngx_connection_t *connection; + + ngx_flag_t ssl; + size_t host_len; + u_char host[NGX_JS_HOST_MAX_LEN]; + in_port_t port; +} ngx_js_http_keepalive_cache_t; + + +#define ngx_js_http_version(major, minor) ((major) * 1000 + (minor)) + + static void ngx_js_http_resolve_handler(ngx_resolver_ctx_t *ctx); static void ngx_js_http_next(ngx_js_http_t *http); static void ngx_js_http_write_handler(ngx_event_t *wev); static void ngx_js_http_read_handler(ngx_event_t *rev); static void ngx_js_http_dummy_handler(ngx_event_t *ev); +static void ngx_js_http_keepalive_close_handler(ngx_event_t *ev); +static void ngx_js_http_keepalive_dummy_handler(ngx_event_t *ev); + +static ngx_int_t ngx_js_http_get_keepalive_connection(ngx_js_http_t *http); +static ngx_int_t ngx_js_http_free_keepalive_connection(ngx_js_http_t *http); static ngx_int_t ngx_js_http_process_status_line(ngx_js_http_t *http); static ngx_int_t ngx_js_http_process_headers(ngx_js_http_t *http); @@ -177,12 +193,13 @@ failed: static void ngx_js_http_close_connection(ngx_connection_t *c) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "js http close connection: %d", c->fd); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "js http close connection: %p:%d", c, c->fd); #if (NGX_SSL) if (c->ssl) { c->ssl->no_wait_shutdown = 1; + c->ssl->no_send_shutdown = 1; if (ngx_ssl_shutdown(c) == NGX_AGAIN) { c->ssl->handler = ngx_js_http_close_connection; @@ -193,6 +210,7 @@ ngx_js_http_close_connection(ngx_connection_t *c) c->destroyed = 1; + ngx_destroy_pool(c->pool); ngx_close_connection(c); } @@ -210,18 +228,32 @@ ngx_js_http_resolve_done(ngx_js_http_t *http) void ngx_js_http_close_peer(ngx_js_http_t *http) { - if (http->peer.connection != NULL) { + if (http->peer.connection == NULL) { + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, http->log, 0, + "js http close peer"); + + if (http->keepalive) { + if (ngx_js_http_free_keepalive_connection(http) != NGX_OK) { + ngx_js_http_close_connection(http->peer.connection); + } + + } else { ngx_js_http_close_connection(http->peer.connection); - http->peer.connection = NULL; } + + http->peer.connection = NULL; } void ngx_js_http_connect(ngx_js_http_t *http) { - ngx_int_t rc; - ngx_addr_t *addr; + ngx_int_t rc; + ngx_addr_t *addr; + ngx_connection_t *c; addr = &http->addrs[http->naddr]; @@ -235,38 +267,51 @@ ngx_js_http_connect(ngx_js_http_t *http) http->peer.log = http->log; http->peer.log_error = NGX_ERROR_ERR; - rc = ngx_event_connect_peer(&http->peer); + rc = ngx_js_http_get_keepalive_connection(http); + if (rc != NGX_OK) { + rc = ngx_event_connect_peer(&http->peer); + if (rc == NGX_ERROR) { + ngx_js_http_error(http, "connect failed"); + return; + } - if (rc == NGX_ERROR) { - ngx_js_http_error(http, "connect failed"); - return; + if (rc == NGX_BUSY || rc == NGX_DECLINED) { + ngx_js_http_next(http); + return; + } } - if (rc == NGX_BUSY || rc == NGX_DECLINED) { - ngx_js_http_next(http); - return; - } + c = http->peer.connection; - http->peer.connection->data = http; - http->peer.connection->pool = http->pool; + c->requests++; + c->data = http; - http->peer.connection->write->handler = ngx_js_http_write_handler; - http->peer.connection->read->handler = ngx_js_http_read_handler; + if (c->pool == NULL) { + /* we need separate pool here to be able to cache SSL connections */ + c->pool = ngx_create_pool(128, http->log); + if (c->pool == NULL) { + ngx_js_http_error(http, "create pool failed"); + return; + } + } + + c->write->handler = ngx_js_http_write_handler; + c->read->handler = ngx_js_http_read_handler; http->process = ngx_js_http_process_status_line; - ngx_add_timer(http->peer.connection->read, http->conf->timeout); - ngx_add_timer(http->peer.connection->write, http->conf->timeout); + ngx_add_timer(c->read, http->conf->timeout); + ngx_add_timer(c->write, http->conf->timeout); #if (NGX_SSL) - if (http->ssl != NULL && http->peer.connection->ssl == NULL) { + if (http->ssl != NULL && c->ssl == NULL) { ngx_js_http_ssl_init_connection(http); return; } #endif if (rc == NGX_OK) { - ngx_js_http_write_handler(http->peer.connection->write); + ngx_js_http_write_handler(c->write); } } @@ -346,10 +391,10 @@ ngx_js_http_ssl_handshake(ngx_js_http_t *http) goto failed; } - if (ngx_ssl_check_host(c, &http->tls_name) != NGX_OK) { + if (ngx_ssl_check_host(c, &http->host) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "js http SSL certificate does not match \"%V\"", - &http->tls_name); + &http->host); goto failed; } } @@ -380,7 +425,7 @@ ngx_js_http_ssl_name(ngx_js_http_t *http) u_char *p; /* as per RFC 6066, literal IPv4 and IPv6 addresses are not permitted */ - ngx_str_t *name = &http->tls_name; + ngx_str_t *name = &http->host; if (name->len == 0 || *name->data == '[') { goto done; @@ -571,6 +616,10 @@ ngx_js_http_read_handler(ngx_event_t *rev) return; } + if (rc == NGX_DONE) { + break; + } + continue; } @@ -631,6 +680,10 @@ ngx_js_http_process_status_line(ngx_js_http_t *http) http->response.status_text.len = hp->status_text_end - hp->status_text; http->process = ngx_js_http_process_headers; + if (http->keepalive) { + http->keepalive = (hp->http_version >= ngx_js_http_version(1, 1)); + } + return http->process(http); } @@ -694,21 +747,33 @@ ngx_js_http_process_headers(ngx_js_http_t *http) && ngx_strncasecmp(hp->header_start, (u_char *) "chunked", vlen) == 0) { - hp->chunked = 1; + http->chunked = 1; + } + + if (len == (sizeof("Connection") - 1) + && ngx_strncasecmp(hp->header_name_start, + (u_char *) "Connection", len) == 0) + { + if (vlen == (sizeof("close") - 1) + && ngx_strncasecmp(hp->header_start, (u_char *) "close", + vlen) == 0) + { + http->keepalive = 0; + } } if (len == (sizeof("Content-Length") - 1) && ngx_strncasecmp(hp->header_name_start, (u_char *) "Content-Length", len) == 0) { - hp->content_length_n = ngx_atoof(hp->header_start, vlen); - if (hp->content_length_n == NGX_ERROR) { + http->content_length_n = ngx_atoof(hp->header_start, vlen); + if (http->content_length_n == NGX_ERROR) { ngx_js_http_error(http, "invalid http content length"); return NGX_ERROR; } if (!http->header_only - && hp->content_length_n + && http->content_length_n > (off_t) http->max_response_body_size) { ngx_js_http_error(http, @@ -762,22 +827,22 @@ ngx_js_http_process_body(ngx_js_http_t *http) } if (!http->header_only - && http->http_parse.chunked - && http->http_parse.content_length_n == -1) + && http->chunked + && http->content_length_n == -1) { ngx_js_http_error(http, "invalid http chunked response"); return NGX_ERROR; } if (http->header_only - || http->http_parse.content_length_n == -1 - || size == http->http_parse.content_length_n) + || http->content_length_n == -1 + || size == http->content_length_n) { http->ready_handler(http); return NGX_DONE; } - if (size < http->http_parse.content_length_n) { + if (size < http->content_length_n) { return NGX_AGAIN; } @@ -787,7 +852,7 @@ ngx_js_http_process_body(ngx_js_http_t *http) b = http->buffer; - if (http->http_parse.chunked) { + if (http->chunked) { rc = ngx_js_http_parse_chunked(&http->http_chunk_parse, b, &http->response.chain); if (rc == NGX_ERROR) { @@ -798,7 +863,7 @@ ngx_js_http_process_body(ngx_js_http_t *http) size = njs_chb_size(&http->response.chain); if (rc == NGX_OK) { - http->http_parse.content_length_n = size; + http->content_length_n = size; } if (size > http->max_response_body_size * 10) { @@ -814,11 +879,11 @@ ngx_js_http_process_body(ngx_js_http_t *http) if (http->header_only) { need = 0; - } else if (http->http_parse.content_length_n == -1) { + } else if (http->content_length_n == -1) { need = http->max_response_body_size - size; } else { - need = http->http_parse.content_length_n - size; + need = http->content_length_n - size; } chsize = ngx_min(need, b->last - b->pos); @@ -1074,7 +1139,7 @@ done: b->pos = p + 1; hp->state = sw_start; - hp->http_version = hp->http_major * 1000 + hp->http_minor; + hp->http_version = ngx_js_http_version(hp->http_major, hp->http_minor); return NGX_OK; } @@ -1549,3 +1614,242 @@ ngx_js_check_header_name(u_char *name, size_t len) return NGX_OK; } + + +static ngx_int_t +ngx_js_http_get_keepalive_connection(ngx_js_http_t *http) +{ + ngx_str_t *host; + ngx_queue_t *q; + ngx_connection_t *c; + ngx_js_loc_conf_t *conf; + ngx_js_http_keepalive_cache_t *cache; + + if (!http->keepalive) { + return NGX_DECLINED; + } + + conf = http->conf; + + host = &http->host; + + for (q = ngx_queue_head(&conf->fetch_keepalive_cache); + q != ngx_queue_sentinel(&conf->fetch_keepalive_cache); + q = ngx_queue_next(q)) + { + cache = ngx_queue_data(q, ngx_js_http_keepalive_cache_t, queue); + + if (host->len != cache->host_len) { + continue; + } + + if ((http->ssl != NULL) != (cache->ssl != 0)) { + continue; + } + + if (ngx_strncasecmp(host->data, cache->host, host->len) != 0) { + continue; + } + + if (http->port != cache->port) { + continue; + } + + c = cache->connection; + ngx_queue_remove(q); + ngx_queue_insert_head(&conf->fetch_keepalive_free, q); + + goto found; + } + + return NGX_DECLINED; + +found: + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, http->log, 0, + "js http keepalive using cached connection: %p:%d", + c, c->fd); + + c->idle = 0; + c->sent = 0; + c->data = NULL; + c->log = http->log; + c->pool->log = http->log; + c->read->log = http->log; + c->write->log = http->log; + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + http->peer.cached = 1; + http->peer.connection = c; + + return NGX_OK; +} + + +static ngx_int_t +ngx_js_http_free_keepalive_connection(ngx_js_http_t *http) +{ + ngx_uint_t i; + ngx_queue_t *q; + ngx_connection_t *c; + ngx_js_loc_conf_t *conf; + ngx_js_http_keepalive_cache_t *cache; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, http->log, 0, + "js http free keepalive connection"); + + c = http->peer.connection; + + if (c == NULL + || c->read->eof + || c->read->error + || c->read->timedout + || c->write->error + || c->write->timedout) + { + return NGX_ERROR; + } + + if (c->requests >= http->conf->fetch_keepalive_requests) { + return NGX_DONE; + } + + if (ngx_current_msec - c->start_time > http->conf->fetch_keepalive_time) { + return NGX_DONE; + } + + if (ngx_terminate || ngx_exiting) { + return NGX_DONE; + } + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, http->log, 0, + "js http free keepalive connection, " + "saving connection: %p:%d", c, c->fd); + + conf = http->conf; + + if (ngx_queue_empty(&conf->fetch_keepalive_cache) + && ngx_queue_empty(&conf->fetch_keepalive_free)) + { + cache = ngx_pcalloc(ngx_cycle->pool, + sizeof(ngx_js_http_keepalive_cache_t) * conf->fetch_keepalive); + if (cache == NULL) { + return NGX_ERROR; + } + + for (i = 0; i < conf->fetch_keepalive; i++) { + ngx_queue_insert_head(&conf->fetch_keepalive_free, + &cache[i].queue); + cache[i].conf = conf; + } + } + + if (ngx_queue_empty(&conf->fetch_keepalive_free)) { + /* evict from cache */ + q = ngx_queue_last(&conf->fetch_keepalive_cache); + ngx_queue_remove(q); + + cache = ngx_queue_data(q, ngx_js_http_keepalive_cache_t, queue); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, + "js http free keepalive connection, evicting: %d", + cache->connection->fd); + ngx_js_http_close_connection(cache->connection); + + } else { + q = ngx_queue_head(&conf->fetch_keepalive_free); + ngx_queue_remove(q); + cache = ngx_queue_data(q, ngx_js_http_keepalive_cache_t, queue); + } + + ngx_queue_insert_head(&conf->fetch_keepalive_cache, q); + + c = http->peer.connection; + http->peer.connection = NULL; + + cache->connection = c; + + cache->ssl = (http->ssl != NULL); + ngx_memcpy(cache->host, http->host.data, http->host.len); + cache->host_len = http->host.len; + cache->port = http->port; + + c->read->delayed = 0; + ngx_add_timer(c->read, conf->fetch_keepalive_timeout); + + if (c->write->timer_set) { + ngx_del_timer(c->write); + } + + c->data = cache; + c->write->handler = ngx_js_http_keepalive_dummy_handler; + c->read->handler = ngx_js_http_keepalive_close_handler; + + c->idle = 1; + c->log = ngx_cycle->log; + c->pool->log = ngx_cycle->log; + c->read->log = ngx_cycle->log; + c->write->log = ngx_cycle->log; + + if (c->read->ready) { + ngx_js_http_keepalive_close_handler(c->read); + } + + return NGX_OK; +} + + +static void +ngx_js_http_keepalive_dummy_handler(ngx_event_t *ev) +{ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "js http keepalive dummy handler"); +} + + +static void +ngx_js_http_keepalive_close_handler(ngx_event_t *ev) +{ + ssize_t n; + ngx_connection_t *c; + ngx_js_loc_conf_t *conf; + ngx_js_http_keepalive_cache_t *cache; + u_char buf[1]; + + c = ev->data; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ev->log, 0, + "js http keepalive close handler: %d", c->fd); + + if (c->close || ev->timedout) { + goto close; + } + + n = recv(c->fd, buf, 1, MSG_PEEK); + + if (n == -1 && ngx_socket_errno == NGX_EAGAIN) { + ev->ready = 0; + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + goto close; + } + + return; + } + +close: + + cache = c->data; + conf = cache->conf; + + ngx_js_http_close_connection(c); + + ngx_queue_remove(&cache->queue); + ngx_queue_insert_head(&conf->fetch_keepalive_free, &cache->queue); +} diff --git a/nginx/ngx_js_http.h b/nginx/ngx_js_http.h index 7adcc130..acaa5802 100644 --- a/nginx/ngx_js_http.h +++ b/nginx/ngx_js_http.h @@ -11,8 +11,10 @@ #define _NGX_JS_HTTP_H_INCLUDED_ -typedef struct ngx_js_http_s ngx_js_http_t; +#define NGX_JS_HOST_MAX_LEN 256 + +typedef struct ngx_js_http_s ngx_js_http_t; typedef struct { ngx_uint_t state; @@ -23,8 +25,6 @@ typedef struct { u_char *status_text; u_char *status_text_end; ngx_uint_t count; - ngx_flag_t chunked; - off_t content_length_n; u_char *header_name_start; u_char *header_name_end; @@ -114,6 +114,7 @@ struct ngx_js_http_s { ngx_addr_t *addrs; ngx_uint_t naddrs; ngx_uint_t naddr; + ngx_str_t host; in_port_t port; ngx_peer_connection_t peer; @@ -124,8 +125,11 @@ struct ngx_js_http_s { unsigned header_only; + ngx_flag_t chunked; + ngx_flag_t keepalive; + off_t content_length_n; + #if (NGX_SSL) - ngx_str_t tls_name; ngx_ssl_t *ssl; njs_bool_t ssl_verify; #endif diff --git a/nginx/ngx_qjs_fetch.c b/nginx/ngx_qjs_fetch.c index f855e099..a211e0fe 100644 --- a/nginx/ngx_qjs_fetch.c +++ b/nginx/ngx_qjs_fetch.c @@ -5,10 +5,6 @@ */ -#include -#include -#include -#include #include "ngx_js.h" #include "ngx_js_http.h" @@ -270,7 +266,14 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, goto fail; } + if (u.host.len >= NGX_JS_HOST_MAX_LEN) { + JS_ThrowInternalError(cx, "Host name too long"); + goto fail; + } + http = &fetch->http; + http->host = u.host; + http->port = u.port; http->response.url = request.url; http->buffer_size = ngx_qjs_external_buffer_size(cx, external); http->max_response_body_size = @@ -418,18 +421,22 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, continue; } + if (h[i].key.len == 10 + && ngx_strncasecmp(h[i].key.data, (u_char *) "Connection", 10) + == 0) + { + continue; + } + njs_chb_append(&http->chain, h[i].key.data, h[i].key.len); njs_chb_append_literal(&http->chain, ": "); njs_chb_append(&http->chain, h[i].value.data, h[i].value.len); njs_chb_append_literal(&http->chain, CRLF); } - njs_chb_append_literal(&http->chain, "Connection: close" CRLF); - -#if (NGX_SSL) - http->tls_name.data = u.host.data; - http->tls_name.len = u.host.len; -#endif + if (!http->keepalive) { + njs_chb_append_literal(&http->chain, "Connection: close" CRLF); + } if (request.body.len != 0) { njs_chb_sprintf(&http->chain, 32, "Content-Length: %uz" CRLF CRLF, @@ -1213,7 +1220,8 @@ ngx_qjs_fetch_alloc(JSContext *cx, ngx_pool_t *pool, ngx_log_t *log, http->conf = conf; - http->http_parse.content_length_n = -1; + http->content_length_n = -1; + http->keepalive = (conf->fetch_keepalive > 0); ngx_qjs_arg(http->response.header_value) = JS_UNDEFINED; diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index 1257abb5..ab4e787d 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -351,6 +351,34 @@ static ngx_command_t ngx_stream_js_commands[] = { offsetof(ngx_stream_js_srv_conf_t, timeout), NULL }, + { ngx_string("js_fetch_keepalive"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_js_srv_conf_t, fetch_keepalive), + NULL }, + + { ngx_string("js_fetch_keepalive_requests"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_js_srv_conf_t, fetch_keepalive_requests), + NULL }, + + { ngx_string("js_fetch_keepalive_time"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_js_srv_conf_t, fetch_keepalive_time), + NULL }, + + { ngx_string("js_fetch_keepalive_timeout"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_js_srv_conf_t, fetch_keepalive_timeout), + NULL }, + #if (NGX_STREAM_SSL) { ngx_string("js_fetch_ciphers"), diff --git a/nginx/t/js_fetch_https_keepalive.t b/nginx/t/js_fetch_https_keepalive.t new file mode 100644 index 00000000..86f72d0a --- /dev/null +++ b/nginx/t/js_fetch_https_keepalive.t @@ -0,0 +1,345 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) F5, Inc. + +# Tests for http njs module, fetch method, https keepalive support. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_ssl rewrite/) + ->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; + + resolver 127.0.0.1:%%PORT_8981_UDP%%; + resolver_timeout 1s; + + location /njs { + js_content test.njs; + } + + location /https { + js_content test.https; + + js_fetch_keepalive 4; + js_fetch_ciphers HIGH:!aNull:!MD5; + js_fetch_protocols TLSv1.1 TLSv1.2; + js_fetch_trusted_certificate myca.crt; + } + + location /sni_isolation { + js_content test.sni_isolation; + + js_fetch_keepalive 4; + js_fetch_ciphers HIGH:!aNull:!MD5; + js_fetch_protocols TLSv1.1 TLSv1.2; + js_fetch_trusted_certificate myca.crt; + } + + location /plain_vs_https_isolation { + js_content test.plain_vs_https_isolation; + + js_fetch_keepalive 4; + js_fetch_ciphers HIGH:!aNull:!MD5; + js_fetch_protocols TLSv1.1 TLSv1.2; + js_fetch_trusted_certificate myca.crt; + } + } + + server { + listen 127.0.0.1:8081 ssl; + server_name ka.example.com; + + keepalive_requests 100; + + ssl_certificate ka.example.com.chained.crt; + ssl_certificate_key ka.example.com.key; + + location /loc { + return 200 CONN:$connection_requests; + } + } + + server { + listen 127.0.0.1:8081 ssl; + server_name 1.example.com; + + ssl_certificate 1.example.com.chained.crt; + ssl_certificate_key 1.example.com.key; + + location /loc { + return 200 "You are at 1.example.com."; + } + } + + server { + listen 127.0.0.1:8082; + server_name plain.example.com; + + keepalive_requests 100; + + location /loc { + return 200 PLAIN:$connection_requests; + } + } +} + +EOF + +my $p1 = port(8081); +my $p2 = port(8082); + +$t->write_file('test.js', < reply.text()) + .then(body => r.return(200, body)) + .catch(e => r.return(501, e.message)) + } + + async function sni_isolation(r) { + try { + let resp = await ngx.fetch(`https://ka.example.com:$p1/loc`); + let body1 = await resp.text(); + + resp = await ngx.fetch(`https://1.example.com:$p1/loc`); + let body2 = await resp.text(); + + resp = await ngx.fetch(`https://ka.example.com:$p1/loc`); + let body3 = await resp.text(); + + r.return(200, `\${body1}|\${body2}|\${body3}`); + + } catch (e) { + r.return(501, e.message); + } + } + + async function plain_vs_https_isolation(r) { + try { + let resp = await ngx.fetch(`https://ka.example.com:$p1/loc`); + let body1 = await resp.text(); + + resp = await ngx.fetch(`http://plain.example.com:$p2/loc`); + let body2 = await resp.text(); + + resp = await ngx.fetch(`https://ka.example.com:$p1/loc`); + let body3 = await resp.text(); + + r.return(200, `\${body1}|\${body2}|\${body3}`); + + } catch (e) { + r.return(501, e.message); + } + } + + export default {njs: test_njs, https, sni_isolation, + plain_vs_https_isolation}; +EOF + +my $d = $t->testdir(); + +$t->write_file('openssl.conf', <write_file('myca.conf', <>$d/openssl.out 2>&1") == 0 + or die "Can't create self-signed certificate for CA: $!\n"; + +foreach my $name ('intermediate', '1.example.com', 'ka.example.com') { + system("openssl req -new " + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.csr -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate signing req for $name: $!\n"; +} + +$t->write_file('certserial', '1000'); +$t->write_file('certindex', ''); + +system("openssl ca -batch -config $d/myca.conf " + . "-keyfile $d/myca.key -cert $d/myca.crt " + . "-subj /CN=intermediate/ -in $d/intermediate.csr " + . "-out $d/intermediate.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't sign certificate for intermediate: $!\n"; + +foreach my $name ('1.example.com', 'ka.example.com') { + system("openssl ca -batch -config $d/myca.conf " + . "-keyfile $d/intermediate.key -cert $d/intermediate.crt " + . "-subj /CN=$name/ -in $d/$name.csr -out $d/$name.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't sign certificate for $name $!\n"; + $t->write_file("$name.chained.crt", $t->read_file("$name.crt") + . $t->read_file('intermediate.crt')); +} + +$t->try_run('no njs.fetch'); + +$t->plan(5); + +$t->run_daemon(\&dns_daemon, port(8981), $t); +$t->waitforfile($t->testdir . '/' . port(8981)); + +############################################################################### + +like(http_get('/https?domain=localhost'), + qr/connect failed/s, 'fetch https wrong CN certificate'); +like(http_get('/https?domain=ka.example.com'), + qr/CONN:1$/s, 'fetch https keepalive'); +like(http_get('/https?domain=ka.example.com'), + qr/CONN:2$/s, 'fetch https keepalive reused'); +like(http_get('/sni_isolation'), + qr/CONN:1\|You are at 1\.example\.com\.\|CONN:2$/s, + 'fetch https keepalive SNI isolation'); +like(http_get('/plain_vs_https_isolation'), + qr/CONN:1\|PLAIN:1\|CONN:2$/s, + 'fetch https->plain->https keepalive isolation'); + +############################################################################### + +sub reply_handler { + my ($recv_data, $port, %extra) = @_; + + my (@name, @rdata); + + use constant NOERROR => 0; + use constant A => 1; + use constant IN => 1; + + # default values + + my ($hdr, $rcode, $ttl) = (0x8180, NOERROR, 3600); + + # decode name + + my ($len, $offset) = (undef, 12); + while (1) { + $len = unpack("\@$offset C", $recv_data); + last if $len == 0; + $offset++; + push @name, unpack("\@$offset A$len", $recv_data); + $offset += $len; + } + + $offset -= 1; + my ($id, $type, $class) = unpack("n x$offset n2", $recv_data); + + my $name = join('.', @name); + + if ($type == A) { + push @rdata, rd_addr($ttl, '127.0.0.1'); + } + + $len = @name; + pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata, + 0, 0, @name, $type, $class) . join('', @rdata); +} + +sub rd_addr { + my ($ttl, $addr) = @_; + + my $code = 'split(/\./, $addr)'; + + return pack 'n3N', 0xc00c, A, IN, $ttl if $addr eq ''; + + pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code); +} + +sub dns_daemon { + my ($port, $t) = @_; + + my ($data, $recv_data); + my $socket = IO::Socket::INET->new( + LocalAddr => '127.0.0.1', + LocalPort => $port, + Proto => 'udp', + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + # signal we are ready + + open my $fh, '>', $t->testdir() . '/' . $port; + close $fh; + + while (1) { + $socket->recv($recv_data, 65536); + $data = reply_handler($recv_data, $port); + $socket->send($data); + } +} + +############################################################################### diff --git a/nginx/t/js_fetch_keepalive.t b/nginx/t/js_fetch_keepalive.t new file mode 100755 index 00000000..100be40c --- /dev/null +++ b/nginx/t/js_fetch_keepalive.t @@ -0,0 +1,289 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) F5, Inc. + +# Tests for http njs module, fetch method keepalive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use IO::Socket::INET; + +use Socket qw/ CRLF /; + +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/) + ->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 /engine { + js_content test.engine; + } + + location /keepalive { + js_fetch_keepalive 4; + js_fetch_keepalive_requests 100; + js_fetch_keepalive_time 60s; + js_fetch_keepalive_timeout 60s; + js_content test.keepalive; + } + + location /keepalive_simultaneous { + js_fetch_keepalive 4; + js_content test.keepalive_simultaneous; + } + + location /keepalive_requests { + js_fetch_keepalive 4; + js_fetch_keepalive_requests 2; + js_content test.keepalive; + } + + location /keepalive_time { + js_fetch_keepalive 4; + js_fetch_keepalive_time 100ms; + js_content test.keepalive; + } + + location /keepalive_timeout { + js_fetch_keepalive 4; + js_fetch_keepalive_timeout 100ms; + js_content test.keepalive; + } + + location /no_keepalive { + js_fetch_keepalive 0; + js_content test.keepalive; + } + } + + server { + listen 127.0.0.1:8081; + keepalive_requests 100; + keepalive_timeout 60s; + + location /count { + add_header Connection-ID $connection_requests; + return 200 $connection_requests; + } + + location /count_close { + add_header Connection close; + add_header Connection-ID $connection_requests; + return 200 $connection_requests; + } + + location /count_close_mixed { + add_header cOnNeCtiOn ClOsE; + add_header Connection-ID $connection_requests; + return 200 $connection_requests; + } + } +} + +EOF + +my $p1 = port(8081); +my $p2 = port(8082); + +$t->write_file('test.js', < setTimeout(resolve, milliseconds)); + } + + async function keepalive(r) { + const path = r.args.path; + let port = $p1; + if (r.args.port) { + port = r.args.port; + } + + let responses = []; + for (let i = 0; i < 3; i++) { + let resp = await ngx.fetch(`http://127.0.0.1:\${port}/\${path}`) + .then(resp => resp.text()) + .catch(err => err.message); + responses.push(resp.trim()); + + if (r.args.sleep) { + await sleep(Number(r.args.sleep)); + } + } + + r.return(200, responses.toString()); + } + + async function keepalive_simultaneous(r) { + let promises = []; + for (let i = 0; i < Number(r.args.n); i++) { + promises.push(ngx.fetch('http://127.0.0.1:$p1/count')); + } + + let results = await Promise.all(promises); + let bodies = await Promise.all(results.map(r => r.text())); + let responses = bodies.map(b => parseInt(b.trim())); + + r.return(200, JSON.stringify(responses)); + } + + export default {engine, keepalive, keepalive_simultaneous}; +EOF + +$t->try_run('no js_fetch_keepalive'); + +$t->run_daemon(\&http_daemon, $p2); +$t->waitforsocket('127.0.0.1:' . $p2); + +$t->plan(16); + +############################################################################### + +like(http_get('/no_keepalive?path=count'), qr/1,1,1/, + 'no keepalive connections'); +like(http_get('/keepalive?path=count_close'), qr/1,1,1/, + 'upstream Connection: close (HTTP/1.1)'); +like(http_get('/keepalive?path=count_close_mixed'), qr/1,1,1/, + 'upstream Connection: close, mixed-case (HTTP/1.1)'); +like(http_get('/keepalive?path=count'), qr/1,2,3/, + 'keepalive reuses connection'); +like(http_get('/keepalive?path=count'), qr/4,5,6/, + 'keepalive reuses connection across requests'); +like(http_get('/keepalive_simultaneous?n=8'), qr/1,1,1,1,1,1,1,1/, + 'keepalive simultaneous requests'); +like(http_get('/keepalive_simultaneous?n=8'), qr/2,2,2,2,1,1,1,1/, + 'keepalive simultaneous requests reused connections'); +like(http_get('/keepalive_requests?path=count'), qr/1,2,1/, + 'keepalive with limited requests per connection'); + +like(http_get('/keepalive_time?path=count'), qr/1,2,3/, + 'keepalive with time limit, first round'); + +select undef, undef, undef, 0.15; + +like(http_get('/keepalive_time?path=count'), qr/4,1,2/, + 'keepalive with time limit, second round'); + +like(http_get('/keepalive_timeout?path=count'), qr/1,2,3/, + 'keepalive with timeout limit, first round'); + +select undef, undef, undef, 0.15; + +like(http_get('/keepalive_timeout?path=count'), qr/1,2,3/, + 'keepalive with timeout limit, second round'); + +like(http_get("/keepalive?path=broken_keepalive&port=$p2&sleep=1"), qr/1,1,1/, + 'upstream broken keepalive (connection closed by upstream)'); +like(http_get("/keepalive?path=http10&port=$p2"), qr/1,1,1/, + 'upstream HTTP/1.0 (no keepalive)'); +like(http_get("/keepalive?path=count&port=$p2&sleep=1"), qr/1,2,3/, + 'normal keepalive'); +like(http_get("/keepalive?path=assumed_keepalive&port=$p2&sleep=1"), qr/4,5,6/, + 'assumed keepalive'); + +############################################################################### + +sub http_daemon { + my $port = shift; + + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalAddr => '127.0.0.1:' . $port, + Listen => 5, + Reuse => 1 + ) or die "Can't create listening socket: $!\n"; + + my $ccount = 0; + my $rcount = 0; + + # dumb server which is able to keep connections alive + + while (my $client = $server->accept()) { + Test::Nginx::log_core('||', + "connection from " . $client->peerhost()); + $client->autoflush(1); + $ccount++; + $rcount = 0; + + while (1) { + my $headers = ''; + my $uri = ''; + + while (<$client>) { + Test::Nginx::log_core('||', $_); + $headers .= $_; + last if (/^\x0d?\x0a?$/); + } + + last if $headers eq ''; + $rcount++; + + $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i; + my $body = $rcount; + + if ($uri eq '/broken_keepalive') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: " . length($body) . CRLF . + "Connection: keep-alive" . CRLF . CRLF . + $body; + + last; + + } elsif ($uri eq '/assumed_keepalive') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: " . length($body) . CRLF . CRLF . + $body; + + } elsif ($uri eq '/count') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: " . length($body) . CRLF . + "Connection: keep-alive" . CRLF . CRLF . + $body; + + } elsif ($uri eq '/http10') { + print $client + "HTTP/1.0 200 OK" . CRLF . + "Content-Length: " . length($body) . CRLF . CRLF . + $body; + } + } + + close $client; + } +} + +############################################################################### diff --git a/nginx/t/stream_js_fetch_keepalive.t b/nginx/t/stream_js_fetch_keepalive.t new file mode 100644 index 00000000..e940eea5 --- /dev/null +++ b/nginx/t/stream_js_fetch_keepalive.t @@ -0,0 +1,200 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) F5, Inc. + +# Tests for stream njs module, fetch method keepalive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use IO::Socket::INET; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http stream/) + ->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 /engine { + js_content test.engine; + } + } + + server { + listen 127.0.0.1:8081; + keepalive_requests 100; + keepalive_timeout 60s; + + location /count { + add_header Connection-ID $connection_requests; + return 200 $connection_requests; + } + + location /headers { + return 200 "Connection: $http_connection"; + } + } +} + +stream { + %%TEST_GLOBALS_STREAM%% + + js_import test.js; + js_var $message; + + server { + listen 127.0.0.1:8082; + js_fetch_keepalive 4; + js_fetch_keepalive_requests 100; + js_fetch_keepalive_time 60s; + js_fetch_keepalive_timeout 60s; + js_preread test.keepalive; + return $message; + } + + server { + listen 127.0.0.1:8083; + js_fetch_keepalive 0; + js_preread test.keepalive; + return $message; + } + + server { + listen 127.0.0.1:8084; + js_fetch_keepalive 4; + js_fetch_keepalive_requests 2; + js_preread test.keepalive; + return $message; + } + + server { + listen 127.0.0.1:8085; + js_fetch_keepalive 4; + js_fetch_keepalive_time 100ms; + js_preread test.keepalive; + return $message; + } + + server { + listen 127.0.0.1:8086; + js_fetch_keepalive 4; + js_fetch_keepalive_timeout 100ms; + js_preread test.keepalive; + return $message; + } + + server { + listen 127.0.0.1:8087; + js_fetch_keepalive 4; + js_preread test.keepalive_simultaneous; + return $message; + } +} + +EOF + +my $p1 = port(8081); + +$t->write_file('test.js', < r.text())); + let responses = bodies.map(b => parseInt(b.trim())); + + s.variables.message = JSON.stringify(responses); + s.done(); + } + + export default {engine, keepalive, keepalive_simultaneous}; +EOF + +$t->try_run('no stream js_fetch_keepalive'); + +$t->plan(10); + +############################################################################### + +like(stream('127.0.0.1:' . port(8083))->io('GO'), qr/\[1,1,1]/, + 'no keepalive connections'); +like(stream('127.0.0.1:' . port(8082))->io('GO'), qr/\[1,2,3]/, + 'keepalive reuses connection'); +like(stream('127.0.0.1:' . port(8082))->io('GO'), qr/\[4,5,6]/, + 'keepalive reuses connection across sessions'); + +like(stream('127.0.0.1:' . port(8087))->io('GO'), qr/^\[(1,){7}1\]$/, + 'keepalive simultaneous requests'); +like(stream('127.0.0.1:' . port(8087))->io('GO'), + qr/\[2,2,2,2,1,1,1,1\]/, + 'keepalive simultaneous requests reused connections'); + +like(stream('127.0.0.1:' . port(8084))->io('GO'), qr/\[1,2,1]/, + 'keepalive with limited requests per connection'); + +like(stream('127.0.0.1:' . port(8085))->io('GO'), qr/\[1,2,3]/, + 'keepalive with time limit, first round'); + +select undef, undef, undef, 0.15; + +like(stream('127.0.0.1:' . port(8085))->io('GO'), qr/\[4,1,2]/, + 'keepalive with time limit, second round'); + +like(stream('127.0.0.1:' . port(8086))->io('GO'), qr/\[1,2,3]/, + 'keepalive with timeout limit, first round'); + +select undef, undef, undef, 0.15; + +like(stream('127.0.0.1:' . port(8086))->io('GO'), qr/\[1,2,3]/, + 'keepalive with timeout limit, second round'); + +############################################################################### From noreply at nginx.com Wed Sep 17 18:56:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 17 Sep 2025 18:56:02 +0000 (UTC) Subject: [njs] Fixed build with gcc-15 and -O3. Message-ID: <20250917185602.24B4B48854@pubserv1.nginx> details: https://github.com/nginx/njs/commit/6b12358be530b158375f97b9b45a90d593da15f9 branches: master commit: 6b12358be530b158375f97b9b45a90d593da15f9 user: Dmitry Volyntsev date: Tue, 16 Sep 2025 18:28:05 -0700 description: Fixed build with gcc-15 and -O3. build/src/njs_object.dep -MT build/src/njs_object.o \ src/njs_object.c In file included from src/njs_main.h:18, from src/njs_object.c:8: In function ‘njs_utf8_copy’, inlined from ‘njs_object_enumerate_string’ at src/njs_object.c:769:21: src/njs_utf8.h:115:20: error: writing 1 byte into a region of size 0 [-Werror=stringop-overflow=] 115 | *dst++ = c; | ~~~~~~~^~~ src/njs_object.c: In function ‘njs_object_enumerate_string’: src/njs_object.c:719:24: note: at offset 4 into destination object ‘buf’ of size 4 719 | u_char buf[4], *c; | ^~~ In function ‘njs_utf8_copy’, inlined from ‘njs_object_enumerate_string’ at src/njs_object.c:769:21: src/njs_utf8.h:115:20: error: writing 1 byte into a region of size 0 [-Werror=stringop-overflow=] GCC-15 does not know that the loop in njs_utf8_copy() is bounded because the input is a valid UTF-8 here. This fixes #967 issue on Github. --- src/njs_array.c | 11 ++++------- src/njs_object.c | 17 ++++++----------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/njs_array.c b/src/njs_array.c index bcf428e9..b8cb9f94 100644 --- a/src/njs_array.c +++ b/src/njs_array.c @@ -788,7 +788,6 @@ static njs_int_t njs_array_prototype_slice_copy(njs_vm_t *vm, njs_value_t *this, int64_t start, int64_t length, njs_value_t *retval) { - u_char *c, buf[4]; size_t size; uint32_t n; njs_int_t ret; @@ -829,17 +828,15 @@ njs_array_prototype_slice_copy(njs_vm_t *vm, njs_value_t *this, do { value = &array->start[n++]; - c = buf; - c = njs_utf8_copy(c, &src, end); - size = c - buf; + size = njs_utf8_next(src, end) - src; - ret = njs_string_new(vm, value, buf, size, 1); + ret = njs_string_new(vm, value, src, size, 1); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } - length--; - } while (length != 0); + src += size; + } while (src != end); } else if (njs_is_object(this)) { diff --git a/src/njs_object.c b/src/njs_object.c index 7f0b1822..26e8182b 100644 --- a/src/njs_object.c +++ b/src/njs_object.c @@ -715,7 +715,6 @@ static njs_int_t njs_object_enumerate_string(njs_vm_t *vm, const njs_value_t *value, njs_array_t *items, uint32_t flags) { - u_char buf[4], *c; uint32_t i, len, size; njs_int_t ret; njs_value_t *item, *string; @@ -763,17 +762,15 @@ njs_object_enumerate_string(njs_vm_t *vm, const njs_value_t *value, end = src + str_prop.size; do { - c = buf; + size = njs_utf8_next(src, end) - src; - c = njs_utf8_copy(c, &src, end); - size = c - buf; - - ret = njs_string_new(vm, item, buf, size, 1); + ret = njs_string_new(vm, item, src, size, 1); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } item++; + src += size; } while (src != end); } @@ -828,12 +825,9 @@ njs_object_enumerate_string(njs_vm_t *vm, const njs_value_t *value, string = &entry->start[1]; - c = buf; - - c = njs_utf8_copy(c, &src, end); - size = c - buf; + size = njs_utf8_next(src, end) - src; - ret = njs_string_new(vm, string, buf, size, 1); + ret = njs_string_new(vm, string, src, size, 1); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } @@ -841,6 +835,7 @@ njs_object_enumerate_string(njs_vm_t *vm, const njs_value_t *value, njs_set_array(item, entry); item++; + src += size; } while (src != end); } From noreply at nginx.com Thu Sep 18 00:37:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 18 Sep 2025 00:37:02 +0000 (UTC) Subject: [njs] Modules: fixed building when http_ssl and stream_ssl unavailable. Message-ID: <20250918003702.A8A7C48854@pubserv1.nginx> details: https://github.com/nginx/njs/commit/4ea1356af08e714076f57d02575f7845d6e80795 branches: master commit: 4ea1356af08e714076f57d02575f7845d6e80795 user: Dmitry Volyntsev date: Wed, 17 Sep 2025 15:34:19 -0700 description: Modules: fixed building when http_ssl and stream_ssl unavailable. This fixes issue introduced in 7b3c8a66. --- .github/workflows/check-pr.yml | 20 +++++++++++++++++++- nginx/ngx_http_js_module.c | 6 +++--- nginx/ngx_js.c | 4 ++-- nginx/ngx_js.h | 2 +- nginx/ngx_js_http.c | 4 ++++ nginx/ngx_stream_js_module.c | 6 +++--- nginx/t/js_webcrypto.t | 17 +++++++++++++++-- nginx/t/stream_js_webcrypto.t | 30 +++++++++++++++++++++++++++--- 8 files changed, 74 insertions(+), 15 deletions(-) diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index c75f3cbe..d5e0edd2 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -12,7 +12,10 @@ jobs: - name: Set the defaults and set up environment run: | - echo NGINX_CONFIGURE_CMD="auto/configure --prefix=/tmp --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-mail --with-mail_ssl_module --with-select_module --with-poll_module --with-http_auth_request_module --with-http_v2_module --with-http_slice_module --with-stream --with-stream_ssl_module --with-stream_ssl_preread_module --with-stream_realip_module --with-threads --with-cpp_test_module --with-compat --with-http_degradation_module --with-http_xslt_module --with-http_image_filter_module --with-http_perl_module --with-http_geoip_module --with-stream_geoip_module" >> $GITHUB_ENV + NGINX_BASE="auto/configure --prefix=/tmp --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-mail --with-select_module --with-poll_module --with-http_auth_request_module --with-http_v2_module --with-http_slice_module --with-stream --with-stream_realip_module --with-threads --with-cpp_test_module --with-compat --with-http_degradation_module --with-http_xslt_module --with-http_image_filter_module --with-http_perl_module --with-http_geoip_module --with-stream_geoip_module" + SSL_MODULES="--with-http_ssl_module --with-mail_ssl_module --with-stream_ssl_module --with-stream_ssl_preread_module" + echo NGINX_CONFIGURE_CMD="$NGINX_BASE $SSL_MODULES" >> $GITHUB_ENV + echo NGINX_CONFIGURE_CMD_NO_SSL="$NGINX_BASE" >> $GITHUB_ENV export DEB_BUILD_MAINT_OPTIONS="hardening=+all" export DEB_CFLAGS_MAINT_APPEND="-fPIC" export DEB_LDFLAGS_MAINT_APPEND=""-Wl,--as-needed"" @@ -126,6 +129,21 @@ jobs: 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: Configure and build nginx and njs modules, no SSL, static modules + run: | + cd nginx-source + NJS_OPENSSL=NO $NGINX_CONFIGURE_CMD_NO_SSL --with-cc-opt="$CC_OPT" --with-ld-opt="$LD_OPT" --add-module=../nginx || cat objs/autoconf.err + $MAKE_UTILITY -j$(nproc) modules + $MAKE_UTILITY -j$(nproc) + + - name: Test njs modules, no SSL, 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 + - name: Create LSAN suppression file run: | cat << EOF > lsan_suppressions.txt diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index f9d9721a..2f3ce936 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -400,7 +400,7 @@ static ngx_conf_bitmask_t ngx_http_js_engines[] = { { ngx_null_string, 0 } }; -#if (NGX_HTTP_SSL) +#if (NGX_SSL) static ngx_conf_bitmask_t ngx_http_js_ssl_protocols[] = { { ngx_string("TLSv1"), NGX_SSL_TLSv1 }, @@ -519,7 +519,7 @@ static ngx_command_t ngx_http_js_commands[] = { offsetof(ngx_http_js_loc_conf_t, timeout), NULL }, -#if (NGX_HTTP_SSL) +#if (NGX_SSL) { ngx_string("js_fetch_ciphers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, @@ -8172,7 +8172,7 @@ ngx_http_js_create_loc_conf(ngx_conf_t *cf) return NULL; } -#if (NGX_HTTP_SSL) +#if (NGX_SSL) conf->ssl_verify = NGX_CONF_UNSET; conf->ssl_verify_depth = NGX_CONF_UNSET; #endif diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index efef3467..053c23c2 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -3996,7 +3996,7 @@ ngx_js_create_conf(ngx_conf_t *cf, size_t size) } -#if defined(NGX_HTTP_SSL) || defined(NGX_STREAM_SSL) +#if (NGX_SSL) static ngx_int_t ngx_js_merge_ssl(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, @@ -4122,7 +4122,7 @@ ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child, return NGX_CONF_ERROR; } -#if defined(NGX_HTTP_SSL) || defined(NGX_STREAM_SSL) +#if (NGX_SSL) if (ngx_js_merge_ssl(cf, conf, prev) != NGX_OK) { return NGX_CONF_ERROR; diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index af19e007..20ea85b1 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -144,7 +144,7 @@ typedef struct { ngx_queue_t fetch_keepalive_free -#if defined(NGX_HTTP_SSL) || defined(NGX_STREAM_SSL) +#if (NGX_SSL) #define NGX_JS_COMMON_LOC_CONF \ _NGX_JS_COMMON_LOC_CONF; \ \ diff --git a/nginx/ngx_js_http.c b/nginx/ngx_js_http.c index f07ccceb..35c67842 100644 --- a/nginx/ngx_js_http.c +++ b/nginx/ngx_js_http.c @@ -1643,9 +1643,11 @@ ngx_js_http_get_keepalive_connection(ngx_js_http_t *http) continue; } +#if (NGX_SSL) if ((http->ssl != NULL) != (cache->ssl != 0)) { continue; } +#endif if (ngx_strncasecmp(host->data, cache->host, host->len) != 0) { continue; @@ -1775,7 +1777,9 @@ ngx_js_http_free_keepalive_connection(ngx_js_http_t *http) cache->connection = c; +#if (NGX_SSL) cache->ssl = (http->ssl != NULL); +#endif ngx_memcpy(cache->host, http->host.data, http->host.len); cache->host_len = http->host.len; cache->port = http->port; diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index ab4e787d..212af400 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -232,7 +232,7 @@ static ngx_conf_bitmask_t ngx_stream_js_engines[] = { { ngx_null_string, 0 } }; -#if (NGX_STREAM_SSL) +#if (NGX_SSL) static ngx_conf_bitmask_t ngx_stream_js_ssl_protocols[] = { { ngx_string("TLSv1"), NGX_SSL_TLSv1 }, @@ -379,7 +379,7 @@ static ngx_command_t ngx_stream_js_commands[] = { offsetof(ngx_stream_js_srv_conf_t, fetch_keepalive_timeout), NULL }, -#if (NGX_STREAM_SSL) +#if (NGX_SSL) { ngx_string("js_fetch_ciphers"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, @@ -3620,7 +3620,7 @@ ngx_stream_js_create_srv_conf(ngx_conf_t *cf) return NULL; } -#if (NGX_STREAM_SSL) +#if (NGX_SSL) conf->ssl_verify = NGX_CONF_UNSET; conf->ssl_verify_depth = NGX_CONF_UNSET; #endif diff --git a/nginx/t/js_webcrypto.t b/nginx/t/js_webcrypto.t index f43f9d6d..33869574 100644 --- a/nginx/t/js_webcrypto.t +++ b/nginx/t/js_webcrypto.t @@ -41,6 +41,10 @@ http { listen 127.0.0.1:8080; server_name localhost; + location /has_crypto { + js_content test.has_crypto; + } + location /random_values_test { js_content test.random_values_test; } @@ -50,6 +54,10 @@ http { EOF $t->write_file('test.js', <write_file('test.js', < (mean - 10 * stdd) && bits1 < (mean + 10 * stdd)); } - export default {random_values_test}; + export default {has_crypto, random_values_test}; EOF -$t->try_run('no njs')->plan(1); +$t->try_run('no njs'); + +plan(skip_all => 'njs crypto module not available') + if http_get('/has_crypto') !~ /true/; + +$t->plan(1); ############################################################################### diff --git a/nginx/t/stream_js_webcrypto.t b/nginx/t/stream_js_webcrypto.t index d7b34396..619a093d 100644 --- a/nginx/t/stream_js_webcrypto.t +++ b/nginx/t/stream_js_webcrypto.t @@ -23,7 +23,7 @@ use Test::Nginx::Stream qw/ stream /; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/stream stream_return/) +my $t = Test::Nginx->new()->has(qw/http stream stream_return/) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -33,6 +33,21 @@ daemon off; events { } +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /has_crypto { + js_content test.has_crypto; + } + } +} + stream { %%TEST_GLOBALS_STREAM%% @@ -49,6 +64,10 @@ stream { EOF $t->write_file('test.js', <write_file('test.js', < (mean - 10 * stdd) && bits1 < (mean + 10 * stdd); } - export default {random_values_test}; + export default {has_crypto, random_values_test}; EOF -$t->try_run('no stream js_var')->plan(1); +$t->try_run('no stream js_var'); + +plan(skip_all => 'njs crypto module not available') + if http_get('/has_crypto') !~ /true/; + +$t->plan(1); ############################################################################### From noreply at nginx.com Thu Sep 18 14:17:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 18 Sep 2025 14:17:02 +0000 (UTC) Subject: [nginx] Fixed inaccurate index directive error report. Message-ID: <20250918141702.3DFD046C50@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/bc71625dcca1f1cbd0db7450af853feb90ebba85 branches: master commit: bc71625dcca1f1cbd0db7450af853feb90ebba85 user: willmafh date: Mon, 8 Sep 2025 22:03:30 +0800 description: Fixed inaccurate index directive error report. --- src/http/modules/ngx_http_index_module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/modules/ngx_http_index_module.c b/src/http/modules/ngx_http_index_module.c index 2ee1dd5a4..18e7049c5 100644 --- a/src/http/modules/ngx_http_index_module.c +++ b/src/http/modules/ngx_http_index_module.c @@ -490,7 +490,7 @@ ngx_http_index_set_index(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (value[i].len == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "index \"%V\" in \"index\" directive is invalid", - &value[1]); + &value[i]); return NGX_CONF_ERROR; } From noreply at nginx.com Tue Sep 23 16:05:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 23 Sep 2025 16:05:02 +0000 (UTC) Subject: [njs] Version 0.9.2. Message-ID: <20250923160502.36DC048854@pubserv1.nginx> details: https://github.com/nginx/njs/commit/b31f7333c772ba837977363536297b2608f64047 branches: master commit: b31f7333c772ba837977363536297b2608f64047 user: Dmitry Volyntsev date: Mon, 22 Sep 2025 10:12:15 -0700 description: Version 0.9.2. --- CHANGES | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CHANGES b/CHANGES index 719e25b4..29b51cee 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,30 @@ +Changes with njs 0.9.2 23 Sep 2025 + + nginx modules: + + *) Feature: added HTTP keepalive support for ngx.fetch() API. + + *) Improvement: added configure time check when js_import is + not defined. + + *) Bugfix: fixed merging of js_path directives. + + *) Bugfix: fixed building when http_ssl and stream_ssl + unavailable. + + Core: + + *) Change: increased the default stack size to 160k for njs VM. + + *) Feature: added njs.on('exit') API for qjs engine. + + *) Improvement: optimized memory consumption while streaming + in qjs. + + *) Bugfix: fixed building qjs engine with clang 19. + + *) Bugfix: fixed building with GCC 15 and -O3. + Changes with njs 0.9.1 10 Jul 2025 nginx modules: From noreply at nginx.com Tue Sep 23 16:06:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 23 Sep 2025 16:06:02 +0000 (UTC) Subject: [njs] Lightweight tag created: 0.9.2 Message-ID: <20250923160602.0281048F5C@pubserv1.nginx> details: https://github.com/nginx/njs/releases/tag/0.9.2 branches: commit: b31f7333c772ba837977363536297b2608f64047 user: Dmitry Volyntsev date: Mon Sep 22 10:12:15 2025 -0700 description: Version 0.9.2.