From noreply at nginx.com Mon Sep 2 13:34:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 2 Sep 2024 13:34:02 +0000 (UTC) Subject: [nginx] Added Code of Conduct. Message-ID: <20240902133402.7997748835@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/e73ac62294ae85977dde1e8159857e6a4d227f26 branches: master commit: e73ac62294ae85977dde1e8159857e6a4d227f26 user: Maryna Herasimovich date: Wed, 28 Aug 2024 20:13:13 -0700 description: Added Code of Conduct. --- CODE_OF_CONDUCT.md | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..a99d8854a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,126 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes +and learning from the experience +- Focusing on what is best not just for us as individuals, but for the +overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances +of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, +without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a +professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards +of acceptable behavior and will take appropriate and fair corrective action +in response to any behavior that they deem inappropriate, threatening, +offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for +moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. All complaints will be reviewed and investigated +promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external +channels like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the +[Contributor Covenant](https://www.contributor-covenant.org), version 2.1, +available at +. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/inclusion). + +For answers to common questions about this code of conduct, see the FAQ at +. Translations are available at +. From noreply at nginx.com Mon Sep 2 16:11:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 2 Sep 2024 16:11:02 +0000 (UTC) Subject: [nginx] Added security policy. Message-ID: <20240902161102.141E648838@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/3b16b46aae979b9a130c5dd42430dda37b623282 branches: master commit: 3b16b46aae979b9a130c5dd42430dda37b623282 user: Maryna Herasimovich date: Wed, 28 Aug 2024 20:51:54 -0700 description: Added security policy. --- SECURITY.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..2b48e47e3 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,20 @@ +# Security Policy + +## Latest Versions + +We advise users to run the most recent mainline or stable release of nginx. + +## Reporting a Vulnerability + +Please report any vulnerabilities via one of the following methods +(in order of preference): + +1. [Report a vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability) +within this repository. We are using the Github workflow that allows us to +manage vulnerabilities in a private manner and to interact with reporters +securely. + +2. [Report directly to F5](https://www.f5.com/services/support/report-a-vulnerability). + +3. Report via email to security-alert at nginx.org. +This method will be deprecated in the future. From noreply at nginx.com Tue Sep 3 12:29:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 3 Sep 2024 12:29:02 +0000 (UTC) Subject: [nginx] Added contributing guidelines. Message-ID: <20240903122902.725404883C@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/da468ec0c03ab7711ab4fcb0517760daaf31ab10 branches: master commit: da468ec0c03ab7711ab4fcb0517760daaf31ab10 user: Maryna Herasimovich date: Wed, 28 Aug 2024 20:43:08 -0700 description: Added contributing guidelines. --- CONTRIBUTING.md | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..864e7989b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,110 @@ +# Contributing Guidelines + +The following is a set of guidelines for contributing to nginx project. +We really appreciate that you are considering contributing! + +## Table of Contents + +- [Ask a Question](#ask-a-question) +- [Report a Bug](#report-a-bug) +- [Suggest a Feature or Enhancement](#suggest-a-feature-or-enhancement) +- [Open a Discussion](#open-a-discussion) +- [Submit a Pull Request](#submit-a-pull-request) +- [Issue Lifecycle](#issue-lifecycle) + +## Ask a Question + +To ask a question, open an issue on GitHub with the label `question`. + +## Report a Bug + +To report a bug, open an issue on GitHub with the label `bug` using the +available bug report issue template. Before reporting a bug, make sure the +issue has not already been reported. + +## Suggest a Feature or Enhancement + +To suggest a feature or enhancement, open an issue on GitHub with the label +`feature` or `enhancement` using the available feature request issue template. +Please ensure the feature or enhancement has not already been suggested. + +## Open a Discussion + +If you want to engage in a conversation with the community and maintainers, +we encourage you to use +[GitHub Discussions](https://github.com/nginx/nginx/discussions). + +## Submit a Pull Request + +Follow this plan to contribute a change to NGINX source code: + +- Fork the NGINX repository +- Create a branch +- Implement your changes in this branch +- Submit a pull request (PR) when your changes are tested and ready for review + +Refer to +[NGINX Development Guide](https://nginx.org/en/docs/dev/development_guide.html) +for questions about NGINX programming. + +### Formatting Changes + +- Changes should be formatted according to the +[code style](https://nginx.org/en/docs/dev/development_guide.html#code_style) +used by NGINX; sometimes, there is no clear rule, in which case examine how +existing NGINX sources are formatted and mimic this style; changes will more +likely be accepted if style corresponds to the surrounding code + +- Keep a clean, concise and meaningful commit history on your branch, rebasing +locally and breaking changes logically into commits before submitting a PR + +- Each commit message should have a single-line subject line followed by verbose +description after an empty line + +- Limit the subject line to 67 characters, and the rest of the commit message +to 76 characters + +- Use subject line prefixes for commits that affect a specific portion of the +code; examples include "Upstream:", "QUIC:", or "Core:"; see the commit history +to get an idea of the prefixes used + +- Reference issues in the the subject line; if the commit fixes an issue, +[name it](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) +accordingly + +### Before Submitting + +- The proposed changes should work properly on a wide range of +[supported platforms](https://nginx.org/en/index.html#tested_os_and_platforms) + +- Try to make it clear why the suggested change is needed, and provide a use +case, if possible + +- Passing your changes through the test suite is a good way to ensure that they +do not cause a regression; the repository with tests can be cloned with the +following command: + +```bash +git clone https://github.com/nginx/nginx-tests.git +``` + +- Submitting a change implies granting project a permission to use it under the +[BSD-2-Clause license](https://github.com/nginx/nginx/blob/master/LICENSE) + +## Issue Lifecycle + +To ensure a balance between work carried out by the NGINX engineering team +while encouraging community involvement on this project, we use the following +issue lifecycle: + +- A new issue is created by a community member + +- An owner on the NGINX engineering team is assigned to the issue; this +owner shepherds the issue through the subsequent stages in the issue lifecycle + +- The owner assigns one or more +[labels](https://github.com/nginx/nginx/issues/labels) to the issue + +- The owner, in collaboration with the wider team (product management and +engineering), determines what milestone to attach to an issue; +generally, milestones correspond to product releases From noreply at nginx.com Wed Sep 4 00:59:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 4 Sep 2024 00:59:02 +0000 (UTC) Subject: [njs] QuickJS: moving njs object creation to common code. Message-ID: <20240904005902.21E2B487FA@pubserv1.nginx> details: https://github.com/nginx/njs/commit/b70ab370b8fd8c7e37a6491697dfe357031f4632 branches: master commit: b70ab370b8fd8c7e37a6491697dfe357031f4632 user: Dmitry Volyntsev date: Mon, 8 Jul 2024 23:35:12 -0700 description: QuickJS: moving njs object creation to common code. So it will be shared between modules and CLI. --- external/njs_shell.c | 87 ++-------------------------------------------- src/qjs.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/qjs.h | 1 + 3 files changed, 102 insertions(+), 84 deletions(-) diff --git a/external/njs_shell.c b/external/njs_shell.c index ec2a3875..addf5a34 100644 --- a/external/njs_shell.c +++ b/external/njs_shell.c @@ -1901,46 +1901,6 @@ njs_qjs_clear_timeout(JSContext *ctx, JSValueConst this_val, int argc, } -static njs_int_t -njs_qjs_set_to_string_tag(JSContext *ctx, JSValueConst val, const char *tag) -{ - JSAtom atom; - JSValue global_obj, symbol, toStringTag; - njs_int_t ret; - - global_obj = JS_GetGlobalObject(ctx); - - symbol = JS_GetPropertyStr(ctx, global_obj, "Symbol"); - JS_FreeValue(ctx, global_obj); - if (JS_IsException(symbol)) { - return -1; - } - - toStringTag = JS_GetPropertyStr(ctx, symbol, "toStringTag"); - if (JS_IsException(toStringTag)) { - JS_FreeValue(ctx, symbol); - return -1; - } - - atom = JS_ValueToAtom(ctx, toStringTag); - - JS_FreeValue(ctx, symbol); - JS_FreeValue(ctx, toStringTag); - - if (atom == JS_ATOM_NULL) { - JS_ThrowInternalError(ctx, "failed to get atom"); - return -1; - } - - ret = JS_DefinePropertyValue(ctx, val, atom, JS_NewString(ctx, tag), - JS_PROP_C_W_E); - - JS_FreeAtom(ctx, atom); - - return ret; -} - - static JSValue njs_qjs_process_getter(JSContext *ctx, JSValueConst this_val) { @@ -1963,7 +1923,7 @@ njs_qjs_process_getter(JSContext *ctx, JSValueConst this_val) return JS_EXCEPTION; } - ret = njs_qjs_set_to_string_tag(ctx, obj, "process"); + ret = qjs_set_to_string_tag(ctx, obj, "process"); if (ret == -1) { JS_FreeValue(ctx, obj); return JS_EXCEPTION; @@ -2066,46 +2026,6 @@ error: return obj; } -static JSValue -njs_qjs_njs_getter(JSContext *ctx, JSValueConst this_val) -{ - JSValue obj; - njs_int_t ret; - - obj = JS_NewObject(ctx); - if (JS_IsException(obj)) { - return JS_EXCEPTION; - } - - ret = njs_qjs_set_to_string_tag(ctx, obj, "njs"); - if (ret == -1) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - - ret = JS_SetPropertyStr(ctx, obj, "version_number", - JS_NewInt32(ctx, NJS_VERSION_NUMBER)); - if (ret == -1) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - - ret = JS_SetPropertyStr(ctx, obj, "version", - JS_NewString(ctx, NJS_VERSION)); - if (ret == -1) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - - ret = JS_SetPropertyStr(ctx, obj, "engine", JS_NewString(ctx, "QuickJS")); - if (ret == -1) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - - return obj; -} - static njs_int_t njs_qjs_global_init(JSContext *ctx, JSValue global_obj); static void njs_qjs_dump_error(JSContext *ctx); @@ -2656,7 +2576,6 @@ njs_qjs_new_262(JSContext *ctx, JSValueConst this_val) static const JSCFunctionListEntry njs_qjs_global_proto[] = { JS_CFUNC_DEF("clearTimeout", 1, njs_qjs_clear_timeout), JS_CFUNC_MAGIC_DEF("print", 0, njs_qjs_console_log, NJS_LOG_INFO), - JS_CGETSET_DEF("njs", njs_qjs_njs_getter, NULL), JS_CGETSET_DEF("process", njs_qjs_process_getter, NULL), JS_CFUNC_MAGIC_DEF("setImmediate", 0, njs_qjs_set_timer, 1), JS_CFUNC_MAGIC_DEF("setTimeout", 0, njs_qjs_set_timer, 0), @@ -2840,9 +2759,9 @@ njs_engine_qjs_init(njs_engine_t *engine, njs_opts_t *opts) goto done; } - ret = njs_qjs_set_to_string_tag(ctx, obj, "Console"); + ret = qjs_set_to_string_tag(ctx, obj, "Console"); if (ret == -1) { - njs_stderror("njs_qjs_set_to_string_tag() failed\n"); + njs_stderror("qjs_set_to_string_tag() failed\n"); ret = NJS_ERROR; goto done; } diff --git a/src/qjs.c b/src/qjs.c index 0a31b748..de6bf17b 100644 --- a/src/qjs.c +++ b/src/qjs.c @@ -5,11 +5,21 @@ */ #include +#include /* NJS_VERSION */ + + +static JSValue qjs_njs_getter(JSContext *ctx, JSValueConst this_val); + + +static const JSCFunctionListEntry qjs_global_proto[] = { + JS_CGETSET_DEF("njs", qjs_njs_getter, NULL), +}; JSContext * qjs_new_context(JSRuntime *rt, _Bool eval) { + JSValue global_obj; JSContext *ctx; qjs_module_t **module; @@ -38,10 +48,98 @@ qjs_new_context(JSRuntime *rt, _Bool eval) } } + global_obj = JS_GetGlobalObject(ctx); + + JS_SetPropertyFunctionList(ctx, global_obj, qjs_global_proto, + njs_nitems(qjs_global_proto)); + + JS_FreeValue(ctx, global_obj); + return ctx; } +static JSValue +qjs_njs_getter(JSContext *ctx, JSValueConst this_val) +{ + int ret; + JSValue obj; + + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) { + return JS_EXCEPTION; + } + + ret = qjs_set_to_string_tag(ctx, obj, "njs"); + if (ret == -1) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + + ret = JS_SetPropertyStr(ctx, obj, "version_number", + JS_NewInt32(ctx, NJS_VERSION_NUMBER)); + if (ret == -1) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + + ret = JS_SetPropertyStr(ctx, obj, "version", + JS_NewString(ctx, NJS_VERSION)); + if (ret == -1) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + + ret = JS_SetPropertyStr(ctx, obj, "engine", JS_NewString(ctx, "QuickJS")); + if (ret == -1) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + + return obj; +} + + +int +qjs_set_to_string_tag(JSContext *ctx, JSValueConst val, const char *tag) +{ + int ret; + JSAtom atom; + JSValue global_obj, symbol, toStringTag; + + global_obj = JS_GetGlobalObject(ctx); + + symbol = JS_GetPropertyStr(ctx, global_obj, "Symbol"); + JS_FreeValue(ctx, global_obj); + if (JS_IsException(symbol)) { + return -1; + } + + toStringTag = JS_GetPropertyStr(ctx, symbol, "toStringTag"); + if (JS_IsException(toStringTag)) { + JS_FreeValue(ctx, symbol); + return -1; + } + + atom = JS_ValueToAtom(ctx, toStringTag); + + JS_FreeValue(ctx, symbol); + JS_FreeValue(ctx, toStringTag); + + if (atom == JS_ATOM_NULL) { + JS_ThrowInternalError(ctx, "failed to get atom"); + return -1; + } + + ret = JS_DefinePropertyValue(ctx, val, atom, JS_NewString(ctx, tag), + JS_PROP_C_W_E); + + JS_FreeAtom(ctx, atom); + + return ret; +} + + int qjs_to_bytes(JSContext *ctx, qjs_bytes_t *bytes, JSValueConst value) { diff --git a/src/qjs.h b/src/qjs.h index f8eabefa..563a5b15 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -64,6 +64,7 @@ typedef struct { const qjs_buffer_encoding_t *qjs_buffer_encoding(JSContext *ctx, JSValueConst value, JS_BOOL thrw); +int qjs_set_to_string_tag(JSContext *ctx, JSValueConst val, const char *tag); typedef struct { int tag; From noreply at nginx.com Wed Sep 4 00:59:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 4 Sep 2024 00:59:02 +0000 (UTC) Subject: [njs] QuickJS: qjs_new_context() accepts now additional modules. Message-ID: <20240904005902.278894883F@pubserv1.nginx> details: https://github.com/nginx/njs/commit/cb8296fa4e2c976b78f07a73263bb0cd5dfb29ab branches: master commit: cb8296fa4e2c976b78f07a73263bb0cd5dfb29ab user: Dmitry Volyntsev date: Tue, 23 Jul 2024 22:55:01 -0700 description: QuickJS: qjs_new_context() accepts now additional modules. --- external/njs_shell.c | 2 +- src/qjs.c | 10 +++++++++- src/qjs.h | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/external/njs_shell.c b/external/njs_shell.c index addf5a34..80693634 100644 --- a/external/njs_shell.c +++ b/external/njs_shell.c @@ -2731,7 +2731,7 @@ njs_engine_qjs_init(njs_engine_t *engine, njs_opts_t *opts) return NJS_ERROR; } - engine->u.qjs.ctx = qjs_new_context(engine->u.qjs.rt, 1); + engine->u.qjs.ctx = qjs_new_context(engine->u.qjs.rt, NULL, 1); if (engine->u.qjs.ctx == NULL) { njs_stderror("JS_NewContext() failed\n"); return NJS_ERROR; diff --git a/src/qjs.c b/src/qjs.c index de6bf17b..b8c53486 100644 --- a/src/qjs.c +++ b/src/qjs.c @@ -17,7 +17,7 @@ static const JSCFunctionListEntry qjs_global_proto[] = { JSContext * -qjs_new_context(JSRuntime *rt, _Bool eval) +qjs_new_context(JSRuntime *rt, qjs_module_t **addons, _Bool eval) { JSValue global_obj; JSContext *ctx; @@ -48,6 +48,14 @@ qjs_new_context(JSRuntime *rt, _Bool eval) } } + if (addons != NULL) { + for (module = addons; *module != NULL; module++) { + if ((*module)->init(ctx, (*module)->name) == NULL) { + return NULL; + } + } + } + global_obj = JS_GetGlobalObject(ctx); JS_SetPropertyFunctionList(ctx, global_obj, qjs_global_proto, diff --git a/src/qjs.h b/src/qjs.h index 563a5b15..531940ea 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -41,7 +41,7 @@ typedef struct { } qjs_module_t; -JSContext *qjs_new_context(JSRuntime *rt, _Bool eval); +JSContext *qjs_new_context(JSRuntime *rt, qjs_module_t **addons, _Bool eval); JSValue qjs_buffer_alloc(JSContext *ctx, size_t size); From noreply at nginx.com Wed Sep 4 00:59:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 4 Sep 2024 00:59:02 +0000 (UTC) Subject: [njs] Introduced libqjs.a. Message-ID: <20240904005902.1D00648753@pubserv1.nginx> details: https://github.com/nginx/njs/commit/82c5b5ad5ceff3f9f7d0cf62bf6cc74cac2c454d branches: master commit: 82c5b5ad5ceff3f9f7d0cf62bf6cc74cac2c454d user: Dmitry Volyntsev date: Tue, 23 Jul 2024 18:37:33 -0700 description: Introduced libqjs.a. --- auto/make | 48 ++++++++++++++++++++++++++++++++++++++++++------ auto/qjs_module | 2 +- auto/qjs_modules | 20 ++++++++------------ auto/sources | 8 ++++---- 4 files changed, 55 insertions(+), 23 deletions(-) diff --git a/auto/make b/auto/make index 4a77335b..cc3b022b 100644 --- a/auto/make +++ b/auto/make @@ -40,11 +40,9 @@ cat << END >> $njs_modules_c END -if [ $NJS_HAVE_QUICKJS = YES ]; then - qjs_modules_c=$NJS_BUILD_DIR/qjs_modules.c -NJS_LIB_SRCS="$NJS_LIB_SRCS $qjs_modules_c" +QJS_LIB_SRCS="$QJS_LIB_SRCS $qjs_modules_c" cat << END > $qjs_modules_c @@ -70,13 +68,20 @@ cat << END >> $qjs_modules_c }; END -fi njs_incs=`echo $NJS_LIB_INCS \ | sed -e "s# *\([^ ]*\)#$njs_regex_cont-I\1#g"` njs_objs=`echo $NJS_LIB_SRCS \ | sed -e "s# *\([^ ]*\.\)c#$NJS_BUILD_DIR/\1o$njs_regex_cont#g"` +qjs_objs=`echo $QJS_LIB_SRCS \ + | sed -e "s# *\([^ ]*\.\)c#$NJS_BUILD_DIR/\1o$njs_regex_cont#g"` + +QJS_LIB="" +if [ $NJS_HAVE_QUICKJS = YES ]; then + QJS_LIB="$NJS_BUILD_DIR/libqjs.a" +fi + cat << END > $NJS_MAKEFILE # This file is auto-generated by configure @@ -96,8 +101,10 @@ default: njs NJS_LIB_INCS = $njs_incs NJS_LIB_OBJS = $njs_objs +QJS_LIB_OBJS = $qjs_objs libnjs: $NJS_BUILD_DIR/libnjs.a pc +libqjs: $NJS_BUILD_DIR/libqjs.a $NJS_BUILD_DIR/libnjs.a: \\ $NJS_BUILD_DIR/njs_auto_config.h \\ @@ -105,6 +112,13 @@ $NJS_BUILD_DIR/libnjs.a: \\ \$(NJS_STATIC_LINK) $NJS_BUILD_DIR/libnjs.a \\ \$(NJS_LIB_OBJS) + +$NJS_BUILD_DIR/libqjs.a: \\ + $NJS_BUILD_DIR/njs_auto_config.h \\ + \$(QJS_LIB_OBJS) + + \$(NJS_STATIC_LINK) $NJS_BUILD_DIR/libqjs.a \\ + \$(QJS_LIB_OBJS) END # object files. @@ -131,17 +145,39 @@ END done +for njs_src in $QJS_LIB_SRCS +do + njs_obj="${njs_src%.c}.o" + njs_dep="${njs_src%.c}.dep" + njs_dep_flags=`njs_gen_dep_flags $njs_dep $njs_obj` + njs_dep_post=`njs_gen_dep_post $njs_dep $njs_obj` + cat << END >> $NJS_MAKEFILE + +$NJS_BUILD_DIR/$njs_obj: $njs_src + \$(NJS_CC) -c \$(NJS_LIB_INCS) \$(NJS_CFLAGS) \\ + \$(NJS_LIB_AUX_CFLAGS) \\ + -o $NJS_BUILD_DIR/$njs_obj \\ + $njs_dep_flags \\ + $njs_src + $njs_dep_post + +-include $NJS_BUILD_DIR/$njs_dep + +END + +done + # njs cli. cat << END >> $NJS_MAKEFILE $NJS_BUILD_DIR/njs: \\ - $NJS_BUILD_DIR/libnjs.a \\ + $NJS_BUILD_DIR/libnjs.a $QJS_LIB \\ external/njs_shell.c \$(NJS_LINK) -o $NJS_BUILD_DIR/njs \$(NJS_LIB_INCS) \\ \$(NJS_CFLAGS) \$(NJS_LIB_AUX_CFLAGS)\\ external/njs_shell.c \\ - $NJS_BUILD_DIR/libnjs.a \\ + $NJS_BUILD_DIR/libnjs.a $QJS_LIB \\ $NJS_LD_OPT -lm $NJS_LIBS $NJS_LIB_AUX_LIBS $NJS_READLINE_LIB END diff --git a/auto/qjs_module b/auto/qjs_module index 38270481..baf9d792 100644 --- a/auto/qjs_module +++ b/auto/qjs_module @@ -2,5 +2,5 @@ # Copyright (C) F5, Inc QJS_LIB_MODULES="$QJS_LIB_MODULES $njs_module_name" -NJS_LIB_SRCS="$NJS_LIB_SRCS $njs_module_srcs" +QJS_LIB_SRCS="$QJS_LIB_SRCS $njs_module_srcs" NJS_LIB_INCS="$NJS_LIB_INCS $njs_module_incs" diff --git a/auto/qjs_modules b/auto/qjs_modules index 9dde2efd..0216d376 100644 --- a/auto/qjs_modules +++ b/auto/qjs_modules @@ -1,20 +1,16 @@ # Copyright (C) Dmitry Volyntsev # Copyright (C) F5, Inc -if [ $NJS_HAVE_QUICKJS = YES ]; then +njs_module_name=qjs_buffer_module +njs_module_incs= +njs_module_srcs=src/qjs_buffer.c - njs_module_name=qjs_buffer_module +. auto/qjs_module + +if [ $NJS_ZLIB = YES -a $NJS_HAVE_ZLIB = YES ]; then + njs_module_name=qjs_zlib_module njs_module_incs= - njs_module_srcs=src/qjs_buffer.c + njs_module_srcs=external/qjs_zlib_module.c . auto/qjs_module - - if [ $NJS_ZLIB = YES -a $NJS_HAVE_ZLIB = YES ]; then - njs_module_name=qjs_zlib_module - njs_module_incs= - njs_module_srcs=external/qjs_zlib_module.c - - . auto/qjs_module - fi - fi diff --git a/auto/sources b/auto/sources index ae208293..9b33eead 100644 --- a/auto/sources +++ b/auto/sources @@ -52,6 +52,10 @@ NJS_LIB_SRCS=" \ src/njs_builtin.c \ " +QJS_LIB_SRCS=" \ + src/qjs.c \ +" + NJS_LIB_TEST_SRCS=" \ src/test/lvlhsh_unit_test.c \ src/test/random_unit_test.c \ @@ -72,10 +76,6 @@ if [ "$NJS_HAVE_LIBBFD" = "YES" -a "$NJS_HAVE_DL_ITERATE_PHDR" = "YES" ]; then NJS_LIB_SRCS="$NJS_LIB_SRCS src/njs_addr2line.c" fi -if [ "$NJS_HAVE_QUICKJS" = "YES" ]; then - NJS_LIB_SRCS="$NJS_LIB_SRCS src/qjs.c" -fi - NJS_TS_SRCS=$(find ts/ -name "*.d.ts" -o -name "*.json") NJS_TEST_TS_SRCS=$(find test/ts/ -name "*.ts" -o -name "*.json") From noreply at nginx.com Wed Sep 4 00:59:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 4 Sep 2024 00:59:02 +0000 (UTC) Subject: [njs] QuickJS: added wrappers for strings creation. Message-ID: <20240904005902.2BD5548841@pubserv1.nginx> details: https://github.com/nginx/njs/commit/65f1e8555e226f0072d69fd673d000639f6428db branches: master commit: 65f1e8555e226f0072d69fd673d000639f6428db user: Dmitry Volyntsev date: Fri, 16 Aug 2024 23:29:48 -0700 description: QuickJS: added wrappers for strings creation. --- src/qjs.c | 20 ++++++++++++++++++++ src/qjs.h | 11 +++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/qjs.c b/src/qjs.c index b8c53486..71aeab64 100644 --- a/src/qjs.c +++ b/src/qjs.c @@ -227,3 +227,23 @@ qjs_typed_array_data(JSContext *ctx, JSValueConst value, njs_str_t *data) return JS_UNDEFINED; } + + +JSValue +qjs_string_create_chb(JSContext *cx, njs_chb_t *chain) +{ + JSValue val; + njs_int_t ret; + njs_str_t str; + + ret = njs_chb_join(chain, &str); + if (ret != NJS_OK) { + return JS_ThrowInternalError(cx, "failed to create string"); + } + + val = JS_NewStringLen(cx, (const char *) str.start, str.length); + + chain->free(cx, str.start); + + return val; +} diff --git a/src/qjs.h b/src/qjs.h index 531940ea..516fea96 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -77,6 +77,17 @@ void qjs_bytes_free(JSContext *ctx, qjs_bytes_t *data); JSValue qjs_typed_array_data(JSContext *ctx, JSValueConst value, njs_str_t *data); +#define qjs_string_create(ctx, data, len) \ + JS_NewStringLen(ctx, (const char *) (data), len) +JSValue qjs_string_create_chb(JSContext *cx, njs_chb_t *chain); + + +static inline JS_BOOL JS_IsNullOrUndefined(JSValueConst v) +{ + return JS_VALUE_GET_TAG(v) == JS_TAG_NULL + || JS_VALUE_GET_TAG(v) == JS_TAG_UNDEFINED; +} + extern qjs_module_t *qjs_modules[]; From noreply at nginx.com Wed Sep 4 00:59:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 4 Sep 2024 00:59:02 +0000 (UTC) Subject: [njs] QuickJS: disabling eval() and Function() in qjs_new_context(). Message-ID: <20240904005902.31AE048842@pubserv1.nginx> details: https://github.com/nginx/njs/commit/8e02600da88bd70a2a4aff88e9bb38fbd849f9f3 branches: master commit: 8e02600da88bd70a2a4aff88e9bb38fbd849f9f3 user: Dmitry Volyntsev date: Wed, 24 Jul 2024 16:30:10 -0700 description: QuickJS: disabling eval() and Function() in qjs_new_context(). This properly disables eval() after previous attempt in c773ebcaad (0.8.5). In QuickJS buint-in C level eval API, which is used by njs, is linked to eval() in JS code. To disable only the JS function manual modification of global object is required. --- external/njs_shell.c | 2 +- src/qjs.c | 31 ++++++++++++++++++++++++++----- src/qjs.h | 2 +- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/external/njs_shell.c b/external/njs_shell.c index 80693634..30d4d8c6 100644 --- a/external/njs_shell.c +++ b/external/njs_shell.c @@ -2731,7 +2731,7 @@ njs_engine_qjs_init(njs_engine_t *engine, njs_opts_t *opts) return NJS_ERROR; } - engine->u.qjs.ctx = qjs_new_context(engine->u.qjs.rt, NULL, 1); + engine->u.qjs.ctx = qjs_new_context(engine->u.qjs.rt, NULL); if (engine->u.qjs.ctx == NULL) { njs_stderror("JS_NewContext() failed\n"); return NJS_ERROR; diff --git a/src/qjs.c b/src/qjs.c index 71aeab64..e7653569 100644 --- a/src/qjs.c +++ b/src/qjs.c @@ -17,8 +17,10 @@ static const JSCFunctionListEntry qjs_global_proto[] = { JSContext * -qjs_new_context(JSRuntime *rt, qjs_module_t **addons, _Bool eval) +qjs_new_context(JSRuntime *rt, qjs_module_t **addons) { + int ret; + JSAtom prop; JSValue global_obj; JSContext *ctx; qjs_module_t **module; @@ -37,10 +39,7 @@ qjs_new_context(JSRuntime *rt, qjs_module_t **addons, _Bool eval) JS_AddIntrinsicTypedArrays(ctx); JS_AddIntrinsicPromise(ctx); JS_AddIntrinsicBigInt(ctx); - - if (eval) { - JS_AddIntrinsicEval(ctx); - } + JS_AddIntrinsicEval(ctx); for (module = qjs_modules; *module != NULL; module++) { if ((*module)->init(ctx, (*module)->name) == NULL) { @@ -61,6 +60,28 @@ qjs_new_context(JSRuntime *rt, qjs_module_t **addons, _Bool eval) 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; + } + + ret = JS_DeleteProperty(ctx, global_obj, prop, 0); + JS_FreeAtom(ctx, prop); + if (ret < 0) { + return NULL; + } + + prop = JS_NewAtom(ctx, "Function"); + if (prop == JS_ATOM_NULL) { + return NULL; + } + + ret = JS_DeleteProperty(ctx, global_obj, prop, 0); + JS_FreeAtom(ctx, prop); + if (ret < 0) { + return NULL; + } + JS_FreeValue(ctx, global_obj); return ctx; diff --git a/src/qjs.h b/src/qjs.h index 516fea96..dff5919b 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -41,7 +41,7 @@ typedef struct { } qjs_module_t; -JSContext *qjs_new_context(JSRuntime *rt, qjs_module_t **addons, _Bool eval); +JSContext *qjs_new_context(JSRuntime *rt, qjs_module_t **addons); JSValue qjs_buffer_alloc(JSContext *ctx, size_t size); From noreply at nginx.com Wed Sep 4 00:59:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 4 Sep 2024 00:59:02 +0000 (UTC) Subject: [njs] Tests: making exception test more portable in stream_js.t. Message-ID: <20240904005902.3617848843@pubserv1.nginx> details: https://github.com/nginx/njs/commit/a3a4dd45a5377723042df2194b06565e9abc0f2f branches: master commit: a3a4dd45a5377723042df2194b06565e9abc0f2f user: Dmitry Volyntsev date: Wed, 28 Aug 2024 16:57:49 -0700 description: Tests: making exception test more portable in stream_js.t. --- nginx/t/stream_js.t | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nginx/t/stream_js.t b/nginx/t/stream_js.t index ed80be65..52ab688e 100644 --- a/nginx/t/stream_js.t +++ b/nginx/t/stream_js.t @@ -366,8 +366,7 @@ $t->write_file('test.js', <read(), qr/\xaa\xbb\xcc\xdd/, $t->stop(); ok(index($t->read_file('error.log'), 'SEE-THIS') > 0, 'stream js log'); -ok(index($t->read_file('error.log'), 'at fs.readFileSync') > 0, +ok(index($t->read_file('error.log'), 'at decodeURI') > 0, 'stream js_preread backtrace'); ok(index($t->read_file('error.log'), 'at filter_except') > 0, 'stream js_filter backtrace'); From noreply at nginx.com Wed Sep 4 13:09:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 4 Sep 2024 13:09:02 +0000 (UTC) Subject: [nginx] Branch created: templates Message-ID: <20240904130902.6D1F7487B6@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/98425cd4c9e3d27f714bd1e250f85747b70b5926 branches: templates commit: 98425cd4c9e3d27f714bd1e250f85747b70b5926 user: Maryna Herasimovich date: Thu, 29 Aug 2024 13:06:48 -0700 description: Added GitHub templates. --- .github/ISSUE_TEMPLATE/bug_report.md | 42 +++++++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 23 +++++++++++++++++ .github/pull_request_template.md | 13 ++++++++++ 3 files changed, 78 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..db117528c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,42 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "" +labels: "" +assignees: "" +--- + +### Describe the bug + +Please describe the bug in full detail and include all of the conditions that caused it, including the relevant part of the configuration as well. +It would substantially reduce the time and effort to understand and resolve the bug. + +### To reproduce + +Steps to reproduce the behavior: + +1. Deploy this project using [...] +2. View output/logs/configuration on [...] +3. See error + +### Expected behavior + +A clear and concise description of what you expected to happen. + +### Your environment + +1. Version of this project or specific commit + +2. Target deployment platform + +Include the result of the following commands: +- nginx -V +- uname -a + +### Additional context + +Add any other context about the problem here. + +### Sensitive Information + +Remember to redact any sensitive information such as authentication credentials or license keys. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..a0b4c03f7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,23 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "" +labels: "" +assignees: "" +--- + +### Is your feature request related to a problem? Please describe + +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +### Describe the solution you'd like + +A clear and concise description of what you want to happen. + +### Describe alternatives you've considered + +A clear and concise description of any alternative solutions or features you've considered. + +### Additional context + +Add any other context or screenshots about the feature request here. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..338e54dd3 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +### Proposed changes + +Describe the use case, issue and detail of the change. +If this PR addresses an issue on GitHub, make sure to include a link to that issue using one of the +[supported keywords](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue) here in this description (not +in the title of the PR). + +### Checklist + +Before creating a PR, run through this checklist and mark each as complete: +- [ ] I have read the [`CONTRIBUTING`](https://github.com/nginx/nginx/blob/master/CONTRIBUTING.md) document +- [ ] If applicable, I have added tests that prove my fix is effective or that my feature works +- [ ] If applicable, I have checked that any relevant tests pass after adding my changes From noreply at nginx.com Wed Sep 4 13:10:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 4 Sep 2024 13:10:02 +0000 (UTC) Subject: [nginx] Branch deleted: templates Message-ID: <20240904131002.2F480487B7@pubserv1.nginx> details: branches: templates commit: 98425cd4c9e3d27f714bd1e250f85747b70b5926 user: date: Wed, 04 Sep 2024 13:10:02 +0000 From noreply at nginx.com Wed Sep 4 15:12:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 4 Sep 2024 15:12:02 +0000 (UTC) Subject: [nginx] Added GitHub templates. Message-ID: <20240904151202.387C2487B9@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/082a3cbe3b7f6c28630f271c74b1787220974ed9 branches: master commit: 082a3cbe3b7f6c28630f271c74b1787220974ed9 user: Maryna Herasimovich date: Thu, 29 Aug 2024 13:06:48 -0700 description: Added GitHub templates. --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 18 +++++++++++++++ .github/pull_request_template.md | 10 ++++++++ 3 files changed, 66 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..efdc1167f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "" +labels: "bug" +--- + +### Environment + +Include the result of the following commands: + - `nginx -V` + - `uname -a` + +### Description + +Describe the bug in full detail including expected and actual behavior. +Specify conditions that caused it. Provide the relevant part of nginx +configuration and debug log. + +- [ ] The bug is reproducible with the latest version of nginx +- [ ] The nginx configuration is minimized to the smallest possible +to reproduce the issue and doesn't contain third-party modules + +#### nginx configuration + +``` +# Your nginx configuration here +``` +or share the configuration in [gist](https://gist.github.com/). + +#### nginx debug log + +It is advised to enable +[debug logging](http://nginx.org/en/docs/debugging_log.html). +``` +# Your nginx debug log here +``` +or share the debug log in [gist](https://gist.github.com/). diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..67471b083 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,18 @@ +--- +name: Feature request +about: Suggest a feature for nginx +title: "" +labels: "feature" +--- + +### Describe the feature you'd like to add to nginx + +A clear and concise description of the feature. + +### Describe the problem this feature solves + +A clear and concise description of the problem. + +### Additional context + +Add any other context about the feature request here. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..0ee1fc364 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,10 @@ +### Proposed changes + +Describe the use case and detail of the change. + +If this pull request addresses an issue on GitHub, make sure to reference that +issue using one of the +[supported keywords](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue). + +Before creating a pull request, make sure to comply with +[`Contributing Guidelines`](/CONTRIBUTING.md). From jiri.setnicka at cdn77.com Wed Sep 4 15:51:22 2024 From: jiri.setnicka at cdn77.com (=?UTF-8?B?SmnFmcOtIFNldG5pxI1rYQ==?=) Date: Wed, 4 Sep 2024 17:51:22 +0200 Subject: Correct way of finalizing a HTTP/2 connection Message-ID: Hello, I noticed that in some situations not all queued HTTP/2 frames may be sent before closing the HTTP/2 connection. This could happen when the ngx_http_v2_finalize_connection is called with some frames in the h2c->last_out queue and with the c->write not ready. In such situation, the GOAWAY frame is pushed into the queue and ngx_http_v2_send_output_queue is called immediately, but no frames are sent due to the not ready c->write. During normal processing, these frames would be sent when the socket became ready again, but during the connection finalization the NGX_AGAIN is silently ignored, the queue in h2c->last_out is thrown away and the TCP connection is closed. Connection finalization with a non-empty queue of frames could be triggered by some error in the connection itself (disconnected client, ...) or by breaking some constraints like a number of concurrent streams. In the first case, the "best-effort" ngx_http_v2_send_output_queue before shutdown is the only thing we can do. But in the second case, the client is still able to receive the remaining frames, some of them useful for him (GOAWAY frame with an error reason, RST_STREAM frame providing information, if the request could be repeated), but these queued frames are never sent. I think if it is worth modifying the ngx_http_v2_finalize_connection and ngx_http_v2_lingering_close in such way, that a lingering write handler would be set in a similar way as the lingering read handler to allow the queue to be sent until the lingering timeout. Any thoughts? Also, I'm not fully sure about the reason behind that the h2c->last_out being set to NULL in ngx_http_v2_finalize_connection, when the event handlers of streams could generate more frames and again populating the h2c->last_out. Is there some reason why it is done in this way? Sincerely Jiří Setnička From noreply at nginx.com Wed Sep 4 16:02:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 4 Sep 2024 16:02:02 +0000 (UTC) Subject: [nginx] Added CI based on GitHub Actions. Message-ID: <20240904160202.9037C487BA@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/042b9cc4db564dc16dfc93c31c14e9a6bb749509 branches: master commit: 042b9cc4db564dc16dfc93c31c14e9a6bb749509 user: Konstantin Pavlov date: Thu, 18 Jul 2024 13:42:43 -0700 description: Added CI based on GitHub Actions. Pushes to master and stable branches will result in buildbot-like checks on multiple OSes and architectures. Pull requests will be checked on a public Ubuntu GitHub runner. --- .github/workflows/buildbot.yml | 11 +++++++++++ .github/workflows/check-pr.yml | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/.github/workflows/buildbot.yml b/.github/workflows/buildbot.yml new file mode 100644 index 000000000..484b74f98 --- /dev/null +++ b/.github/workflows/buildbot.yml @@ -0,0 +1,11 @@ +name: buildbot + +on: + push: + branches: + - master + - 'stable-1.*' + +jobs: + buildbot: + uses: nginx/ci-self-hosted/.github/workflows/nginx-buildbot.yml at main diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml new file mode 100644 index 000000000..92c0ea6bf --- /dev/null +++ b/.github/workflows/check-pr.yml @@ -0,0 +1,8 @@ +name: check-pr + +on: + pull_request: + +jobs: + check-pr: + uses: nginx/ci-self-hosted/.github/workflows/nginx-check-pr.yml at main From noreply at nginx.com Wed Sep 4 16:38:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 4 Sep 2024 16:38:02 +0000 (UTC) Subject: [njs] Fixed handling of encode arg in fs.readdir() and fs.realpath(). Message-ID: <20240904163802.C621A48855@pubserv1.nginx> details: https://github.com/nginx/njs/commit/6259cca26b6747fa8ade15c4b602b9ad190cb14a branches: master commit: 6259cca26b6747fa8ade15c4b602b9ad190cb14a user: Dmitry Volyntsev date: Tue, 3 Sep 2024 21:32:33 -0700 description: Fixed handling of encode arg in fs.readdir() and fs.realpath(). The issue was introduced in 98c6570f3 (0.8.0). --- external/njs_fs_module.c | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/external/njs_fs_module.c b/external/njs_fs_module.c index 5f9a48e2..de378cee 100644 --- a/external/njs_fs_module.c +++ b/external/njs_fs_module.c @@ -1940,14 +1940,19 @@ njs_fs_readdir(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, encoding = NULL; - if (!njs_value_is_string(njs_value_arg(&encode))) { + + if (njs_value_is_string(njs_value_arg(&encode))) { njs_value_string_get(njs_value_arg(&encode), &s); - if (!njs_strstr_eq(&s, &string_buffer)) { - encoding = njs_buffer_encoding(vm, njs_value_arg(&encode), 1); - if (njs_slow_path(encoding == NULL)) { - return NJS_ERROR; - } + } else { + s.length = 0; + s.start = NULL; + } + + if (!njs_strstr_eq(&s, &string_buffer)) { + encoding = njs_buffer_encoding(vm, njs_value_arg(&encode), 1); + if (njs_slow_path(encoding == NULL)) { + return NJS_ERROR; } } @@ -2080,14 +2085,18 @@ njs_fs_realpath(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, encoding = NULL; - if (!njs_value_is_string(njs_value_arg(&encode))) { + if (njs_value_is_string(njs_value_arg(&encode))) { njs_value_string_get(njs_value_arg(&encode), &s); - if (!njs_strstr_eq(&s, &string_buffer)) { - encoding = njs_buffer_encoding(vm, njs_value_arg(&encode), 1); - if (njs_slow_path(encoding == NULL)) { - return NJS_ERROR; - } + } else { + s.length = 0; + s.start = NULL; + } + + if (!njs_strstr_eq(&s, &string_buffer)) { + encoding = njs_buffer_encoding(vm, njs_value_arg(&encode), 1); + if (njs_slow_path(encoding == NULL)) { + return NJS_ERROR; } } From noreply at nginx.com Fri Sep 6 11:37:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 6 Sep 2024 11:37:02 +0000 (UTC) Subject: [nginx] Fixed a typo in win-utf. Message-ID: <20240906113702.1C1B348842@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/00637cce366f17b78fe1ed5c1ef0e534143045f6 branches: master commit: 00637cce366f17b78fe1ed5c1ef0e534143045f6 user: Shaikh Yaser date: Thu, 5 Sep 2024 20:47:54 +0530 description: Fixed a typo in win-utf. --- conf/win-utf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/win-utf b/conf/win-utf index ed8bc007a..d0b7116c8 100644 --- a/conf/win-utf +++ b/conf/win-utf @@ -37,7 +37,7 @@ charset_map windows-1251 utf-8 { AA D084 ; # capital Ukrainian YE AB C2AB ; # left-pointing double angle quotation mark AC C2AC ; # not sign - AD C2AD ; # soft hypen + AD C2AD ; # soft hyphen AE C2AE ; # (R) AF D087 ; # capital Ukrainian YI From arut at nginx.com Fri Sep 6 15:11:25 2024 From: arut at nginx.com (Roman Arutyunyan) Date: Fri, 6 Sep 2024 19:11:25 +0400 Subject: NGINX has moved to Github! Message-ID: Hello from NGINX! Today we're thrilled to announce that the official NGINX Open Source development repository has moved from Mercurial to GitHub [1][2][3], where we will now start accepting contributions in the form of Pull Requests. Additionally, starting today, we will begin accepting bugs reports, feature requests and enhancements directly through GitHub, under the "Issues" tab. Moreover, we've moved our community forums to the GitHub "Discussions" area, where you will now be able to engage in conversation, ask, and answer questions. Important: to report a security vulnerability, please follow our security policy [4]. We understand that changes like these may require adjustment, so to give you more time, we will continue accepting patches and provide community support via mailing lists until December 31st, 2024. We believe these changes will serve to centralize, modernize and expand access to NGINX development and communities. They represent our continued commitment to open source, as outlined in the blog post [5]. Most of all, we can't wait to see all of your contributions, discussions and feedback, as we move into this next chapter for NGINX. [1] https://github.com/nginx/nginx [2] https://github.com/nginx/nginx-tests [3] https://github.com/nginx/nginx.org [4] https://github.com/nginx/nginx/blob/master/SECURITY.md [5] https://www.f5.com/company/blog/nginx/meetup-recap-nginxs-commitments-to-the-open-source-community On behalf of the NGINX Team, Roman Arutyunyan arut at nginx.com From tpxp at live.fr Fri Sep 6 16:32:05 2024 From: tpxp at live.fr (Thomas P.) Date: Fri, 6 Sep 2024 16:32:05 +0000 Subject: NGINX has moved to Github! In-Reply-To: References: Message-ID: Hello, Congratulations on the transition, I'm sure this will make contributing to nginx easier! Just wondering about the state of patches that were previously sent to this mailing list that haven't received feedback yet. I sent one some time ago, shall I submit it again as pull request to get more traction or will patches sent to this mailing list eventually get reviewed? Cheers, Thomas P. ________________________________ De : nginx-devel de la part de Roman Arutyunyan Envoyé : vendredi 6 septembre 2024 17:11 À : nginx-devel at nginx.org Objet : NGINX has moved to Github! Hello from NGINX! Today we're thrilled to announce that the official NGINX Open Source development repository has moved from Mercurial to GitHub [1][2][3], where we will now start accepting contributions in the form of Pull Requests. Additionally, starting today, we will begin accepting bugs reports, feature requests and enhancements directly through GitHub, under the "Issues" tab. Moreover, we've moved our community forums to the GitHub "Discussions" area, where you will now be able to engage in conversation, ask, and answer questions. Important: to report a security vulnerability, please follow our security policy [4]. We understand that changes like these may require adjustment, so to give you more time, we will continue accepting patches and provide community support via mailing lists until December 31st, 2024. We believe these changes will serve to centralize, modernize and expand access to NGINX development and communities. They represent our continued commitment to open source, as outlined in the blog post [5]. Most of all, we can't wait to see all of your contributions, discussions and feedback, as we move into this next chapter for NGINX. [1] https://github.com/nginx/nginx [2] https://github.com/nginx/nginx-tests [3] https://github.com/nginx/nginx.org [4] https://github.com/nginx/nginx/blob/master/SECURITY.md [5] https://www.f5.com/company/blog/nginx/meetup-recap-nginxs-commitments-to-the-open-source-community On behalf of the NGINX Team, Roman Arutyunyan arut at nginx.com _______________________________________________ nginx-devel mailing list nginx-devel at nginx.org https://mailman.nginx.org/mailman/listinfo/nginx-devel -------------- next part -------------- An HTML attachment was scrubbed... URL: From pclicoder at gmail.com Fri Sep 6 16:44:55 2024 From: pclicoder at gmail.com (Praveen Chaudhary) Date: Fri, 6 Sep 2024 09:44:55 -0700 Subject: NGINX has moved to Github! In-Reply-To: References: Message-ID: This is awesome news, Roman. Thanks for the effort towards it, I really appreciate it. On Fri, Sep 6, 2024 at 8:12 AM Roman Arutyunyan wrote: > Hello from NGINX! > > Today we're thrilled to announce that the official NGINX Open Source > development > repository has moved from Mercurial to GitHub [1][2][3], where we will now > start > accepting contributions in the form of Pull Requests. Additionally, > starting > today, we will begin accepting bugs reports, feature requests and > enhancements > directly through GitHub, under the "Issues" tab. Moreover, we've moved our > community forums to the GitHub "Discussions" area, where you will now be > able > to engage in conversation, ask, and answer questions. > > Important: to report a security vulnerability, please follow our security > policy [4]. > > We understand that changes like these may require adjustment, so to give > you > more time, we will continue accepting patches and provide community support > via mailing lists until December 31st, 2024. > > We believe these changes will serve to centralize, modernize and expand > access > to NGINX development and communities. They represent our continued > commitment > to open source, as outlined in the blog post [5]. Most of all, we can't > wait to > see all of your contributions, discussions and feedback, as we move into > this > next chapter for NGINX. > > [1] https://github.com/nginx/nginx > [2] https://github.com/nginx/nginx-tests > [3] https://github.com/nginx/nginx.org > [4] https://github.com/nginx/nginx/blob/master/SECURITY.md > [5] > https://www.f5.com/company/blog/nginx/meetup-recap-nginxs-commitments-to-the-open-source-community > > > On behalf of the NGINX Team, > > Roman Arutyunyan > arut at nginx.com > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel > -------------- next part -------------- An HTML attachment was scrubbed... URL: From thresh at nginx.com Fri Sep 6 18:21:36 2024 From: thresh at nginx.com (Konstantin Pavlov) Date: Fri, 6 Sep 2024 11:21:36 -0700 Subject: NGINX has moved to Github! In-Reply-To: References: Message-ID: <71be18e3-0631-484c-8a8e-9e87f2007ef6@nginx.com> Hello! On 06/09/2024 8:11 AM, Roman Arutyunyan wrote: > Hello from NGINX! > > Today we're thrilled to announce that the official NGINX Open Source development > repository has moved from Mercurial to GitHub [1][2][3], where we will now start > accepting contributions in the form of Pull Requests. > > [1] https://github.com/nginx/nginx > [2] https://github.com/nginx/nginx-tests > [3] https://github.com/nginx/nginx.org > [4] https://github.com/nginx/nginx/blob/master/SECURITY.md > [5] https://www.f5.com/company/blog/nginx/meetup-recap-nginxs-commitments-to-the-open-source-community For the sake of web engine indexing... NGINX commit logs sometimes reference mercurial changeset IDs, like: $ hg export -r 9248 --template "{node}\n{desc}\n" f7d53c7f70140b1cd1eaf51ce4346a873692f879 Optimized chain link usage (ticket #2614). Previously chain links could sometimes be dropped instead of being reused, which could result in increased memory consumption during long requests. A similar chain link issue in ngx_http_gzip_filter_module was fixed in da46bfc484ef (1.11.10). Based on a patch by Sangmin Lee. To get access to those node identifiers through git, use the following: $ git clone https://github.com/nginx/nginx && cd nginx $ git fetch origin refs/notes/hg:refs/notes/hg $ git config core.notesRef refs/notes/hg Now you will see notes in e.g. git log, making it easy to search for & identify commits in question: commit ea8270c6142869367c5608bff92df9f5b3f32d37 Date:   Thu May 23 19:15:38 2024 +0400     Optimized chain link usage (ticket #2614).     Previously chain links could sometimes be dropped instead of being reused,     which could result in increased memory consumption during long requests.     A similar chain link issue in ngx_http_gzip_filter_module was fixed in     da46bfc484ef (1.11.10).     Based on a patch by Sangmin Lee. Notes (hg):     f7d53c7f70140b1cd1eaf51ce4346a873692f879 From pluknet at nginx.com Mon Sep 9 17:34:24 2024 From: pluknet at nginx.com (Sergey Kandaurov) Date: Mon, 9 Sep 2024 21:34:24 +0400 Subject: NGINX has moved to Github! In-Reply-To: References: Message-ID: > On 6 Sep 2024, at 20:32, Thomas P. wrote: > > Hello, > > Congratulations on the transition, I'm sure this will make contributing to nginx easier! > > Just wondering about the state of patches that were previously sent to this mailing list that haven't received feedback yet. I sent one some time ago, shall I submit it again as pull request to get more traction or will patches sent to this mailing list eventually get reviewed? > Hello, The intention is to pick up patches eventually and get them through PR. If you are able to resubmit them yourself, that should facilitate the process. -- Sergey Kandaurov From sambhavgupta at microsoft.com Tue Sep 10 08:50:56 2024 From: sambhavgupta at microsoft.com (Sambhav Gupta) Date: Tue, 10 Sep 2024 08:50:56 +0000 Subject: Nginx allocates directive names from cycle pool Message-ID: Hi Nginx experts, While reviewing the Nginx source code, I noticed that during the conf file parsing, directive names are allocated from the cycle pool. For instance: ssl_certificate /path/to/cert/file; It makes sense to allocate the directive's argument (e.g., the file path) from a pool that persists for the lifetime of the cycle, as modules generally don't clone these arguments but store them directly in their respective confs. However, wouldn't it be more efficient to allocate the directive name itself from a temporary pool, one that only lives during the configuration parsing process? Since the directive name is only needed for comparison against the module's command array, it seems unnecessary for it to persist for the entire cycle. I'd appreciate any insights or thoughts on this approach. Best regards, Sambhav -------------- next part -------------- An HTML attachment was scrubbed... URL: From noreply at nginx.com Wed Sep 11 15:46:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 11 Sep 2024 15:46:02 +0000 (UTC) Subject: [nginx] README: incorporate feedback Message-ID: <20240911154602.5B19A48C1D@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/3da24cb9b83fe97e60576eef16776a615e1fabc6 branches: master commit: 3da24cb9b83fe97e60576eef16776a615e1fabc6 user: Michael Vernik date: Mon, 9 Sep 2024 22:46:35 -0700 description: README: incorporate feedback --- README.md | 38 +++++++++----------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index ea7716b49..e2f731740 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,6 @@ Enterprise distributions, commercial support and training are available from [F5 - [Compiling](#compiling) - [Location of binary and installation](#location-of-binary-and-installation) - [Running and testing the installed binary](#running-and-testing-the-installed-binary) -- [Technical specifications](#technical-specifications) - - [Supported distributions](#supported-distributions) - - [Windows](#windows) - - [Supported deployment environments](#supported-deployment-environments) - [Asking questions and reporting issues](#asking-questions-reporting-issues-and-contributing) - [Contributing code](#contributing-code) - [Additional help and resources](#additional-help-and-resources) @@ -53,12 +49,9 @@ NGINX is installed software with binary packages available for all major operati > While nearly all popular Linux-based operating systems are distributed with a community version of nginx, we highly advise installation and usage of official [packages](https://nginx.org/en/linux_packages.html) or sources from this repository. Doing so ensures that you're using the most recent release or source code, including the latest feature-set, fixes and security patches. ## Modules -NGINX is comprised of individual modules, each extending core functionality by providing additional, configurable features. See "Modules reference" at the bottom of [nginx documentation](https://nginx.org/en/docs/) for a complete list of native modules. +NGINX is comprised of individual modules, each extending core functionality by providing additional, configurable features. See "Modules reference" at the bottom of [nginx documentation](https://nginx.org/en/docs/) for a complete list of official modules. -NGINX supports static and dynamic modules. Static modules are defined at build-time and compiled into the resulting binaries. [Dynamic modules](https://nginx.org/en/linux_packages.html#dynmodules) (for example, [njs](https://github.com/nginx/njs)) are built and distributed separately. They can be added to, or removed from, an NGINX installation at any time. - -> [!IMPORTANT] -> Official NGINX package distributions are built with all native open source static modules. +NGINX supports static and dynamic modules. Static modules are defined at build-time and compiled into the resulting binaries. See [Dynamic Modules](#dynamic-modules) for more information on how they work, as well as, how to obtain, install, and configure them. ## Configurations NGINX is highly flexible and configurable. Provisioning the software is achieved via text-based config file(s) organized in functional sections called "Contexts", accepting a vast amount of configuration parameters called "[Directives](https://nginx.org/en/docs/dirindex.html)". See [Configuration File's Structure](https://nginx.org/en/docs/beginners_guide.html#conf_structure) for a comprehensive definition of Directives and Contexts. @@ -77,7 +70,7 @@ The number of [worker processes](https://nginx.org/en/docs/ngx_core_module.html# > Processes synchronize data through shared memory. For this reason, many NGINX directives require the allocation of shared memory zones. As an example, when configuring [rate limiting](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req), connecting clients must be tracked in a [common memory zone](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req_zone) so all worker processes can know how many times a particular client has accessed the server in a span of time. # Downloading and installing -Follow these steps to download and install precompiled NGINX binaries. You may also choose to [build the module locally from source code](#building-from-source). +Follow these steps to download and install precompiled NGINX binaries. You may also choose to [build NGINX locally from source code](#building-from-source). ## Stable and Mainline binaries NGINX binaries are built and distributed in two versions: stable and mainline. You'll need to [decide which is appropriate for your purposes](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#choosing-between-a-stable-or-a-mainline-version). @@ -101,11 +94,14 @@ For more information on installing NGINX on FreeBSD system, visit https://nginx. Windows executables for mainline and stable releases can be found on the main [NGINX download page](https://nginx.org/en/download.html). Note that the current implementation of NGINX for Windows is at the Proof-of-Concept stage and should only be used for development and testing purposes. ## Dynamic modules -NGINX version 1.9.11 added support for [Dynamic Modules](https://nginx.org/en/docs/ngx_core_module.html#load_module). Unlike standard, Static modules, which must be complied into NGINX binaries at build-time, Dynamic modules can be downloaded, installed, and configured at any point. [Official dynamic module binaries](https://nginx.org/en/linux_packages.html#dynmodules) are available from the same package repository as the core NGINX binaries described in previous steps. +NGINX version 1.9.11 added support for [Dynamic Modules](https://nginx.org/en/docs/ngx_core_module.html#load_module). Unlike standard, Static modules, which must be complied into NGINX binaries at build-time, Dynamic modules can be downloaded, installed, and configured after the core NGINX binaries have been built. [Official dynamic module binaries](https://nginx.org/en/linux_packages.html#dynmodules) are available from the same package repository as the core NGINX binaries described in previous steps. > [!TIP] > [NGINX JavaScript (njs)](https://github.com/nginx/njs), is a popular NGINX dynamic module that enables the extension of core NGINX functionality using familiar JavaScript syntax. +> [!IMPORTANT] +> If desired, dynamic modules can also be built statically into NGINX at compile time. + # Getting started with NGINX For a gentle introduction to NGINX basics, please see our [Beginner’s Guide](https://nginx.org/en/docs/beginners_guide.html). @@ -160,7 +156,7 @@ git clone https://github.com/nginx/nginx.git ``` ## Configuring the build -Prior to building NGINX (and most other Linux source packages), you must issue the `configure` command with [appropriate flags](https://nginx.org/en/docs/configure.html). This will generate a Makefile in your NGINX source root directory that can then be used to compile NGINX with [options specified during configuration](https://nginx.org/en/docs/configure.html). +Prior to building NGINX (and most other Linux source packages), you must run the `configure` script with [appropriate flags](https://nginx.org/en/docs/configure.html). This will generate a Makefile in your NGINX source root directory that can then be used to compile NGINX with [options specified during configuration](https://nginx.org/en/docs/configure.html). From the NGINX source code repository's root directory: @@ -179,7 +175,7 @@ make ``` ## Location of binary and installation -After successful compilation, a binary will be generated at `/objs/nginx`. To install this binary, issue the following command from the source root directory: +After successful compilation, a binary will be generated at `/objs/nginx`. To install this binary, issue the following command from the source root directory: ```bash sudo make install @@ -210,22 +206,6 @@ The output of which should start with: Welcome to nginx! ``` -# Technical specifications -Pre-built NGINX binaries are available and supported across a wide number of operating systems. Please refer to [this complete list](https://nginx.org/en/) of operating systems, architectures, libraries and other features supported by NGINX. - -## Supported distributions -A [complete list of available Linux packages](https://nginx.org/en/linux_packages.html#distributions) can be found on the binary download page. - -See [Tested OS Platforms](https://nginx.org/en/#tested_os_and_platforms) for a list of operating systems that NGINX is confirmed to run on. - -## Windows -Windows support is tested on Windows XP, Windows Server 2003, Windows 7, Windows 10. [Windows executables](https://nginx.org/en/download.html) can be found on the download page. Note that the current implementation of NGINX for Windows is at the Proof-of-Concept stage and should only be used for development and testing purposes. - -## Supported deployment environments -- Container -- Public cloud (AWS, Google Cloud Platform, Microsoft Azure) -- Virtual machine - # Asking questions and reporting issues We encourage you to engage with us. - [NGINX GitHub Discussions](https://github.com/nginx/nginx/discussions), is the go-to place to start asking questions and sharing your thoughts. From noreply at nginx.com Wed Sep 11 15:46:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 11 Sep 2024 15:46:02 +0000 (UTC) Subject: [nginx] README: added more info/link to Windows version on nginx.org Message-ID: <20240911154602.5EE2948C1E@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/9bdc2bacbe9cd44d09370dd950504f443469fa85 branches: master commit: 9bdc2bacbe9cd44d09370dd950504f443469fa85 user: Michael Vernik date: Mon, 9 Sep 2024 23:40:22 -0700 description: README: added more info/link to Windows version on nginx.org --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e2f731740..c6c18f56c 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ Once a repository has been added and verified, follow [these steps](https://ngin For more information on installing NGINX on FreeBSD system, visit https://nginx.org/en/docs/install.html ## Windows executables -Windows executables for mainline and stable releases can be found on the main [NGINX download page](https://nginx.org/en/download.html). Note that the current implementation of NGINX for Windows is at the Proof-of-Concept stage and should only be used for development and testing purposes. +Windows executables for mainline and stable releases can be found on the main [NGINX download page](https://nginx.org/en/download.html). Note that the current implementation of NGINX for Windows is at the Proof-of-Concept stage and should only be used for development and testing purposes. For additional information, please see [nginx for Windows](https://nginx.org/en/docs/windows.html). ## Dynamic modules NGINX version 1.9.11 added support for [Dynamic Modules](https://nginx.org/en/docs/ngx_core_module.html#load_module). Unlike standard, Static modules, which must be complied into NGINX binaries at build-time, Dynamic modules can be downloaded, installed, and configured after the core NGINX binaries have been built. [Official dynamic module binaries](https://nginx.org/en/linux_packages.html#dynmodules) are available from the same package repository as the core NGINX binaries described in previous steps. From noreply at nginx.com Wed Sep 11 15:46:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 11 Sep 2024 15:46:02 +0000 (UTC) Subject: [nginx] Added README.md. Message-ID: <20240911154602.4ED9448C1A@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/07c31459e51cb115ce0440e55c13391a5f823cfe branches: master commit: 07c31459e51cb115ce0440e55c13391a5f823cfe user: Michael Vernik date: Sat, 31 Aug 2024 22:42:09 -0700 description: Added README.md. --- README | 3 - README.md | 248 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ misc/GNUmakefile | 2 +- 3 files changed, 249 insertions(+), 4 deletions(-) diff --git a/README b/README deleted file mode 100644 index 2f68e14e0..000000000 --- a/README +++ /dev/null @@ -1,3 +0,0 @@ - -Documentation is available at http://nginx.org - diff --git a/README.md b/README.md new file mode 100644 index 000000000..ea7716b49 --- /dev/null +++ b/README.md @@ -0,0 +1,248 @@ + + + + NGINX Banner + + +NGINX (pronounced "engine x" or "en-jin-eks") is the world's most popular Web Server, high performance Load Balancer, Reverse Proxy, API Gateway and Content Cache. + +NGINX is free and open source software, distributed under the terms of a simplified [2-clause BSD-like license](LICENSE). + +Enterprise distributions, commercial support and training are available from [F5, Inc](https://www.f5.com/products/nginx). + +> [!IMPORTANT] +> The goal of this README is to provide a basic, structured introduction to NGINX for novice users. Please refer to the [full NGINX documentation](https://nginx.org/en/docs/) for detailed information on [installing](https://nginx.org/en/docs/install.html), [building](https://nginx.org/en/docs/configure.html), [configuring](https://nginx.org/en/docs/dirindex.html), [debugging](https://nginx.org/en/docs/debugging_log.html), and more. These documentation pages also contain a more detailed [Beginners Guide](https://nginx.org/en/docs/beginners_guide.html), How-Tos, [Development guide](https://nginx.org/en/docs/dev/development_guide.html), and a complete module and [directive reference](https://nginx.org/en/docs/dirindex.html). + +# Table of contents +- [How it works](#how-it-works) + - [Modules](#modules) + - [Configurations](#configurations) + - [Runtime](#runtime) +- [Downloading and installing](#downloading-and-installing) + - [Stable and Mainline binaries](#stable-and-mainline-binaries) + - [Linux binary installation process](#linux-binary-installation-process) + - [FreeBSD installation process](#freebsd-installation-process) + - [Windows executables](#windows-executables) + - [Dynamic modules](#dynamic-modules) +- [Getting started with NGINX](#getting-started-with-nginx) + - [Installing SSL certificates and enabling TLS encryption](#installing-ssl-certificates-and-enabling-tls-encryption) + - [Load Balancing](#load-balancing) + - [Rate limiting](#rate-limiting) + - [Content caching](#content-caching) +- [Building from source](#building-from-source) + - [Installing dependencies](#installing-dependencies) + - [Cloning the NGINX GitHub repository](#cloning-the-nginx-github-repository) + - [Configuring the build](#configuring-the-build) + - [Compiling](#compiling) + - [Location of binary and installation](#location-of-binary-and-installation) + - [Running and testing the installed binary](#running-and-testing-the-installed-binary) +- [Technical specifications](#technical-specifications) + - [Supported distributions](#supported-distributions) + - [Windows](#windows) + - [Supported deployment environments](#supported-deployment-environments) +- [Asking questions and reporting issues](#asking-questions-reporting-issues-and-contributing) +- [Contributing code](#contributing-code) +- [Additional help and resources](#additional-help-and-resources) +- [Changelog](#changelog) +- [License](#license) + +# How it works +NGINX is installed software with binary packages available for all major operating systems and Linux distributions. See [Tested OS and Platforms](https://nginx.org/en/#tested_os_and_platforms) for a full list of compatible systems. + +> [!IMPORTANT] +> While nearly all popular Linux-based operating systems are distributed with a community version of nginx, we highly advise installation and usage of official [packages](https://nginx.org/en/linux_packages.html) or sources from this repository. Doing so ensures that you're using the most recent release or source code, including the latest feature-set, fixes and security patches. + +## Modules +NGINX is comprised of individual modules, each extending core functionality by providing additional, configurable features. See "Modules reference" at the bottom of [nginx documentation](https://nginx.org/en/docs/) for a complete list of native modules. + +NGINX supports static and dynamic modules. Static modules are defined at build-time and compiled into the resulting binaries. [Dynamic modules](https://nginx.org/en/linux_packages.html#dynmodules) (for example, [njs](https://github.com/nginx/njs)) are built and distributed separately. They can be added to, or removed from, an NGINX installation at any time. + +> [!IMPORTANT] +> Official NGINX package distributions are built with all native open source static modules. + +## Configurations +NGINX is highly flexible and configurable. Provisioning the software is achieved via text-based config file(s) organized in functional sections called "Contexts", accepting a vast amount of configuration parameters called "[Directives](https://nginx.org/en/docs/dirindex.html)". See [Configuration File's Structure](https://nginx.org/en/docs/beginners_guide.html#conf_structure) for a comprehensive definition of Directives and Contexts. + +> [!NOTE] +> The set of directives available to your distribution of NGINX is dependent on which [modules](#modules) have been made available to it. + +## Runtime +Rather than running in a single, monolithic process, NGINX is architected to scale beyond OS process stack limitations by operating as a collection of processes. They include: +- A "master" process that maintains worker processes, as well as, reads and evaluates configuration files. +- One or more "worker" processes that process data (eg. HTTP requests). + +The number of [worker processes](https://nginx.org/en/docs/ngx_core_module.html#worker_processes) is typically set to the number of CPU cores on the system. In most cases, this optimally balances load across available system resources, as NGINX is designed to efficiently distribute work across all available worker processes. + +> [!TIP] +> Processes synchronize data through shared memory. For this reason, many NGINX directives require the allocation of shared memory zones. As an example, when configuring [rate limiting](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req), connecting clients must be tracked in a [common memory zone](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req_zone) so all worker processes can know how many times a particular client has accessed the server in a span of time. + +# Downloading and installing +Follow these steps to download and install precompiled NGINX binaries. You may also choose to [build the module locally from source code](#building-from-source). + +## Stable and Mainline binaries +NGINX binaries are built and distributed in two versions: stable and mainline. You'll need to [decide which is appropriate for your purposes](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#choosing-between-a-stable-or-a-mainline-version). + +## Linux binary installation process +The NGINX binary installation process takes advantage of package managers native to specific Linux distributions. For this reason, first-time installations involve adding the official NGINX package repository to your system's package manager. + +### GPG keys +GPG signing keys are used to verify the authenticity of packages. Read more [important details](https://nginx.org/en/linux_packages.html#signatures) on our GPG/PGP keys. + +### Upgrades +Future upgrades to the latest version can be managed using the same package manager without the need to manually download and verify binaries. + +### Installation instructions +Once a repository has been added and verified, follow [these steps](https://nginx.org/en/linux_packages.html) to install NGINX binaries using the package manager native to your Linux distribution. + +## FreeBSD installation process +For more information on installing NGINX on FreeBSD system, visit https://nginx.org/en/docs/install.html + +## Windows executables +Windows executables for mainline and stable releases can be found on the main [NGINX download page](https://nginx.org/en/download.html). Note that the current implementation of NGINX for Windows is at the Proof-of-Concept stage and should only be used for development and testing purposes. + +## Dynamic modules +NGINX version 1.9.11 added support for [Dynamic Modules](https://nginx.org/en/docs/ngx_core_module.html#load_module). Unlike standard, Static modules, which must be complied into NGINX binaries at build-time, Dynamic modules can be downloaded, installed, and configured at any point. [Official dynamic module binaries](https://nginx.org/en/linux_packages.html#dynmodules) are available from the same package repository as the core NGINX binaries described in previous steps. + +> [!TIP] +> [NGINX JavaScript (njs)](https://github.com/nginx/njs), is a popular NGINX dynamic module that enables the extension of core NGINX functionality using familiar JavaScript syntax. + +# Getting started with NGINX +For a gentle introduction to NGINX basics, please see our [Beginner’s Guide](https://nginx.org/en/docs/beginners_guide.html). + +## Installing SSL certificates and enabling TLS encryption +See [Configuring HTTPS servers](https://nginx.org/en/docs/http/configuring_https_servers.html) for a quick guide on how to enable secure traffic to your NGINX installation. + +## Load Balancing +For a quick start guide on configuring NGINX as a Load Balancer, please see [Using nginx as HTTP load balancer](https://nginx.org/en/docs/http/load_balancing.html). + +## Rate limiting +See our [Rate Limiting with NGINX](https://blog.nginx.org/blog/rate-limiting-nginx) blog post for an overview of core concepts for provisioning NGINX as an API Gateway. + +## Content caching +See [A Guide to Caching with NGINX and NGINX Plus](https://blog.nginx.org/blog/nginx-caching-guide) blog post for an overview of how to use NGINX as a content cache (e.g. edge server of a content delivery network). + +# Building from source +The following steps can be used to build NGINX from source code available in this repository. + +## Installing dependencies +Most Linux distributions will require several dependencies to be installed in order to build NGINX. The following instructions are specific to the `apt` package manager, widely available on most Ubuntu/Debian distributions and their derivatives. + +> [!TIP] +> It is always a good idea to update your package repository lists prior to installing new packages. +> ```bash +> sudo apt update +> ``` + +### Installing compiler and make utility +Use the following command to install the GNU C compiler and Make utility. + +```bash +sudo apt install gcc make +``` + +### Installing dependency libraries + +```bash +sudo apt install libpcre3-dev zlib1g-dev +``` + +> [!WARNING] +> This is the minimal set of dependency libraries needed to build NGINX. Other dependencies may be required if you choose to build NGINX with additional modules. Monitor the output of the `configure` command discussed in the following sections for information on which modules may be missing. For example, if you plan to use SSL certificates to encrypt traffic with TLS, you'll need to install the OpenSSL library. To do so, issue the following command. + +>```bash +>sudo apt install libssl-dev + +## Cloning the NGINX GitHub repository +Using your preferred method, clone the NGINX repository into your development directory. See [Cloning a GitHub Repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) for additional help. + +```bash +git clone https://github.com/nginx/nginx.git +``` + +## Configuring the build +Prior to building NGINX (and most other Linux source packages), you must issue the `configure` command with [appropriate flags](https://nginx.org/en/docs/configure.html). This will generate a Makefile in your NGINX source root directory that can then be used to compile NGINX with [options specified during configuration](https://nginx.org/en/docs/configure.html). + +From the NGINX source code repository's root directory: + +```bash +auto/configure +``` + +> [!IMPORTANT] +> Configuring the build without any flags will compile NGINX with the minimal set of options. Please refer to https://nginx.org/en/docs/configure.html for a full list of available build configuration options. + +## Compiling +The `configure` script will generate a `Makefile` in the NGINX source root directory upon successful execution. To compile NGINX into a binary, issue the following command from that same directory: + +```bash +make +``` + +## Location of binary and installation +After successful compilation, a binary will be generated at `/objs/nginx`. To install this binary, issue the following command from the source root directory: + +```bash +sudo make install +``` + +> [!IMPORTANT] +> The binary will be installed into the `/usr/local/nginx/` directory. + +## Running and testing the installed binary +To run the installed binary, issue the following command: + +```bash +sudo /usr/local/nginx/sbin/nginx +``` + +You may test NGINX operation using `curl`. + +```bash +curl localhost +``` + +The output of which should start with: + +```html + + + +Welcome to nginx! +``` + +# Technical specifications +Pre-built NGINX binaries are available and supported across a wide number of operating systems. Please refer to [this complete list](https://nginx.org/en/) of operating systems, architectures, libraries and other features supported by NGINX. + +## Supported distributions +A [complete list of available Linux packages](https://nginx.org/en/linux_packages.html#distributions) can be found on the binary download page. + +See [Tested OS Platforms](https://nginx.org/en/#tested_os_and_platforms) for a list of operating systems that NGINX is confirmed to run on. + +## Windows +Windows support is tested on Windows XP, Windows Server 2003, Windows 7, Windows 10. [Windows executables](https://nginx.org/en/download.html) can be found on the download page. Note that the current implementation of NGINX for Windows is at the Proof-of-Concept stage and should only be used for development and testing purposes. + +## Supported deployment environments +- Container +- Public cloud (AWS, Google Cloud Platform, Microsoft Azure) +- Virtual machine + +# Asking questions and reporting issues +We encourage you to engage with us. +- [NGINX GitHub Discussions](https://github.com/nginx/nginx/discussions), is the go-to place to start asking questions and sharing your thoughts. +- Our [GitHub Issues](https://github.com/nginx/nginx/issues) page offers space to submit and discuss specific issues, report bugs, and suggest enhancements. + +# Contributing code +Please see the [Contributing](CONTRIBUTING.md) guide for information on how to contribute code. + +# Additional help and resources +- See the [NGINX Community Blog](https://blog.nginx.org/) for more tips, tricks and HOW-TOs related to NGINX and related projects. +- Access [nginx.org](https://nginx.org/), your go-to source for all documentation, information and software related to the NGINX suite of projects. + +# Changelog +See our [changelog](https://nginx.org/en/CHANGES) to keep track of updates. + +# License +[2-clause BSD-like license](LICENSE) + +--- +Additional documentation available at: https://nginx.org/en/docs diff --git a/misc/GNUmakefile b/misc/GNUmakefile index 4b6a12759..fcf34ec34 100644 --- a/misc/GNUmakefile +++ b/misc/GNUmakefile @@ -92,7 +92,7 @@ zip: export sed -i '' -e "s/$$/`printf '\r'`/" $(TEMP)/$(NGINX)/conf/* mv $(TEMP)/$(NGINX)/LICENSE $(TEMP)/$(NGINX)/docs.new - mv $(TEMP)/$(NGINX)/README $(TEMP)/$(NGINX)/docs.new + mv $(TEMP)/$(NGINX)/README.md $(TEMP)/$(NGINX)/docs.new mv $(TEMP)/$(NGINX)/docs/html $(TEMP)/$(NGINX) rm -r $(TEMP)/$(NGINX)/docs From noreply at nginx.com Wed Sep 11 15:46:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 11 Sep 2024 15:46:02 +0000 (UTC) Subject: [nginx] Cut down version of README.md. Message-ID: <20240911154602.5254148C1B@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/973ece2c84c0fd35ac0671c19970a13185967749 branches: master commit: 973ece2c84c0fd35ac0671c19970a13185967749 user: Sergey Kandaurov date: Thu, 5 Sep 2024 18:32:12 +0400 description: Cut down version of README.md. --- README.md | 283 ++++++++++++++++---------------------------------------------- 1 file changed, 71 insertions(+), 212 deletions(-) diff --git a/README.md b/README.md index ea7716b49..0f76c6ecb 100644 --- a/README.md +++ b/README.md @@ -1,248 +1,107 @@ - NGINX Banner + nginx Banner -NGINX (pronounced "engine x" or "en-jin-eks") is the world's most popular Web Server, high performance Load Balancer, Reverse Proxy, API Gateway and Content Cache. - -NGINX is free and open source software, distributed under the terms of a simplified [2-clause BSD-like license](LICENSE). - -Enterprise distributions, commercial support and training are available from [F5, Inc](https://www.f5.com/products/nginx). - -> [!IMPORTANT] -> The goal of this README is to provide a basic, structured introduction to NGINX for novice users. Please refer to the [full NGINX documentation](https://nginx.org/en/docs/) for detailed information on [installing](https://nginx.org/en/docs/install.html), [building](https://nginx.org/en/docs/configure.html), [configuring](https://nginx.org/en/docs/dirindex.html), [debugging](https://nginx.org/en/docs/debugging_log.html), and more. These documentation pages also contain a more detailed [Beginners Guide](https://nginx.org/en/docs/beginners_guide.html), How-Tos, [Development guide](https://nginx.org/en/docs/dev/development_guide.html), and a complete module and [directive reference](https://nginx.org/en/docs/dirindex.html). - # Table of contents -- [How it works](#how-it-works) - - [Modules](#modules) - - [Configurations](#configurations) - - [Runtime](#runtime) -- [Downloading and installing](#downloading-and-installing) - - [Stable and Mainline binaries](#stable-and-mainline-binaries) - - [Linux binary installation process](#linux-binary-installation-process) - - [FreeBSD installation process](#freebsd-installation-process) - - [Windows executables](#windows-executables) - - [Dynamic modules](#dynamic-modules) -- [Getting started with NGINX](#getting-started-with-nginx) - - [Installing SSL certificates and enabling TLS encryption](#installing-ssl-certificates-and-enabling-tls-encryption) - - [Load Balancing](#load-balancing) - - [Rate limiting](#rate-limiting) - - [Content caching](#content-caching) -- [Building from source](#building-from-source) - - [Installing dependencies](#installing-dependencies) - - [Cloning the NGINX GitHub repository](#cloning-the-nginx-github-repository) - - [Configuring the build](#configuring-the-build) - - [Compiling](#compiling) - - [Location of binary and installation](#location-of-binary-and-installation) - - [Running and testing the installed binary](#running-and-testing-the-installed-binary) -- [Technical specifications](#technical-specifications) - - [Supported distributions](#supported-distributions) - - [Windows](#windows) - - [Supported deployment environments](#supported-deployment-environments) -- [Asking questions and reporting issues](#asking-questions-reporting-issues-and-contributing) -- [Contributing code](#contributing-code) -- [Additional help and resources](#additional-help-and-resources) +- [Overview](#overview) +- [Documentation](#documentation) +- [Installation](#installation) + - [Stable and mainline versions](#stable-and-mainline-versions) + - [Prebuilt binary package](#prebuilt-binary-package) + - [Building from source](#building-from-source) +- [Windows](#windows) +- [Support](#support) +- [Contributing](#contributing) +- [Security](#security) - [Changelog](#changelog) - [License](#license) -# How it works -NGINX is installed software with binary packages available for all major operating systems and Linux distributions. See [Tested OS and Platforms](https://nginx.org/en/#tested_os_and_platforms) for a full list of compatible systems. - -> [!IMPORTANT] -> While nearly all popular Linux-based operating systems are distributed with a community version of nginx, we highly advise installation and usage of official [packages](https://nginx.org/en/linux_packages.html) or sources from this repository. Doing so ensures that you're using the most recent release or source code, including the latest feature-set, fixes and security patches. - -## Modules -NGINX is comprised of individual modules, each extending core functionality by providing additional, configurable features. See "Modules reference" at the bottom of [nginx documentation](https://nginx.org/en/docs/) for a complete list of native modules. - -NGINX supports static and dynamic modules. Static modules are defined at build-time and compiled into the resulting binaries. [Dynamic modules](https://nginx.org/en/linux_packages.html#dynmodules) (for example, [njs](https://github.com/nginx/njs)) are built and distributed separately. They can be added to, or removed from, an NGINX installation at any time. - -> [!IMPORTANT] -> Official NGINX package distributions are built with all native open source static modules. - -## Configurations -NGINX is highly flexible and configurable. Provisioning the software is achieved via text-based config file(s) organized in functional sections called "Contexts", accepting a vast amount of configuration parameters called "[Directives](https://nginx.org/en/docs/dirindex.html)". See [Configuration File's Structure](https://nginx.org/en/docs/beginners_guide.html#conf_structure) for a comprehensive definition of Directives and Contexts. - -> [!NOTE] -> The set of directives available to your distribution of NGINX is dependent on which [modules](#modules) have been made available to it. - -## Runtime -Rather than running in a single, monolithic process, NGINX is architected to scale beyond OS process stack limitations by operating as a collection of processes. They include: -- A "master" process that maintains worker processes, as well as, reads and evaluates configuration files. -- One or more "worker" processes that process data (eg. HTTP requests). - -The number of [worker processes](https://nginx.org/en/docs/ngx_core_module.html#worker_processes) is typically set to the number of CPU cores on the system. In most cases, this optimally balances load across available system resources, as NGINX is designed to efficiently distribute work across all available worker processes. - -> [!TIP] -> Processes synchronize data through shared memory. For this reason, many NGINX directives require the allocation of shared memory zones. As an example, when configuring [rate limiting](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req), connecting clients must be tracked in a [common memory zone](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req_zone) so all worker processes can know how many times a particular client has accessed the server in a span of time. - -# Downloading and installing -Follow these steps to download and install precompiled NGINX binaries. You may also choose to [build the module locally from source code](#building-from-source). - -## Stable and Mainline binaries -NGINX binaries are built and distributed in two versions: stable and mainline. You'll need to [decide which is appropriate for your purposes](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#choosing-between-a-stable-or-a-mainline-version). - -## Linux binary installation process -The NGINX binary installation process takes advantage of package managers native to specific Linux distributions. For this reason, first-time installations involve adding the official NGINX package repository to your system's package manager. +# Overview +nginx ("engine x") is an HTTP web server, reverse proxy, +content cache, load balancer, TCP/UDP proxy server, and mail proxy server. +Known for flexibility and high performance with low resource utilization. +Originally written by [Igor Sysoev](http://sysoev.ru/en/). -### GPG keys -GPG signing keys are used to verify the authenticity of packages. Read more [important details](https://nginx.org/en/linux_packages.html#signatures) on our GPG/PGP keys. +nginx runs on most popupar [operating systems](https://nginx.org/en/#tested_os_and_platforms). -### Upgrades -Future upgrades to the latest version can be managed using the same package manager without the need to manually download and verify binaries. +The official home page of the nginx project is [nginx.org](https://nginx.org). -### Installation instructions -Once a repository has been added and verified, follow [these steps](https://nginx.org/en/linux_packages.html) to install NGINX binaries using the package manager native to your Linux distribution. +# Documentation +Reference documentation is available at: https://nginx.org/en/docs. -## FreeBSD installation process -For more information on installing NGINX on FreeBSD system, visit https://nginx.org/en/docs/install.html +# Installation +nginx can be installed in [various ways](https://nginx.org/en/docs/install.html) +depending on your requirement. -## Windows executables -Windows executables for mainline and stable releases can be found on the main [NGINX download page](https://nginx.org/en/download.html). Note that the current implementation of NGINX for Windows is at the Proof-of-Concept stage and should only be used for development and testing purposes. +## Stable and mainline versions +nginx is available in two versions: +- **mainline** includes the latest features and bug fixes and is always up to date +- **stable** doesn’t include all of the latest features, but has critical bug fixes that are always backported from the mainline version -## Dynamic modules -NGINX version 1.9.11 added support for [Dynamic Modules](https://nginx.org/en/docs/ngx_core_module.html#load_module). Unlike standard, Static modules, which must be complied into NGINX binaries at build-time, Dynamic modules can be downloaded, installed, and configured at any point. [Official dynamic module binaries](https://nginx.org/en/linux_packages.html#dynmodules) are available from the same package repository as the core NGINX binaries described in previous steps. +## Prebuilt binary package +This is a quick and easy way to install nginx. +The package includes almost all official nginx modules and is available +for most popular operating systems. +See [Installing a Prebuilt Package](http://nginx.org/en/linux_packages.html). -> [!TIP] -> [NGINX JavaScript (njs)](https://github.com/nginx/njs), is a popular NGINX dynamic module that enables the extension of core NGINX functionality using familiar JavaScript syntax. - -# Getting started with NGINX -For a gentle introduction to NGINX basics, please see our [Beginner’s Guide](https://nginx.org/en/docs/beginners_guide.html). - -## Installing SSL certificates and enabling TLS encryption -See [Configuring HTTPS servers](https://nginx.org/en/docs/http/configuring_https_servers.html) for a quick guide on how to enable secure traffic to your NGINX installation. - -## Load Balancing -For a quick start guide on configuring NGINX as a Load Balancer, please see [Using nginx as HTTP load balancer](https://nginx.org/en/docs/http/load_balancing.html). - -## Rate limiting -See our [Rate Limiting with NGINX](https://blog.nginx.org/blog/rate-limiting-nginx) blog post for an overview of core concepts for provisioning NGINX as an API Gateway. - -## Content caching -See [A Guide to Caching with NGINX and NGINX Plus](https://blog.nginx.org/blog/nginx-caching-guide) blog post for an overview of how to use NGINX as a content cache (e.g. edge server of a content delivery network). - -# Building from source -The following steps can be used to build NGINX from source code available in this repository. - -## Installing dependencies -Most Linux distributions will require several dependencies to be installed in order to build NGINX. The following instructions are specific to the `apt` package manager, widely available on most Ubuntu/Debian distributions and their derivatives. - -> [!TIP] -> It is always a good idea to update your package repository lists prior to installing new packages. -> ```bash -> sudo apt update -> ``` - -### Installing compiler and make utility -Use the following command to install the GNU C compiler and Make utility. +## Building from source +This way is more flexible: you can add particular modules, including +third‑party modules, or apply the latest security patches. +### Building from source tarballs +Official nginx tarballs: https://nginx.org/en/download.html. ```bash -sudo apt install gcc make -``` - -### Installing dependency libraries - -```bash -sudo apt install libpcre3-dev zlib1g-dev +tar xf nginx-1.X.Y.tar.gz +cd nginx-1.X.Y +./configure ... +make ``` -> [!WARNING] -> This is the minimal set of dependency libraries needed to build NGINX. Other dependencies may be required if you choose to build NGINX with additional modules. Monitor the output of the `configure` command discussed in the following sections for information on which modules may be missing. For example, if you plan to use SSL certificates to encrypt traffic with TLS, you'll need to install the OpenSSL library. To do so, issue the following command. - ->```bash ->sudo apt install libssl-dev - -## Cloning the NGINX GitHub repository -Using your preferred method, clone the NGINX repository into your development directory. See [Cloning a GitHub Repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) for additional help. +See [configure options](https://nginx.org/en/docs/configure.html). +### Building from source repository ```bash git clone https://github.com/nginx/nginx.git -``` - -## Configuring the build -Prior to building NGINX (and most other Linux source packages), you must issue the `configure` command with [appropriate flags](https://nginx.org/en/docs/configure.html). This will generate a Makefile in your NGINX source root directory that can then be used to compile NGINX with [options specified during configuration](https://nginx.org/en/docs/configure.html). - -From the NGINX source code repository's root directory: - -```bash -auto/configure -``` - -> [!IMPORTANT] -> Configuring the build without any flags will compile NGINX with the minimal set of options. Please refer to https://nginx.org/en/docs/configure.html for a full list of available build configuration options. - -## Compiling -The `configure` script will generate a `Makefile` in the NGINX source root directory upon successful execution. To compile NGINX into a binary, issue the following command from that same directory: - -```bash +cd nginx +auto/configure ... make ``` -## Location of binary and installation -After successful compilation, a binary will be generated at `/objs/nginx`. To install this binary, issue the following command from the source root directory: - -```bash -sudo make install -``` +See [configure options](https://nginx.org/en/docs/configure.html). -> [!IMPORTANT] -> The binary will be installed into the `/usr/local/nginx/` directory. +# Windows +[Windows executables](https://nginx.org/en/download.html) are tested +on Windows XP, Windows Server 2003, Windows 7, Windows 10. +Note that nginx for Windows is considered to be of a +[beta](http://nginx.org/en/docs/windows.html) quality and should only +be used for development and testing purposes. -## Running and testing the installed binary -To run the installed binary, issue the following command: +# Support +Enterprise distributions, commercial support and training are +available from [F5, Inc](https://www.f5.com/products/nginx). -```bash -sudo /usr/local/nginx/sbin/nginx -``` - -You may test NGINX operation using `curl`. - -```bash -curl localhost -``` - -The output of which should start with: - -```html - - - -Welcome to nginx! -``` - -# Technical specifications -Pre-built NGINX binaries are available and supported across a wide number of operating systems. Please refer to [this complete list](https://nginx.org/en/) of operating systems, architectures, libraries and other features supported by NGINX. - -## Supported distributions -A [complete list of available Linux packages](https://nginx.org/en/linux_packages.html#distributions) can be found on the binary download page. - -See [Tested OS Platforms](https://nginx.org/en/#tested_os_and_platforms) for a list of operating systems that NGINX is confirmed to run on. - -## Windows -Windows support is tested on Windows XP, Windows Server 2003, Windows 7, Windows 10. [Windows executables](https://nginx.org/en/download.html) can be found on the download page. Note that the current implementation of NGINX for Windows is at the Proof-of-Concept stage and should only be used for development and testing purposes. - -## Supported deployment environments -- Container -- Public cloud (AWS, Google Cloud Platform, Microsoft Azure) -- Virtual machine - -# Asking questions and reporting issues +# Contributing We encourage you to engage with us. -- [NGINX GitHub Discussions](https://github.com/nginx/nginx/discussions), is the go-to place to start asking questions and sharing your thoughts. -- Our [GitHub Issues](https://github.com/nginx/nginx/issues) page offers space to submit and discuss specific issues, report bugs, and suggest enhancements. -# Contributing code -Please see the [Contributing](CONTRIBUTING.md) guide for information on how to contribute code. +- [GitHub Issues](https://github.com/nginx/nginx/issues) +offers space to report specific issues, bugs, and suggest enhancements +- [GitHub Pull Requests](https://github.com/nginx/nginx/pulls) +offers space to contribute your changes +- [GitHub Discussions](https://github.com/nginx/nginx/discussions) +is the go-to place to start asking questions and sharing your thoughts + +See [Contributing Guidelines](CONTRIBUTING.md) for information on how +to participate in nginx development. -# Additional help and resources -- See the [NGINX Community Blog](https://blog.nginx.org/) for more tips, tricks and HOW-TOs related to NGINX and related projects. -- Access [nginx.org](https://nginx.org/), your go-to source for all documentation, information and software related to the NGINX suite of projects. +# Security +See [Security Policy](SECURITY.md) for information on how to report +a vulnerability. # Changelog -See our [changelog](https://nginx.org/en/CHANGES) to keep track of updates. +See [CHANGES](https://nginx.org/en/CHANGES) to keep track of updates. # License -[2-clause BSD-like license](LICENSE) - ---- -Additional documentation available at: https://nginx.org/en/docs +nginx is distributed under the terms of the simplified +[2-clause BSD-like license](LICENSE). From noreply at nginx.com Wed Sep 11 15:46:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 11 Sep 2024 15:46:02 +0000 (UTC) Subject: [nginx] Revert "Cut down version of README.md." Message-ID: <20240911154602.56DAA48C1C@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/c5a0cdf3c3bc42f81879dc23b6b4df73db97e93b branches: master commit: c5a0cdf3c3bc42f81879dc23b6b4df73db97e93b user: Michael Vernik date: Fri, 6 Sep 2024 12:19:36 -0700 description: Revert "Cut down version of README.md." This reverts commit 87a708a6d726b92a4b0db99e7a3381cbd8c117dd. --- README.md | 283 ++++++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 212 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 0f76c6ecb..ea7716b49 100644 --- a/README.md +++ b/README.md @@ -1,107 +1,248 @@ - nginx Banner + NGINX Banner +NGINX (pronounced "engine x" or "en-jin-eks") is the world's most popular Web Server, high performance Load Balancer, Reverse Proxy, API Gateway and Content Cache. + +NGINX is free and open source software, distributed under the terms of a simplified [2-clause BSD-like license](LICENSE). + +Enterprise distributions, commercial support and training are available from [F5, Inc](https://www.f5.com/products/nginx). + +> [!IMPORTANT] +> The goal of this README is to provide a basic, structured introduction to NGINX for novice users. Please refer to the [full NGINX documentation](https://nginx.org/en/docs/) for detailed information on [installing](https://nginx.org/en/docs/install.html), [building](https://nginx.org/en/docs/configure.html), [configuring](https://nginx.org/en/docs/dirindex.html), [debugging](https://nginx.org/en/docs/debugging_log.html), and more. These documentation pages also contain a more detailed [Beginners Guide](https://nginx.org/en/docs/beginners_guide.html), How-Tos, [Development guide](https://nginx.org/en/docs/dev/development_guide.html), and a complete module and [directive reference](https://nginx.org/en/docs/dirindex.html). + # Table of contents -- [Overview](#overview) -- [Documentation](#documentation) -- [Installation](#installation) - - [Stable and mainline versions](#stable-and-mainline-versions) - - [Prebuilt binary package](#prebuilt-binary-package) - - [Building from source](#building-from-source) -- [Windows](#windows) -- [Support](#support) -- [Contributing](#contributing) -- [Security](#security) +- [How it works](#how-it-works) + - [Modules](#modules) + - [Configurations](#configurations) + - [Runtime](#runtime) +- [Downloading and installing](#downloading-and-installing) + - [Stable and Mainline binaries](#stable-and-mainline-binaries) + - [Linux binary installation process](#linux-binary-installation-process) + - [FreeBSD installation process](#freebsd-installation-process) + - [Windows executables](#windows-executables) + - [Dynamic modules](#dynamic-modules) +- [Getting started with NGINX](#getting-started-with-nginx) + - [Installing SSL certificates and enabling TLS encryption](#installing-ssl-certificates-and-enabling-tls-encryption) + - [Load Balancing](#load-balancing) + - [Rate limiting](#rate-limiting) + - [Content caching](#content-caching) +- [Building from source](#building-from-source) + - [Installing dependencies](#installing-dependencies) + - [Cloning the NGINX GitHub repository](#cloning-the-nginx-github-repository) + - [Configuring the build](#configuring-the-build) + - [Compiling](#compiling) + - [Location of binary and installation](#location-of-binary-and-installation) + - [Running and testing the installed binary](#running-and-testing-the-installed-binary) +- [Technical specifications](#technical-specifications) + - [Supported distributions](#supported-distributions) + - [Windows](#windows) + - [Supported deployment environments](#supported-deployment-environments) +- [Asking questions and reporting issues](#asking-questions-reporting-issues-and-contributing) +- [Contributing code](#contributing-code) +- [Additional help and resources](#additional-help-and-resources) - [Changelog](#changelog) - [License](#license) -# Overview -nginx ("engine x") is an HTTP web server, reverse proxy, -content cache, load balancer, TCP/UDP proxy server, and mail proxy server. -Known for flexibility and high performance with low resource utilization. -Originally written by [Igor Sysoev](http://sysoev.ru/en/). +# How it works +NGINX is installed software with binary packages available for all major operating systems and Linux distributions. See [Tested OS and Platforms](https://nginx.org/en/#tested_os_and_platforms) for a full list of compatible systems. + +> [!IMPORTANT] +> While nearly all popular Linux-based operating systems are distributed with a community version of nginx, we highly advise installation and usage of official [packages](https://nginx.org/en/linux_packages.html) or sources from this repository. Doing so ensures that you're using the most recent release or source code, including the latest feature-set, fixes and security patches. + +## Modules +NGINX is comprised of individual modules, each extending core functionality by providing additional, configurable features. See "Modules reference" at the bottom of [nginx documentation](https://nginx.org/en/docs/) for a complete list of native modules. + +NGINX supports static and dynamic modules. Static modules are defined at build-time and compiled into the resulting binaries. [Dynamic modules](https://nginx.org/en/linux_packages.html#dynmodules) (for example, [njs](https://github.com/nginx/njs)) are built and distributed separately. They can be added to, or removed from, an NGINX installation at any time. + +> [!IMPORTANT] +> Official NGINX package distributions are built with all native open source static modules. + +## Configurations +NGINX is highly flexible and configurable. Provisioning the software is achieved via text-based config file(s) organized in functional sections called "Contexts", accepting a vast amount of configuration parameters called "[Directives](https://nginx.org/en/docs/dirindex.html)". See [Configuration File's Structure](https://nginx.org/en/docs/beginners_guide.html#conf_structure) for a comprehensive definition of Directives and Contexts. + +> [!NOTE] +> The set of directives available to your distribution of NGINX is dependent on which [modules](#modules) have been made available to it. + +## Runtime +Rather than running in a single, monolithic process, NGINX is architected to scale beyond OS process stack limitations by operating as a collection of processes. They include: +- A "master" process that maintains worker processes, as well as, reads and evaluates configuration files. +- One or more "worker" processes that process data (eg. HTTP requests). + +The number of [worker processes](https://nginx.org/en/docs/ngx_core_module.html#worker_processes) is typically set to the number of CPU cores on the system. In most cases, this optimally balances load across available system resources, as NGINX is designed to efficiently distribute work across all available worker processes. + +> [!TIP] +> Processes synchronize data through shared memory. For this reason, many NGINX directives require the allocation of shared memory zones. As an example, when configuring [rate limiting](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req), connecting clients must be tracked in a [common memory zone](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req_zone) so all worker processes can know how many times a particular client has accessed the server in a span of time. + +# Downloading and installing +Follow these steps to download and install precompiled NGINX binaries. You may also choose to [build the module locally from source code](#building-from-source). + +## Stable and Mainline binaries +NGINX binaries are built and distributed in two versions: stable and mainline. You'll need to [decide which is appropriate for your purposes](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#choosing-between-a-stable-or-a-mainline-version). + +## Linux binary installation process +The NGINX binary installation process takes advantage of package managers native to specific Linux distributions. For this reason, first-time installations involve adding the official NGINX package repository to your system's package manager. -nginx runs on most popupar [operating systems](https://nginx.org/en/#tested_os_and_platforms). +### GPG keys +GPG signing keys are used to verify the authenticity of packages. Read more [important details](https://nginx.org/en/linux_packages.html#signatures) on our GPG/PGP keys. -The official home page of the nginx project is [nginx.org](https://nginx.org). +### Upgrades +Future upgrades to the latest version can be managed using the same package manager without the need to manually download and verify binaries. -# Documentation -Reference documentation is available at: https://nginx.org/en/docs. +### Installation instructions +Once a repository has been added and verified, follow [these steps](https://nginx.org/en/linux_packages.html) to install NGINX binaries using the package manager native to your Linux distribution. -# Installation -nginx can be installed in [various ways](https://nginx.org/en/docs/install.html) -depending on your requirement. +## FreeBSD installation process +For more information on installing NGINX on FreeBSD system, visit https://nginx.org/en/docs/install.html -## Stable and mainline versions -nginx is available in two versions: -- **mainline** includes the latest features and bug fixes and is always up to date -- **stable** doesn’t include all of the latest features, but has critical bug fixes that are always backported from the mainline version +## Windows executables +Windows executables for mainline and stable releases can be found on the main [NGINX download page](https://nginx.org/en/download.html). Note that the current implementation of NGINX for Windows is at the Proof-of-Concept stage and should only be used for development and testing purposes. -## Prebuilt binary package -This is a quick and easy way to install nginx. -The package includes almost all official nginx modules and is available -for most popular operating systems. -See [Installing a Prebuilt Package](http://nginx.org/en/linux_packages.html). +## Dynamic modules +NGINX version 1.9.11 added support for [Dynamic Modules](https://nginx.org/en/docs/ngx_core_module.html#load_module). Unlike standard, Static modules, which must be complied into NGINX binaries at build-time, Dynamic modules can be downloaded, installed, and configured at any point. [Official dynamic module binaries](https://nginx.org/en/linux_packages.html#dynmodules) are available from the same package repository as the core NGINX binaries described in previous steps. -## Building from source -This way is more flexible: you can add particular modules, including -third‑party modules, or apply the latest security patches. +> [!TIP] +> [NGINX JavaScript (njs)](https://github.com/nginx/njs), is a popular NGINX dynamic module that enables the extension of core NGINX functionality using familiar JavaScript syntax. + +# Getting started with NGINX +For a gentle introduction to NGINX basics, please see our [Beginner’s Guide](https://nginx.org/en/docs/beginners_guide.html). + +## Installing SSL certificates and enabling TLS encryption +See [Configuring HTTPS servers](https://nginx.org/en/docs/http/configuring_https_servers.html) for a quick guide on how to enable secure traffic to your NGINX installation. + +## Load Balancing +For a quick start guide on configuring NGINX as a Load Balancer, please see [Using nginx as HTTP load balancer](https://nginx.org/en/docs/http/load_balancing.html). + +## Rate limiting +See our [Rate Limiting with NGINX](https://blog.nginx.org/blog/rate-limiting-nginx) blog post for an overview of core concepts for provisioning NGINX as an API Gateway. + +## Content caching +See [A Guide to Caching with NGINX and NGINX Plus](https://blog.nginx.org/blog/nginx-caching-guide) blog post for an overview of how to use NGINX as a content cache (e.g. edge server of a content delivery network). + +# Building from source +The following steps can be used to build NGINX from source code available in this repository. + +## Installing dependencies +Most Linux distributions will require several dependencies to be installed in order to build NGINX. The following instructions are specific to the `apt` package manager, widely available on most Ubuntu/Debian distributions and their derivatives. + +> [!TIP] +> It is always a good idea to update your package repository lists prior to installing new packages. +> ```bash +> sudo apt update +> ``` + +### Installing compiler and make utility +Use the following command to install the GNU C compiler and Make utility. -### Building from source tarballs -Official nginx tarballs: https://nginx.org/en/download.html. ```bash -tar xf nginx-1.X.Y.tar.gz -cd nginx-1.X.Y -./configure ... -make +sudo apt install gcc make ``` -See [configure options](https://nginx.org/en/docs/configure.html). +### Installing dependency libraries + +```bash +sudo apt install libpcre3-dev zlib1g-dev +``` + +> [!WARNING] +> This is the minimal set of dependency libraries needed to build NGINX. Other dependencies may be required if you choose to build NGINX with additional modules. Monitor the output of the `configure` command discussed in the following sections for information on which modules may be missing. For example, if you plan to use SSL certificates to encrypt traffic with TLS, you'll need to install the OpenSSL library. To do so, issue the following command. + +>```bash +>sudo apt install libssl-dev + +## Cloning the NGINX GitHub repository +Using your preferred method, clone the NGINX repository into your development directory. See [Cloning a GitHub Repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) for additional help. -### Building from source repository ```bash git clone https://github.com/nginx/nginx.git -cd nginx -auto/configure ... +``` + +## Configuring the build +Prior to building NGINX (and most other Linux source packages), you must issue the `configure` command with [appropriate flags](https://nginx.org/en/docs/configure.html). This will generate a Makefile in your NGINX source root directory that can then be used to compile NGINX with [options specified during configuration](https://nginx.org/en/docs/configure.html). + +From the NGINX source code repository's root directory: + +```bash +auto/configure +``` + +> [!IMPORTANT] +> Configuring the build without any flags will compile NGINX with the minimal set of options. Please refer to https://nginx.org/en/docs/configure.html for a full list of available build configuration options. + +## Compiling +The `configure` script will generate a `Makefile` in the NGINX source root directory upon successful execution. To compile NGINX into a binary, issue the following command from that same directory: + +```bash make ``` -See [configure options](https://nginx.org/en/docs/configure.html). +## Location of binary and installation +After successful compilation, a binary will be generated at `/objs/nginx`. To install this binary, issue the following command from the source root directory: -# Windows -[Windows executables](https://nginx.org/en/download.html) are tested -on Windows XP, Windows Server 2003, Windows 7, Windows 10. -Note that nginx for Windows is considered to be of a -[beta](http://nginx.org/en/docs/windows.html) quality and should only -be used for development and testing purposes. +```bash +sudo make install +``` -# Support -Enterprise distributions, commercial support and training are -available from [F5, Inc](https://www.f5.com/products/nginx). +> [!IMPORTANT] +> The binary will be installed into the `/usr/local/nginx/` directory. -# Contributing -We encourage you to engage with us. +## Running and testing the installed binary +To run the installed binary, issue the following command: -- [GitHub Issues](https://github.com/nginx/nginx/issues) -offers space to report specific issues, bugs, and suggest enhancements -- [GitHub Pull Requests](https://github.com/nginx/nginx/pulls) -offers space to contribute your changes -- [GitHub Discussions](https://github.com/nginx/nginx/discussions) -is the go-to place to start asking questions and sharing your thoughts +```bash +sudo /usr/local/nginx/sbin/nginx +``` -See [Contributing Guidelines](CONTRIBUTING.md) for information on how -to participate in nginx development. +You may test NGINX operation using `curl`. -# Security -See [Security Policy](SECURITY.md) for information on how to report -a vulnerability. +```bash +curl localhost +``` + +The output of which should start with: + +```html + + + +Welcome to nginx! +``` + +# Technical specifications +Pre-built NGINX binaries are available and supported across a wide number of operating systems. Please refer to [this complete list](https://nginx.org/en/) of operating systems, architectures, libraries and other features supported by NGINX. + +## Supported distributions +A [complete list of available Linux packages](https://nginx.org/en/linux_packages.html#distributions) can be found on the binary download page. + +See [Tested OS Platforms](https://nginx.org/en/#tested_os_and_platforms) for a list of operating systems that NGINX is confirmed to run on. + +## Windows +Windows support is tested on Windows XP, Windows Server 2003, Windows 7, Windows 10. [Windows executables](https://nginx.org/en/download.html) can be found on the download page. Note that the current implementation of NGINX for Windows is at the Proof-of-Concept stage and should only be used for development and testing purposes. + +## Supported deployment environments +- Container +- Public cloud (AWS, Google Cloud Platform, Microsoft Azure) +- Virtual machine + +# Asking questions and reporting issues +We encourage you to engage with us. +- [NGINX GitHub Discussions](https://github.com/nginx/nginx/discussions), is the go-to place to start asking questions and sharing your thoughts. +- Our [GitHub Issues](https://github.com/nginx/nginx/issues) page offers space to submit and discuss specific issues, report bugs, and suggest enhancements. + +# Contributing code +Please see the [Contributing](CONTRIBUTING.md) guide for information on how to contribute code. + +# Additional help and resources +- See the [NGINX Community Blog](https://blog.nginx.org/) for more tips, tricks and HOW-TOs related to NGINX and related projects. +- Access [nginx.org](https://nginx.org/), your go-to source for all documentation, information and software related to the NGINX suite of projects. # Changelog -See [CHANGES](https://nginx.org/en/CHANGES) to keep track of updates. +See our [changelog](https://nginx.org/en/CHANGES) to keep track of updates. # License -nginx is distributed under the terms of the simplified -[2-clause BSD-like license](LICENSE). +[2-clause BSD-like license](LICENSE) + +--- +Additional documentation available at: https://nginx.org/en/docs From noreply at nginx.com Wed Sep 11 15:46:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 11 Sep 2024 15:46:02 +0000 (UTC) Subject: [nginx] README: add details about stable and master branches Message-ID: <20240911154602.6302D48C1F@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/0daee9a4806595bfae752a5b09a0bcc2c5e7af30 branches: master commit: 0daee9a4806595bfae752a5b09a0bcc2c5e7af30 user: Michael Vernik date: Mon, 9 Sep 2024 23:53:39 -0700 description: README: add details about stable and master branches --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index c6c18f56c..20003e99f 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,9 @@ Follow these steps to download and install precompiled NGINX binaries. You may a ## Stable and Mainline binaries NGINX binaries are built and distributed in two versions: stable and mainline. You'll need to [decide which is appropriate for your purposes](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#choosing-between-a-stable-or-a-mainline-version). +> [!TIP] +> Stable binaries are built from the various stable branches and mainline binaries are built from the [master](https://github.com/nginx/nginx/tree/master) branch maintained in this repository. + ## Linux binary installation process The NGINX binary installation process takes advantage of package managers native to specific Linux distributions. For this reason, first-time installations involve adding the official NGINX package repository to your system's package manager. From noreply at nginx.com Wed Sep 11 15:46:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 11 Sep 2024 15:46:02 +0000 (UTC) Subject: [nginx] README: clarified worker process description Message-ID: <20240911154602.66F0648C20@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/1f5ffb57a1ed7459bdb19bc460e35e7c94f881ef branches: master commit: 1f5ffb57a1ed7459bdb19bc460e35e7c94f881ef user: Michael Vernik date: Tue, 10 Sep 2024 00:10:53 -0700 description: README: clarified worker process description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 20003e99f..7f1f880b6 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Rather than running in a single, monolithic process, NGINX is architected to sca - A "master" process that maintains worker processes, as well as, reads and evaluates configuration files. - One or more "worker" processes that process data (eg. HTTP requests). -The number of [worker processes](https://nginx.org/en/docs/ngx_core_module.html#worker_processes) is typically set to the number of CPU cores on the system. In most cases, this optimally balances load across available system resources, as NGINX is designed to efficiently distribute work across all available worker processes. +The number of [worker processes](https://nginx.org/en/docs/ngx_core_module.html#worker_processes) is defined in the configuration file and may be fixed for a given configuration or automatically adjusted to the number of available CPU cores. In most cases, the latter option optimally balances load across available system resources, as NGINX is designed to efficiently distribute work across all worker processes. > [!TIP] > Processes synchronize data through shared memory. For this reason, many NGINX directives require the allocation of shared memory zones. As an example, when configuring [rate limiting](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req), connecting clients must be tracked in a [common memory zone](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req_zone) so all worker processes can know how many times a particular client has accessed the server in a span of time. From noreply at nginx.com Wed Sep 11 15:46:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 11 Sep 2024 15:46:02 +0000 (UTC) Subject: [nginx] README: remove signing key references recommended by https://github.com/nginx/nginx/pull/120#discussion_r1751383741 Message-ID: <20240911154602.6EF8A48C22@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/732364c1df74dc6e25c219ca70751f886d3985bf branches: master commit: 732364c1df74dc6e25c219ca70751f886d3985bf user: Michael Vernik date: Tue, 10 Sep 2024 12:59:33 -0700 description: README: remove signing key references recommended by https://github.com/nginx/nginx/pull/120#discussion_r1751383741 --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 6290cac20..9b38da454 100644 --- a/README.md +++ b/README.md @@ -89,9 +89,6 @@ NGINX binaries are built and distributed in two versions: stable and mainline. Y ## Linux binary installation process The NGINX binary installation process takes advantage of package managers native to specific Linux distributions. For this reason, first-time installations involve adding the official NGINX package repository to your system's package manager. -### GPG keys -GPG signing keys are used to verify the authenticity of packages. Read more [important details](https://nginx.org/en/linux_packages.html#signatures) on our GPG/PGP keys. - ### Upgrades Future upgrades to the latest version can be managed using the same package manager without the need to manually download and verify binaries. From noreply at nginx.com Wed Sep 11 15:46:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 11 Sep 2024 15:46:02 +0000 (UTC) Subject: [nginx] README: updated stable/mainline binary description Message-ID: <20240911154602.7B38948C25@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/197f1e228cfa4577294c5c6adc4157e756e3e04e branches: master commit: 197f1e228cfa4577294c5c6adc4157e756e3e04e user: Michael Vernik date: Tue, 10 Sep 2024 15:14:55 -0700 description: README: updated stable/mainline binary description --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 59aaceabb..e18c6b202 100644 --- a/README.md +++ b/README.md @@ -81,10 +81,7 @@ The number of [worker processes](https://nginx.org/en/docs/ngx_core_module.html# Follow these steps to download and install precompiled NGINX binaries. You may also choose to [build NGINX locally from source code](#building-from-source). ## Stable and Mainline binaries -NGINX binaries are built and distributed in two versions: stable and mainline. You'll need to [decide which is appropriate for your purposes](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#choosing-between-a-stable-or-a-mainline-version). - -> [!TIP] -> Stable binaries are built from the various stable branches and mainline binaries are built from the [master](https://github.com/nginx/nginx/tree/master) branch maintained in this repository. +NGINX binaries are built and distributed in two versions: stable and mainline. Stable binaries are built from stable branches and only contain critical fixes backported from the mainline version. Mainline binaries are built from the [master branch](https://github.com/nginx/nginx/tree/master) and contain the latest features and bugfixes. You'll need to [decide which is appropriate for your purposes](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#choosing-between-a-stable-or-a-mainline-version). ## Linux binary installation process The NGINX binary installation process takes advantage of package managers native to specific Linux distributions. For this reason, first-time installations involve adding the official NGINX package repository to your system's package manager. Follow [these steps](https://nginx.org/en/linux_packages.html) to download, verify, and install NGINX binaries using the package manager appropriate for your Linux distribution. From noreply at nginx.com Wed Sep 11 15:46:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 11 Sep 2024 15:46:02 +0000 (UTC) Subject: [nginx] README: updated modules docs Message-ID: <20240911154602.6AF8748C21@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/be22c101ba24e0654410e02d5ba49b796631febb branches: master commit: be22c101ba24e0654410e02d5ba49b796631febb user: Michael Vernik date: Tue, 10 Sep 2024 12:33:29 -0700 description: README: updated modules docs --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7f1f880b6..6290cac20 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,15 @@ NGINX is installed software with binary packages available for all major operati ## Modules NGINX is comprised of individual modules, each extending core functionality by providing additional, configurable features. See "Modules reference" at the bottom of [nginx documentation](https://nginx.org/en/docs/) for a complete list of official modules. -NGINX supports static and dynamic modules. Static modules are defined at build-time and compiled into the resulting binaries. See [Dynamic Modules](#dynamic-modules) for more information on how they work, as well as, how to obtain, install, and configure them. +NGINX modules can be built and distributed as static or dynamic modules. Static modules are defined at build-time, compiled, and distributed in the resulting binaries. See [Dynamic Modules](#dynamic-modules) for more information on how they work, as well as, how to obtain, install, and configure them. + +> [!TIP] +> You can issue the following command to see which static modules your NGINX binaries were built with: +```bash +sudo nginx -V +``` +> See [Configuring the build](#configuring-the-build) for information on how to include specific Static modules into your nginx build. + ## Configurations NGINX is highly flexible and configurable. Provisioning the software is achieved via text-based config file(s) organized in functional sections called "Contexts", accepting a vast amount of configuration parameters called "[Directives](https://nginx.org/en/docs/dirindex.html)". See [Configuration File's Structure](https://nginx.org/en/docs/beginners_guide.html#conf_structure) for a comprehensive definition of Directives and Contexts. @@ -97,7 +105,7 @@ For more information on installing NGINX on FreeBSD system, visit https://nginx. Windows executables for mainline and stable releases can be found on the main [NGINX download page](https://nginx.org/en/download.html). Note that the current implementation of NGINX for Windows is at the Proof-of-Concept stage and should only be used for development and testing purposes. For additional information, please see [nginx for Windows](https://nginx.org/en/docs/windows.html). ## Dynamic modules -NGINX version 1.9.11 added support for [Dynamic Modules](https://nginx.org/en/docs/ngx_core_module.html#load_module). Unlike standard, Static modules, which must be complied into NGINX binaries at build-time, Dynamic modules can be downloaded, installed, and configured after the core NGINX binaries have been built. [Official dynamic module binaries](https://nginx.org/en/linux_packages.html#dynmodules) are available from the same package repository as the core NGINX binaries described in previous steps. +NGINX version 1.9.11 added support for [Dynamic Modules](https://nginx.org/en/docs/ngx_core_module.html#load_module). Unlike Static modules, dynamically built modules can be downloaded, installed, and configured after the core NGINX binaries have been built. [Official dynamic module binaries](https://nginx.org/en/linux_packages.html#dynmodules) are available from the same package repository as the core NGINX binaries described in previous steps. > [!TIP] > [NGINX JavaScript (njs)](https://github.com/nginx/njs), is a popular NGINX dynamic module that enables the extension of core NGINX functionality using familiar JavaScript syntax. From noreply at nginx.com Wed Sep 11 15:46:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 11 Sep 2024 15:46:02 +0000 (UTC) Subject: [nginx] README: remove definition of "contexts" from section on config files per: https://github.com/nginx/nginx/pull/120#discussion_r1751366278 Message-ID: <20240911154602.72E9D48C23@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/291a8869350777dd94bc532f003b800877505693 branches: master commit: 291a8869350777dd94bc532f003b800877505693 user: Michael Vernik date: Tue, 10 Sep 2024 13:10:55 -0700 description: README: remove definition of "contexts" from section on config files per: https://github.com/nginx/nginx/pull/120#discussion_r1751366278 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b38da454..c8b2b1090 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ sudo nginx -V ## Configurations -NGINX is highly flexible and configurable. Provisioning the software is achieved via text-based config file(s) organized in functional sections called "Contexts", accepting a vast amount of configuration parameters called "[Directives](https://nginx.org/en/docs/dirindex.html)". See [Configuration File's Structure](https://nginx.org/en/docs/beginners_guide.html#conf_structure) for a comprehensive definition of Directives and Contexts. +NGINX is highly flexible and configurable. Provisioning the software is achieved via text-based config file(s) accepting parameters called "[Directives](https://nginx.org/en/docs/dirindex.html)". See [Configuration File's Structure](https://nginx.org/en/docs/beginners_guide.html#conf_structure) for a comprehensive description of how NGINX configuration files work. > [!NOTE] > The set of directives available to your distribution of NGINX is dependent on which [modules](#modules) have been made available to it. From noreply at nginx.com Wed Sep 11 15:46:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 11 Sep 2024 15:46:02 +0000 (UTC) Subject: [nginx] README: clarified binary download and install process on Linux Message-ID: <20240911154602.7706148C24@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/a8a1a11268eab48ad3ae19748e92594dc5e39355 branches: master commit: a8a1a11268eab48ad3ae19748e92594dc5e39355 user: Michael Vernik date: Tue, 10 Sep 2024 13:23:13 -0700 description: README: clarified binary download and install process on Linux --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index c8b2b1090..59aaceabb 100644 --- a/README.md +++ b/README.md @@ -87,14 +87,11 @@ NGINX binaries are built and distributed in two versions: stable and mainline. Y > Stable binaries are built from the various stable branches and mainline binaries are built from the [master](https://github.com/nginx/nginx/tree/master) branch maintained in this repository. ## Linux binary installation process -The NGINX binary installation process takes advantage of package managers native to specific Linux distributions. For this reason, first-time installations involve adding the official NGINX package repository to your system's package manager. +The NGINX binary installation process takes advantage of package managers native to specific Linux distributions. For this reason, first-time installations involve adding the official NGINX package repository to your system's package manager. Follow [these steps](https://nginx.org/en/linux_packages.html) to download, verify, and install NGINX binaries using the package manager appropriate for your Linux distribution. ### Upgrades Future upgrades to the latest version can be managed using the same package manager without the need to manually download and verify binaries. -### Installation instructions -Once a repository has been added and verified, follow [these steps](https://nginx.org/en/linux_packages.html) to install NGINX binaries using the package manager native to your Linux distribution. - ## FreeBSD installation process For more information on installing NGINX on FreeBSD system, visit https://nginx.org/en/docs/install.html From noreply at nginx.com Wed Sep 11 15:51:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 11 Sep 2024 15:51:02 +0000 (UTC) Subject: [nginx] Branch created: revert-120-update-readme Message-ID: <20240911155102.8742648C1B@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/2c68c1d35a48de6682eef1f316dd2e62c8af27e1 branches: revert-120-update-readme commit: 2c68c1d35a48de6682eef1f316dd2e62c8af27e1 user: Nina Forsyth date: Wed, 11 Sep 2024 08:50:32 -0700 description: Revert "Added README.md." This reverts commit 07c31459e51cb115ce0440e55c13391a5f823cfe. --- README | 3 + README.md | 248 ------------------------------------------------------- misc/GNUmakefile | 2 +- 3 files changed, 4 insertions(+), 249 deletions(-) diff --git a/README b/README new file mode 100644 index 000000000..2f68e14e0 --- /dev/null +++ b/README @@ -0,0 +1,3 @@ + +Documentation is available at http://nginx.org + diff --git a/README.md b/README.md deleted file mode 100644 index ea7716b49..000000000 --- a/README.md +++ /dev/null @@ -1,248 +0,0 @@ - - - - NGINX Banner - - -NGINX (pronounced "engine x" or "en-jin-eks") is the world's most popular Web Server, high performance Load Balancer, Reverse Proxy, API Gateway and Content Cache. - -NGINX is free and open source software, distributed under the terms of a simplified [2-clause BSD-like license](LICENSE). - -Enterprise distributions, commercial support and training are available from [F5, Inc](https://www.f5.com/products/nginx). - -> [!IMPORTANT] -> The goal of this README is to provide a basic, structured introduction to NGINX for novice users. Please refer to the [full NGINX documentation](https://nginx.org/en/docs/) for detailed information on [installing](https://nginx.org/en/docs/install.html), [building](https://nginx.org/en/docs/configure.html), [configuring](https://nginx.org/en/docs/dirindex.html), [debugging](https://nginx.org/en/docs/debugging_log.html), and more. These documentation pages also contain a more detailed [Beginners Guide](https://nginx.org/en/docs/beginners_guide.html), How-Tos, [Development guide](https://nginx.org/en/docs/dev/development_guide.html), and a complete module and [directive reference](https://nginx.org/en/docs/dirindex.html). - -# Table of contents -- [How it works](#how-it-works) - - [Modules](#modules) - - [Configurations](#configurations) - - [Runtime](#runtime) -- [Downloading and installing](#downloading-and-installing) - - [Stable and Mainline binaries](#stable-and-mainline-binaries) - - [Linux binary installation process](#linux-binary-installation-process) - - [FreeBSD installation process](#freebsd-installation-process) - - [Windows executables](#windows-executables) - - [Dynamic modules](#dynamic-modules) -- [Getting started with NGINX](#getting-started-with-nginx) - - [Installing SSL certificates and enabling TLS encryption](#installing-ssl-certificates-and-enabling-tls-encryption) - - [Load Balancing](#load-balancing) - - [Rate limiting](#rate-limiting) - - [Content caching](#content-caching) -- [Building from source](#building-from-source) - - [Installing dependencies](#installing-dependencies) - - [Cloning the NGINX GitHub repository](#cloning-the-nginx-github-repository) - - [Configuring the build](#configuring-the-build) - - [Compiling](#compiling) - - [Location of binary and installation](#location-of-binary-and-installation) - - [Running and testing the installed binary](#running-and-testing-the-installed-binary) -- [Technical specifications](#technical-specifications) - - [Supported distributions](#supported-distributions) - - [Windows](#windows) - - [Supported deployment environments](#supported-deployment-environments) -- [Asking questions and reporting issues](#asking-questions-reporting-issues-and-contributing) -- [Contributing code](#contributing-code) -- [Additional help and resources](#additional-help-and-resources) -- [Changelog](#changelog) -- [License](#license) - -# How it works -NGINX is installed software with binary packages available for all major operating systems and Linux distributions. See [Tested OS and Platforms](https://nginx.org/en/#tested_os_and_platforms) for a full list of compatible systems. - -> [!IMPORTANT] -> While nearly all popular Linux-based operating systems are distributed with a community version of nginx, we highly advise installation and usage of official [packages](https://nginx.org/en/linux_packages.html) or sources from this repository. Doing so ensures that you're using the most recent release or source code, including the latest feature-set, fixes and security patches. - -## Modules -NGINX is comprised of individual modules, each extending core functionality by providing additional, configurable features. See "Modules reference" at the bottom of [nginx documentation](https://nginx.org/en/docs/) for a complete list of native modules. - -NGINX supports static and dynamic modules. Static modules are defined at build-time and compiled into the resulting binaries. [Dynamic modules](https://nginx.org/en/linux_packages.html#dynmodules) (for example, [njs](https://github.com/nginx/njs)) are built and distributed separately. They can be added to, or removed from, an NGINX installation at any time. - -> [!IMPORTANT] -> Official NGINX package distributions are built with all native open source static modules. - -## Configurations -NGINX is highly flexible and configurable. Provisioning the software is achieved via text-based config file(s) organized in functional sections called "Contexts", accepting a vast amount of configuration parameters called "[Directives](https://nginx.org/en/docs/dirindex.html)". See [Configuration File's Structure](https://nginx.org/en/docs/beginners_guide.html#conf_structure) for a comprehensive definition of Directives and Contexts. - -> [!NOTE] -> The set of directives available to your distribution of NGINX is dependent on which [modules](#modules) have been made available to it. - -## Runtime -Rather than running in a single, monolithic process, NGINX is architected to scale beyond OS process stack limitations by operating as a collection of processes. They include: -- A "master" process that maintains worker processes, as well as, reads and evaluates configuration files. -- One or more "worker" processes that process data (eg. HTTP requests). - -The number of [worker processes](https://nginx.org/en/docs/ngx_core_module.html#worker_processes) is typically set to the number of CPU cores on the system. In most cases, this optimally balances load across available system resources, as NGINX is designed to efficiently distribute work across all available worker processes. - -> [!TIP] -> Processes synchronize data through shared memory. For this reason, many NGINX directives require the allocation of shared memory zones. As an example, when configuring [rate limiting](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req), connecting clients must be tracked in a [common memory zone](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req_zone) so all worker processes can know how many times a particular client has accessed the server in a span of time. - -# Downloading and installing -Follow these steps to download and install precompiled NGINX binaries. You may also choose to [build the module locally from source code](#building-from-source). - -## Stable and Mainline binaries -NGINX binaries are built and distributed in two versions: stable and mainline. You'll need to [decide which is appropriate for your purposes](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#choosing-between-a-stable-or-a-mainline-version). - -## Linux binary installation process -The NGINX binary installation process takes advantage of package managers native to specific Linux distributions. For this reason, first-time installations involve adding the official NGINX package repository to your system's package manager. - -### GPG keys -GPG signing keys are used to verify the authenticity of packages. Read more [important details](https://nginx.org/en/linux_packages.html#signatures) on our GPG/PGP keys. - -### Upgrades -Future upgrades to the latest version can be managed using the same package manager without the need to manually download and verify binaries. - -### Installation instructions -Once a repository has been added and verified, follow [these steps](https://nginx.org/en/linux_packages.html) to install NGINX binaries using the package manager native to your Linux distribution. - -## FreeBSD installation process -For more information on installing NGINX on FreeBSD system, visit https://nginx.org/en/docs/install.html - -## Windows executables -Windows executables for mainline and stable releases can be found on the main [NGINX download page](https://nginx.org/en/download.html). Note that the current implementation of NGINX for Windows is at the Proof-of-Concept stage and should only be used for development and testing purposes. - -## Dynamic modules -NGINX version 1.9.11 added support for [Dynamic Modules](https://nginx.org/en/docs/ngx_core_module.html#load_module). Unlike standard, Static modules, which must be complied into NGINX binaries at build-time, Dynamic modules can be downloaded, installed, and configured at any point. [Official dynamic module binaries](https://nginx.org/en/linux_packages.html#dynmodules) are available from the same package repository as the core NGINX binaries described in previous steps. - -> [!TIP] -> [NGINX JavaScript (njs)](https://github.com/nginx/njs), is a popular NGINX dynamic module that enables the extension of core NGINX functionality using familiar JavaScript syntax. - -# Getting started with NGINX -For a gentle introduction to NGINX basics, please see our [Beginner’s Guide](https://nginx.org/en/docs/beginners_guide.html). - -## Installing SSL certificates and enabling TLS encryption -See [Configuring HTTPS servers](https://nginx.org/en/docs/http/configuring_https_servers.html) for a quick guide on how to enable secure traffic to your NGINX installation. - -## Load Balancing -For a quick start guide on configuring NGINX as a Load Balancer, please see [Using nginx as HTTP load balancer](https://nginx.org/en/docs/http/load_balancing.html). - -## Rate limiting -See our [Rate Limiting with NGINX](https://blog.nginx.org/blog/rate-limiting-nginx) blog post for an overview of core concepts for provisioning NGINX as an API Gateway. - -## Content caching -See [A Guide to Caching with NGINX and NGINX Plus](https://blog.nginx.org/blog/nginx-caching-guide) blog post for an overview of how to use NGINX as a content cache (e.g. edge server of a content delivery network). - -# Building from source -The following steps can be used to build NGINX from source code available in this repository. - -## Installing dependencies -Most Linux distributions will require several dependencies to be installed in order to build NGINX. The following instructions are specific to the `apt` package manager, widely available on most Ubuntu/Debian distributions and their derivatives. - -> [!TIP] -> It is always a good idea to update your package repository lists prior to installing new packages. -> ```bash -> sudo apt update -> ``` - -### Installing compiler and make utility -Use the following command to install the GNU C compiler and Make utility. - -```bash -sudo apt install gcc make -``` - -### Installing dependency libraries - -```bash -sudo apt install libpcre3-dev zlib1g-dev -``` - -> [!WARNING] -> This is the minimal set of dependency libraries needed to build NGINX. Other dependencies may be required if you choose to build NGINX with additional modules. Monitor the output of the `configure` command discussed in the following sections for information on which modules may be missing. For example, if you plan to use SSL certificates to encrypt traffic with TLS, you'll need to install the OpenSSL library. To do so, issue the following command. - ->```bash ->sudo apt install libssl-dev - -## Cloning the NGINX GitHub repository -Using your preferred method, clone the NGINX repository into your development directory. See [Cloning a GitHub Repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) for additional help. - -```bash -git clone https://github.com/nginx/nginx.git -``` - -## Configuring the build -Prior to building NGINX (and most other Linux source packages), you must issue the `configure` command with [appropriate flags](https://nginx.org/en/docs/configure.html). This will generate a Makefile in your NGINX source root directory that can then be used to compile NGINX with [options specified during configuration](https://nginx.org/en/docs/configure.html). - -From the NGINX source code repository's root directory: - -```bash -auto/configure -``` - -> [!IMPORTANT] -> Configuring the build without any flags will compile NGINX with the minimal set of options. Please refer to https://nginx.org/en/docs/configure.html for a full list of available build configuration options. - -## Compiling -The `configure` script will generate a `Makefile` in the NGINX source root directory upon successful execution. To compile NGINX into a binary, issue the following command from that same directory: - -```bash -make -``` - -## Location of binary and installation -After successful compilation, a binary will be generated at `/objs/nginx`. To install this binary, issue the following command from the source root directory: - -```bash -sudo make install -``` - -> [!IMPORTANT] -> The binary will be installed into the `/usr/local/nginx/` directory. - -## Running and testing the installed binary -To run the installed binary, issue the following command: - -```bash -sudo /usr/local/nginx/sbin/nginx -``` - -You may test NGINX operation using `curl`. - -```bash -curl localhost -``` - -The output of which should start with: - -```html - - - -Welcome to nginx! -``` - -# Technical specifications -Pre-built NGINX binaries are available and supported across a wide number of operating systems. Please refer to [this complete list](https://nginx.org/en/) of operating systems, architectures, libraries and other features supported by NGINX. - -## Supported distributions -A [complete list of available Linux packages](https://nginx.org/en/linux_packages.html#distributions) can be found on the binary download page. - -See [Tested OS Platforms](https://nginx.org/en/#tested_os_and_platforms) for a list of operating systems that NGINX is confirmed to run on. - -## Windows -Windows support is tested on Windows XP, Windows Server 2003, Windows 7, Windows 10. [Windows executables](https://nginx.org/en/download.html) can be found on the download page. Note that the current implementation of NGINX for Windows is at the Proof-of-Concept stage and should only be used for development and testing purposes. - -## Supported deployment environments -- Container -- Public cloud (AWS, Google Cloud Platform, Microsoft Azure) -- Virtual machine - -# Asking questions and reporting issues -We encourage you to engage with us. -- [NGINX GitHub Discussions](https://github.com/nginx/nginx/discussions), is the go-to place to start asking questions and sharing your thoughts. -- Our [GitHub Issues](https://github.com/nginx/nginx/issues) page offers space to submit and discuss specific issues, report bugs, and suggest enhancements. - -# Contributing code -Please see the [Contributing](CONTRIBUTING.md) guide for information on how to contribute code. - -# Additional help and resources -- See the [NGINX Community Blog](https://blog.nginx.org/) for more tips, tricks and HOW-TOs related to NGINX and related projects. -- Access [nginx.org](https://nginx.org/), your go-to source for all documentation, information and software related to the NGINX suite of projects. - -# Changelog -See our [changelog](https://nginx.org/en/CHANGES) to keep track of updates. - -# License -[2-clause BSD-like license](LICENSE) - ---- -Additional documentation available at: https://nginx.org/en/docs diff --git a/misc/GNUmakefile b/misc/GNUmakefile index fcf34ec34..4b6a12759 100644 --- a/misc/GNUmakefile +++ b/misc/GNUmakefile @@ -92,7 +92,7 @@ zip: export sed -i '' -e "s/$$/`printf '\r'`/" $(TEMP)/$(NGINX)/conf/* mv $(TEMP)/$(NGINX)/LICENSE $(TEMP)/$(NGINX)/docs.new - mv $(TEMP)/$(NGINX)/README.md $(TEMP)/$(NGINX)/docs.new + mv $(TEMP)/$(NGINX)/README $(TEMP)/$(NGINX)/docs.new mv $(TEMP)/$(NGINX)/docs/html $(TEMP)/$(NGINX) rm -r $(TEMP)/$(NGINX)/docs From noreply at nginx.com Wed Sep 11 16:50:01 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 11 Sep 2024 16:50:01 +0000 (UTC) Subject: [nginx] Branch deleted: revert-120-update-readme Message-ID: <20240911165001.E6BF048C1C@pubserv1.nginx> details: branches: revert-120-update-readme commit: 2c68c1d35a48de6682eef1f316dd2e62c8af27e1 user: date: Wed, 11 Sep 2024 16:50:01 +0000 From thresh at nginx.com Wed Sep 11 17:00:17 2024 From: thresh at nginx.com (Konstantin Pavlov) Date: Wed, 11 Sep 2024 10:00:17 -0700 Subject: [nginx] Branch deleted: revert-120-update-readme In-Reply-To: <20240911165001.E6BF048C1C@pubserv1.nginx> References: <20240911165001.E6BF048C1C@pubserv1.nginx> Message-ID: Hello, And master branch has been restored back to 00637cce366f17b78fe1ed5c1ef0e534143045f6. On 11/09/2024 9:50 AM, noreply at nginx.com wrote: > details: > branches: revert-120-update-readme > commit: 2c68c1d35a48de6682eef1f316dd2e62c8af27e1 > user: > date: Wed, 11 Sep 2024 16:50:01 +0000 > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel -------------- next part -------------- An HTML attachment was scrubbed... URL: From noreply at nginx.com Wed Sep 11 20:26:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 11 Sep 2024 20:26:02 +0000 (UTC) Subject: [njs] Configure: accept spaces in PATH. Message-ID: <20240911202602.0FE0648C1D@pubserv1.nginx> details: https://github.com/nginx/njs/commit/287198152865605dbebff5e88cc6fb54dc725875 branches: master commit: 287198152865605dbebff5e88cc6fb54dc725875 user: Vadim Zhestikov date: Thu, 5 Sep 2024 19:31:28 -0700 description: Configure: accept spaces in PATH. --- auto/expect | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/auto/expect b/auto/expect index ee12a518..a4a5419c 100644 --- a/auto/expect +++ b/auto/expect @@ -21,9 +21,9 @@ if [ $njs_found = yes -a $NJS_HAVE_READLINE = YES ]; then cat << END >> $NJS_MAKEFILE shell_test_njs: njs test/shell_test.exp - PATH=$NJS_BUILD_DIR:\$(PATH) LANG=C.UTF-8 TERM=screen \ + PATH="$NJS_BUILD_DIR:\$(PATH)" LANG=C.UTF-8 TERM=screen \ expect -f test/shell_test.exp - PATH=$NJS_BUILD_DIR:\$(PATH) LANG=C.UTF-8 TERM=screen \ + PATH="$NJS_BUILD_DIR:\$(PATH)" LANG=C.UTF-8 TERM=screen \ expect -f test/shell_test_njs.exp END @@ -33,7 +33,7 @@ if [ $NJS_HAVE_QUICKJS = YES ]; then shell_test: shell_test_njs shell_test_quickjs shell_test_quickjs: njs test/shell_test.exp - PATH=$NJS_BUILD_DIR:\$(PATH) LANG=C.UTF-8 TERM=screen NJS_ENGINE=QuickJS \ + PATH="$NJS_BUILD_DIR:\$(PATH)" LANG=C.UTF-8 TERM=screen NJS_ENGINE=QuickJS \ expect -f test/shell_test.exp END From noreply at nginx.com Fri Sep 13 17:24:54 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 13 Sep 2024 17:24:54 +0000 (UTC) Subject: [nginx] Proxy: proxy_pass_trailers directive. Message-ID: <20240913172454.08FBF4911C@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/1a64c196a7d43f83a14fec20ce8936e599c92865 branches: master commit: 1a64c196a7d43f83a14fec20ce8936e599c92865 user: Sergey Kandaurov date: Tue, 10 Sep 2024 16:48:11 +0400 description: Proxy: proxy_pass_trailers directive. The directive allows to pass upstream response trailers to client. --- src/http/modules/ngx_http_proxy_module.c | 233 +++++++++++++++++++++++++++++-- src/http/ngx_http.h | 2 +- src/http/ngx_http_parse.c | 11 +- src/http/ngx_http_request_body.c | 4 +- src/http/ngx_http_upstream.h | 2 +- 5 files changed, 235 insertions(+), 17 deletions(-) diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c index b774c866e..f9a373744 100644 --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -138,6 +138,8 @@ typedef struct { ngx_chain_t *free; ngx_chain_t *busy; + ngx_buf_t *trailers; + unsigned head:1; unsigned internal_chunked:1; unsigned header_sent:1; @@ -163,6 +165,8 @@ static ngx_int_t ngx_http_proxy_non_buffered_copy_filter(void *data, ssize_t bytes); static ngx_int_t ngx_http_proxy_non_buffered_chunked_filter(void *data, ssize_t bytes); +static ngx_int_t ngx_http_proxy_process_trailer(ngx_http_request_t *r, + ngx_buf_t *buf); static void ngx_http_proxy_abort_request(ngx_http_request_t *r); static void ngx_http_proxy_finalize_request(ngx_http_request_t *r, ngx_int_t rc); @@ -457,6 +461,13 @@ static ngx_command_t ngx_http_proxy_commands[] = { offsetof(ngx_http_proxy_loc_conf_t, upstream.pass_request_body), NULL }, + { ngx_string("proxy_pass_trailers"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_proxy_loc_conf_t, upstream.pass_trailers), + NULL }, + { ngx_string("proxy_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, @@ -2181,11 +2192,12 @@ ngx_http_proxy_copy_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) static ngx_int_t ngx_http_proxy_chunked_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) { - ngx_int_t rc; - ngx_buf_t *b, **prev; - ngx_chain_t *cl; - ngx_http_request_t *r; - ngx_http_proxy_ctx_t *ctx; + ngx_int_t rc; + ngx_buf_t *b, **prev; + ngx_chain_t *cl; + ngx_http_request_t *r; + ngx_http_proxy_ctx_t *ctx; + ngx_http_proxy_loc_conf_t *plcf; if (buf->pos == buf->last) { return NGX_OK; @@ -2216,11 +2228,39 @@ ngx_http_proxy_chunked_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) } b = NULL; + + if (ctx->trailers) { + rc = ngx_http_proxy_process_trailer(r, buf); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + + /* a whole response has been parsed successfully */ + + p->length = 0; + r->upstream->keepalive = !r->upstream->headers_in.connection_close; + + if (buf->pos != buf->last) { + ngx_log_error(NGX_LOG_WARN, p->log, 0, + "upstream sent data after trailers"); + r->upstream->keepalive = 0; + } + } + + goto free_buf; + } + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + prev = &buf->shadow; for ( ;; ) { - rc = ngx_http_parse_chunked(r, buf, &ctx->chunked); + rc = ngx_http_parse_chunked(r, buf, &ctx->chunked, + plcf->upstream.pass_trailers); if (rc == NGX_OK) { @@ -2275,6 +2315,19 @@ ngx_http_proxy_chunked_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) if (rc == NGX_DONE) { + if (plcf->upstream.pass_trailers) { + rc = ngx_http_proxy_process_trailer(r, buf); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_AGAIN) { + p->length = 1; + break; + } + } + /* a whole response has been parsed successfully */ p->length = 0; @@ -2306,6 +2359,8 @@ ngx_http_proxy_chunked_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) return NGX_ERROR; } +free_buf: + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, p->log, 0, "http proxy chunked state %ui, length %O", ctx->chunked.state, p->length); @@ -2401,11 +2456,14 @@ ngx_http_proxy_non_buffered_chunked_filter(void *data, ssize_t bytes) { ngx_http_request_t *r = data; - ngx_int_t rc; - ngx_buf_t *b, *buf; - ngx_chain_t *cl, **ll; - ngx_http_upstream_t *u; - ngx_http_proxy_ctx_t *ctx; + ngx_int_t rc; + ngx_buf_t *b, *buf; + ngx_chain_t *cl, **ll; + ngx_http_upstream_t *u; + ngx_http_proxy_ctx_t *ctx; + ngx_http_proxy_loc_conf_t *plcf; + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); @@ -2419,13 +2477,38 @@ ngx_http_proxy_non_buffered_chunked_filter(void *data, ssize_t bytes) buf->pos = buf->last; buf->last += bytes; + if (ctx->trailers) { + rc = ngx_http_proxy_process_trailer(r, buf); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + + /* a whole response has been parsed successfully */ + + r->upstream->keepalive = !u->headers_in.connection_close; + u->length = 0; + + if (buf->pos != buf->last) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent data after trailers"); + u->keepalive = 0; + } + } + + return NGX_OK; + } + for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) { ll = &cl->next; } for ( ;; ) { - rc = ngx_http_parse_chunked(r, buf, &ctx->chunked); + rc = ngx_http_parse_chunked(r, buf, &ctx->chunked, + plcf->upstream.pass_trailers); if (rc == NGX_OK) { @@ -2467,6 +2550,19 @@ ngx_http_proxy_non_buffered_chunked_filter(void *data, ssize_t bytes) if (rc == NGX_DONE) { + if (plcf->upstream.pass_trailers) { + rc = ngx_http_proxy_process_trailer(r, buf); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_AGAIN) { + u->length = 1; + break; + } + } + /* a whole response has been parsed successfully */ u->keepalive = !u->headers_in.connection_close; @@ -2497,6 +2593,115 @@ ngx_http_proxy_non_buffered_chunked_filter(void *data, ssize_t bytes) } +static ngx_int_t +ngx_http_proxy_process_trailer(ngx_http_request_t *r, ngx_buf_t *buf) +{ + size_t len; + ngx_int_t rc; + ngx_buf_t *b; + ngx_table_elt_t *h; + ngx_http_proxy_ctx_t *ctx; + ngx_http_proxy_loc_conf_t *plcf; + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + + if (ctx->trailers == NULL) { + ctx->trailers = ngx_create_temp_buf(r->pool, + plcf->upstream.buffer_size); + if (ctx->trailers == NULL) { + return NGX_ERROR; + } + } + + b = ctx->trailers; + len = ngx_min(buf->last - buf->pos, b->end - b->last); + + b->last = ngx_cpymem(b->last, buf->pos, len); + + for ( ;; ) { + + rc = ngx_http_parse_header_line(r, b, 1); + + if (rc == NGX_OK) { + + /* a header line has been parsed successfully */ + + h = ngx_list_push(&r->upstream->headers_in.trailers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = r->header_hash; + + h->key.len = r->header_name_end - r->header_name_start; + h->value.len = r->header_end - r->header_start; + + h->key.data = ngx_pnalloc(r->pool, + h->key.len + 1 + h->value.len + 1 + h->key.len); + if (h->key.data == NULL) { + h->hash = 0; + return NGX_ERROR; + } + + h->value.data = h->key.data + h->key.len + 1; + h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1; + + ngx_memcpy(h->key.data, r->header_name_start, h->key.len); + h->key.data[h->key.len] = '\0'; + ngx_memcpy(h->value.data, r->header_start, h->value.len); + h->value.data[h->value.len] = '\0'; + + if (h->key.len == r->lowcase_index) { + ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len); + + } else { + ngx_strlow(h->lowcase_key, h->key.data, h->key.len); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy trailer: \"%V: %V\"", + &h->key, &h->value); + continue; + } + + if (rc == NGX_HTTP_PARSE_HEADER_DONE) { + + /* a whole header has been parsed successfully */ + + buf->pos += len - (b->last - b->pos); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy trailer done"); + + return NGX_OK; + } + + if (rc == NGX_AGAIN) { + buf->pos += len; + + if (b->last == b->end) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too big trailers"); + return NGX_ERROR; + } + + return NGX_AGAIN; + } + + /* rc == NGX_HTTP_PARSE_INVALID_HEADER */ + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid trailer: \"%*s\\x%02xd...\"", + r->header_end - r->header_name_start, + r->header_name_start, *r->header_end); + + return NGX_ERROR; + } +} + + static void ngx_http_proxy_abort_request(ngx_http_request_t *r) { @@ -3379,6 +3584,7 @@ ngx_http_proxy_create_loc_conf(ngx_conf_t *cf) conf->upstream.pass_request_headers = NGX_CONF_UNSET; conf->upstream.pass_request_body = NGX_CONF_UNSET; + conf->upstream.pass_trailers = NGX_CONF_UNSET; #if (NGX_HTTP_CACHE) conf->upstream.cache = NGX_CONF_UNSET; @@ -3721,6 +3927,9 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->upstream.pass_request_body, prev->upstream.pass_request_body, 1); + ngx_conf_merge_value(conf->upstream.pass_trailers, + prev->upstream.pass_trailers, 0); + ngx_conf_merge_value(conf->upstream.intercept_errors, prev->upstream.intercept_errors, 0); diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index e06464ebd..cb4a1e68a 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -117,7 +117,7 @@ ngx_int_t ngx_http_arg(ngx_http_request_t *r, u_char *name, size_t len, void ngx_http_split_args(ngx_http_request_t *r, ngx_str_t *uri, ngx_str_t *args); ngx_int_t ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, - ngx_http_chunked_t *ctx); + ngx_http_chunked_t *ctx, ngx_uint_t keep_trailers); ngx_http_request_t *ngx_http_create_request(ngx_connection_t *c); diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index f7e50388f..a45c04554 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -2140,7 +2140,7 @@ ngx_http_split_args(ngx_http_request_t *r, ngx_str_t *uri, ngx_str_t *args) ngx_int_t ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, - ngx_http_chunked_t *ctx) + ngx_http_chunked_t *ctx, ngx_uint_t keep_trailers) { u_char *pos, ch, c; ngx_int_t rc; @@ -2218,6 +2218,9 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, state = sw_last_chunk_extension_almost_done; break; case LF: + if (keep_trailers) { + goto done; + } state = sw_trailer; break; case ';': @@ -2297,12 +2300,18 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, state = sw_last_chunk_extension_almost_done; break; case LF: + if (keep_trailers) { + goto done; + } state = sw_trailer; } break; case sw_last_chunk_extension_almost_done: if (ch == LF) { + if (keep_trailers) { + goto done; + } state = sw_trailer; break; } diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c index afb042395..93c69220c 100644 --- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -870,7 +870,7 @@ ngx_http_discard_request_body_filter(ngx_http_request_t *r, ngx_buf_t *b) for ( ;; ) { - rc = ngx_http_parse_chunked(r, b, rb->chunked); + rc = ngx_http_parse_chunked(r, b, rb->chunked, 0); if (rc == NGX_OK) { @@ -1131,7 +1131,7 @@ ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in) cl->buf->file_pos, cl->buf->file_last - cl->buf->file_pos); - rc = ngx_http_parse_chunked(r, cl->buf, rb->chunked); + rc = ngx_http_parse_chunked(r, cl->buf, rb->chunked, 0); if (rc == NGX_OK) { diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h index cb16f2b4d..db0e1424e 100644 --- a/src/http/ngx_http_upstream.h +++ b/src/http/ngx_http_upstream.h @@ -176,6 +176,7 @@ typedef struct { ngx_flag_t request_buffering; ngx_flag_t pass_request_headers; ngx_flag_t pass_request_body; + ngx_flag_t pass_trailers; ngx_flag_t ignore_client_abort; ngx_flag_t intercept_errors; @@ -224,7 +225,6 @@ typedef struct { signed store:2; unsigned intercept_404:1; unsigned change_buffering:1; - unsigned pass_trailers:1; unsigned preserve_output:1; #if (NGX_HTTP_SSL || NGX_COMPAT) From pluknet at nginx.com Mon Sep 16 13:18:36 2024 From: pluknet at nginx.com (Sergey Kandaurov) Date: Mon, 16 Sep 2024 17:18:36 +0400 Subject: [PATCH 3 of 6] SSL: caching certificates In-Reply-To: <5154d2a2-6b87-4a7b-814b-834f34b793a5@nginx.com> References: <0d87e1495981ca541d8c.1724277894@enoparse.local> <5154d2a2-6b87-4a7b-814b-834f34b793a5@nginx.com> Message-ID: <9F4CAE43-A332-4560-A0FB-736E4653F5B7@nginx.com> > On 30 Aug 2024, at 03:28, Aleksei Bavshin wrote: > > On 8/21/2024 3:04 PM, Sergey Kandaurov wrote: >> # HG changeset patch >> # User Sergey Kandaurov >> # Date 1721762857 0 >> # Tue Jul 23 19:27:37 2024 +0000 >> # Node ID 0d87e1495981ca541d8cdb947d94f20a686545a3 >> # Parent 6fbe0bcb81696bba12d186e5c15323046bcac2d9 >> SSL: caching certificates. >> Certificate chains are now loaded once. >> The certificate cache provides each chain as a unique stack of referenced >> counted elements. This shallow copy is required because OpenSSL's stacks >> aren't reference counted. >> Based on previous work by Mini Hawthorne. >> diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c >> --- a/src/event/ngx_event_openssl.c >> +++ b/src/event/ngx_event_openssl.c >> @@ -18,8 +18,6 @@ typedef struct { >> } ngx_openssl_conf_t; >> -static X509 *ngx_ssl_load_certificate(ngx_pool_t *pool, char **err, >> - ngx_str_t *cert, STACK_OF(X509) **chain); >> static EVP_PKEY *ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, >> ngx_str_t *key, ngx_array_t *passwords); >> static int ngx_ssl_password_callback(char *buf, int size, int rwflag, >> @@ -443,8 +441,9 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ >> STACK_OF(X509) *chain; >> ngx_ssl_name_t *name; >> - x509 = ngx_ssl_load_certificate(cf->pool, &err, cert, &chain); >> - if (x509 == NULL) { >> + chain = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_CERT, &err, cert, NULL); >> + >> + if (chain == NULL) { >> if (err != NULL) { >> ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, >> "cannot load certificate \"%s\": %s", >> @@ -454,6 +453,8 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ >> return NGX_ERROR; >> } >> + x509 = sk_X509_shift(chain); >> + >> if (SSL_CTX_use_certificate(ssl->ctx, x509) == 0) { >> ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, >> "SSL_CTX_use_certificate(\"%s\") failed", cert->data); >> @@ -568,8 +569,10 @@ ngx_ssl_connection_certificate(ngx_conne >> EVP_PKEY *pkey; >> STACK_OF(X509) *chain; >> - x509 = ngx_ssl_load_certificate(pool, &err, cert, &chain); >> - if (x509 == NULL) { >> + chain = ngx_ssl_cache_connection_fetch(NGX_SSL_CACHE_CERT, &err, cert, >> + NULL); >> + >> + if (chain == NULL) { >> if (err != NULL) { >> ngx_ssl_error(NGX_LOG_ERR, c->log, 0, >> "cannot load certificate \"%s\": %s", >> @@ -579,6 +582,8 @@ ngx_ssl_connection_certificate(ngx_conne >> return NGX_ERROR; >> } >> + x509 = sk_X509_shift(chain); >> + >> if (SSL_use_certificate(c->ssl->connection, x509) == 0) { >> ngx_ssl_error(NGX_LOG_ERR, c->log, 0, >> "SSL_use_certificate(\"%s\") failed", cert->data); >> @@ -630,96 +635,6 @@ ngx_ssl_connection_certificate(ngx_conne >> } >> -static X509 * >> -ngx_ssl_load_certificate(ngx_pool_t *pool, char **err, ngx_str_t *cert, >> - STACK_OF(X509) **chain) >> -{ >> - BIO *bio; >> - X509 *x509, *temp; >> - u_long n; >> - >> - if (ngx_strncmp(cert->data, "data:", sizeof("data:") - 1) == 0) { >> - >> - bio = BIO_new_mem_buf(cert->data + sizeof("data:") - 1, >> - cert->len - (sizeof("data:") - 1)); >> - if (bio == NULL) { >> - *err = "BIO_new_mem_buf() failed"; >> - return NULL; >> - } >> - >> - } else { >> - >> - if (ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, cert) >> - != NGX_OK) >> - { >> - *err = NULL; >> - return NULL; >> - } >> - >> - bio = BIO_new_file((char *) cert->data, "r"); >> - if (bio == NULL) { >> - *err = "BIO_new_file() failed"; >> - return NULL; >> - } >> - } >> - >> - /* certificate itself */ >> - >> - x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL); >> - if (x509 == NULL) { >> - *err = "PEM_read_bio_X509_AUX() failed"; >> - BIO_free(bio); >> - return NULL; >> - } >> - >> - /* rest of the chain */ >> - >> - *chain = sk_X509_new_null(); >> - if (*chain == NULL) { >> - *err = "sk_X509_new_null() failed"; >> - BIO_free(bio); >> - X509_free(x509); >> - return NULL; >> - } >> - >> - for ( ;; ) { >> - >> - temp = PEM_read_bio_X509(bio, NULL, NULL, NULL); >> - if (temp == NULL) { >> - n = ERR_peek_last_error(); >> - >> - if (ERR_GET_LIB(n) == ERR_LIB_PEM >> - && ERR_GET_REASON(n) == PEM_R_NO_START_LINE) >> - { >> - /* end of file */ >> - ERR_clear_error(); >> - break; >> - } >> - >> - /* some real error */ >> - >> - *err = "PEM_read_bio_X509() failed"; >> - BIO_free(bio); >> - X509_free(x509); >> - sk_X509_pop_free(*chain, X509_free); >> - return NULL; >> - } >> - >> - if (sk_X509_push(*chain, temp) == 0) { >> - *err = "sk_X509_push() failed"; >> - BIO_free(bio); >> - X509_free(x509); >> - sk_X509_pop_free(*chain, X509_free); >> - return NULL; >> - } >> - } >> - >> - BIO_free(bio); >> - >> - return x509; >> -} >> - >> - >> static EVP_PKEY * >> ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, >> ngx_str_t *key, ngx_array_t *passwords) >> diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h >> --- a/src/event/ngx_event_openssl.h >> +++ b/src/event/ngx_event_openssl.h >> @@ -202,6 +202,9 @@ typedef struct { >> #define NGX_SSL_BUFSIZE 16384 >> +#define NGX_SSL_CACHE_CERT 0 >> + >> + >> ngx_int_t ngx_ssl_init(ngx_log_t *log); >> ngx_int_t ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data); >> diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c >> --- a/src/event/ngx_event_openssl_cache.c >> +++ b/src/event/ngx_event_openssl_cache.c >> @@ -39,6 +39,12 @@ typedef struct { >> static ngx_ssl_cache_node_t *ngx_ssl_cache_lookup(ngx_ssl_cache_t *cache, >> ngx_ssl_cache_type_t *type, ngx_str_t *id, uint32_t hash); >> +static void *ngx_ssl_cache_cert_create(ngx_str_t *id, char **err, void *data); >> +static void ngx_ssl_cache_cert_free(void *data); >> +static void *ngx_ssl_cache_cert_ref(char **err, void *data); >> + >> +static BIO *ngx_ssl_cache_create_bio(ngx_str_t *id, char **err); >> + >> static void *ngx_openssl_cache_create_conf(ngx_cycle_t *cycle); >> static void ngx_ssl_cache_cleanup(void *data); >> static void ngx_ssl_cache_node_insert(ngx_rbtree_node_t *temp, >> @@ -70,6 +76,10 @@ ngx_module_t ngx_openssl_cache_module = >> static ngx_ssl_cache_type_t ngx_ssl_cache_types[] = { >> + /* NGX_SSL_CACHE_CERT */ >> + { ngx_ssl_cache_cert_create, >> + ngx_ssl_cache_cert_free, >> + ngx_ssl_cache_cert_ref }, >> }; >> @@ -191,6 +201,166 @@ ngx_ssl_cache_lookup(ngx_ssl_cache_t *ca >> static void * >> +ngx_ssl_cache_cert_create(ngx_str_t *id, char **err, void *data) >> +{ >> + BIO *bio; >> + X509 *x509; >> + u_long n; >> + STACK_OF(X509) *chain; >> + >> + chain = sk_X509_new_null(); >> + if (chain == NULL) { >> + *err = "sk_X509_new_null() failed"; >> + return NULL; >> + } >> + >> + bio = ngx_ssl_cache_create_bio(id, err); >> + if (bio == NULL) { >> + sk_X509_pop_free(chain, X509_free); >> + return NULL; >> + } >> + >> + /* certificate itself */ >> + >> + x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL); >> + if (x509 == NULL) { >> + *err = "PEM_read_bio_X509_AUX() failed"; >> + BIO_free(bio); >> + sk_X509_pop_free(chain, X509_free); >> + return NULL; >> + } >> + >> + if (sk_X509_push(chain, x509) == 0) { >> + *err = "sk_X509_push() failed"; >> + BIO_free(bio); >> + X509_free(x509); >> + sk_X509_pop_free(chain, X509_free); >> + return NULL; >> + } >> + >> + /* rest of the chain */ >> + >> + for ( ;; ) { >> + >> + x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); >> + if (x509 == NULL) { >> + n = ERR_peek_last_error(); >> + >> + if (ERR_GET_LIB(n) == ERR_LIB_PEM >> + && ERR_GET_REASON(n) == PEM_R_NO_START_LINE) >> + { >> + /* end of file */ >> + ERR_clear_error(); >> + break; >> + } >> + >> + /* some real error */ >> + >> + *err = "PEM_read_bio_X509() failed"; >> + BIO_free(bio); >> + sk_X509_pop_free(chain, X509_free); >> + return NULL; >> + } >> + >> + if (sk_X509_push(chain, x509) == 0) { >> + *err = "sk_X509_push() failed"; >> + BIO_free(bio); >> + X509_free(x509); >> + sk_X509_pop_free(chain, X509_free); >> + return NULL; >> + } >> + } >> + >> + BIO_free(bio); >> + >> + return chain; >> +} >> + >> + >> +static void >> +ngx_ssl_cache_cert_free(void *data) >> +{ >> + sk_X509_pop_free(data, X509_free); >> +} >> + >> + >> +static void * >> +ngx_ssl_cache_cert_ref(char **err, void *data) >> +{ >> + int n, i; >> + X509 *x509; >> + STACK_OF(X509) *chain; >> + >> + chain = sk_X509_dup(data); >> + if (chain == NULL) { >> + *err = "sk_X509_dup() failed"; >> + return NULL; >> + } >> + >> + n = sk_X509_num(chain); >> + >> + for (i = 0; i < n; i++) { >> + x509 = sk_X509_value(chain, i); >> + >> +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) >> + X509_up_ref(x509); >> +#else >> + CRYPTO_add(&x509->references, 1, CRYPTO_LOCK_X509); >> +#endif >> + } >> + >> + return chain; >> +} >> + >> + >> +static BIO * >> +ngx_ssl_cache_create_bio(ngx_str_t *id, char **err) >> +{ >> + BIO *bio; >> + ngx_str_t path; >> + ngx_pool_t *temp_pool; >> + >> + if (ngx_strncmp(id->data, "data:", sizeof("data:") - 1) == 0) { >> + >> + bio = BIO_new_mem_buf(id->data + sizeof("data:") - 1, >> + id->len - (sizeof("data:") - 1)); >> + if (bio == NULL) { >> + *err = "BIO_new_mem_buf() failed"; >> + } >> + >> + return bio; >> + } >> + >> + temp_pool = ngx_create_pool(NGX_MAX_PATH, ngx_cycle->log); >> + if (temp_pool == NULL) { >> + *err = NULL; >> + return NULL; >> + } >> + >> + ngx_memcpy(&path, id, sizeof(ngx_str_t)); >> + >> + if (ngx_get_full_name(temp_pool, >> + (ngx_str_t *) &ngx_cycle->conf_prefix, >> + &path) >> + != NGX_OK) >> + { >> + *err = NULL; >> + ngx_destroy_pool(temp_pool); >> + return NULL; >> + } >> + >> + bio = BIO_new_file((char *) path.data, "r"); >> + if (bio == NULL) { >> + *err = "BIO_new_file() failed"; >> + } >> + >> + ngx_destroy_pool(temp_pool); >> + >> + return bio; >> +} >> + >> + >> +static void * >> ngx_openssl_cache_create_conf(ngx_cycle_t *cycle) >> { >> ngx_ssl_cache_t *cache; >> _______________________________________________ >> nginx-devel mailing list >> nginx-devel at nginx.org >> https://mailman.nginx.org/mailman/listinfo/nginx-devel > > The patch introduces a small difference in the behavior: we no longer resolve the full name preemptively and modify the paths in place. > This results in: > > * possible duplication if the same object is referenced both by absolute and relative paths > * relative certificate paths in the name_rbtree and in the OCSP stapling log Agree, these side-effects should be fixed. > > Below is the patch addressing that and a test update. Note that a few lines dealing with NGX_SSL_CACHE_KEY should be applied to the patch 4. > Applied, with some larger rewrite. > Otherwise, the series looks good to me. > > (There's one more difference in behavior; we started accepting "data:" in the trusted CA and CRL configuration directives. I've been reviewing with assumption that it is an intended consequence.) > This is also an unintentional change. Fixed as well with some modifications to your patch. Since we've moved to GitHub, see the updated patch series at https://github.com/nginx/nginx/pull/140 Specifically, force-pushes from c61f1a1 to c51af6b. [..] > diff --git a/ssl_cache.t b/ssl_cache.t > index 96bd6a83ef..44935c3386 100644 > --- a/ssl_cache.t > +++ b/ssl_cache.t > @@ -29,8 +29,10 @@ my $t = Test::Nginx->new(); > > plan(skip_all => "not yet") unless $t->has_version('1.27.2'); > > +my $d = $t->testdir(); > + > $t->has(qw/http http_ssl socket_ssl/)->has_daemon('openssl') > - ->write_file_expand('nginx.conf', <<'EOF'); > + ->write_file_expand('nginx.conf', << "EOF"); > > %%TEST_GLOBALS%% > > @@ -64,12 +66,22 @@ http { > ssl_client_certificate root.crt.fifo; > ssl_crl root.crl.fifo; > } > + > + server { > + listen 127.0.0.1:8445 ssl; > + server_name localhost; > + > + ssl_certificate $d/localhost.crt.fifo; A %%TESTDIR%% template is used for such expansion. Applied locally, tnx. > + ssl_certificate_key $d/localhost.key.fifo; > + > + ssl_verify_client on; > + ssl_client_certificate $d/root.crt.fifo; > + ssl_crl $d/root.crl.fifo; > + } > } > > EOF > > -my $d = $t->testdir(); > - > $t->write_file('openssl.conf', < [ req ] > default_bits = 2048 > @@ -135,12 +147,13 @@ foreach my $name ('root.crt', 'root.crl', 'localhost.crt', 'localhost.key') { > > $t->write_file('t', ''); > > -$t->plan(2)->run(); > +$t->plan(3)->run(); > > ############################################################################### > > like(get(8443), qr/200 OK/, 'cached certificate'); > like(get(8444, 'client'), qr/200 OK/, 'cached CA and CRL'); > +like(get(8445, 'client'), qr/200 OK/, 'cached objects with absolute path'); > > ############################################################################### -- Sergey Kandaurov From noreply at nginx.com Wed Sep 18 01:06:01 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 18 Sep 2024 01:06:01 +0000 (UTC) Subject: [njs] CI: removed DUMP_LEAKS flag from QuickJS build. Message-ID: <20240918010602.00BB549130@pubserv1.nginx> details: https://github.com/nginx/njs/commit/50e6a1141db346d1396ff7588a4f6e64327e5bdf branches: master commit: 50e6a1141db346d1396ff7588a4f6e64327e5bdf user: Dmitry Volyntsev date: Wed, 11 Sep 2024 21:53:38 -0700 description: CI: removed DUMP_LEAKS flag from QuickJS build. While it is useful for debugging what objects are leaking at the end this flag hides QuickJS JSString leaks from Address Sanitizer. --- .github/workflows/check-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index 2f151197..e26b1e29 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -39,7 +39,7 @@ jobs: - name: Check out and build quickjs run: | git clone https://github.com/bellard/quickjs - cd quickjs && curl -OL http://pp.nginx.com/pluknet/quickjs.patch && git apply quickjs.patch + cd quickjs CFLAGS=$CC_OPT LDFLAGS=$LD_OPT $MAKE_UTILITY -j$(nproc) libquickjs.a - name: Configure and make njs From noreply at nginx.com Wed Sep 18 01:06:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 18 Sep 2024 01:06:02 +0000 (UTC) Subject: [njs] Modules: introduced engine API. Message-ID: <20240918010602.0AE1749131@pubserv1.nginx> details: https://github.com/nginx/njs/commit/9b6744129ce4c18e74b5e41cd1da66f2cbadcf36 branches: master commit: 9b6744129ce4c18e74b5e41cd1da66f2cbadcf36 user: Dmitry Volyntsev date: Mon, 10 Jun 2024 22:58:24 -0700 description: Modules: introduced engine API. No functional changes. --- nginx/ngx_http_js_module.c | 337 ++++++++++++++------------- nginx/ngx_js.c | 533 ++++++++++++++++++++++++++++++++----------- nginx/ngx_js.h | 98 ++++++-- nginx/ngx_js_fetch.c | 12 +- nginx/ngx_stream_js_module.c | 385 +++++++++++++++++-------------- 5 files changed, 873 insertions(+), 492 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 7f2eded2..35f988d0 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -48,14 +48,13 @@ typedef struct { #define NJS_HEADER_ARRAY 0x4 -typedef struct { +typedef struct ngx_http_js_ctx_s ngx_http_js_ctx_t; + +struct ngx_http_js_ctx_s { NGX_JS_COMMON_CTX; - ngx_log_t *log; ngx_uint_t done; ngx_int_t status; - njs_opaque_value_t retval; - njs_opaque_value_t request; - njs_opaque_value_t args; + njs_opaque_value_t rargs; njs_opaque_value_t request_body; njs_opaque_value_t response_body; ngx_str_t redirect_uri; @@ -65,9 +64,13 @@ typedef struct { ngx_chain_t **last_out; ngx_chain_t *free; ngx_chain_t *busy; + ngx_int_t (*body_filter)(ngx_http_request_t *r, + ngx_http_js_loc_conf_t *jlcf, + ngx_http_js_ctx_t *ctx, + ngx_chain_t *in); ngx_js_periodic_t *periodic; -} ngx_http_js_ctx_t; +}; typedef struct { @@ -299,6 +302,11 @@ 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); +static ngx_conf_bitmask_t ngx_http_js_engines[] = { + { ngx_string("njs"), NGX_ENGINE_NJS }, + { ngx_null_string, 0 } +}; + #if (NGX_HTTP_SSL) static ngx_conf_bitmask_t ngx_http_js_ssl_protocols[] = { @@ -313,6 +321,13 @@ static ngx_conf_bitmask_t ngx_http_js_ssl_protocols[] = { static ngx_command_t ngx_http_js_commands[] = { + { ngx_string("js_engine"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_js_engine, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_js_loc_conf_t, type), + &ngx_http_js_engines }, + { ngx_string("js_import"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE13, ngx_js_import, @@ -960,8 +975,8 @@ ngx_http_js_content_event_handler(ngx_http_request_t *r) ctx->status = NGX_HTTP_INTERNAL_SERVER_ERROR; - rc = ngx_js_name_call(ctx->vm, &jlcf->content, r->connection->log, - &ctx->request, 1); + rc = ctx->engine->call((ngx_js_ctx_t *) ctx, &jlcf->content, &ctx->args[0], + 1); if (rc == NGX_ERROR) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); @@ -990,7 +1005,7 @@ ngx_http_js_content_write_event_handler(ngx_http_request_t *r) ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); - if (!ngx_vm_pending(ctx)) { + if (!ngx_js_ctx_pending(ctx)) { ngx_http_js_content_finalize(r, ctx); return; } @@ -1086,13 +1101,13 @@ ngx_http_js_header_filter(ngx_http_request_t *r) ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); ctx->filter = 1; - pending = ngx_vm_pending(ctx); + pending = ngx_js_ctx_pending(ctx); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http js header call \"%V\"", &jlcf->header_filter); - rc = ngx_js_name_call(ctx->vm, &jlcf->header_filter, r->connection->log, - &ctx->request, 1); + rc = ctx->engine->call((ngx_js_ctx_t *) ctx, &jlcf->header_filter, + &ctx->args[0], 1); if (rc == NGX_ERROR) { return NGX_ERROR; @@ -1110,51 +1125,28 @@ ngx_http_js_header_filter(ngx_http_request_t *r) static ngx_int_t -ngx_http_js_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +ngx_http_njs_body_filter(ngx_http_request_t *r, ngx_http_js_loc_conf_t *jlcf, + ngx_http_js_ctx_t *ctx, ngx_chain_t *in) { - size_t len; - u_char *p; - ngx_int_t rc; - njs_int_t ret, pending; - ngx_buf_t *b; - ngx_chain_t *out, *cl; - ngx_connection_t *c; - ngx_http_js_ctx_t *ctx; - njs_opaque_value_t last_key, last; - ngx_http_js_loc_conf_t *jlcf; - njs_opaque_value_t arguments[3]; + size_t len; + u_char *p; + njs_vm_t *vm; + ngx_int_t rc; + njs_int_t ret, pending; + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_connection_t *c; + njs_opaque_value_t last_key, last; + njs_opaque_value_t arguments[3]; static const njs_str_t last_str = njs_str("last"); - jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); - - if (jlcf->body_filter.len == 0 || in == NULL) { - return ngx_http_next_body_filter(r, in); - } - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http js body filter"); - - rc = ngx_http_js_init_vm(r, ngx_http_js_request_proto_id); - - if (rc == NGX_ERROR || rc == NGX_DECLINED) { - return NGX_ERROR; - } - - ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); - - if (ctx->done) { - return ngx_http_next_body_filter(r, in); - } - c = r->connection; + vm = ctx->engine->u.njs.vm; - ctx->filter = 1; - ctx->last_out = &out; + njs_value_assign(&arguments[0], &ctx->args[0]); - njs_value_assign(&arguments[0], &ctx->request); - - njs_vm_value_string_create(ctx->vm, njs_value_arg(&last_key), + njs_vm_value_string_create(vm, njs_value_arg(&last_key), last_str.start, last_str.length); while (in != NULL) { @@ -1166,7 +1158,7 @@ ngx_http_js_body_filter(ngx_http_request_t *r, ngx_chain_t *in) p = ngx_pnalloc(r->pool, len); if (p == NULL) { - njs_vm_memory_error(ctx->vm); + njs_vm_memory_error(vm); return NJS_ERROR; } @@ -1174,7 +1166,7 @@ ngx_http_js_body_filter(ngx_http_request_t *r, ngx_chain_t *in) ngx_memcpy(p, b->pos, len); } - ret = ngx_js_prop(ctx->vm, jlcf->buffer_type, + ret = ngx_js_prop(vm, jlcf->buffer_type, njs_value_arg(&arguments[1]), p, len); if (ret != NJS_OK) { return ret; @@ -1182,20 +1174,20 @@ ngx_http_js_body_filter(ngx_http_request_t *r, ngx_chain_t *in) njs_value_boolean_set(njs_value_arg(&last), b->last_buf); - ret = njs_vm_object_alloc(ctx->vm, njs_value_arg(&arguments[2]), + ret = njs_vm_object_alloc(vm, njs_value_arg(&arguments[2]), njs_value_arg(&last_key), njs_value_arg(&last), NULL); if (ret != NJS_OK) { return ret; } - pending = ngx_vm_pending(ctx); + pending = ngx_js_ctx_pending(ctx); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http js body call \"%V\"", &jlcf->body_filter); - rc = ngx_js_name_call(ctx->vm, &jlcf->body_filter, c->log, - &arguments[0], 3); + rc = ctx->engine->call((ngx_js_ctx_t *) ctx, &jlcf->body_filter, + &arguments[0], 3); if (rc == NGX_ERROR) { return NGX_ERROR; @@ -1225,13 +1217,54 @@ ngx_http_js_body_filter(ngx_http_request_t *r, ngx_chain_t *in) in = in->next; } + return NGX_OK; +} + + +static ngx_int_t +ngx_http_js_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + ngx_int_t rc; + ngx_chain_t *out; + ngx_http_js_ctx_t *ctx; + ngx_http_js_loc_conf_t *jlcf; + + jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); + + if (jlcf->body_filter.len == 0 || in == NULL) { + return ngx_http_next_body_filter(r, in); + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http js body filter"); + + rc = ngx_http_js_init_vm(r, ngx_http_js_request_proto_id); + + if (rc == NGX_ERROR || rc == NGX_DECLINED) { + return NGX_ERROR; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + + if (ctx->done) { + return ngx_http_next_body_filter(r, in); + } + + ctx->filter = 1; + ctx->last_out = &out; + + rc = ctx->body_filter(r, jlcf, ctx, in); + if (rc != NGX_OK) { + return NGX_ERROR; + } + *ctx->last_out = NULL; - if (out != NULL || c->buffered) { + if (out != NULL || r->connection->buffered) { rc = ngx_http_next_body_filter(r, out); - ngx_chain_update_chains(c->pool, &ctx->free, &ctx->busy, &out, - (ngx_buf_tag_t) &ngx_http_js_module); + ngx_chain_update_chains(r->connection->pool, &ctx->free, &ctx->busy, + &out, (ngx_buf_tag_t) &ngx_http_js_module); } else { rc = NGX_OK; @@ -1249,8 +1282,7 @@ ngx_http_js_variable_set(ngx_http_request_t *r, ngx_http_variable_value_t *v, ngx_int_t rc; njs_int_t pending; - ngx_str_t *fname; - njs_str_t value; + ngx_str_t *fname, value; ngx_http_js_ctx_t *ctx; fname = &vdata->fname; @@ -1271,10 +1303,9 @@ ngx_http_js_variable_set(ngx_http_request_t *r, ngx_http_variable_value_t *v, ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); - pending = ngx_vm_pending(ctx); + pending = ngx_js_ctx_pending(ctx); - rc = ngx_js_name_invoke(ctx->vm, fname, r->connection->log, &ctx->request, - 1, &ctx->retval); + rc = ctx->engine->call((ngx_js_ctx_t *) ctx, fname, &ctx->args[0], 1); if (rc == NGX_ERROR) { v->not_found = 1; @@ -1287,15 +1318,15 @@ ngx_http_js_variable_set(ngx_http_request_t *r, ngx_http_variable_value_t *v, return NGX_ERROR; } - if (ngx_js_string(ctx->vm, njs_value_arg(&ctx->retval), &value) != NGX_OK) { + if (ctx->engine->string(ctx->engine, &ctx->retval, &value) != NGX_OK) { return NGX_ERROR; } - v->len = value.length; + v->len = value.len; v->valid = 1; v->no_cacheable = vdata->flags & NGX_NJS_VAR_NOCACHE; v->not_found = 0; - v->data = value.start; + v->data = value.data; return NGX_OK; } @@ -1331,18 +1362,12 @@ ngx_http_js_variable_var(ngx_http_request_t *r, ngx_http_variable_value_t *v, static ngx_int_t ngx_http_js_init_vm(ngx_http_request_t *r, njs_int_t proto_id) { - njs_int_t rc; - ngx_str_t exception; - njs_str_t key; - ngx_uint_t i; ngx_http_js_ctx_t *ctx; - njs_opaque_value_t retval; ngx_pool_cleanup_t *cln; - ngx_js_named_path_t *preload; ngx_http_js_loc_conf_t *jlcf; jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); - if (jlcf->vm == NULL) { + if (jlcf->engine == NULL) { return NGX_DECLINED; } @@ -1354,71 +1379,33 @@ ngx_http_js_init_vm(ngx_http_request_t *r, njs_int_t proto_id) return NGX_ERROR; } - ngx_js_ctx_init((ngx_js_ctx_t *) ctx); - - njs_value_invalid_set(njs_value_arg(&ctx->retval)); + ngx_js_ctx_init((ngx_js_ctx_t *) ctx, r->connection->log); ngx_http_set_ctx(r, ctx, ngx_http_js_module); } - if (ctx->vm) { + if (ctx->engine) { return NGX_OK; } - ctx->vm = njs_vm_clone(jlcf->vm, r); - if (ctx->vm == NULL) { + ctx->engine = jlcf->engine->clone((ngx_js_ctx_t *) ctx, + (ngx_js_loc_conf_t *) jlcf, proto_id, r); + if (ctx->engine == NULL) { return NGX_ERROR; } - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http js vm clone: %p from: %p", ctx->vm, jlcf->vm); + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http js vm clone %s: %p from: %p", jlcf->engine->name, + ctx->engine, jlcf->engine); cln = ngx_pool_cleanup_add(r->pool, 0); if (cln == NULL) { return NGX_ERROR; } - ctx->log = r->connection->log; - cln->handler = ngx_http_js_cleanup_ctx; cln->data = ctx; - /* bind objects from preload vm */ - - if (jlcf->preload_objects != NGX_CONF_UNSET_PTR) { - preload = jlcf->preload_objects->elts; - - for (i = 0; i < jlcf->preload_objects->nelts; i++) { - key.start = preload[i].name.data; - key.length = preload[i].name.len; - - rc = njs_vm_value(jlcf->preload_vm, &key, njs_value_arg(&retval)); - if (rc != NJS_OK) { - return NGX_ERROR; - } - - rc = njs_vm_bind(ctx->vm, &key, njs_value_arg(&retval), 0); - if (rc != NJS_OK) { - return NGX_ERROR; - } - } - } - - if (njs_vm_start(ctx->vm, njs_value_arg(&retval)) == NJS_ERROR) { - ngx_js_exception(ctx->vm, &exception); - - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "js exception: %V", &exception); - - return NGX_ERROR; - } - - rc = njs_vm_external_create(ctx->vm, njs_value_arg(&ctx->request), - proto_id, r, 0); - if (rc != NJS_OK) { - return NGX_ERROR; - } - return NGX_OK; } @@ -1426,16 +1413,22 @@ ngx_http_js_init_vm(ngx_http_request_t *r, njs_int_t proto_id) static void ngx_http_js_cleanup_ctx(void *data) { - ngx_http_js_ctx_t *ctx = data; + ngx_http_request_t *r; + ngx_http_js_loc_conf_t *jlcf; + + ngx_http_js_ctx_t *ctx = data; - if (ngx_vm_pending(ctx)) { + if (ngx_js_ctx_pending(ctx)) { ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "pending events"); } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, "http js vm destroy: %p", - ctx->vm); + ctx->engine); + + r = ngx_js_ctx_external(ctx); + jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); - ngx_js_ctx_destroy((ngx_js_ctx_t *) ctx); + ngx_js_ctx_destroy((ngx_js_ctx_t *) ctx, (ngx_js_loc_conf_t *) jlcf); } @@ -2657,7 +2650,7 @@ ngx_http_js_ext_get_args(njs_vm_t *vm, njs_object_prop_t *prop, ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); - args = njs_value_arg(&ctx->args); + args = njs_value_arg(&ctx->rargs); if (njs_value_is_null(args)) { data = (r->args.len != 0) ? r->args.data : (u_char *) ""; @@ -3109,9 +3102,8 @@ ngx_http_js_ext_subrequest(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, ngx_str_t uri, rargs; ngx_uint_t method, methods_max, has_body, detached, promise; - njs_value_t *value, *arg, *options; + njs_value_t *value, *arg, *options, *callback; ngx_js_event_t *event; - njs_function_t *callback; ngx_http_js_ctx_t *ctx; njs_opaque_value_t lvalue; ngx_http_request_t *r, *sr; @@ -3168,7 +3160,7 @@ ngx_http_js_ext_subrequest(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } } else if (njs_value_is_function(arg)) { - callback = njs_value_function(arg); + callback = arg; } else if (njs_value_is_object(arg)) { options = arg; @@ -3237,7 +3229,7 @@ ngx_http_js_ext_subrequest(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } else { - callback = njs_value_function(arg); + callback = arg; } } @@ -3254,35 +3246,33 @@ ngx_http_js_ext_subrequest(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, if (!detached) { ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t)); if (ps == NULL) { - njs_vm_memory_error(ctx->vm); + njs_vm_memory_error(vm); return NJS_ERROR; } promise = !!(callback == NULL); - event = njs_mp_zalloc(njs_vm_memory_pool(ctx->vm), + event = njs_mp_zalloc(njs_vm_memory_pool(vm), sizeof(ngx_js_event_t) + promise * (sizeof(njs_opaque_value_t) * 2)); if (njs_slow_path(event == NULL)) { - njs_vm_memory_error(ctx->vm); + njs_vm_memory_error(vm); return NJS_ERROR; } - event->vm = ctx->vm; event->fd = ctx->event_id++; if (promise) { - event->args = (njs_value_t *) &event[1]; - rc = njs_vm_promise_create(ctx->vm, retval, - njs_value_arg(event->args)); + event->args = (njs_opaque_value_t *) &event[1]; + rc = njs_vm_promise_create(vm, retval, njs_value_arg(event->args)); if (rc != NJS_OK) { return NJS_ERROR; } - callback = njs_value_function(njs_value_arg(event->args)); + callback = njs_value_arg(event->args); } - event->function = callback; + njs_value_assign(&event->function, callback); ps->handler = ngx_http_js_subrequest_done; ps->data = event; @@ -3303,7 +3293,7 @@ ngx_http_js_ext_subrequest(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, if (ngx_http_subrequest(r, &uri, rargs.len ? &rargs : NULL, &sr, ps, flags) != NGX_OK) { - njs_vm_error(ctx->vm, "subrequest creation failed"); + njs_vm_error(vm, "subrequest creation failed"); return NJS_ERROR; } @@ -3358,7 +3348,7 @@ ngx_http_js_ext_subrequest(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, memory_error: - njs_vm_error(ctx->vm, "internal error"); + njs_vm_error(vm, "internal error"); return NJS_ERROR; } @@ -3370,6 +3360,7 @@ ngx_http_js_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc) ngx_js_event_t *event = data; njs_int_t ret; + njs_vm_t *vm; ngx_http_js_ctx_t *ctx; njs_opaque_value_t reply; @@ -3407,7 +3398,9 @@ ngx_http_js_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc) return NGX_ERROR; } - ret = njs_vm_external_create(ctx->vm, njs_value_arg(&reply), + vm = ctx->engine->u.njs.vm; + + ret = njs_vm_external_create(vm, njs_value_arg(&reply), ngx_http_js_request_proto_id, r, 0); if (ret != NJS_OK) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, @@ -3416,7 +3409,8 @@ ngx_http_js_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc) return NGX_ERROR; } - rc = ngx_js_call(ctx->vm, event->function, njs_value_arg(&reply), 1); + rc = ngx_js_call(vm, njs_value_function(njs_value_arg(&event->function)), + &reply, 1); ngx_js_del_event(ctx, event); @@ -3447,7 +3441,7 @@ ngx_http_js_ext_get_parent(njs_vm_t *vm, njs_object_prop_t *prop, return NJS_DECLINED; } - njs_value_assign(retval, njs_value_arg(&ctx->request)); + njs_value_assign(retval, njs_value_arg(&ctx->args[0])); return NJS_OK; } @@ -4182,8 +4176,8 @@ ngx_http_js_periodic_handler(ngx_event_t *ev) r->count++; - rc = ngx_js_name_invoke(ctx->vm, &periodic->method, &periodic->log, - &ctx->request, 1, &ctx->retval); + rc = ctx->engine->call((ngx_js_ctx_t *) ctx, &periodic->method, + &ctx->args[0], 1); if (rc == NGX_AGAIN) { rc = NGX_OK; @@ -4210,7 +4204,7 @@ ngx_http_js_periodic_write_handler(ngx_event_t *ev) ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); - if (!ngx_vm_pending(ctx)) { + if (!ngx_js_ctx_pending(ctx)) { ngx_http_js_periodic_finalize(r, NGX_OK); return; } @@ -4247,9 +4241,9 @@ ngx_http_js_periodic_finalize(ngx_http_request_t *r, ngx_int_t rc) ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http js periodic finalize: \"%V\" rc: %i c: %i pending: %i", &ctx->periodic->method, rc, r->count, - ngx_vm_pending(ctx)); + ngx_js_ctx_pending(ctx)); - if (r->count > 1 || (rc == NGX_OK && ngx_vm_pending(ctx))) { + if (r->count > 1 || (rc == NGX_OK && ngx_js_ctx_pending(ctx))) { return; } @@ -4426,22 +4420,51 @@ ngx_js_http_init(njs_vm_t *vm) } +static ngx_engine_t * +ngx_engine_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, + njs_int_t proto_id, void *external) +{ + njs_int_t rc; + ngx_engine_t *engine; + ngx_http_js_ctx_t *hctx; + + engine = ngx_njs_clone(ctx, cf, external); + if (engine == NULL) { + return NULL; + } + + rc = njs_vm_external_create(engine->u.njs.vm, njs_value_arg(&ctx->args[0]), + proto_id, njs_vm_external_ptr(engine->u.njs.vm), + 0); + if (rc != NJS_OK) { + return NULL; + } + + hctx = (ngx_http_js_ctx_t *) ctx; + hctx->body_filter = ngx_http_njs_body_filter; + + return engine; +} + + static ngx_int_t ngx_http_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf) { - njs_vm_opt_t options; + ngx_engine_opts_t options; ngx_js_main_conf_t *jmcf; - njs_vm_opt_init(&options); + memset(&options, 0, sizeof(ngx_engine_opts_t)); - 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; + options.engine = conf->type; - options.backtrace = 1; - options.metas = &ngx_http_js_metas; - options.addons = njs_http_js_addon_modules; - options.argv = ngx_argv; - options.argc = ngx_argc; + if (conf->type == NGX_ENGINE_NJS) { + jmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_js_module); + ngx_http_js_uptr[NGX_JS_MAIN_CONF_INDEX] = (uintptr_t) jmcf; + + options.u.njs.metas = &ngx_http_js_metas; + options.u.njs.addons = njs_http_js_addon_modules; + options.clone = ngx_engine_njs_clone; + } return ngx_js_init_conf_vm(cf, conf, &options); } diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index 4c1f29f3..ce4988f9 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -24,7 +24,8 @@ typedef struct { typedef struct { - void *promise; + void *promise_obj; + njs_opaque_value_t promise; njs_opaque_value_t message; } ngx_js_rejected_promise_t; @@ -43,6 +44,19 @@ typedef struct { } njs_module_info_t; +static ngx_int_t ngx_engine_njs_init(ngx_engine_t *engine, + ngx_engine_opts_t *opts); +static ngx_int_t ngx_engine_njs_compile(ngx_js_loc_conf_t *conf, ngx_log_t *log, + u_char *start, size_t size); +static ngx_int_t ngx_engine_njs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname, + njs_opaque_value_t *args, njs_uint_t nargs); +static void *ngx_engine_njs_external(ngx_engine_t *engine); +static ngx_int_t ngx_engine_njs_pending(ngx_engine_t *engine); +static ngx_int_t ngx_engine_njs_string(ngx_engine_t *e, + njs_opaque_value_t *value, ngx_str_t *str); +static void ngx_engine_njs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, + ngx_js_loc_conf_t *conf); + static njs_int_t ngx_js_ext_build(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); static njs_int_t ngx_js_ext_conf_file_path(njs_vm_t *vm, @@ -70,6 +84,15 @@ static njs_int_t njs_set_immediate(njs_vm_t *vm, njs_value_t *args, static njs_int_t njs_clear_timeout(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); static njs_int_t ngx_js_unhandled_rejection(ngx_js_ctx_t *ctx); +static void ngx_js_rejection_tracker(njs_vm_t *vm, njs_external_ptr_t unused, + njs_bool_t is_handled, njs_value_t *promise, njs_value_t *reason); +static njs_mod_t *ngx_js_module_loader(njs_vm_t *vm, + njs_external_ptr_t external, njs_str_t *name); +static njs_int_t ngx_js_module_lookup(ngx_js_loc_conf_t *conf, + njs_module_info_t *info); +static njs_int_t ngx_js_module_read(njs_mp_t *mp, int fd, njs_str_t *text); +static njs_int_t ngx_js_set_cwd(njs_mp_t *mp, ngx_js_loc_conf_t *conf, + njs_str_t *path); static void ngx_js_cleanup_vm(void *data); static njs_int_t ngx_js_core_init(njs_vm_t *vm); @@ -354,82 +377,234 @@ njs_module_t *njs_js_addon_modules_shared[] = { static njs_int_t ngx_js_console_proto_id; -ngx_int_t -ngx_js_call(njs_vm_t *vm, njs_function_t *func, njs_value_t *args, - njs_uint_t nargs) +static ngx_engine_t * +ngx_create_engine(ngx_engine_opts_t *opts) { - njs_int_t ret; - ngx_str_t exception; - ngx_connection_t *c; + njs_mp_t *mp; + ngx_int_t rc; + ngx_engine_t *engine; - ret = njs_vm_call(vm, func, args, nargs); - if (ret == NJS_ERROR) { - ngx_js_exception(vm, &exception); + mp = njs_mp_fast_create(2 * getpagesize(), 128, 512, 16); + if (mp == NULL) { + return NULL; + } - c = ngx_external_connection(vm, njs_vm_external_ptr(vm)); + engine = njs_mp_zalloc(mp, sizeof(ngx_engine_t)); + if (engine == NULL) { + return NULL; + } - ngx_log_error(NGX_LOG_ERR, c->log, 0, - "js exception: %V", &exception); + engine->pool = mp; + engine->clone = opts->clone; + + switch (opts->engine) { + case NGX_ENGINE_NJS: + rc = ngx_engine_njs_init(engine, opts); + if (rc != NGX_OK) { + return NULL; + } + + engine->name = "njs"; + engine->type = NGX_ENGINE_NJS; + engine->compile = ngx_engine_njs_compile; + engine->call = ngx_engine_njs_call; + engine->external = ngx_engine_njs_external; + engine->pending = ngx_engine_njs_pending; + engine->string = ngx_engine_njs_string; + engine->destroy = opts->destroy ? opts->destroy + : ngx_engine_njs_destroy; + break; + + default: + return NULL; + } + + return engine; +} + + +static ngx_int_t +ngx_engine_njs_init(ngx_engine_t *engine, ngx_engine_opts_t *opts) +{ + njs_vm_t *vm; + ngx_int_t rc; + njs_vm_opt_t vm_options; + + njs_vm_opt_init(&vm_options); + + vm_options.backtrace = 1; + vm_options.addons = opts->u.njs.addons; + vm_options.metas = opts->u.njs.metas; + vm_options.file = opts->file; + vm_options.argv = ngx_argv; + vm_options.argc = ngx_argc; + + vm = njs_vm_create(&vm_options); + if (vm == NULL) { return NGX_ERROR; } - for ( ;; ) { - ret = njs_vm_execute_pending_job(vm); - if (ret <= NJS_OK) { - c = ngx_external_connection(vm, njs_vm_external_ptr(vm)); + njs_vm_set_rejection_tracker(vm, ngx_js_rejection_tracker, NULL); - if (ret == NJS_ERROR) { - ngx_js_exception(vm, &exception); + rc = ngx_js_set_cwd(njs_vm_memory_pool(vm), opts->conf, &vm_options.file); + if (rc != NGX_OK) { + return NGX_ERROR; + } - ngx_log_error(NGX_LOG_ERR, c->log, 0, - "js job exception: %V", &exception); - return NGX_ERROR; - } + njs_vm_set_module_loader(vm, ngx_js_module_loader, opts->conf); - break; + engine->u.njs.vm = vm; + + return NJS_OK; +} + + +static ngx_int_t +ngx_engine_njs_compile(ngx_js_loc_conf_t *conf, ngx_log_t *log, u_char *start, + size_t size) +{ + u_char *end; + njs_vm_t *vm; + njs_int_t rc; + njs_str_t text; + ngx_uint_t i; + njs_value_t *value; + njs_opaque_value_t exception, lvalue; + ngx_js_named_path_t *import; + + static const njs_str_t line_number_key = njs_str("lineNumber"); + static const njs_str_t file_name_key = njs_str("fileName"); + + vm = conf->engine->u.njs.vm; + end = start + size; + + rc = njs_vm_compile(vm, &start, end); + + if (rc != NJS_OK) { + njs_vm_exception_get(vm, njs_value_arg(&exception)); + njs_vm_value_string(vm, &text, njs_value_arg(&exception)); + + value = njs_vm_object_prop(vm, njs_value_arg(&exception), + &file_name_key, &lvalue); + if (value == NULL) { + value = njs_vm_object_prop(vm, njs_value_arg(&exception), + &line_number_key, &lvalue); + + if (value != NULL) { + i = njs_value_number(value) - 1; + + if (i < conf->imports->nelts) { + import = conf->imports->elts; + ngx_log_error(NGX_LOG_EMERG, log, 0, + "%*s, included in %s:%ui", text.length, + text.start, import[i].file, import[i].line); + return NGX_ERROR; + } + } } + + ngx_log_error(NGX_LOG_EMERG, log, 0, "%*s", text.length, text.start); + return NGX_ERROR; + } + + if (start != end) { + ngx_log_error(NGX_LOG_EMERG, log, 0, + "extra characters in js script: \"%*s\"", + end - start, start); + return NGX_ERROR; } return NGX_OK; } -ngx_int_t -ngx_js_name_call(njs_vm_t *vm, ngx_str_t *fname, ngx_log_t *log, - njs_opaque_value_t *args, njs_uint_t nargs) +ngx_engine_t * +ngx_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external) { - njs_opaque_value_t unused; + njs_vm_t *vm; + njs_int_t rc; + njs_str_t key; + ngx_str_t exception; + ngx_uint_t i; + ngx_engine_t *engine; + njs_opaque_value_t retval; + ngx_js_named_path_t *preload; + + vm = njs_vm_clone(cf->engine->u.njs.vm, external); + if (vm == NULL) { + return NULL; + } + + engine = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(ngx_engine_t)); + if (engine == NULL) { + return NULL; + } - return ngx_js_name_invoke(vm, fname, log, args, nargs, &unused); + memcpy(engine, cf->engine, sizeof(ngx_engine_t)); + engine->pool = njs_vm_memory_pool(vm); + engine->u.njs.vm = vm; + + /* bind objects from preload vm */ + + if (cf->preload_objects != NGX_CONF_UNSET_PTR) { + preload = cf->preload_objects->elts; + + for (i = 0; i < cf->preload_objects->nelts; i++) { + key.start = preload[i].name.data; + key.length = preload[i].name.len; + + rc = njs_vm_value(cf->preload_vm, &key, njs_value_arg(&retval)); + if (rc != NJS_OK) { + return NULL; + } + + rc = njs_vm_bind(vm, &key, njs_value_arg(&retval), 0); + if (rc != NJS_OK) { + return NULL; + } + } + } + + if (njs_vm_start(vm, njs_value_arg(&retval)) == NJS_ERROR) { + ngx_js_exception(vm, &exception); + + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception); + + return NULL; + } + + return engine; } -ngx_int_t -ngx_js_name_invoke(njs_vm_t *vm, ngx_str_t *fname, ngx_log_t *log, - njs_opaque_value_t *args, njs_uint_t nargs, njs_opaque_value_t *retval) +static ngx_int_t +ngx_engine_njs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname, + njs_opaque_value_t *args, njs_uint_t nargs) { + njs_vm_t *vm; njs_int_t ret; njs_str_t name; ngx_str_t exception; - ngx_js_ctx_t *ctx; njs_function_t *func; name.start = fname->data; name.length = fname->len; + vm = ctx->engine->u.njs.vm; + func = njs_vm_function(vm, &name); if (func == NULL) { - ngx_log_error(NGX_LOG_ERR, log, 0, + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js function \"%V\" not found", fname); return NGX_ERROR; } ret = njs_vm_invoke(vm, func, njs_value_arg(args), nargs, - njs_value_arg(retval)); + njs_value_arg(&ctx->retval)); if (ret == NJS_ERROR) { ngx_js_exception(vm, &exception); - ngx_log_error(NGX_LOG_ERR, log, 0, + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception); return NGX_ERROR; @@ -441,7 +616,7 @@ ngx_js_name_invoke(njs_vm_t *vm, ngx_str_t *fname, ngx_log_t *log, if (ret == NJS_ERROR) { ngx_js_exception(vm, &exception); - ngx_log_error(NGX_LOG_ERR, log, 0, + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js job exception: %V", &exception); return NGX_ERROR; } @@ -450,12 +625,10 @@ ngx_js_name_invoke(njs_vm_t *vm, ngx_str_t *fname, ngx_log_t *log, } } - ctx = ngx_external_ctx(vm, njs_vm_external_ptr(vm)); - if (ngx_js_unhandled_rejection(ctx)) { ngx_js_exception(vm, &exception); - ngx_log_error(NGX_LOG_ERR, log, 0, "js exception: %V", &exception); + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception); return NGX_ERROR; } @@ -463,6 +636,110 @@ ngx_js_name_invoke(njs_vm_t *vm, ngx_str_t *fname, ngx_log_t *log, } +static void * +ngx_engine_njs_external(ngx_engine_t *engine) +{ + return njs_vm_external_ptr(engine->u.njs.vm); +} + +static ngx_int_t +ngx_engine_njs_pending(ngx_engine_t *e) +{ + return njs_vm_pending(e->u.njs.vm); +} + + +static ngx_int_t +ngx_engine_njs_string(ngx_engine_t *e, njs_opaque_value_t *value, + ngx_str_t *str) +{ + ngx_int_t rc; + njs_str_t s; + + rc = ngx_js_string(e->u.njs.vm, njs_value_arg(value), &s); + + str->data = s.start; + str->len = s.length; + + return rc; +} + + +static void +ngx_engine_njs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, + ngx_js_loc_conf_t *conf) +{ + ngx_js_event_t *event; + njs_rbtree_node_t *node; + + if (ctx != NULL) { + node = njs_rbtree_min(&ctx->waiting_events); + + while (njs_rbtree_is_there_successor(&ctx->waiting_events, node)) { + event = (ngx_js_event_t *) ((u_char *) node + - offsetof(ngx_js_event_t, node)); + + if (event->destructor != NULL) { + event->destructor(event); + } + + node = njs_rbtree_node_successor(&ctx->waiting_events, node); + } + } + + njs_vm_destroy(e->u.njs.vm); + + /* + * when ctx !=NULL e->pool is vm pool, in such case it is destroyed + * by njs_vm_destroy(). + */ + + if (ctx == NULL) { + njs_mp_destroy(e->pool); + } +} + + +ngx_int_t +ngx_js_call(njs_vm_t *vm, njs_function_t *func, njs_opaque_value_t *args, + njs_uint_t nargs) +{ + njs_int_t ret; + ngx_str_t exception; + ngx_connection_t *c; + + ret = njs_vm_call(vm, func, njs_value_arg(args), nargs); + if (ret == NJS_ERROR) { + ngx_js_exception(vm, &exception); + + c = ngx_external_connection(vm, njs_vm_external_ptr(vm)); + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "js exception: %V", &exception); + return NGX_ERROR; + } + + for ( ;; ) { + ret = njs_vm_execute_pending_job(vm); + if (ret <= NJS_OK) { + c = ngx_external_connection(vm, njs_vm_external_ptr(vm)); + + if (ret == NJS_ERROR) { + ngx_js_exception(vm, &exception); + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "js job exception: %V", &exception); + return NGX_ERROR; + } + + break; + } + } + + return NGX_OK; +} + + ngx_int_t ngx_js_exception(njs_vm_t *vm, ngx_str_t *s) { @@ -554,33 +831,18 @@ ngx_js_event_rbtree_compare(njs_rbtree_node_t *node1, njs_rbtree_node_t *node2) void -ngx_js_ctx_init(ngx_js_ctx_t *ctx) +ngx_js_ctx_init(ngx_js_ctx_t *ctx, ngx_log_t *log) { + ctx->log = log; ctx->event_id = 0; njs_rbtree_init(&ctx->waiting_events, ngx_js_event_rbtree_compare); } void -ngx_js_ctx_destroy(ngx_js_ctx_t *ctx) +ngx_js_ctx_destroy(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *conf) { - ngx_js_event_t *event; - njs_rbtree_node_t *node; - - node = njs_rbtree_min(&ctx->waiting_events); - - while (njs_rbtree_is_there_successor(&ctx->waiting_events, node)) { - event = (ngx_js_event_t *) ((u_char *) node - - offsetof(ngx_js_event_t, node)); - - if (event->destructor != NULL) { - event->destructor(njs_vm_external_ptr(event->vm), event); - } - - node = njs_rbtree_node_successor(&ctx->waiting_events, node); - } - - njs_vm_destroy(ctx->vm); + ctx->engine->destroy(ctx->engine, ctx, conf); } @@ -1047,9 +1309,10 @@ ngx_js_timer_handler(ngx_event_t *ev) event = (ngx_js_event_t *) ((u_char *) ev - offsetof(ngx_js_event_t, ev)); - vm = event->vm; + vm = event->ctx; - rc = ngx_js_call(vm, event->function, event->args, event->nargs); + rc = ngx_js_call(vm, njs_value_function(njs_value_arg(&event->function)), + event->args, event->nargs); ctx = ngx_external_ctx(vm, njs_vm_external_ptr(vm)); ngx_js_del_event(ctx, event); @@ -1059,7 +1322,7 @@ ngx_js_timer_handler(ngx_event_t *ev) static void -ngx_js_clear_timer(njs_external_ptr_t external, ngx_js_event_t *event) +ngx_js_clear_timer(ngx_js_event_t *event) { if (event->ev.timer_set) { ngx_del_timer(&event->ev); @@ -1106,10 +1369,10 @@ njs_set_timer(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - event->vm = vm; - event->function = njs_value_function(njs_argument(args, 1)); + event->ctx = vm; + njs_value_assign(&event->function, njs_argument(args, 1)); event->nargs = nargs; - event->args = (njs_value_t *) ((u_char *) event + sizeof(ngx_js_event_t)); + event->args = (njs_opaque_value_t *) &event[1]; event->destructor = ngx_js_clear_timer; ctx = ngx_external_ctx(vm, njs_vm_external_ptr(vm)); @@ -1343,6 +1606,47 @@ ngx_js_import(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } +char * +ngx_js_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *p = conf; + + ngx_str_t *value; + ngx_uint_t *type, m; + ngx_conf_bitmask_t *mask; + + type = (size_t *) (p + cmd->offset); + if (*type != NGX_CONF_UNSET_UINT) { + return "is duplicate"; + } + + value = cf->args->elts; + mask = cmd->post; + + for (m = 0; mask[m].name.len != 0; m++) { + + if (mask[m].name.len != value[1].len + || ngx_strcasecmp(mask[m].name.data, value[1].data) != 0) + { + continue; + } + + *type = mask[m].mask; + + break; + } + + if (mask[m].name.len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid value \"%s\"", value[1].data); + + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + char * ngx_js_preload_object(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { @@ -1552,14 +1856,16 @@ ngx_js_merge_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, ngx_js_named_path_t *import, *pi, *pij, *preload; if (conf->imports == NGX_CONF_UNSET_PTR + && conf->type == NGX_CONF_UNSET_UINT && conf->paths == NGX_CONF_UNSET_PTR && conf->preload_objects == NGX_CONF_UNSET_PTR) { - if (prev->vm != NULL) { + if (prev->engine != NULL) { conf->preload_objects = prev->preload_objects; conf->imports = prev->imports; + conf->type = prev->type; conf->paths = prev->paths; - conf->vm = prev->vm; + conf->engine = prev->engine; conf->preload_vm = prev->preload_vm; @@ -1703,9 +2009,10 @@ ngx_js_rejection_tracker(njs_vm_t *vm, njs_external_ptr_t unused, promise_obj = njs_value_ptr(promise); for (i = 0; i < length; i++) { - if (rejected_promise[i].promise == promise_obj) { - njs_arr_remove(ctx->rejected_promises, - &rejected_promise[i]); + if (njs_value_ptr(njs_value_arg(&rejected_promise[i].promise)) + == promise_obj) + { + njs_arr_remove(ctx->rejected_promises, &rejected_promise[i]); break; } @@ -1727,7 +2034,7 @@ ngx_js_rejection_tracker(njs_vm_t *vm, njs_external_ptr_t unused, return; } - rejected_promise->promise = njs_value_ptr(promise); + njs_value_assign(&rejected_promise->promise, promise); njs_value_assign(&rejected_promise->message, reason); } @@ -1918,13 +2225,13 @@ current_dir: static njs_int_t -ngx_js_set_cwd(njs_vm_t *vm, ngx_js_loc_conf_t *conf, njs_str_t *path) +ngx_js_set_cwd(njs_mp_t *mp, ngx_js_loc_conf_t *conf, njs_str_t *path) { ngx_str_t cwd; ngx_js_file_dirname(path, &cwd); - conf->cwd.data = njs_mp_alloc(njs_vm_memory_pool(vm), cwd.len); + conf->cwd.data = njs_mp_alloc(mp, cwd.len); if (conf->cwd.data == NULL) { return NJS_ERROR; } @@ -1969,7 +2276,7 @@ ngx_js_module_loader(njs_vm_t *vm, njs_external_ptr_t external, njs_str_t *name) prev_cwd = conf->cwd; - ret = ngx_js_set_cwd(vm, conf, &info.file); + ret = ngx_js_set_cwd(njs_vm_memory_pool(vm), conf, &info.file); if (ret != NJS_OK) { njs_vm_internal_error(vm, "while setting cwd for \"%V\" module", &info.file); @@ -1992,22 +2299,15 @@ ngx_js_module_loader(njs_vm_t *vm, njs_external_ptr_t external, njs_str_t *name) ngx_int_t ngx_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, - njs_vm_opt_t *options) + ngx_engine_opts_t *options) { + u_char *start, *p; size_t size; - u_char *start, *end, *p; ngx_str_t *m, file; - njs_int_t rc; - njs_str_t text; ngx_uint_t i; - njs_value_t *value; ngx_pool_cleanup_t *cln; - njs_opaque_value_t lvalue, exception; ngx_js_named_path_t *import; - static const njs_str_t line_number_key = njs_str("lineNumber"); - static const njs_str_t file_name_key = njs_str("fileName"); - if (conf->preload_objects != NGX_CONF_UNSET_PTR) { if (ngx_js_init_preload_vm(cf, (ngx_js_loc_conf_t *)conf) != NGX_OK) { return NGX_ERROR; @@ -2054,9 +2354,10 @@ ngx_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, options->file.start = file.data; options->file.length = file.len; + options->conf = conf; - conf->vm = njs_vm_create(options); - if (conf->vm == NULL) { + conf->engine = ngx_create_engine(options); + if (conf->engine == NULL) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "failed to create js VM"); return NGX_ERROR; } @@ -2069,17 +2370,6 @@ ngx_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, cln->handler = ngx_js_cleanup_vm; cln->data = conf; - njs_vm_set_rejection_tracker(conf->vm, ngx_js_rejection_tracker, - NULL); - - rc = ngx_js_set_cwd(conf->vm, conf, &options->file); - if (rc != NJS_OK) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "failed to set cwd"); - return NGX_ERROR; - } - - njs_vm_set_module_loader(conf->vm, ngx_js_module_loader, conf); - if (conf->paths != NGX_CONF_UNSET_PTR) { m = conf->paths->elts; @@ -2090,52 +2380,14 @@ ngx_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, } } - end = start + size; - - rc = njs_vm_compile(conf->vm, &start, end); - - if (rc != NJS_OK) { - njs_vm_exception_get(conf->vm, njs_value_arg(&exception)); - njs_vm_value_string(conf->vm, &text, njs_value_arg(&exception)); - - value = njs_vm_object_prop(conf->vm, njs_value_arg(&exception), - &file_name_key, &lvalue); - if (value == NULL) { - value = njs_vm_object_prop(conf->vm, njs_value_arg(&exception), - &line_number_key, &lvalue); - - if (value != NULL) { - i = njs_value_number(value) - 1; - - if (i < conf->imports->nelts) { - import = conf->imports->elts; - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "%*s, included in %s:%ui", text.length, - text.start, import[i].file, import[i].line); - return NGX_ERROR; - } - } - } - - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "%*s", text.length, - text.start); - return NGX_ERROR; - } - - if (start != end) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "extra characters in js script: \"%*s\"", - end - start, start); - return NGX_ERROR; - } - - return NGX_OK; + return conf->engine->compile(conf, cf->log, start, size); } static njs_int_t ngx_js_unhandled_rejection(ngx_js_ctx_t *ctx) { + njs_vm_t *vm; njs_int_t ret; njs_str_t message; ngx_js_rejected_promise_t *rejected_promise; @@ -2146,15 +2398,16 @@ ngx_js_unhandled_rejection(ngx_js_ctx_t *ctx) return 0; } + vm = ctx->engine->u.njs.vm; rejected_promise = ctx->rejected_promises->start; - ret = njs_vm_value_to_string(ctx->vm, &message, + ret = njs_vm_value_to_string(vm, &message, njs_value_arg(&rejected_promise->message)); if (njs_slow_path(ret != NJS_OK)) { return -1; } - njs_vm_error(ctx->vm, "unhandled promise rejection: %V", &message); + njs_vm_error(vm, "unhandled promise rejection: %V", &message); njs_arr_destroy(ctx->rejected_promises); ctx->rejected_promises = NULL; @@ -2168,7 +2421,7 @@ ngx_js_cleanup_vm(void *data) { ngx_js_loc_conf_t *jscf = data; - njs_vm_destroy(jscf->vm); + jscf->engine->destroy(jscf->engine, NULL, NULL); if (jscf->preload_objects != NGX_CONF_UNSET_PTR) { njs_vm_destroy(jscf->preload_vm); @@ -2187,6 +2440,7 @@ ngx_js_create_conf(ngx_conf_t *cf, size_t size) } conf->paths = NGX_CONF_UNSET_PTR; + conf->type = NGX_CONF_UNSET_UINT; conf->imports = NGX_CONF_UNSET_PTR; conf->preload_objects = NGX_CONF_UNSET_PTR; @@ -2251,6 +2505,7 @@ ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child, ngx_js_loc_conf_t *prev = parent; ngx_js_loc_conf_t *conf = child; + ngx_conf_merge_uint_value(conf->type, prev->type, NGX_ENGINE_NJS); ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000); ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 16384); ngx_conf_merge_size_value(conf->max_response_body_size, diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index 373dd499..a3bbd541 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -20,6 +20,8 @@ #include "ngx_js_shared_dict.h" +#define NGX_ENGINE_NJS 1 + #define NGX_JS_UNSET 0 #define NGX_JS_DEPRECATED 1 #define NGX_JS_STRING 2 @@ -36,9 +38,11 @@ #define ngx_js_buffer_type(btype) ((btype) & ~NGX_JS_DEPRECATED) +typedef struct ngx_js_loc_conf_s ngx_js_loc_conf_t; typedef struct ngx_js_event_s ngx_js_event_t; typedef struct ngx_js_dict_s ngx_js_dict_t; typedef struct ngx_js_ctx_s ngx_js_ctx_t; +typedef struct ngx_engine_s ngx_engine_t; typedef ngx_pool_t *(*ngx_external_pool_pt)(njs_external_ptr_t e); @@ -60,14 +64,13 @@ typedef struct { struct ngx_js_event_s { - njs_vm_t *vm; - njs_function_t *function; - njs_value_t *args; + void *ctx; + njs_opaque_value_t function; + njs_opaque_value_t *args; ngx_socket_t fd; NJS_RBTREE_NODE (node); njs_uint_t nargs; - void (*destructor)(njs_external_ptr_t external, - ngx_js_event_t *event); + void (*destructor)(ngx_js_event_t *event); ngx_event_t ev; void *data; }; @@ -79,7 +82,8 @@ struct ngx_js_event_s { #define _NGX_JS_COMMON_LOC_CONF \ - njs_vm_t *vm; \ + ngx_uint_t type; \ + ngx_engine_t *engine; \ ngx_str_t cwd; \ ngx_array_t *imports; \ ngx_array_t *paths; \ @@ -109,7 +113,10 @@ struct ngx_js_event_s { #define NGX_JS_COMMON_CTX \ - njs_vm_t *vm; \ + ngx_engine_t *engine; \ + ngx_log_t *log; \ + njs_opaque_value_t args[3]; \ + njs_opaque_value_t retval; \ njs_arr_t *rejected_promises; \ njs_rbtree_t waiting_events; \ ngx_socket_t event_id @@ -122,7 +129,7 @@ struct ngx_js_event_s { #define ngx_js_del_event(ctx, event) \ do { \ if ((event)->destructor) { \ - (event)->destructor(njs_vm_external_ptr((event)->vm), event); \ + (event)->destructor(event); \ } \ \ njs_rbtree_delete(&(ctx)->waiting_events, &(event)->node); \ @@ -134,9 +141,9 @@ typedef struct { } ngx_js_main_conf_t; -typedef struct { +struct ngx_js_loc_conf_s { NGX_JS_COMMON_LOC_CONF; -} ngx_js_loc_conf_t; +}; typedef struct { @@ -150,6 +157,54 @@ struct ngx_js_ctx_s { }; +typedef struct ngx_engine_opts_s { + unsigned engine; + union { + struct { + njs_vm_meta_t *metas; + njs_module_t **addons; + } njs; + } u; + + njs_str_t file; + ngx_js_loc_conf_t *conf; + ngx_engine_t *(*clone)(ngx_js_ctx_t *ctx, + ngx_js_loc_conf_t *cf, njs_int_t pr_id, + void *external); + void (*destroy)(ngx_engine_t *e, ngx_js_ctx_t *ctx, + ngx_js_loc_conf_t *conf); +} ngx_engine_opts_t; + + +struct ngx_engine_s { + union { + struct { + njs_vm_t *vm; + } njs; + } u; + + ngx_int_t (*compile)(ngx_js_loc_conf_t *conf, ngx_log_t *lg, + u_char *start, size_t size); + ngx_int_t (*call)(ngx_js_ctx_t *ctx, ngx_str_t *fname, + njs_opaque_value_t *args, + njs_uint_t nargs); + ngx_engine_t *(*clone)(ngx_js_ctx_t *ctx, + ngx_js_loc_conf_t *cf, njs_int_t pr_id, + void *external); + void *(*external)(ngx_engine_t *e); + ngx_int_t (*pending)(ngx_engine_t *e); + ngx_int_t (*string)(ngx_engine_t *e, + njs_opaque_value_t *value, + ngx_str_t *str); + void (*destroy)(ngx_engine_t *e, ngx_js_ctx_t *ctx, + ngx_js_loc_conf_t *conf); + + unsigned type; + const char *name; + njs_mp_t *pool; +}; + + #define ngx_external_connection(vm, e) \ (*((ngx_connection_t **) ((u_char *) (e) + njs_vm_meta(vm, 0)))) #define ngx_external_pool(vm, e) \ @@ -182,19 +237,21 @@ struct ngx_js_ctx_s { : njs_vm_value_buffer_set(vm, value, start, len)) -#define ngx_vm_pending(ctx) \ - (njs_vm_pending((ctx)->vm) || !njs_rbtree_is_empty(&(ctx)->waiting_events)) +void ngx_js_ctx_init(ngx_js_ctx_t *ctx, ngx_log_t *log); + +#define ngx_js_ctx_pending(ctx) \ + ((ctx)->engine->pending(ctx->engine) \ + || !njs_rbtree_is_empty(&(ctx)->waiting_events)) +#define ngx_js_ctx_external(ctx) \ + ((ctx)->engine->external(ctx->engine)) -void ngx_js_ctx_init(ngx_js_ctx_t *ctx); -void ngx_js_ctx_destroy(ngx_js_ctx_t *ctx); -ngx_int_t ngx_js_call(njs_vm_t *vm, njs_function_t *func, njs_value_t *args, - njs_uint_t nargs); -ngx_int_t ngx_js_name_call(njs_vm_t *vm, ngx_str_t *fname, ngx_log_t *log, +void ngx_js_ctx_destroy(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *conf); +ngx_int_t ngx_js_call(njs_vm_t *vm, njs_function_t *func, njs_opaque_value_t *args, njs_uint_t nargs); -ngx_int_t ngx_js_name_invoke(njs_vm_t *vm, ngx_str_t *fname, ngx_log_t *log, - njs_opaque_value_t *args, njs_uint_t nargs, njs_opaque_value_t *retval); ngx_int_t ngx_js_exception(njs_vm_t *vm, ngx_str_t *s); +ngx_engine_t *ngx_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, + void *external); njs_int_t ngx_js_ext_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t level, njs_value_t *retval); @@ -203,13 +260,14 @@ void ngx_js_log(njs_vm_t *vm, njs_external_ptr_t external, void ngx_js_logger(ngx_connection_t *c, ngx_uint_t level, const u_char *start, size_t length); char * ngx_js_import(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +char * ngx_js_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); char * ngx_js_preload_object(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); ngx_int_t ngx_js_init_preload_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf); ngx_int_t ngx_js_merge_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, ngx_js_loc_conf_t *prev, ngx_int_t (*init_vm)(ngx_conf_t *cf, ngx_js_loc_conf_t *conf)); ngx_int_t ngx_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, - njs_vm_opt_t *options); + ngx_engine_opts_t *opt); ngx_js_loc_conf_t *ngx_js_create_conf(ngx_conf_t *cf, size_t size); char * ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child, ngx_int_t (*init_vm)(ngx_conf_t *cf, ngx_js_loc_conf_t *conf)); diff --git a/nginx/ngx_js_fetch.c b/nginx/ngx_js_fetch.c index cea80469..1c5a961d 100644 --- a/nginx/ngx_js_fetch.c +++ b/nginx/ngx_js_fetch.c @@ -174,8 +174,7 @@ static njs_int_t ngx_js_headers_fill(njs_vm_t *vm, ngx_js_headers_t *headers, njs_value_t *init); static ngx_js_http_t *ngx_js_http_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log); -static void njs_js_http_destructor(njs_external_ptr_t external, - ngx_js_event_t *event); +static void njs_js_http_destructor(ngx_js_event_t *event); static void ngx_js_resolve_handler(ngx_resolver_ctx_t *ctx); static njs_int_t ngx_js_fetch_promissified_result(njs_vm_t *vm, njs_value_t *result, njs_int_t rc, njs_value_t *retval); @@ -1309,8 +1308,8 @@ ngx_js_http_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log) ctx = ngx_external_ctx(vm, njs_vm_external_ptr(vm)); - event->vm = vm; - event->function = callback; + event->ctx = vm; + njs_value_function_set(njs_value_arg(&event->function), callback); event->destructor = njs_js_http_destructor; event->fd = ctx->event_id++; event->data = http; @@ -1439,7 +1438,7 @@ ngx_js_http_close_connection(ngx_connection_t *c) static void -njs_js_http_destructor(njs_external_ptr_t external, ngx_js_event_t *event) +njs_js_http_destructor(ngx_js_event_t *event) { ngx_js_http_t *http; @@ -1530,7 +1529,8 @@ ngx_js_http_fetch_done(ngx_js_http_t *http, njs_opaque_value_t *retval, vm = http->vm; event = http->event; - rc = ngx_js_call(vm, event->function, njs_value_arg(&arguments), 2); + rc = ngx_js_call(vm, njs_value_function(njs_value_arg(&event->function)), + &arguments[0], 2); ctx = ngx_external_ctx(vm, njs_vm_external_ptr(vm)); ngx_js_del_event(ctx, event); diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index 295b2fcf..565f4e66 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -12,6 +12,8 @@ #include "ngx_js.h" +typedef struct ngx_stream_js_ctx_s ngx_stream_js_ctx_t; + typedef struct { NGX_JS_COMMON_LOC_CONF; @@ -22,7 +24,7 @@ typedef struct { typedef struct { - njs_function_t *function; + njs_opaque_value_t function; ngx_uint_t data_type; } ngx_stream_js_ev_t; @@ -47,30 +49,34 @@ typedef struct { } ngx_js_periodic_t; -typedef struct { +struct ngx_stream_js_ctx_s { NGX_JS_COMMON_CTX; - njs_opaque_value_t retval; - njs_opaque_value_t args[3]; ngx_buf_t *buf; ngx_chain_t **last_out; ngx_chain_t *free; ngx_chain_t *upstream_busy; ngx_chain_t *downstream_busy; ngx_int_t status; + ngx_int_t (*run_event)(ngx_stream_session_t *s, + ngx_stream_js_ctx_t *ctx, + ngx_stream_js_ev_t *event, + ngx_uint_t from_upstream); + ngx_int_t (*body_filter)(ngx_stream_session_t *s, + ngx_stream_js_ctx_t *ctx, + ngx_chain_t *in, + ngx_uint_t from_upstream); #define NGX_JS_EVENT_UPLOAD 0 #define NGX_JS_EVENT_DOWNLOAD 1 #define NGX_JS_EVENT_MAX 2 - ngx_stream_js_ev_t events[2]; + ngx_stream_js_ev_t events[NGX_JS_EVENT_MAX]; unsigned filter:1; unsigned in_progress:1; ngx_js_periodic_t *periodic; -} ngx_stream_js_ctx_t; +}; #define ngx_stream_pending(ctx) \ - (ngx_vm_pending(ctx) \ - || (ctx)->events[NGX_JS_EVENT_UPLOAD].function != NULL \ - || (ctx)->events[NGX_JS_EVENT_DOWNLOAD].function != NULL) + (ngx_js_ctx_pending(ctx) || ngx_stream_js_pending_events(ctx)) static ngx_int_t ngx_stream_js_access_handler(ngx_stream_session_t *s); @@ -79,6 +85,8 @@ static ngx_int_t ngx_stream_js_phase_handler(ngx_stream_session_t *s, ngx_str_t *name); static ngx_int_t ngx_stream_js_body_filter(ngx_stream_session_t *s, ngx_chain_t *in, ngx_uint_t from_upstream); +static ngx_int_t ngx_stream_njs_body_filter(ngx_stream_session_t *s, + ngx_stream_js_ctx_t *ctx, ngx_chain_t *in, ngx_uint_t from_upstream); static ngx_int_t ngx_stream_js_next_filter(ngx_stream_session_t *s, ngx_stream_js_ctx_t *ctx, ngx_chain_t *out, ngx_uint_t from_upstream); static ngx_int_t ngx_stream_js_variable_set(ngx_stream_session_t *s, @@ -87,12 +95,13 @@ static ngx_int_t ngx_stream_js_variable_var(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_js_init_vm(ngx_stream_session_t *s, njs_int_t proto_id); +static ngx_int_t ngx_stream_js_pending_events(ngx_stream_js_ctx_t *ctx); static void ngx_stream_js_drop_events(ngx_stream_js_ctx_t *ctx); static void ngx_stream_js_cleanup(void *data); -static njs_int_t ngx_stream_js_run_event(ngx_stream_session_t *s, +static ngx_int_t ngx_stream_js_run_event(ngx_stream_session_t *s, ngx_stream_js_ctx_t *ctx, ngx_stream_js_ev_t *event, ngx_uint_t from_upstream); -static njs_function_t **ngx_stream_js_event(ngx_stream_session_t *s, +static ngx_stream_js_ev_t *ngx_stream_js_event(ngx_stream_session_t *s, njs_str_t *event); static njs_int_t ngx_stream_js_ext_get_remote_address(njs_vm_t *vm, @@ -156,6 +165,11 @@ static char *ngx_stream_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, 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 }, + { ngx_null_string, 0 } +}; + #if (NGX_STREAM_SSL) static ngx_conf_bitmask_t ngx_stream_js_ssl_protocols[] = { @@ -170,6 +184,13 @@ static ngx_conf_bitmask_t ngx_stream_js_ssl_protocols[] = { static ngx_command_t ngx_stream_js_commands[] = { + { ngx_string("js_engine"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, + ngx_js_engine, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_js_srv_conf_t, type), + &ngx_stream_js_engines }, + { ngx_string("js_import"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE13, ngx_js_import, @@ -693,10 +714,7 @@ ngx_stream_js_preread_handler(ngx_stream_session_t *s) static ngx_int_t ngx_stream_js_phase_handler(ngx_stream_session_t *s, ngx_str_t *name) { - ngx_str_t exception; - njs_int_t ret; ngx_int_t rc; - ngx_connection_t *c; ngx_stream_js_ctx_t *ctx; if (name->len == 0) { @@ -711,8 +729,6 @@ ngx_stream_js_phase_handler(ngx_stream_session_t *s, ngx_str_t *name) return rc; } - c = s->connection; - ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); if (!ctx->in_progress) { @@ -723,36 +739,33 @@ ngx_stream_js_phase_handler(ngx_stream_session_t *s, ngx_str_t *name) ctx->status = NGX_ERROR; - ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, ctx->log, 0, "stream js phase call \"%V\"", name); - rc = ngx_js_name_call(ctx->vm, name, c->log, &ctx->args[0], 1); + rc = ctx->engine->call((ngx_js_ctx_t *) ctx, name, &ctx->args[0], 1); if (rc == NGX_ERROR) { return rc; } } - ret = ngx_stream_js_run_event(s, ctx, &ctx->events[NGX_JS_EVENT_UPLOAD], 0); - if (ret != NJS_OK) { - ngx_js_exception(ctx->vm, &exception); - - ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %V", - &exception); - + rc = ctx->run_event(s, ctx, &ctx->events[NGX_JS_EVENT_UPLOAD], 0); + if (rc != NGX_OK) { return NGX_ERROR; } if (ngx_stream_pending(ctx)) { ctx->in_progress = 1; - rc = ctx->events[NGX_JS_EVENT_UPLOAD].function ? NGX_AGAIN : NGX_DONE; + rc = (ctx->events[NGX_JS_EVENT_UPLOAD].data_type != NGX_JS_UNSET) + ? NGX_AGAIN + : NGX_DONE; } else { ctx->in_progress = 0; rc = ctx->status; } - ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream js phase rc: %i", + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, ctx->log, 0, "stream js phase rc: %i", rc); return rc; @@ -769,11 +782,8 @@ ngx_stream_js_body_filter(ngx_stream_session_t *s, ngx_chain_t *in, ngx_uint_t from_upstream) { ngx_int_t rc; - ngx_str_t exception; - njs_int_t ret; - ngx_chain_t *out, *cl; + ngx_chain_t *out; ngx_connection_t *c; - ngx_stream_js_ev_t *event; ngx_stream_js_ctx_t *ctx; ngx_stream_js_srv_conf_t *jscf; @@ -803,7 +813,8 @@ ngx_stream_js_body_filter(ngx_stream_session_t *s, ngx_chain_t *in, ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream js filter call \"%V\"" , &jscf->filter); - rc = ngx_js_name_call(ctx->vm, &jscf->filter, c->log, &ctx->args[0], 1); + rc = ctx->engine->call((ngx_js_ctx_t *) ctx, &jscf->filter, + &ctx->args[0], 1); if (rc == NGX_ERROR) { return rc; @@ -814,37 +825,9 @@ ngx_stream_js_body_filter(ngx_stream_session_t *s, ngx_chain_t *in, ctx->last_out = &out; - while (in) { - ctx->buf = in->buf; - - event = ngx_stream_event(from_upstream); - - if (event->function != NULL) { - ret = ngx_stream_js_run_event(s, ctx, event, from_upstream); - if (ret != NJS_OK) { - ngx_js_exception(ctx->vm, &exception); - - ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %V", - &exception); - - return NGX_ERROR; - } - - ctx->buf->pos = ctx->buf->last; - - } else { - cl = ngx_alloc_chain_link(c->pool); - if (cl == NULL) { - return NGX_ERROR; - } - - cl->buf = ctx->buf; - - *ctx->last_out = cl; - ctx->last_out = &cl->next; - } - - in = in->next; + rc = ctx->body_filter(s, ctx, in, from_upstream); + if (rc != NGX_OK) { + return NGX_ERROR; } ctx->buf = NULL; @@ -895,8 +878,7 @@ ngx_stream_js_variable_set(ngx_stream_session_t *s, ngx_int_t rc; njs_int_t pending; - ngx_str_t *fname; - njs_str_t value; + ngx_str_t *fname, value; ngx_stream_js_ctx_t *ctx; fname = &vdata->fname; @@ -919,8 +901,7 @@ ngx_stream_js_variable_set(ngx_stream_session_t *s, pending = ngx_stream_pending(ctx); - rc = ngx_js_name_invoke(ctx->vm, fname, s->connection->log, &ctx->args[0], - 1, &ctx->retval); + rc = ctx->engine->call((ngx_js_ctx_t *) ctx, fname, &ctx->args[0], 1); if (rc == NGX_ERROR) { v->not_found = 1; @@ -933,15 +914,15 @@ ngx_stream_js_variable_set(ngx_stream_session_t *s, return NGX_ERROR; } - if (ngx_js_string(ctx->vm, njs_value_arg(&ctx->retval), &value) != NGX_OK) { + if (ctx->engine->string(ctx->engine, &ctx->retval, &value) != NGX_OK) { return NGX_ERROR; } - v->len = value.length; + v->len = value.len; v->valid = 1; v->no_cacheable = vdata->flags & NGX_NJS_VAR_NOCACHE; v->not_found = 0; - v->data = value.start; + v->data = value.data; return NGX_OK; } @@ -977,18 +958,12 @@ ngx_stream_js_variable_var(ngx_stream_session_t *s, static ngx_int_t ngx_stream_js_init_vm(ngx_stream_session_t *s, njs_int_t proto_id) { - njs_int_t rc; - njs_str_t key; - ngx_str_t exception; - ngx_uint_t i; - njs_opaque_value_t retval; ngx_pool_cleanup_t *cln; - ngx_js_named_path_t *preload; ngx_stream_js_ctx_t *ctx; ngx_stream_js_srv_conf_t *jscf; jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module); - if (jscf->vm == NULL) { + if (jscf->engine == NULL) { return NGX_DECLINED; } @@ -1000,24 +975,24 @@ ngx_stream_js_init_vm(ngx_stream_session_t *s, njs_int_t proto_id) return NGX_ERROR; } - ngx_js_ctx_init((ngx_js_ctx_t *) ctx); - - njs_value_invalid_set(njs_value_arg(&ctx->retval)); + ngx_js_ctx_init((ngx_js_ctx_t *) ctx, s->connection->log); ngx_stream_set_ctx(s, ctx, ngx_stream_js_module); } - if (ctx->vm) { + if (ctx->engine) { return NGX_OK; } - ctx->vm = njs_vm_clone(jscf->vm, s); - if (ctx->vm == NULL) { + ctx->engine = jscf->engine->clone((ngx_js_ctx_t *) ctx, + (ngx_js_loc_conf_t *) jscf, proto_id, s); + if (ctx->engine == NULL) { return NGX_ERROR; } - ngx_log_debug2(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, - "stream js vm clone: %p from: %p", ctx->vm, jscf->vm); + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "stream js vm clone: %p from: %p", ctx->engine, + jscf->engine); cln = ngx_pool_cleanup_add(s->connection->pool, 0); if (cln == NULL) { @@ -1027,43 +1002,22 @@ ngx_stream_js_init_vm(ngx_stream_session_t *s, njs_int_t proto_id) cln->handler = ngx_stream_js_cleanup; cln->data = s; - /* bind objects from preload vm */ - - if (jscf->preload_objects != NGX_CONF_UNSET_PTR) { - preload = jscf->preload_objects->elts; + return NGX_OK; +} - for (i = 0; i < jscf->preload_objects->nelts; i++) { - key.start = preload[i].name.data; - key.length = preload[i].name.len; - rc = njs_vm_value(jscf->preload_vm, &key, njs_value_arg(&retval)); - if (rc != NJS_OK) { - return NGX_ERROR; - } +static ngx_int_t +ngx_stream_js_pending_events(ngx_stream_js_ctx_t *ctx) +{ + ngx_uint_t i; - rc = njs_vm_bind(ctx->vm, &key, njs_value_arg(&retval), 0); - if (rc != NJS_OK) { - return NGX_ERROR; - } + for (i = 0; i < NGX_JS_EVENT_MAX; i++) { + if (ctx->events[i].data_type != NGX_JS_UNSET) { + return 1; } } - if (njs_vm_start(ctx->vm, njs_value_arg(&retval)) == NJS_ERROR) { - ngx_js_exception(ctx->vm, &exception); - - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "js exception: %V", &exception); - - return NGX_ERROR; - } - - rc = njs_vm_external_create(ctx->vm, njs_value_arg(&ctx->args[0]), - proto_id, s, 0); - if (rc != NJS_OK) { - return NGX_ERROR; - } - - return NGX_OK; + return 0; } @@ -1073,9 +1027,11 @@ ngx_stream_js_drop_events(ngx_stream_js_ctx_t *ctx) ngx_uint_t i; for (i = 0; i < NGX_JS_EVENT_MAX; i++) { - if (ctx->events[i].function != NULL) { - ctx->events[i].function = NULL; - } + /* + * event[i].data_type = NGX_JS_UNSET + * event[i].function = JS_NULL + */ + memset(&ctx->events[i], 0, sizeof(ngx_stream_js_ev_t)); } } @@ -1083,38 +1039,41 @@ ngx_stream_js_drop_events(ngx_stream_js_ctx_t *ctx) static void ngx_stream_js_cleanup(void *data) { - ngx_stream_js_ctx_t *ctx; + ngx_stream_js_ctx_t *ctx; + ngx_stream_js_srv_conf_t *jscf; ngx_stream_session_t *s = data; ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); - ngx_stream_js_drop_events(ctx); - - if (ngx_vm_pending(ctx)) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "pending events"); + if (ngx_js_ctx_pending(ctx)) { + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "pending events"); } - ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, - "stream js vm destroy: %p", ctx->vm); + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "stream js vm destroy: %p", ctx->engine); + + jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module); - ngx_js_ctx_destroy((ngx_js_ctx_t *) ctx); + ngx_js_ctx_destroy((ngx_js_ctx_t *) ctx, (ngx_js_loc_conf_t *) jscf); } -static njs_int_t +static ngx_int_t ngx_stream_js_run_event(ngx_stream_session_t *s, ngx_stream_js_ctx_t *ctx, ngx_stream_js_ev_t *event, ngx_uint_t from_upstream) { size_t len; u_char *p; + njs_vm_t *vm; njs_int_t ret; + ngx_str_t exception; ngx_buf_t *b; uintptr_t flags; ngx_connection_t *c; - if (event->function == NULL) { - return NJS_OK; + if (!njs_value_is_function(njs_value_arg(&event->function))) { + return NGX_OK; } c = s->connection; @@ -1122,36 +1081,90 @@ ngx_stream_js_run_event(ngx_stream_session_t *s, ngx_stream_js_ctx_t *ctx, len = b ? b->last - b->pos : 0; + vm = ctx->engine->u.njs.vm; + p = ngx_pnalloc(c->pool, len); if (p == NULL) { - njs_vm_memory_error(ctx->vm); - return NJS_ERROR; + njs_vm_memory_error(vm); + goto error; } if (len) { ngx_memcpy(p, b->pos, len); } - ret = ngx_js_prop(ctx->vm, event->data_type, njs_value_arg(&ctx->args[1]), + ret = ngx_js_prop(vm, event->data_type, njs_value_arg(&ctx->args[1]), p, len); if (ret != NJS_OK) { - return ret; + goto error; } flags = from_upstream << 1 | (uintptr_t) (b && b->last_buf); - ret = njs_vm_external_create(ctx->vm, njs_value_arg(&ctx->args[2]), + ret = njs_vm_external_create(vm, njs_value_arg(&ctx->args[2]), ngx_stream_js_session_flags_proto_id, (void *) flags, 0); if (ret != NJS_OK) { + goto error; + } + + ret = ngx_js_call(vm, njs_value_function(njs_value_arg(&event->function)), + &ctx->args[1], 2); + + if (ret == NJS_ERROR) { +error: + ngx_js_exception(vm, &exception); + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %V", + &exception); + return NGX_ERROR; } - return ngx_js_call(ctx->vm, event->function, njs_value_arg(&ctx->args[1]), - 2); + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_njs_body_filter(ngx_stream_session_t *s, ngx_stream_js_ctx_t *ctx, + ngx_chain_t *in, ngx_uint_t from_upstream) +{ + ngx_int_t rc; + ngx_chain_t *cl; + ngx_stream_js_ev_t *event; + + while (in) { + ctx->buf = in->buf; + + event = ngx_stream_event(from_upstream); + + if (njs_value_is_function(njs_value_arg(&event->function))) { + rc = ngx_stream_js_run_event(s, ctx, event, from_upstream); + if (rc != NGX_OK) { + return NGX_ERROR; + } + + ctx->buf->pos = ctx->buf->last; + + } else { + cl = ngx_alloc_chain_link(s->connection->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = ctx->buf; + + *ctx->last_out = cl; + ctx->last_out = &cl->next; + } + + in = in->next; + } + + return NGX_OK; } -static njs_function_t ** +static ngx_stream_js_ev_t * ngx_stream_js_event(ngx_stream_session_t *s, njs_str_t *event) { ngx_uint_t i, n, type; @@ -1204,7 +1217,7 @@ ngx_stream_js_event(ngx_stream_session_t *s, njs_str_t *event) } if (i == n) { - njs_vm_error(ctx->vm, "unknown event \"%V\"", event); + njs_vm_error(ctx->engine->u.njs.vm, "unknown event \"%V\"", event); return NULL; } @@ -1213,13 +1226,13 @@ ngx_stream_js_event(ngx_stream_session_t *s, njs_str_t *event) for (n = 0; n < NGX_JS_EVENT_MAX; n++) { type = ctx->events[n].data_type; if (type != NGX_JS_UNSET && type != events[i].data_type) { - njs_vm_error(ctx->vm, "mixing string and buffer events" - " is not allowed"); + njs_vm_error(ctx->engine->u.njs.vm, "mixing string and buffer" + " events is not allowed"); return NULL; } } - return &ctx->events[events[i].id].function; + return &ctx->events[events[i].id]; } @@ -1305,10 +1318,10 @@ static njs_int_t ngx_stream_js_ext_on(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { - njs_str_t name; - njs_value_t *callback; - njs_function_t **cb; - ngx_stream_session_t *s; + njs_str_t name; + njs_value_t *callback; + ngx_stream_js_ev_t *event; + ngx_stream_session_t *s; s = njs_vm_external(vm, ngx_stream_js_session_proto_id, njs_argument(args, 0)); @@ -1328,17 +1341,17 @@ ngx_stream_js_ext_on(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - cb = ngx_stream_js_event(s, &name); - if (cb == NULL) { + event = ngx_stream_js_event(s, &name); + if (event == NULL) { return NJS_ERROR; } - if (*cb != NULL) { + if (njs_value_is_function(njs_value_arg(&event->function))) { njs_vm_error(vm, "event handler \"%V\" is already set", &name); return NJS_ERROR; } - *cb = njs_value_function(callback); + njs_value_assign(&event->function, callback); njs_value_undefined_set(retval); @@ -1350,9 +1363,9 @@ static njs_int_t ngx_stream_js_ext_off(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { - njs_str_t name; - njs_function_t **callback; - ngx_stream_session_t *s; + njs_str_t name; + ngx_stream_js_ev_t *event; + ngx_stream_session_t *s; s = njs_vm_external(vm, ngx_stream_js_session_proto_id, njs_argument(args, 0)); @@ -1366,12 +1379,13 @@ ngx_stream_js_ext_off(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - callback = ngx_stream_js_event(s, &name); - if (callback == NULL) { + event = ngx_stream_js_event(s, &name); + if (event == NULL) { return NJS_ERROR; } - *callback = NULL; + njs_value_null_set(njs_value_arg(&event->function)); + event->data_type = NGX_JS_UNSET; njs_value_undefined_set(retval); @@ -1764,22 +1778,52 @@ ngx_js_stream_init(njs_vm_t *vm) } +static ngx_engine_t * +ngx_engine_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, + njs_int_t proto_id, void *external) +{ + njs_int_t rc; + ngx_engine_t *engine; + ngx_stream_js_ctx_t *sctx; + + engine = ngx_njs_clone(ctx, cf, external); + if (engine == NULL) { + return NULL; + } + + sctx = (ngx_stream_js_ctx_t *) ctx; + sctx->run_event = ngx_stream_js_run_event; + sctx->body_filter = ngx_stream_njs_body_filter; + + rc = njs_vm_external_create(engine->u.njs.vm, njs_value_arg(&ctx->args[0]), + proto_id, njs_vm_external_ptr(engine->u.njs.vm), + 0); + if (rc != NJS_OK) { + return NULL; + } + + return engine; +} + + static ngx_int_t ngx_stream_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf) { - njs_vm_opt_t options; + ngx_engine_opts_t options; ngx_js_main_conf_t *jmcf; - njs_vm_opt_init(&options); + memset(&options, 0, sizeof(ngx_engine_opts_t)); - 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; + options.engine = conf->type; - options.backtrace = 1; - options.metas = &ngx_stream_js_metas; - options.addons = njs_stream_js_addon_modules; - options.argv = ngx_argv; - options.argc = ngx_argc; + if (conf->type == NGX_ENGINE_NJS) { + jmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_js_module); + ngx_stream_js_uptr[NGX_JS_MAIN_CONF_INDEX] = (uintptr_t) jmcf; + + options.u.njs.metas = &ngx_stream_js_metas; + options.u.njs.addons = njs_stream_js_addon_modules; + options.clone = ngx_engine_njs_clone; + } return ngx_js_init_conf_vm(cf, conf, &options); } @@ -1881,8 +1925,8 @@ ngx_stream_js_periodic_handler(ngx_event_t *ev) s->received++; - rc = ngx_js_name_invoke(ctx->vm, &periodic->method, &periodic->log, - &ctx->args[0], 1, &ctx->retval); + rc = ctx->engine->call((ngx_js_ctx_t *) ctx, &periodic->method, + &ctx->args[0], 1); if (rc == NGX_AGAIN) { rc = NGX_OK; @@ -1925,7 +1969,7 @@ ngx_stream_js_periodic_event_handler(ngx_event_t *ev) ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); - if (!ngx_vm_pending(ctx)) { + if (!ngx_js_ctx_pending(ctx)) { ngx_stream_js_periodic_finalize(s, NGX_OK); return; } @@ -1942,9 +1986,9 @@ ngx_stream_js_periodic_finalize(ngx_stream_session_t *s, ngx_int_t rc) ngx_log_debug4(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, "stream js periodic finalize: \"%V\" rc: %i c: %i " "pending: %i", &ctx->periodic->method, rc, s->received, - ngx_vm_pending(ctx)); + ngx_js_ctx_pending(ctx)); - if (s->received > 1 || (rc == NGX_OK && ngx_vm_pending(ctx))) { + if (s->received > 1 || (rc == NGX_OK && ngx_js_ctx_pending(ctx))) { return; } @@ -2348,6 +2392,7 @@ ngx_stream_js_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_stream_js_srv_conf_t *prev = parent; ngx_stream_js_srv_conf_t *conf = child; + ngx_conf_merge_uint_value(conf->type, prev->type, NGX_ENGINE_NJS); ngx_conf_merge_str_value(conf->access, prev->access, ""); ngx_conf_merge_str_value(conf->preread, prev->preread, ""); ngx_conf_merge_str_value(conf->filter, prev->filter, ""); From noreply at nginx.com Wed Sep 18 01:06:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 18 Sep 2024 01:06:02 +0000 (UTC) Subject: [njs] Modules: introduced QuickJS engine. Message-ID: <20240918010602.2219149134@pubserv1.nginx> details: https://github.com/nginx/njs/commit/201b127679e27fe63eff5c1b4356ec4ed9ec4611 branches: master commit: 201b127679e27fe63eff5c1b4356ec4ed9ec4611 user: Dmitry Volyntsev date: Fri, 14 Jun 2024 20:54:28 -0700 description: Modules: introduced QuickJS engine. "js_engine" directive is introduced which sets JavaScript engine. When the module is built with QuickJS library "js_engine qjs;" sets QuickJS engine for the current block. By default njs engine is used. For example, nginx.conf: location /a { js_engine qjs; # will be handled by QuickJS js_content main.handler; } location /b { # will be handled by njs js_content main.handler; } QuickJS engine implements drop-in replacement for nginx/njs objects with the following exceptions: * nginx module API to be added later: ngx.fetch(), ngx.shared.dict. * Built-in modules to be added later: fs, crypto, WebCrypto, xml. * NJS specific API: njs.dump(), njs.on(), console.dump(). * js_preload_object directive. --- .github/workflows/check-pr.yml | 65 + nginx/config | 100 +- nginx/config.make | 10 +- nginx/ngx_http_js_module.c | 3121 +++++++++++++++++++++++++++++++++++- nginx/ngx_js.c | 1838 +++++++++++++++++++-- nginx/ngx_js.h | 131 ++ nginx/ngx_stream_js_module.c | 1137 ++++++++++++- nginx/t/js_console.t | 20 +- nginx/t/js_dump.t | 22 +- nginx/t/js_engine.t | 140 ++ nginx/t/js_fetch.t | 16 +- nginx/t/js_fetch_https.t | 16 +- nginx/t/js_fetch_objects.t | 18 +- nginx/t/js_fetch_resolver.t | 16 +- nginx/t/js_fetch_timeout.t | 17 +- nginx/t/js_fetch_verify.t | 16 +- nginx/t/js_object.t | 18 +- nginx/t/js_periodic.t | 16 +- nginx/t/js_preload_object.t | 16 +- nginx/t/js_shared_dict.t | 18 +- nginx/t/stream_js_console.t | 38 +- nginx/t/stream_js_exit.t | 16 +- nginx/t/stream_js_fetch.t | 16 +- nginx/t/stream_js_fetch_https.t | 16 +- nginx/t/stream_js_fetch_init.t | 16 +- nginx/t/stream_js_object.t | 85 +- nginx/t/stream_js_preload_object.t | 27 +- nginx/t/stream_js_shared_dict.t | 16 +- src/qjs.h | 6 + src/qjs_buffer.c | 23 +- 30 files changed, 6751 insertions(+), 259 deletions(-) diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index e26b1e29..7dd2bd57 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -84,3 +84,68 @@ jobs: TEST_NGINX_BINARY: "${{ github.workspace }}/nginx-source/objs/nginx" TEST_NGINX_GLOBALS: "load_module ${{ github.workspace }}/nginx-source/objs/ngx_http_js_module.so; load_module ${{ github.workspace }}/nginx-source/objs/ngx_stream_js_module.so;" TEST_NGINX_VERBOSE: 1 + + - name: Create LSAN suppression file + run: | + cat << EOF > lsan_suppressions.txt + leak:ngx_event_process_init + EOF + + - name: Configure and build nginx and njs modules with quickjs, static modules + run: | + cd nginx-source + $NGINX_CONFIGURE_CMD --with-cc-opt="$CC_OPT -I${{ github.workspace }}/quickjs -fsanitize=address" --with-ld-opt="$LD_OPT -L${{ github.workspace }}/quickjs -fsanitize=address" --add-module=../nginx || cat objs/autoconf.err + $MAKE_UTILITY -j$(nproc) + + - name: Test njs modules, static modules + run: | + ulimit -c unlimited + prove -v -j$(nproc) -Inginx-tests/lib --state=save nginx/t . || prove -v -Inginx-tests/lib --state=failed + env: + TEST_NGINX_BINARY: "${{ github.workspace }}/nginx-source/objs/nginx" + TEST_NGINX_VERBOSE: 1 + ASAN_OPTIONS: "detect_odr_violation=0:report_globals=0" + LSAN_OPTIONS: "suppressions=${{ github.workspace }}/lsan_suppressions.txt" + + - name: Test njs modules (js_engine qjs), static modules + run: | + ulimit -c unlimited + prove -v -j$(nproc) -Inginx-tests/lib --state=save nginx/t . || prove -v -Inginx-tests/lib --state=failed + env: + TEST_NGINX_BINARY: "${{ github.workspace }}/nginx-source/objs/nginx" + TEST_NGINX_GLOBALS_HTTP: "js_engine qjs;" + TEST_NGINX_GLOBALS_STREAM: "js_engine qjs;" + TEST_NGINX_VERBOSE: 1 + ASAN_OPTIONS: "detect_odr_violation=0:report_globals=0" + LSAN_OPTIONS: "suppressions=${{ github.workspace }}/lsan_suppressions.txt" + + - name: Configure and build nginx and njs modules with quickjs, dynamic modules + run: | + cd nginx-source + $NGINX_CONFIGURE_CMD --with-debug --with-cc-opt="$CC_OPT -I${{ github.workspace }}/quickjs -fsanitize=address" --with-ld-opt="$LD_OPT -L${{ github.workspace }}/quickjs -fsanitize=address" --add-dynamic-module=../nginx || cat objs/autoconf.err + $MAKE_UTILITY -j$(nproc) modules + $MAKE_UTILITY -j$(nproc) + + - name: Test njs modules, dynamic modules + run: | + ulimit -c unlimited + prove -v -j$(nproc) -Inginx-tests/lib --state=save nginx/t . || prove -v -Inginx-tests/lib --state=failed + env: + TEST_NGINX_BINARY: "${{ github.workspace }}/nginx-source/objs/nginx" + TEST_NGINX_GLOBALS: "load_module ${{ github.workspace }}/nginx-source/objs/ngx_http_js_module.so; load_module ${{ github.workspace }}/nginx-source/objs/ngx_stream_js_module.so;" + TEST_NGINX_VERBOSE: 1 + ASAN_OPTIONS: "detect_odr_violation=0:report_globals=0:fast_unwind_on_malloc=0" + LSAN_OPTIONS: "suppressions=${{ github.workspace }}/lsan_suppressions.txt" + + - name: Test njs modules (js_engine qjs), dynamic modules + run: | + ulimit -c unlimited + prove -v -j$(nproc) -Inginx-tests/lib --state=save nginx/t . || prove -v -Inginx-tests/lib --state=failed + env: + TEST_NGINX_BINARY: "${{ github.workspace }}/nginx-source/objs/nginx" + TEST_NGINX_GLOBALS: "load_module ${{ github.workspace }}/nginx-source/objs/ngx_stream_js_module.so; load_module ${{ github.workspace }}/nginx-source/objs/ngx_http_js_module.so;" + TEST_NGINX_GLOBALS_HTTP: "js_engine qjs;" + TEST_NGINX_GLOBALS_STREAM: "js_engine qjs;" + TEST_NGINX_VERBOSE: 1 + ASAN_OPTIONS: "detect_odr_violation=0:report_globals=0:fast_unwind_on_malloc=0" + LSAN_OPTIONS: "suppressions=${{ github.workspace }}/lsan_suppressions.txt" diff --git a/nginx/config b/nginx/config index 700ae4ab..436f06cb 100644 --- a/nginx/config +++ b/nginx/config @@ -3,6 +3,7 @@ ngx_addon_name="ngx_js_module" NJS_OPENSSL=${NJS_OPENSSL:-YES} NJS_LIBXSLT=${NJS_LIBXSLT:-YES} NJS_ZLIB=${NJS_ZLIB:-YES} +NJS_QUICKJS=${NJS_QUICKJS:-YES} NJS_DEPS="$ngx_addon_dir/ngx_js.h \ $ngx_addon_dir/ngx_js_fetch.h \ @@ -12,9 +13,78 @@ NJS_SRCS="$ngx_addon_dir/ngx_js.c \ $ngx_addon_dir/ngx_js_regex.c \ $ngx_addon_dir/ngx_js_shared_dict.c" +QJS_DEPS="" +QJS_SRCS="" + NJS_OPENSSL_LIB= NJS_XSLT_LIB= NJS_ZLIB_LIB= +NJS_QUICKJS_LIB= +NJS_QUICKJS_INC= +NJS_HAVE_QUICKJS= + +if [ $NJS_QUICKJS != NO ]; then + + ngx_feature="QuickJS library -lquickjs.lto" + ngx_feature_name=NJS_HAVE_QUICKJS + ngx_feature_run=yes + ngx_feature_incs="#if defined(__GNUC__) && (__GNUC__ >= 8) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored \"-Wcast-function-type\" + #endif + + #include " + ngx_feature_path="" + ngx_feature_libs="-lquickjs.lto -lm -ldl -lpthread" + ngx_feature_test="JSRuntime *rt; + + rt = JS_NewRuntime(); + (void) JS_GetClassID; + JS_FreeRuntime(rt); + return 0;" + . auto/feature + + if [ $ngx_found = no ]; then + ngx_feature="QuickJS library -lquickjs" + ngx_feature_libs="-lquickjs -lm -ldl -lpthread" + + . auto/feature + fi + + if [ $ngx_found = no ]; then + ngx_feature="QuickJS library -I/usr/include/quickjs/ -L/usr/lib/quickjs/ -lquickjs.lto" + ngx_feature_path="/usr/include/quickjs/" + ngx_feature_libs="-L/usr/lib/quickjs/ -lquickjs.lto -lm -ldl -lpthread" + + . auto/feature + fi + + if [ $ngx_found = no ]; then + ngx_feature="QuickJS library -I/usr/include/quickjs/ -L/usr/lib/quickjs/ -lquickjs" + ngx_feature_libs="-L/usr/lib/quickjs/ -lquickjs -lm -ldl -lpthread" + + . auto/feature + fi + + if [ $ngx_found = yes ]; then + + ngx_feature="QuickJS JS_NewTypedArray()" + ngx_feature_test="(void) JS_NewTypedArray; + return 0;" + + . auto/feature + + if [ $ngx_found = yes ]; then + have=NJS_HAVE_QUICKJS_NEW_TYPED_ARRAY . auto/have + fi + + NJS_HAVE_QUICKJS=YES + NJS_QUICKJS_LIB="$ngx_feature_libs" + NJS_QUICKJS_INC="$ngx_feature_path" + + echo " enabled QuickJS engine" + fi +fi if [ $NJS_OPENSSL != NO ]; then NJS_OPENSSL_LIB=OPENSSL @@ -37,17 +107,30 @@ if [ $NJS_ZLIB != NO ]; then have=NJS_HAVE_ZLIB . auto/have NJS_SRCS="$NJS_SRCS $ngx_addon_dir/../external/njs_zlib_module.c" + if [ "$NJS_HAVE_QUICKJS" = "YES" ]; then + NJS_SRCS="$NJS_SRCS $ngx_addon_dir/../external/qjs_zlib_module.c" + fi + echo " enabled zlib module" fi + +NJS_ENGINE_DEP="$ngx_addon_dir/../build/libnjs.a" +NJS_ENGINE_LIB="$ngx_addon_dir/../build/libnjs.a" +if [ "$NJS_HAVE_QUICKJS" = "YES" ]; then + NJS_ENGINE_DEP="$ngx_addon_dir/../build/libqjs.a" + NJS_ENGINE_LIB="$ngx_addon_dir/../build/libnjs.a $ngx_addon_dir/../build/libqjs.a" +fi + if [ $HTTP != NO ]; then ngx_module_type=HTTP_AUX_FILTER ngx_module_name=ngx_http_js_module - ngx_module_incs="$ngx_addon_dir/../src $ngx_addon_dir/../build" - ngx_module_deps="$ngx_addon_dir/../build/libnjs.a $NJS_DEPS" - ngx_module_srcs="$ngx_addon_dir/ngx_http_js_module.c $NJS_SRCS" + ngx_module_incs="$ngx_addon_dir/../src $ngx_addon_dir/../build \ + $NJS_QUICKJS_INC" + ngx_module_deps="$NJS_ENGINE_DEP $NJS_DEPS $QJS_DEPS" + ngx_module_srcs="$ngx_addon_dir/ngx_http_js_module.c $NJS_SRCS $QJS_SRCS" ngx_module_libs="PCRE $NJS_OPENSSL_LIB $NJS_XSLT_LIB $NJS_ZLIB_LIB \ - $ngx_addon_dir/../build/libnjs.a -lm" + $NJS_QUICKJS_LIB $NJS_ENGINE_LIB -lm" . auto/module @@ -59,11 +142,12 @@ fi if [ $STREAM != NO ]; then ngx_module_type=STREAM ngx_module_name=ngx_stream_js_module - ngx_module_incs="$ngx_addon_dir/../src $ngx_addon_dir/../build" - ngx_module_deps="$ngx_addon_dir/../build/libnjs.a $NJS_DEPS" - ngx_module_srcs="$ngx_addon_dir/ngx_stream_js_module.c $NJS_SRCS" + ngx_module_incs="$ngx_addon_dir/../src $ngx_addon_dir/../build \ + $NJS_QUICKJS_INC" + ngx_module_deps="$NJS_ENGINE_DEP $NJS_DEPS $QJS_DEPS" + ngx_module_srcs="$ngx_addon_dir/ngx_stream_js_module.c $NJS_SRCS $QJS_SRCS" ngx_module_libs="PCRE $NJS_OPENSSL_LIB $NJS_XSLT_LIB $NJS_ZLIB_LIB \ - $ngx_addon_dir/../build/libnjs.a -lm" + $NJS_QUICKJS_LIB $NJS_ENGINE_LIB -lm" . auto/module fi diff --git a/nginx/config.make b/nginx/config.make index cf7859e9..2fa40063 100644 --- a/nginx/config.make +++ b/nginx/config.make @@ -3,7 +3,15 @@ cat << END >> $NGX_MAKEFILE $ngx_addon_dir/../build/libnjs.a: $NGX_MAKEFILE cd $ngx_addon_dir/.. \\ && if [ -f build/Makefile ]; then \$(MAKE) clean; fi \\ - && CFLAGS="\$(CFLAGS)" CC="\$(CC)" ./configure --no-openssl --no-libxml2 --no-zlib --no-pcre \\ + && CFLAGS="\$(CFLAGS)" CC="\$(CC)" ./configure --no-openssl \\ + --no-libxml2 --no-zlib --no-pcre --no-quickjs \\ && \$(MAKE) libnjs +$ngx_addon_dir/../build/libqjs.a: $NGX_MAKEFILE + cd $ngx_addon_dir/.. \\ + && if [ -f build/Makefile ]; then \$(MAKE) clean; fi \\ + && CFLAGS="\$(CFLAGS)" CC="\$(CC)" ./configure --no-openssl \\ + --no-libxml2 --no-zlib --no-pcre \\ + && \$(MAKE) libnjs libqjs + END diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 35f988d0..4a50a949 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -46,6 +46,7 @@ typedef struct { #define NJS_HEADER_SEMICOLON 0x1 #define NJS_HEADER_SINGLE 0x2 #define NJS_HEADER_ARRAY 0x4 +#define NJS_HEADER_GET 0x8 typedef struct ngx_http_js_ctx_s ngx_http_js_ctx_t; @@ -86,6 +87,20 @@ typedef njs_int_t (*njs_http_js_header_handler_t)(njs_vm_t *vm, typedef njs_int_t (*njs_http_js_header_handler122_t)(njs_vm_t *vm, ngx_http_request_t *r, ngx_list_t *headers, njs_str_t *name, njs_value_t *setval, njs_value_t *retval); +#if (NJS_HAVE_QUICKJS) +typedef int (*njs_http_qjs_header_handler_t)(JSContext *cx, + ngx_http_request_t *r, ngx_str_t *name, JSPropertyDescriptor *pdesc, + JSValue *value, unsigned flags); + + +typedef struct { + ngx_http_request_t *request; + JSValue args; + JSValue request_body; + JSValue response_body; +} ngx_http_qjs_request_t; + +#endif typedef struct { @@ -260,6 +275,88 @@ static njs_int_t ngx_http_js_server(njs_vm_t *vm, ngx_http_request_t *r, unsigned flags, njs_str_t *name, njs_value_t *setval, njs_value_t *retval); +#if (NJS_HAVE_QUICKJS) +static JSValue ngx_http_qjs_ext_to_string_tag(JSContext *cx, + JSValueConst this_val); +static JSValue ngx_http_qjs_ext_args(JSContext *cx, JSValueConst this_val); +static JSValue ngx_http_qjs_ext_done(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue ngx_http_qjs_ext_finish(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue ngx_http_qjs_ext_headers_in(JSContext *cx, + JSValueConst this_val); +static JSValue ngx_http_qjs_ext_headers_out(JSContext *cx, + JSValueConst this_val); +static JSValue ngx_http_qjs_ext_http_version(JSContext *cx, + JSValueConst this_val); +static JSValue ngx_http_qjs_ext_internal(JSContext *cx, JSValueConst this_val); +static JSValue ngx_http_qjs_ext_internal_redirect(JSContext *cx, + JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue ngx_http_qjs_ext_log(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv, int level); +static JSValue ngx_http_qjs_ext_periodic_to_string_tag(JSContext *cx, + JSValueConst this_val); +static JSValue ngx_http_qjs_ext_periodic_variables(JSContext *cx, + JSValueConst this_val, int type); +static JSValue ngx_http_qjs_ext_parent(JSContext *cx, JSValueConst this_val); +static JSValue ngx_http_qjs_ext_remote_address(JSContext *cx, + JSValueConst this_val); +static JSValue ngx_http_qjs_ext_request_body(JSContext *cx, + JSValueConst this_val, int type); +static JSValue ngx_http_qjs_ext_response_body(JSContext *cx, + JSValueConst this_val, int type); +static JSValue ngx_http_qjs_ext_return(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue ngx_http_qjs_ext_send(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue ngx_http_qjs_ext_send_buffer(JSContext *cx, + JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue ngx_http_qjs_ext_send_header(JSContext *cx, + JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue ngx_http_qjs_ext_set_return_value(JSContext *cx, + JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue ngx_http_qjs_ext_status_get(JSContext *cx, + JSValueConst this_val); +static JSValue ngx_http_qjs_ext_status_set(JSContext *cx, JSValueConst this_val, + JSValueConst value); +static JSValue ngx_http_qjs_ext_string(JSContext *cx, JSValueConst this_val, + int offset); +static JSValue ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue ngx_http_qjs_ext_raw_headers(JSContext *cx, + JSValueConst this_val, int out); +static JSValue ngx_http_qjs_ext_variables(JSContext *cx, + JSValueConst this_val, int type); + +static int ngx_http_qjs_variables_own_property(JSContext *cx, + JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop); +static int ngx_http_qjs_variables_set_property(JSContext *cx, JSValueConst obj, + JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); + +static int ngx_http_qjs_headers_in_own_property(JSContext *cx, + JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop); +static int ngx_http_qjs_headers_in_own_property_names(JSContext *ctx, + JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj); + +static int ngx_http_qjs_headers_out_own_property(JSContext *cx, + JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop); +static int ngx_http_qjs_headers_out_own_property_names(JSContext *cx, + JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj); +static int ngx_http_qjs_headers_out_set_property(JSContext *cx, + JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, + int flags); +static int ngx_http_qjs_headers_out_define_own_property(JSContext *cx, + JSValueConst this_obj, JSAtom prop, JSValueConst val, JSValueConst getter, + JSValueConst setter, int flags); +static int ngx_http_qjs_headers_out_delete_property(JSContext *cx, + JSValueConst obj, JSAtom prop); + +static ngx_http_request_t *ngx_http_qjs_request(JSValueConst val); +static JSValue ngx_http_qjs_request_make(JSContext *cx, ngx_int_t proto_id, + ngx_http_request_t *r); +static void ngx_http_qjs_request_finalizer(JSRuntime *rt, JSValue val); +#endif + static ngx_pool_t *ngx_http_js_pool(ngx_http_request_t *r); static ngx_resolver_t *ngx_http_js_resolver(ngx_http_request_t *r); static ngx_msec_t ngx_http_js_resolver_timeout(ngx_http_request_t *r); @@ -304,6 +401,9 @@ static ngx_int_t ngx_http_js_parse_unsafe_uri(ngx_http_request_t *r, static ngx_conf_bitmask_t ngx_http_js_engines[] = { { ngx_string("njs"), NGX_ENGINE_NJS }, +#if (NJS_HAVE_QUICKJS) + { ngx_string("qjs"), NGX_ENGINE_QJS }, +#endif { ngx_null_string, 0 } }; @@ -328,6 +428,13 @@ static ngx_command_t ngx_http_js_commands[] = { offsetof(ngx_http_js_loc_conf_t, type), &ngx_http_js_engines }, + { ngx_string("js_context_reuse"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_js_loc_conf_t, reuse), + NULL }, + { ngx_string("js_import"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE13, ngx_js_import, @@ -497,8 +604,8 @@ static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; -static njs_int_t ngx_http_js_request_proto_id; -static njs_int_t ngx_http_js_periodic_session_proto_id; +static njs_int_t ngx_http_js_request_proto_id = 1; +static njs_int_t ngx_http_js_periodic_session_proto_id = 2; static njs_external_t ngx_http_js_ext_request[] = { @@ -924,6 +1031,125 @@ static ngx_http_js_entry_t ngx_http_methods[] = { }; +#if (NJS_HAVE_QUICKJS) + +static const JSCFunctionListEntry ngx_http_qjs_ext_request[] = { + JS_CGETSET_DEF("[Symbol.toStringTag]", ngx_http_qjs_ext_to_string_tag, + NULL), + JS_CGETSET_DEF("args", ngx_http_qjs_ext_args, NULL), + JS_CFUNC_DEF("done", 0, ngx_http_qjs_ext_done), + JS_CFUNC_MAGIC_DEF("error", 1, ngx_http_qjs_ext_log, NGX_LOG_ERR), + JS_CFUNC_DEF("finish", 0, ngx_http_qjs_ext_finish), + JS_CGETSET_DEF("headersIn", ngx_http_qjs_ext_headers_in, NULL), + JS_CGETSET_DEF("headersOut", ngx_http_qjs_ext_headers_out, NULL), + JS_CGETSET_DEF("httpVersion", ngx_http_qjs_ext_http_version, NULL), + JS_CGETSET_DEF("internal", ngx_http_qjs_ext_internal, NULL), + JS_CFUNC_DEF("internalRedirect", 1, ngx_http_qjs_ext_internal_redirect), + JS_CFUNC_MAGIC_DEF("log", 1, ngx_http_qjs_ext_log, NGX_LOG_INFO), + JS_CGETSET_MAGIC_DEF("method", ngx_http_qjs_ext_string, NULL, + offsetof(ngx_http_request_t, method_name)), + JS_CGETSET_DEF("parent", ngx_http_qjs_ext_parent, NULL), + JS_CGETSET_MAGIC_DEF("rawHeadersIn", ngx_http_qjs_ext_raw_headers, NULL, 0), + JS_CGETSET_MAGIC_DEF("rawHeadersOut", ngx_http_qjs_ext_raw_headers, NULL, + 1), + JS_CGETSET_MAGIC_DEF("rawVariables", ngx_http_qjs_ext_variables, + NULL, NGX_JS_BUFFER), + JS_CGETSET_DEF("remoteAddress", ngx_http_qjs_ext_remote_address, NULL), + JS_CGETSET_MAGIC_DEF("requestBuffer", ngx_http_qjs_ext_request_body, NULL, + NGX_JS_BUFFER), + JS_CGETSET_MAGIC_DEF("requestText", ngx_http_qjs_ext_request_body, NULL, + NGX_JS_STRING), + JS_CGETSET_MAGIC_DEF("responseBuffer", ngx_http_qjs_ext_response_body, NULL, + NGX_JS_BUFFER), + JS_CGETSET_MAGIC_DEF("responseText", ngx_http_qjs_ext_response_body, NULL, + NGX_JS_STRING), + JS_CFUNC_DEF("return", 2, ngx_http_qjs_ext_return), + JS_CFUNC_DEF("send", 1, ngx_http_qjs_ext_send), + JS_CFUNC_DEF("sendBuffer", 2, ngx_http_qjs_ext_send_buffer), + JS_CFUNC_DEF("sendHeader", 0, ngx_http_qjs_ext_send_header), + JS_CFUNC_DEF("setReturnValue", 1, ngx_http_qjs_ext_set_return_value), + JS_CGETSET_DEF("status", ngx_http_qjs_ext_status_get, + ngx_http_qjs_ext_status_set), + JS_CFUNC_DEF("subrequest", 3, ngx_http_qjs_ext_subrequest), + JS_CGETSET_MAGIC_DEF("uri", ngx_http_qjs_ext_string, NULL, + offsetof(ngx_http_request_t, uri)), + JS_CGETSET_MAGIC_DEF("variables", ngx_http_qjs_ext_variables, + NULL, NGX_JS_STRING), + JS_CFUNC_MAGIC_DEF("warn", 1, ngx_http_qjs_ext_log, NGX_LOG_WARN), +}; + + +static const JSCFunctionListEntry ngx_http_qjs_ext_periodic[] = { + JS_CGETSET_DEF("[Symbol.toStringTag]", + ngx_http_qjs_ext_periodic_to_string_tag, NULL), + JS_CGETSET_MAGIC_DEF("rawVariables", ngx_http_qjs_ext_periodic_variables, + NULL, NGX_JS_BUFFER), + JS_CGETSET_MAGIC_DEF("variables", ngx_http_qjs_ext_periodic_variables, + NULL, NGX_JS_STRING), +}; + + +static JSClassDef ngx_http_qjs_request_class = { + "Request", + .finalizer = ngx_http_qjs_request_finalizer, +}; + + +static JSClassDef ngx_http_qjs_periodic_class = { + "PeriodicSession", + .finalizer = NULL, +}; + + +static JSClassDef ngx_http_qjs_variables_class = { + "Variables", + .finalizer = NULL, + .exotic = & (JSClassExoticMethods) { + .get_own_property = ngx_http_qjs_variables_own_property, + .set_property = ngx_http_qjs_variables_set_property, + }, +}; + + +static JSClassDef ngx_http_qjs_headers_in_class = { + "headersIn", + .finalizer = NULL, + .exotic = & (JSClassExoticMethods) { + .get_own_property = ngx_http_qjs_headers_in_own_property, + .get_own_property_names = ngx_http_qjs_headers_in_own_property_names, + }, +}; + + +static JSClassDef ngx_http_qjs_headers_out_class = { + "headersOut", + .finalizer = NULL, + .exotic = & (JSClassExoticMethods) { + .get_own_property = ngx_http_qjs_headers_out_own_property, + .get_own_property_names = ngx_http_qjs_headers_out_own_property_names, + .set_property = ngx_http_qjs_headers_out_set_property, + .define_own_property = ngx_http_qjs_headers_out_define_own_property, + .delete_property = ngx_http_qjs_headers_out_delete_property, + }, +}; + + +qjs_module_t *njs_http_qjs_addon_modules[] = { + &ngx_qjs_ngx_module, + /* + * Shared addons should be in the same order and the same positions + * in all nginx modules. + */ +#ifdef NJS_HAVE_ZLIB + &qjs_zlib_module, +#endif + NULL, +}; + + +#endif + + static ngx_int_t ngx_http_js_content_handler(ngx_http_request_t *r) { @@ -1426,6 +1652,14 @@ ngx_http_js_cleanup_ctx(void *data) ctx->engine); r = ngx_js_ctx_external(ctx); + + /* + * Restoring the original module context, because it can be reset + * by internalRedirect() method. Proper ctx is required for + * ngx_http_qjs_request_finalizer() to work correctly. + */ + ngx_http_set_ctx(r, ctx, ngx_http_js_module); + jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); ngx_js_ctx_destroy((ngx_js_ctx_t *) ctx, (ngx_js_loc_conf_t *) jlcf); @@ -4447,25 +4681,2886 @@ ngx_engine_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, } +#if (NJS_HAVE_QUICKJS) + static ngx_int_t -ngx_http_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf) +ngx_http_qjs_query_string_decode(njs_chb_t *chain, const u_char *start, + size_t size) { - ngx_engine_opts_t options; - ngx_js_main_conf_t *jmcf; + u_char *dst; + uint32_t cp; + const u_char *p, *end; + njs_unicode_decode_t ctx; - memset(&options, 0, sizeof(ngx_engine_opts_t)); + static const int8_t hex[256] + njs_aligned(32) = + { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; - options.engine = conf->type; + njs_utf8_decode_init(&ctx); - if (conf->type == NGX_ENGINE_NJS) { - jmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_js_module); - ngx_http_js_uptr[NGX_JS_MAIN_CONF_INDEX] = (uintptr_t) jmcf; + cp = 0; - options.u.njs.metas = &ngx_http_js_metas; - options.u.njs.addons = njs_http_js_addon_modules; - options.clone = ngx_engine_njs_clone; + p = start; + end = p + size; + + while (p < end) { + if (*p == '%' && end - p > 2 && hex[p[1]] >= 0 && hex[p[2]] >= 0) { + cp = njs_utf8_consume(&ctx, (hex[p[1]] << 4) | hex[p[2]]); + p += 3; + + } else { + if (*p == '+') { + cp = ' '; + p++; + + } else { + cp = njs_utf8_decode(&ctx, &p, end); + } + } + + if (cp > NJS_UNICODE_MAX_CODEPOINT) { + if (cp == NJS_UNICODE_CONTINUE) { + continue; + } + + cp = NJS_UNICODE_REPLACEMENT; + } + + dst = njs_chb_reserve(chain, 4); + if (dst == NULL) { + return NGX_ERROR; + } + + njs_chb_written(chain, njs_utf8_encode(dst, cp) - dst); + } + + if (cp == NJS_UNICODE_CONTINUE) { + dst = njs_chb_reserve(chain, 3); + if (dst == NULL) { + return NGX_ERROR; + } + + njs_chb_written(chain, + njs_utf8_encode(dst, NJS_UNICODE_REPLACEMENT) - dst); + } + + return NGX_OK; +} + + +static JSValue +ngx_http_qjs_ext_to_string_tag(JSContext *cx, + JSValueConst this_val) +{ + return JS_NewString(cx, "Request"); +} + + +static JSValue +ngx_http_qjs_ext_args(JSContext *cx, JSValueConst this_val) +{ + u_char *start, *end, *p, *v; + uint32_t len; + JSAtom key; + JSValue args, val, prev, length, arr; + njs_str_t decoded; + njs_int_t ret; + ngx_int_t rc; + njs_chb_t chain; + ngx_http_request_t *r; + ngx_http_qjs_request_t *req; + + req = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_REQUEST); + if (req == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + if (!JS_IsUndefined(req->args)) { + return JS_DupValue(cx, req->args); + } + + args = JS_NewObject(cx); + if (JS_IsException(args)) { + return JS_EXCEPTION; + } + + NJS_CHB_CTX_INIT(&chain, cx); + + r = req->request; + + rc = ngx_http_qjs_query_string_decode(&chain, r->args.data, r->args.len); + if (rc != NGX_OK) { + njs_chb_destroy(&chain); + return JS_ThrowOutOfMemory(cx); + } + + ret = njs_chb_join(&chain, &decoded); + njs_chb_destroy(&chain); + + if (ret != NJS_OK) { + return JS_ThrowOutOfMemory(cx); + } + + start = decoded.start; + end = start + decoded.length; + + while (start < end) { + p = ngx_strlchr(start, end, '&'); + if (p == NULL) { + p = end; + } + + v = ngx_strlchr(start, p, '='); + if (v == NULL) { + v = p; + } + + if (v == start) { + start = p + 1; + continue; + } + + key = JS_NewAtomLen(cx, (const char *) start, v - start); + if (key == JS_ATOM_NULL) { + chain.free(cx, decoded.start); + return JS_EXCEPTION; + } + + val = qjs_string_create(cx, v + 1, p - v - 1); + if (JS_IsException(val)) { + chain.free(cx, decoded.start); + JS_FreeAtom(cx, key); + return JS_EXCEPTION; + } + + prev = JS_GetProperty(cx, args, key); + if (JS_IsException(prev)) { + chain.free(cx, decoded.start); + JS_FreeAtom(cx, key); + JS_FreeValue(cx, val); + return JS_EXCEPTION; + } + + if (JS_IsUndefined(prev)) { + if (JS_SetProperty(cx, args, key, val) < 0) { + goto exception; + } + + } else if (JS_IsArray(cx, prev)) { + length = JS_GetPropertyStr(cx, prev, "length"); + + if (JS_ToUint32(cx, &len, length)) { + goto exception; + } + + JS_FreeValue(cx, length); + + if (JS_SetPropertyUint32(cx, prev, len, val) < 0) { + goto exception; + } + + JS_FreeValue(cx, prev); + + } else { + + arr = JS_NewArray(cx); + if (JS_IsException(arr)) { + goto exception; + } + + if (JS_SetPropertyUint32(cx, arr, 0, prev) < 0) { + goto exception; + } + + if (JS_SetPropertyUint32(cx, arr, 1, val) < 0) { + goto exception; + } + + if (JS_SetProperty(cx, args, key, arr) < 0) { + goto exception; + } + } + + JS_FreeAtom(cx, key); + start = p + 1; + } + + chain.free(cx, decoded.start); + req->args = args; + + return JS_DupValue(cx, args); + +exception: + + chain.free(cx, decoded.start); + JS_FreeAtom(cx, key); + JS_FreeValue(cx, val); + JS_FreeValue(cx, prev); + + return JS_EXCEPTION; +} + + +static JSValue +ngx_http_qjs_ext_done(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + ngx_http_js_ctx_t *ctx; + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + + if (!ctx->filter) { + return JS_ThrowTypeError(cx, "cannot set done while not filtering"); + } + + ctx->done = 1; + + return JS_UNDEFINED; +} + + +static JSValue +ngx_http_qjs_ext_finish(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + ngx_http_js_ctx_t *ctx; + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + if (ngx_http_send_special(r, NGX_HTTP_LAST) == NGX_ERROR) { + return JS_ThrowInternalError(cx, "failed to send response"); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + + ctx->status = NGX_OK; + + return JS_UNDEFINED; +} + + +static JSValue +ngx_http_qjs_ext_headers_in(JSContext *cx, JSValueConst this_val) +{ + JSValue obj; + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_HTTP_HEADERS_IN); + + JS_SetOpaque(obj, r); + + return obj; +} + + +static JSValue +ngx_http_qjs_ext_headers_out(JSContext *cx, JSValueConst this_val) +{ + JSValue obj; + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + obj = JS_NewObjectProtoClass(cx, JS_NULL, + NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT); + + JS_SetOpaque(obj, r); + + return obj; +} + + +static JSValue +ngx_http_qjs_ext_http_version(JSContext *cx, JSValueConst this_val) +{ + ngx_str_t v; + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + switch (r->http_version) { + case NGX_HTTP_VERSION_9: + ngx_str_set(&v, "0.9"); + break; + + case NGX_HTTP_VERSION_10: + ngx_str_set(&v, "1.0"); + break; + + case NGX_HTTP_VERSION_11: + ngx_str_set(&v, "1.1"); + break; + + case NGX_HTTP_VERSION_20: + ngx_str_set(&v, "2.0"); + break; + +#if (NGX_HTTP_VERSION_30) + case NGX_HTTP_VERSION_30: + ngx_str_set(&v, "3.0"); + break; +#endif + + default: + ngx_str_set(&v, ""); + break; } + return qjs_string_create(cx, v.data, v.len); +} + + +static JSValue +ngx_http_qjs_ext_internal(JSContext *cx, JSValueConst this_val) +{ + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + return JS_NewBool(cx, r->internal); +} + + +static JSValue +ngx_http_qjs_ext_internal_redirect(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + ngx_http_js_ctx_t *ctx; + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + if (r->parent != NULL) { + return JS_ThrowTypeError(cx, + "internalRedirect cannot be called from a subrequest"); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + + if (ctx->filter) { + return JS_ThrowTypeError(cx, + "internalRedirect cannot be called while filtering"); + } + + if (ngx_qjs_string(ctx->engine, argv[0], &ctx->redirect_uri) != NGX_OK) { + return JS_EXCEPTION; + } + + ctx->status = NGX_DONE; + + return JS_UNDEFINED; +} + + +static JSValue +ngx_http_qjs_ext_log(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv, int level) +{ + int n; + const char *msg; + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + for (n = 0; n < argc; n++) { + msg = JS_ToCString(cx, argv[n]); + + ngx_js_logger(r->connection, level, (u_char *) msg, ngx_strlen(msg)); + + JS_FreeCString(cx, msg); + } + + return JS_UNDEFINED; +} + + +static JSValue +ngx_http_qjs_ext_periodic_to_string_tag(JSContext *cx, + JSValueConst this_val) +{ + return JS_NewString(cx, "PeriodicSession"); +} + + +static JSValue +ngx_http_qjs_ext_periodic_variables(JSContext *cx, + JSValueConst this_val, int type) +{ + JSValue obj; + ngx_http_qjs_request_t *req; + + req = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_PERIODIC); + if (req == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a periodic object"); + } + + obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_HTTP_VARS); + + /* + * Using lowest bit of the pointer to store the buffer type. + */ + type = (type == NGX_JS_BUFFER) ? 1 : 0; + JS_SetOpaque(obj, (void *) ((uintptr_t) req->request | (uintptr_t) type)); + + return obj; +} + + +static JSValue +ngx_http_qjs_ext_parent(JSContext *cx, JSValueConst this_val) +{ + ngx_http_js_ctx_t *ctx; + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + ctx = r->parent ? ngx_http_get_module_ctx(r->parent, ngx_http_js_module) + : NULL; + + if (ctx == NULL) { + return JS_UNDEFINED; + } + + return JS_DupValue(cx, ngx_qjs_arg(ctx->args[0])); +} + + +static JSValue +ngx_http_qjs_ext_remote_address(JSContext *cx, JSValueConst this_val) +{ + ngx_connection_t *c; + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + c = r->connection; + + return qjs_string_create(cx, c->addr_text.data, c->addr_text.len); +} + + +static JSValue +ngx_http_qjs_ext_response_body(JSContext *cx, JSValueConst this_val, int type) +{ + u_char *p; + size_t len; + uint32_t buffer_type; + ngx_buf_t *b; + JSValue body; + ngx_http_request_t *r; + ngx_http_qjs_request_t *req; + + req = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_REQUEST); + if (req == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + buffer_type = ngx_js_buffer_type(type); + + if (!JS_IsUndefined(req->response_body)) { + if ((buffer_type == NGX_JS_STRING) == JS_IsString(req->response_body)) { + return JS_DupValue(cx, req->response_body); + } + } + + r = req->request; + + b = r->out ? r->out->buf : NULL; + + if (b == NULL) { + return JS_UNDEFINED; + } + + len = b->last - b->pos; + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + return JS_ThrowOutOfMemory(cx); + } + + if (len) { + ngx_memcpy(p, b->pos, len); + } + + body = ngx_qjs_prop(cx, buffer_type, p, len); + if (JS_IsException(body)) { + return JS_EXCEPTION; + } + + req->response_body = body; + + return JS_DupValue(cx, req->response_body); +} + + +static JSValue +ngx_http_qjs_ext_request_body(JSContext *cx, JSValueConst this_val, int type) +{ + u_char *p, *data; + size_t len; + JSValue body; + uint32_t buffer_type; + ngx_buf_t *buf; + ngx_chain_t *cl; + ngx_http_request_t *r; + ngx_http_qjs_request_t *req; + + req = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_REQUEST); + if (req == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + buffer_type = ngx_js_buffer_type(type); + + if (!JS_IsUndefined(req->request_body)) { + if ((buffer_type == NGX_JS_STRING) == JS_IsString(req->request_body)) { + return JS_DupValue(cx, req->request_body); + } + + JS_FreeValue(cx, req->request_body); + } + + r = req->request; + + if (r->request_body == NULL || r->request_body->bufs == NULL) { + return JS_UNDEFINED; + } + + if (r->request_body->temp_file) { + return JS_ThrowTypeError(cx, "request body is in a file"); + } + + cl = r->request_body->bufs; + buf = cl->buf; + + if (cl->next == NULL) { + len = buf->last - buf->pos; + data = buf->pos; + + goto done; + } + + len = buf->last - buf->pos; + cl = cl->next; + + for ( /* void */ ; cl; cl = cl->next) { + buf = cl->buf; + len += buf->last - buf->pos; + } + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + return JS_ThrowOutOfMemory(cx); + } + + data = p; + cl = r->request_body->bufs; + + for ( /* void */ ; cl; cl = cl->next) { + buf = cl->buf; + p = ngx_cpymem(p, buf->pos, buf->last - buf->pos); + } + +done: + + body = ngx_qjs_prop(cx, buffer_type, data, len); + if (JS_IsException(body)) { + return JS_EXCEPTION; + } + + req->request_body = body; + + return JS_DupValue(cx, req->request_body); +} + + +static JSValue +ngx_http_qjs_ext_return(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + ngx_str_t body; + ngx_int_t status; + ngx_http_js_ctx_t *ctx; + ngx_http_request_t *r; + ngx_http_complex_value_t cv; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + if (ngx_qjs_integer(cx, argv[0], &status) != NGX_OK) { + return JS_EXCEPTION; + } + + if (status < 0 || status > 999) { + return JS_ThrowRangeError(cx, "code is out of range"); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + + if (ngx_qjs_string(ctx->engine, argv[1], &body) != NGX_OK) { + return JS_ThrowOutOfMemory(cx); + } + + if (status < NGX_HTTP_BAD_REQUEST || body.len) { + ngx_memzero(&cv, sizeof(ngx_http_complex_value_t)); + + cv.value.data = body.data; + cv.value.len = body.len; + + ctx->status = ngx_http_send_response(r, status, NULL, &cv); + + if (ctx->status == NGX_ERROR) { + return JS_ThrowTypeError(cx, "failed to send response"); + } + + } else { + ctx->status = status; + } + + return JS_UNDEFINED; +} + + +static JSValue +ngx_http_qjs_ext_status_get(JSContext *cx, JSValueConst this_val) +{ + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + return JS_NewInt32(cx, r->headers_out.status); +} + + +static JSValue +ngx_http_qjs_ext_status_set(JSContext *cx, JSValueConst this_val, + JSValueConst value) +{ + ngx_int_t n; + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + if (ngx_qjs_integer(cx, value, &n) != NGX_OK) { + return JS_EXCEPTION; + } + + r->headers_out.status = n; + r->headers_out.status_line.len = 0; + + return JS_UNDEFINED; +} + + +static JSValue +ngx_http_qjs_ext_string(JSContext *cx, JSValueConst this_val, int offset) +{ + ngx_str_t *field; + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + field = (ngx_str_t *) ((u_char *) r + offset); + + return qjs_string_create(cx, field->data, field->len); +} + + +static JSValue +ngx_http_qjs_ext_send(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + ngx_str_t s; + ngx_buf_t *b; + ngx_uint_t n; + ngx_chain_t *out, *cl, **ll; + ngx_http_js_ctx_t *ctx; + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + + if (ctx->filter) { + return JS_ThrowTypeError(cx, "cannot send while in body filter"); + } + + out = NULL; + ll = &out; + + for (n = 0; n < (ngx_uint_t) argc; n++) { + if (ngx_qjs_string(ctx->engine, argv[n], &s) != NGX_OK) { + return JS_ThrowTypeError(cx, "failed to convert arg"); + } + + if (s.len == 0) { + continue; + } + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return JS_ThrowInternalError(cx, "failed to allocate buffer"); + } + + b->start = s.data; + b->pos = b->start; + b->end = s.data + s.len; + b->last = b->end; + b->memory = 1; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return JS_ThrowInternalError(cx, "failed to allocate chain link"); + } + + cl->buf = b; + + *ll = cl; + ll = &cl->next; + } + + *ll = NULL; + + if (ngx_http_output_filter(r, out) == NGX_ERROR) { + return JS_ThrowInternalError(cx, "failed to send response"); + } + + return JS_UNDEFINED; +} + + +static JSValue +ngx_http_qjs_ext_send_buffer(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + unsigned last_buf, flush; + JSValue flags, value; + ngx_str_t buffer; + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_http_js_ctx_t *ctx; + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + + if (!ctx->filter) { + return JS_ThrowTypeError(cx, "cannot send buffer while not filtering"); + } + + if (ngx_qjs_string(ctx->engine, argv[0], &buffer) != NGX_OK) { + return JS_ThrowTypeError(cx, "failed get buffer arg"); + } + + flush = ctx->buf->flush; + last_buf = ctx->buf->last_buf; + + flags = argv[1]; + + if (JS_IsObject(flags)) { + value = JS_GetPropertyStr(cx, flags, "flush"); + if (JS_IsException(value)) { + return JS_EXCEPTION; + } + + flush = JS_ToBool(cx, value); + JS_FreeValue(cx, value); + + value = JS_GetPropertyStr(cx, flags, "last"); + if (JS_IsException(value)) { + return JS_EXCEPTION; + } + + last_buf = JS_ToBool(cx, value); + JS_FreeValue(cx, value); + } + + cl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (cl == NULL) { + return JS_ThrowOutOfMemory(cx); + } + + b = cl->buf; + + b->flush = flush; + b->last_buf = last_buf; + + b->memory = (buffer.len ? 1 : 0); + b->sync = (buffer.len ? 0 : 1); + b->tag = (ngx_buf_tag_t) &ngx_http_js_module; + + b->start = buffer.data; + b->end = buffer.data + buffer.len; + b->pos = b->start; + b->last = b->end; + + *ctx->last_out = cl; + ctx->last_out = &cl->next; + + return JS_UNDEFINED; +} + + +static JSValue +ngx_http_qjs_ext_send_header(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + if (ngx_http_set_content_type(r) != NGX_OK) { + return JS_ThrowInternalError(cx, "failed to set content type"); + } + + if (ngx_http_send_header(r) == NGX_ERROR) { + return JS_ThrowInternalError(cx, "failed to send header"); + } + + return JS_UNDEFINED; +} + + +static JSValue +ngx_http_qjs_ext_set_return_value(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + ngx_js_ctx_t *ctx; + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + + JS_FreeValue(cx, ngx_qjs_arg(ctx->retval)); + ngx_qjs_arg(ctx->retval) = JS_DupValue(cx, argv[0]); + + return JS_UNDEFINED; +} + + +static ngx_int_t +ngx_http_qjs_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc) +{ + ngx_qjs_event_t *event = data; + + JSValue reply; + JSContext *cx; + ngx_http_js_ctx_t *ctx, *sctx; + + if (rc != NGX_OK || r->connection->error || r->buffered) { + return rc; + } + + sctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + + if (sctx && sctx->done) { + return NGX_OK; + } + + if (sctx == NULL) { + sctx = ngx_pcalloc(r->pool, sizeof(ngx_http_js_ctx_t)); + if (sctx == NULL) { + return NGX_ERROR; + } + + ngx_http_set_ctx(r, sctx, ngx_http_js_module); + + ngx_qjs_arg(sctx->response_body) = JS_UNDEFINED; + } + + sctx->done = 1; + + ctx = ngx_http_get_module_ctx(r->parent, ngx_http_js_module); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "js subrequest done s: %ui parent ctx: %p", + r->headers_out.status, ctx); + + if (ctx == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "js subrequest: failed to get the parent context"); + + return NGX_ERROR; + } + + cx = ctx->engine->u.qjs.ctx; + + if (!JS_IsObject(ngx_qjs_arg(sctx->args[0]))) { + reply = ngx_http_qjs_request_make(cx, NGX_QJS_CLASS_ID_HTTP_REQUEST, r); + if (JS_IsException(reply)) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "js subrequest reply creation failed"); + return NGX_ERROR; + } + + + } else { + reply = JS_DupValue(cx, ngx_qjs_arg(sctx->args[0])); + } + + rc = ngx_qjs_call((ngx_js_ctx_t *) ctx, event->function, &reply, 1); + + JS_FreeValue(cx, reply); + ngx_js_del_event(ctx, event); + + ngx_http_js_event_finalize(r->parent, rc); + + return NGX_OK; +} + + +static void +ngx_http_js_subrequest_event_destructor(ngx_qjs_event_t *event) +{ + JSContext *cx; + + cx = event->ctx; + + JS_FreeValue(cx, event->function); + JS_FreeValue(cx, event->args[0]); + JS_FreeValue(cx, event->args[1]); +} + + +static JSValue +ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue arg, options, callback, value, retval; + ngx_int_t rc; + ngx_str_t uri, args, method_name, body_arg; + ngx_uint_t method, methods_max, has_body, detached, flags, + promise; + ngx_qjs_event_t *event; + ngx_http_js_ctx_t *ctx; + ngx_http_request_t *r, *sr; + ngx_http_request_body_t *rb; + ngx_http_post_subrequest_t *ps; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + + if (r->main != r) { + return JS_ThrowTypeError(cx, "subrequest can only be created for " + "the primary request"); + } + + if (ngx_qjs_string(ctx->engine, argv[0], &uri) != NGX_OK) { + return JS_ThrowTypeError(cx, "failed to convert uri arg"); + } + + if (uri.len == 0) { + return JS_ThrowTypeError(cx, "uri is empty"); + } + + options = JS_UNDEFINED; + callback = JS_UNDEFINED; + + method = 0; + methods_max = sizeof(ngx_http_methods) / sizeof(ngx_http_methods[0]); + + args.len = 0; + args.data = NULL; + + method_name.len = 0; + method_name.data = NULL; + + has_body = 0; + detached = 0; + + arg = argv[1]; + + if (JS_IsString(arg)) { + if (ngx_qjs_string(ctx->engine, arg, &args) != NGX_OK) { + return JS_ThrowTypeError(cx, "failed to convert args"); + } + + } else if (JS_IsFunction(cx, arg)) { + callback = arg; + + } else if (JS_IsObject(arg)) { + options = arg; + + } else if (!JS_IsNullOrUndefined(arg)) { + return JS_ThrowTypeError(cx, "failed to convert args"); + } + + if (!JS_IsUndefined(options)) { + value = JS_GetPropertyStr(cx, options, "args"); + if (JS_IsException(value)) { + return JS_EXCEPTION; + } + + if (!JS_IsUndefined(value)) { + rc = ngx_qjs_string(ctx->engine, value, &args); + JS_FreeValue(cx, value); + + if (rc != NGX_OK) { + return JS_ThrowTypeError(cx, "failed to convert options.args"); + } + } + + value = JS_GetPropertyStr(cx, options, "detached"); + if (JS_IsException(value)) { + return JS_EXCEPTION; + } + + if (!JS_IsUndefined(value)) { + detached = JS_ToBool(cx, value); + JS_FreeValue(cx, value); + } + + value = JS_GetPropertyStr(cx, options, "method"); + if (JS_IsException(value)) { + return JS_EXCEPTION; + } + + if (!JS_IsUndefined(value)) { + rc = ngx_qjs_string(ctx->engine, value, &method_name); + JS_FreeValue(cx, value); + + if (rc != NGX_OK) { + return JS_ThrowTypeError(cx, "failed to convert option.method"); + } + + while (method < methods_max) { + if (method_name.len == ngx_http_methods[method].name.len + && ngx_memcmp(method_name.data, + ngx_http_methods[method].name.data, + method_name.len) + == 0) + { + break; + } + + method++; + } + } + + value = JS_GetPropertyStr(cx, options, "body"); + if (JS_IsException(value)) { + return JS_EXCEPTION; + } + + if (!JS_IsUndefined(value)) { + rc = ngx_qjs_string(ctx->engine, value, &body_arg); + JS_FreeValue(cx, value); + + if (rc != NGX_OK) { + return JS_ThrowTypeError(cx, "failed to convert option.body"); + } + + has_body = 1; + } + } + + flags = NGX_HTTP_LOG_UNSAFE; + + if (ngx_http_parse_unsafe_uri(r, &uri, &args, &flags) != NGX_OK) { + return JS_ThrowTypeError(cx, "unsafe uri"); + } + + arg = argv[2]; + + if (JS_IsUndefined(callback) && !JS_IsNullOrUndefined(arg)) { + if (!JS_IsFunction(cx, arg)) { + return JS_ThrowTypeError(cx, "callback is not a function"); + } + + callback = arg; + } + + if (detached && !JS_IsUndefined(callback)) { + return JS_ThrowTypeError(cx, "detached flag and callback are mutually " + "exclusive"); + } + + promise = 0; + retval = JS_UNDEFINED; + flags = NGX_HTTP_SUBREQUEST_BACKGROUND; + + if (!detached) { + ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t)); + if (ps == NULL) { + return JS_ThrowOutOfMemory(cx); + } + + promise = !!JS_IsUndefined(callback); + + event = ngx_pcalloc(r->pool, sizeof(ngx_qjs_event_t) + + sizeof(JSValue) * 2); + if (event == NULL) { + return JS_ThrowOutOfMemory(cx); + } + + event->ctx = cx; + event->fd = ctx->event_id++; + event->args = (JSValue *) &event[1]; + event->destructor = ngx_http_js_subrequest_event_destructor; + + if (promise) { + retval = JS_NewPromiseCapability(cx, &event->args[0]); + if (JS_IsException(retval)) { + return JS_EXCEPTION; + } + + callback = event->args[0]; + + } else { + event->args[0] = JS_UNDEFINED; + event->args[1] = JS_UNDEFINED; + } + + event->function = JS_DupValue(cx, callback); + + ps->handler = ngx_http_qjs_subrequest_done; + ps->data = event; + + flags |= NGX_HTTP_SUBREQUEST_IN_MEMORY; + + } else { + ps = NULL; + event = NULL; + } + + if (ngx_http_subrequest(r, &uri, args.len ? &args : NULL, &sr, ps, flags) + != NGX_OK) + { + return JS_ThrowInternalError(cx, "subrequest creation failed"); + } + + if (event != NULL) { + ngx_js_add_event(ctx, event); + } + + if (method != methods_max) { + sr->method = ngx_http_methods[method].value; + sr->method_name = ngx_http_methods[method].name; + + } else { + sr->method = NGX_HTTP_UNKNOWN; + sr->method_name = method_name; + } + + sr->header_only = (sr->method == NGX_HTTP_HEAD) || JS_IsUndefined(callback); + + if (has_body) { + rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); + if (rb == NULL) { + goto memory_error; + } + + if (body_arg.len != 0) { + rb->bufs = ngx_alloc_chain_link(r->pool); + if (rb->bufs == NULL) { + goto memory_error; + } + + rb->bufs->next = NULL; + + rb->bufs->buf = ngx_calloc_buf(r->pool); + if (rb->bufs->buf == NULL) { + goto memory_error; + } + + rb->bufs->buf->memory = 1; + rb->bufs->buf->last_buf = 1; + + rb->bufs->buf->pos = body_arg.data; + rb->bufs->buf->last = body_arg.data + body_arg.len; + } + + sr->request_body = rb; + sr->headers_in.content_length_n = body_arg.len; + sr->headers_in.chunked = 0; + } + + return retval; + +memory_error: + + return JS_ThrowOutOfMemory(cx); +} + + +static JSValue +ngx_http_qjs_ext_raw_headers(JSContext *cx, JSValueConst this_val, int out) +{ + JSValue array, elem, key, val; + uint32_t idx; + ngx_uint_t i; + ngx_list_t *headers; + ngx_list_part_t *part; + ngx_table_elt_t *header, *h; + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + headers = (out) ? &r->headers_out.headers : &r->headers_in.headers; + + array = JS_NewArray(cx); + if (JS_IsException(array)) { + return JS_EXCEPTION; + } + + idx = 0; + part = &headers->part; + header = part->elts; + + for (i = 0; /* void */ ; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + h = &header[i]; + + if (h->hash == 0) { + continue; + } + + elem = JS_NewArray(cx); + if (JS_IsException(elem)) { + JS_FreeValue(cx, array); + return JS_EXCEPTION; + } + + if (JS_DefinePropertyValueUint32(cx, array, idx++, elem, + JS_PROP_C_W_E) < 0) + { + JS_FreeValue(cx, elem); + JS_FreeValue(cx, array); + return JS_EXCEPTION; + } + + key = qjs_string_create(cx, h->key.data, h->key.len); + if (JS_IsException(key)) { + JS_FreeValue(cx, array); + return JS_EXCEPTION; + } + + if (JS_DefinePropertyValueUint32(cx, elem, 0, key, JS_PROP_C_W_E) < 0) { + JS_FreeValue(cx, key); + JS_FreeValue(cx, array); + return JS_EXCEPTION; + } + + val = qjs_string_create(cx, h->value.data, h->value.len); + if (JS_IsException(val)) { + JS_FreeValue(cx, array); + return JS_EXCEPTION; + } + + if (JS_DefinePropertyValueUint32(cx, elem, 1, val, JS_PROP_C_W_E) < 0) { + JS_FreeValue(cx, val); + JS_FreeValue(cx, array); + return JS_EXCEPTION; + } + } + + return array; +} + + +static JSValue +ngx_http_qjs_ext_variables(JSContext *cx, JSValueConst this_val, int type) +{ + JSValue obj; + ngx_http_request_t *r; + + r = ngx_http_qjs_request(this_val); + if (r == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a request object"); + } + + obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_HTTP_VARS); + + /* + * Using lowest bit of the pointer to store the buffer type. + */ + type = (type == NGX_JS_BUFFER) ? 1 : 0; + JS_SetOpaque(obj, (void *) ((uintptr_t) r | (uintptr_t) type)); + + return obj; +} + + +static int +ngx_http_qjs_variables_own_property(JSContext *cx, JSPropertyDescriptor *pdesc, + JSValueConst obj, JSAtom prop) +{ + uint32_t buffer_type; + ngx_str_t name; + ngx_uint_t i, key, start, length, is_capture; + ngx_http_request_t *r; + ngx_http_variable_value_t *vv; + + r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_VARS); + + buffer_type = ((uintptr_t) r & 1) ? NGX_JS_BUFFER : NGX_JS_STRING; + r = (ngx_http_request_t *) ((uintptr_t) r & ~(uintptr_t) 1); + + if (r == NULL) { + (void) JS_ThrowInternalError(cx, "\"this\" is not a request object"); + return -1; + } + + name.data = (u_char *) JS_AtomToCString(cx, prop); + if (name.data == NULL) { + return -1; + } + + name.len = ngx_strlen(name.data); + + is_capture = 1; + for (i = 0; i < name.len; i++) { + if (name.data[i] < '0' || name.data[i] > '9') { + is_capture = 0; + break; + } + } + + if (is_capture) { + key = ngx_atoi(name.data, name.len) * 2; + JS_FreeCString(cx, (char *) name.data); + if (r->captures == NULL || r->captures_data == NULL + || r->ncaptures <= key) + { + return 0; + } + + + if (pdesc != NULL) { + pdesc->flags = JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + + start = r->captures[key]; + length = r->captures[key + 1] - start; + pdesc->value = ngx_qjs_prop(cx, buffer_type, + &r->captures_data[start], length); + } + + return 1; + } + + key = ngx_hash_strlow(name.data, name.data, name.len); + + vv = ngx_http_get_variable(r, &name, key); + JS_FreeCString(cx, (char *) name.data); + if (vv == NULL || vv->not_found) { + return 0; + } + + if (pdesc != NULL) { + pdesc->flags = JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = ngx_qjs_prop(cx, buffer_type, vv->data, vv->len); + } + + return 1; +} + + +static int +ngx_http_qjs_variables_set_property(JSContext *cx, JSValueConst obj, + JSAtom prop, JSValueConst value, JSValueConst receiver, int flags) +{ + ngx_str_t name, s; + ngx_uint_t key; + ngx_http_js_ctx_t *ctx; + ngx_http_request_t *r; + ngx_http_variable_t *v; + ngx_http_variable_value_t *vv; + ngx_http_core_main_conf_t *cmcf; + + r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_VARS); + + r = (ngx_http_request_t *) ((uintptr_t) r & ~(uintptr_t) 1); + + if (r == NULL) { + (void) JS_ThrowInternalError(cx, "\"this\" is not a request object"); + return -1; + } + + name.data = (u_char *) JS_AtomToCString(cx, prop); + if (name.data == NULL) { + return -1; + } + + name.len = ngx_strlen(name.data); + + key = ngx_hash_strlow(name.data, name.data, name.len); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + v = ngx_hash_find(&cmcf->variables_hash, key, name.data, name.len); + JS_FreeCString(cx, (char *) name.data); + + if (v == NULL) { + (void) JS_ThrowInternalError(cx, "variable not found"); + return -1; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + + if (ngx_qjs_string(ctx->engine, value, &s) != NGX_OK) { + return -1; + } + + if (v->set_handler != NULL) { + vv = ngx_pcalloc(r->pool, sizeof(ngx_http_variable_value_t)); + if (vv == NULL) { + (void) JS_ThrowOutOfMemory(cx); + return -1; + } + + vv->valid = 1; + vv->not_found = 0; + vv->data = s.data; + vv->len = s.len; + + v->set_handler(r, vv, v->data); + + return 1; + } + + if (!(v->flags & NGX_HTTP_VAR_INDEXED)) { + (void) JS_ThrowTypeError(cx, "variable is not writable"); + return -1; + } + + vv = &r->variables[v->index]; + + vv->valid = 1; + vv->not_found = 0; + + vv->data = ngx_pnalloc(r->pool, s.len); + if (vv->data == NULL) { + vv->valid = 0; + (void) JS_ThrowOutOfMemory(cx); + return -1; + } + + vv->len = s.len; + ngx_memcpy(vv->data, s.data, vv->len); + + return 1; +} + + +static int +ngx_http_qjs_ext_keys_header(JSContext *cx, ngx_list_t *headers, JSValue keys, + JSPropertyEnum **ptab, uint32_t *plen) +{ + JSAtom key; + ngx_uint_t item; + ngx_list_part_t *part; + ngx_table_elt_t *header, *h; + + part = &headers->part; + item = 0; + + while (part) { + if (item >= part->nelts) { + part = part->next; + item = 0; + continue; + } + + header = part->elts; + h = &header[item++]; + + if (h->hash == 0) { + continue; + } + + key = JS_NewAtomLen(cx, (const char *) h->key.data, h->key.len); + if (key == JS_ATOM_NULL) { + return -1; + } + + if (JS_DefinePropertyValue(cx, keys, key, JS_UNDEFINED, + JS_PROP_ENUMERABLE) < 0) + { + JS_FreeAtom(cx, key); + return -1; + } + + JS_FreeAtom(cx, key); + } + + return JS_GetOwnPropertyNames(cx, ptab, plen, keys, JS_GPN_STRING_MASK); +} + + +static int +ngx_http_qjs_headers_in_own_property_names(JSContext *cx, + JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj) +{ + int ret; + JSValue keys; + ngx_http_request_t *r; + + r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_IN); + if (r == NULL) { + (void) JS_ThrowInternalError(cx, "\"this\" is not a headers_in object"); + return -1; + } + + keys = JS_NewObject(cx); + if (JS_IsException(keys)) { + return -1; + } + + ret = ngx_http_qjs_ext_keys_header(cx, &r->headers_in.headers, keys, ptab, + plen); + JS_FreeValue(cx, keys); + + return ret; +} + + +static njs_int_t +ngx_http_qjs_header_generic(JSContext *cx, ngx_http_request_t *r, + ngx_list_t *headers, ngx_table_elt_t **ph, ngx_str_t *name, + JSPropertyDescriptor *pdesc, unsigned flags) +{ + int ret; + u_char sep; + njs_chb_t chain; + JSValue val; + ngx_uint_t i; + ngx_list_part_t *part; + ngx_table_elt_t *header, *h; + + if (ph == NULL) { + /* iterate over all headers */ + + ph = &header; + part = &headers->part; + h = part->elts; + + for (i = 0; /* void */ ; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + h = part->elts; + i = 0; + } + + if (h[i].hash == 0 + || name->len != h[i].key.len + || ngx_strncasecmp(name->data, h[i].key.data, name->len) + != 0) + { + continue; + } + + *ph = &h[i]; + ph = &h[i].next; + } + + *ph = NULL; + ph = &header; + } + + if (*ph == NULL) { + return 0; + } + + if (flags & NJS_HEADER_ARRAY) { + if (pdesc == NULL) { + return 1; + } + + pdesc->flags = JS_PROP_ENUMERABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = JS_NewArray(cx); + if (JS_IsException(pdesc->value)) { + return -1; + } + + for (h = *ph, i = 0; h; h = h->next, i++) { + val = qjs_string_create(cx, h->value.data, h->value.len); + if (JS_IsException(val)) { + JS_FreeValue(cx, pdesc->value); + return -1; + } + + if (JS_DefinePropertyValueUint32(cx, pdesc->value, i, val, + JS_PROP_ENUMERABLE) < 0) + { + JS_FreeValue(cx, pdesc->value); + return -1; + } + } + + return 1; + } + + if ((*ph)->next == NULL || flags & NJS_HEADER_SINGLE) { + if (pdesc != NULL) { + pdesc->flags = JS_PROP_ENUMERABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = qjs_string_create(cx, (*ph)->value.data, + (*ph)->value.len); + if (JS_IsException(pdesc->value)) { + return -1; + } + } + + return 1; + } + + NJS_CHB_CTX_INIT(&chain, cx); + + sep = flags & NJS_HEADER_SEMICOLON ? ';' : ','; + + for (h = *ph; h; h = h->next) { + njs_chb_append(&chain, h->value.data, h->value.len); + njs_chb_append(&chain, &sep, 1); + njs_chb_append_literal(&chain, " "); + } + + ret = 1; + + if (pdesc != NULL) { + pdesc->flags = JS_PROP_ENUMERABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = qjs_string_create_chb(cx, &chain); + if (JS_IsException(pdesc->value)) { + ret = -1; + goto done; + } + } + +done: + + njs_chb_destroy(&chain); + + return ret; +} + + +static int +ngx_http_qjs_header_in(JSContext *cx, ngx_http_request_t *r, unsigned flags, + ngx_str_t *name, JSPropertyDescriptor *pdesc) +{ + u_char *lowcase_key; + ngx_uint_t hash; + ngx_table_elt_t **ph; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + /* look up hashed headers */ + + lowcase_key = ngx_pnalloc(r->pool, name->len); + if (lowcase_key == NULL) { + (void) JS_ThrowOutOfMemory(cx); + return -1; + } + + hash = ngx_hash_strlow(lowcase_key, name->data, name->len); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, hash, lowcase_key, + name->len); + + ph = NULL; + + if (hh) { + if (hh->offset == offsetof(ngx_http_headers_in_t, cookie)) { + flags |= NJS_HEADER_SEMICOLON; + } + + ph = (ngx_table_elt_t **) ((char *) &r->headers_in + hh->offset); + } + + return ngx_http_qjs_header_generic(cx, r, &r->headers_in.headers, ph, name, + pdesc, flags); +} + + +static int +ngx_http_qjs_headers_in_own_property(JSContext *cx, JSPropertyDescriptor *pdesc, + JSValueConst obj, JSAtom prop) +{ + int ret; + unsigned flags; + ngx_str_t name, *h; + ngx_http_request_t *r; + + static ngx_str_t single_headers_in[] = { + ngx_string("Content-Type"), + ngx_string("ETag"), + ngx_string("From"), + ngx_string("Max-Forwards"), + ngx_string("Referer"), + ngx_string("Proxy-Authorization"), + ngx_string("User-Agent"), + ngx_string(""), + }; + + r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_IN); + if (r == NULL) { + (void) JS_ThrowInternalError(cx, "\"this\" is not a headers_in object"); + return -1; + } + + name.data = (u_char *) JS_AtomToCString(cx, prop); + if (name.data == NULL) { + return -1; + } + + name.len = ngx_strlen(name.data); + + flags = 0; + + for (h = single_headers_in; h->len > 0; h++) { + if (h->len == name.len + && ngx_strncasecmp(h->data, name.data, name.len) == 0) + { + flags |= NJS_HEADER_SINGLE; + break; + } + } + + ret = ngx_http_qjs_header_in(cx, r, flags, &name, pdesc); + JS_FreeCString(cx, (char *) name.data); + + return ret; +} + + +static int +ngx_http_qjs_headers_out_own_property_names(JSContext *cx, + JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj) +{ + int ret; + JSAtom key; + JSValue keys; + ngx_http_request_t *r; + + r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT); + if (r == NULL) { + (void) JS_ThrowInternalError(cx, "\"this\" is not a headers_out" + " object"); + return -1; + } + + keys = JS_NewObject(cx); + if (JS_IsException(keys)) { + return -1; + } + + if (r->headers_out.content_type.len) { + key = JS_NewAtomLen(cx, "Content-Type", njs_length("Content-Type")); + if (key == JS_ATOM_NULL) { + return -1; + } + + if (JS_DefinePropertyValue(cx, keys, key, JS_UNDEFINED, + JS_PROP_ENUMERABLE) < 0) + { + JS_FreeAtom(cx, key); + return -1; + } + + JS_FreeAtom(cx, key); + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + key = JS_NewAtomLen(cx, "Content-Length", njs_length("Content-Length")); + if (key == JS_ATOM_NULL) { + return -1; + } + + if (JS_DefinePropertyValue(cx, keys, key, JS_UNDEFINED, + JS_PROP_ENUMERABLE) < 0) + { + JS_FreeAtom(cx, key); + return -1; + } + + JS_FreeAtom(cx, key); + } + + ret = ngx_http_qjs_ext_keys_header(cx, &r->headers_out.headers, keys, ptab, + plen); + JS_FreeValue(cx, keys); + + return ret; +} + + +static int +ngx_http_qjs_headers_out_handler(JSContext *cx, ngx_http_request_t *r, + ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value, + unsigned flags) +{ + u_char *p; + int64_t length; + uint32_t i; + ngx_int_t rc; + ngx_str_t s; + JSValue v; + ngx_list_part_t *part; + ngx_table_elt_t *header, *h, **ph; + ngx_http_js_ctx_t *ctx; + + if (flags & NJS_HEADER_GET) { + return ngx_http_qjs_header_generic(cx, r, &r->headers_out.headers, NULL, + name, pdesc, flags); + } + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */ ; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + h = &header[i]; + + if (h->hash == 0 + || h->key.len != name->len + || ngx_strncasecmp(h->key.data, name->data, name->len) != 0) + { + continue; + } + + h->hash = 0; + h->next = NULL; + } + + if (value == NULL) { + return 1; + } + + if (JS_IsArray(cx, *value)) { + v = JS_GetPropertyStr(cx, *value, "length"); + if (JS_IsException(v)) { + return -1; + } + + if (JS_ToInt64(cx, &length, v) < 0) { + JS_FreeValue(cx, v); + return -1; + } + + JS_FreeValue(cx, v); + + } else { + v = *value; + length = 1; + } + + ph = &header; + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + + for (i = 0; i < (uint32_t) length; i++) { + if (JS_IsArray(cx, *value)) { + v = JS_GetPropertyUint32(cx, *value, i); + if (JS_IsException(v)) { + return -1; + } + } + + rc = ngx_qjs_string(ctx->engine, v, &s); + + if (JS_IsArray(cx, *value)) { + JS_FreeValue(cx, v); + } + + if (rc != NGX_OK) { + return -1; + } + + if (s.len == 0) { + continue; + } + + h = ngx_list_push(&r->headers_out.headers); + if (h == NULL) { + (void) JS_ThrowOutOfMemory(cx); + return -1; + } + + p = ngx_pnalloc(r->pool, name->len); + if (p == NULL) { + h->hash = 0; + (void) JS_ThrowOutOfMemory(cx); + return -1; + } + + ngx_memcpy(p, name->data, name->len); + + h->key.data = p; + h->key.len = name->len; + + p = ngx_pnalloc(r->pool, s.len); + if (p == NULL) { + h->hash = 0; + (void) JS_ThrowOutOfMemory(cx); + return -1; + } + + ngx_memcpy(p, s.data, s.len); + + h->value.data = p; + h->value.len = s.len; + h->hash = 1; + + *ph = h; + ph = &h->next; + } + + *ph = NULL; + + return NJS_OK; +} + + +static int +ngx_http_qjs_headers_out_special_handler(JSContext *cx, ngx_http_request_t *r, + ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value, + unsigned flags, ngx_table_elt_t **hh) +{ + u_char *p; + uint32_t length; + JSValue len, setval; + ngx_str_t s; + ngx_uint_t i, rc; + ngx_list_t *headers; + ngx_list_part_t *part; + ngx_table_elt_t *header, *h; + ngx_http_js_ctx_t *ctx; + + if (flags & NJS_HEADER_GET) { + return ngx_http_qjs_headers_out_handler(cx, r, name, pdesc, NULL, + flags | NJS_HEADER_SINGLE); + } + + if (value != NULL) { + if (JS_IsArray(cx, *value)) { + len = JS_GetPropertyStr(cx, *value, "length"); + if (JS_IsException(len)) { + return -1; + } + + if (JS_ToUint32(cx, &length, len) < 0) { + JS_FreeValue(cx, len); + return -1; + } + + JS_FreeValue(cx, len); + + setval = JS_GetPropertyUint32(cx, *value, length - 1); + if (JS_IsException(setval)) { + return -1; + } + + } else { + setval = *value; + } + + } else { + setval = JS_UNDEFINED; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + + rc = ngx_qjs_string(ctx->engine, setval, &s); + + if (value != NULL && JS_IsArray(cx, *value)) { + JS_FreeValue(cx, setval); + } + + if (rc != NGX_OK) { + return -1; + } + + headers = &r->headers_out.headers; + part = &headers->part; + header = part->elts; + + for (i = 0; /* void */ ; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + h = &header[i]; + + if (h->hash == 0) { + continue; + } + + if (h->key.len == name->len + && ngx_strncasecmp(h->key.data, name->data, name->len) == 0) + { + goto done; + } + } + + h = NULL; + +done: + + if (h != NULL && s.len == 0) { + h->hash = 0; + h = NULL; + } + + if (h == NULL && s.len != 0) { + h = ngx_list_push(headers); + if (h == NULL) { + (void) JS_ThrowOutOfMemory(cx); + return -1; + } + + p = ngx_pnalloc(r->pool, name->len); + if (p == NULL) { + h->hash = 0; + (void) JS_ThrowOutOfMemory(cx); + return -1; + } + + ngx_memcpy(p, name->data, name->len); + + h->key.data = p; + h->key.len = name->len; + } + + if (h != NULL) { + p = ngx_pnalloc(r->pool, s.len); + if (p == NULL) { + h->hash = 0; + (void) JS_ThrowOutOfMemory(cx); + return -1; + } + + ngx_memcpy(p, s.data, s.len); + + h->value.data = p; + h->value.len = s.len; + h->hash = 1; + } + + if (hh != NULL) { + *hh = h; + } + + return 1; +} + + +static int +ngx_http_qjs_headers_out_content_encoding(JSContext *cx, ngx_http_request_t *r, + ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value, + unsigned flags) +{ + int ret; + ngx_table_elt_t *h; + + ret = ngx_http_qjs_headers_out_special_handler(cx, r, name, pdesc, value, + flags, &h); + if (ret < 0) { + return -1; + } + + if (!(flags & NJS_HEADER_GET)) { + r->headers_out.content_encoding = h; + } + + return ret; +} + + +static int +ngx_http_qjs_headers_out_content_length(JSContext *cx, ngx_http_request_t *r, + ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value, + unsigned flags) +{ + int ret; + u_char *p; + ngx_int_t n; + ngx_table_elt_t *h; + u_char content_len[NGX_OFF_T_LEN]; + + if (flags & NJS_HEADER_GET) { + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + p = ngx_sprintf(content_len, "%O", r->headers_out.content_length_n); + + if (pdesc != NULL) { + pdesc->flags = JS_PROP_C_W_E; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = qjs_string_create(cx, content_len, + p - content_len); + if (JS_IsException(pdesc->value)) { + return -1; + } + } + + return 1; + } + } + + ret = ngx_http_qjs_headers_out_special_handler(cx, r, name, pdesc, value, + flags, &h); + if (ret < 0) { + return -1; + } + + if (!(flags & NJS_HEADER_GET)) { + if (h != NULL) { + n = ngx_atoi(h->value.data, h->value.len); + if (n == NGX_ERROR) { + h->hash = 0; + (void) JS_ThrowInternalError(cx, "failed converting argument " + "to positive integer"); + return -1; + } + + r->headers_out.content_length = h; + r->headers_out.content_length_n = n; + + } else { + ngx_http_clear_content_length(r); + } + } + + return ret; +} + + +static int +ngx_http_qjs_headers_out_content_type(JSContext *cx, ngx_http_request_t *r, + ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value, + unsigned flags) +{ + uint32_t length; + JSValue len, setval; + ngx_int_t rc; + ngx_str_t *hdr, s; + ngx_http_js_ctx_t *ctx; + + if (flags & NJS_HEADER_GET) { + hdr = &r->headers_out.content_type; + + if (pdesc != NULL) { + pdesc->flags = JS_PROP_C_W_E; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + + if (hdr->len == 0) { + pdesc->value = JS_UNDEFINED; + return 1; + } + + pdesc->value = qjs_string_create(cx, hdr->data, hdr->len); + if (JS_IsException(pdesc->value)) { + return -1; + } + } + + return 1; + } + + if (value == NULL) { + r->headers_out.content_type.len = 0; + r->headers_out.content_type_len = 0; + r->headers_out.content_type.data = NULL; + r->headers_out.content_type_lowcase = NULL; + return 1; + } + + if (JS_IsArray(cx, *value)) { + len = JS_GetPropertyStr(cx, *value, "length"); + if (JS_IsException(len)) { + return -1; + } + + if (JS_ToUint32(cx, &length, len) < 0) { + JS_FreeValue(cx, len); + return -1; + } + + JS_FreeValue(cx, len); + + setval = JS_GetPropertyUint32(cx, *value, length - 1); + if (JS_IsException(setval)) { + return -1; + } + + } else { + setval = *value; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + + rc = ngx_qjs_string(ctx->engine, setval, &s); + + if (JS_IsArray(cx, *value)) { + JS_FreeValue(cx, setval); + } + + if (rc != NGX_OK) { + return -1; + } + + r->headers_out.content_type.len = s.len; + r->headers_out.content_type_len = r->headers_out.content_type.len; + r->headers_out.content_type.data = s.data; + r->headers_out.content_type_lowcase = NULL; + + return 1; +} + + +static int +ngx_http_qjs_headers_out_date(JSContext *cx, ngx_http_request_t *r, + ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value, + unsigned flags) +{ + int ret; + ngx_table_elt_t *h; + + ret = ngx_http_qjs_headers_out_special_handler(cx, r, name, pdesc, value, + flags, &h); + if (ret < 0) { + return -1; + } + + if (!(flags & NJS_HEADER_GET)) { + r->headers_out.date = h; + } + + return ret; +} + + +static int +ngx_http_qjs_headers_out_last_modified(JSContext *cx, ngx_http_request_t *r, + ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value, + unsigned flags) +{ + int ret; + ngx_table_elt_t *h; + + ret = ngx_http_qjs_headers_out_special_handler(cx, r, name, pdesc, value, + flags, &h); + if (ret < 0) { + return -1; + } + + if (!(flags & NJS_HEADER_GET)) { + r->headers_out.last_modified = h; + } + + return ret; +} + + +static int +ngx_http_qjs_headers_out_location(JSContext *cx, ngx_http_request_t *r, + ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value, + unsigned flags) +{ + int ret; + ngx_table_elt_t *h; + + ret = ngx_http_qjs_headers_out_special_handler(cx, r, name, pdesc, value, + flags, &h); + if (ret < 0) { + return -1; + } + + if (!(flags & NJS_HEADER_GET)) { + r->headers_out.location = h; + } + + return ret; +} + + +static int +ngx_http_qjs_headers_out_server(JSContext *cx, ngx_http_request_t *r, + ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value, + unsigned flags) +{ + int ret; + ngx_table_elt_t *h; + + ret = ngx_http_qjs_headers_out_special_handler(cx, r, name, pdesc, value, + flags, &h); + if (ret < 0) { + return -1; + } + + if (!(flags & NJS_HEADER_GET)) { + r->headers_out.server = h; + } + + return ret; +} + + +static int +ngx_http_qjs_headers_out(JSContext *cx, ngx_http_request_t *r, + ngx_str_t *name, JSPropertyDescriptor *pdesc, JSValue *value, + unsigned flags) +{ + ngx_http_js_header_t *h; + + static ngx_http_js_header_t headers_out[] = { +#define header(name, fl, h) { njs_str(name), fl, (uintptr_t) h } + header("Age", NJS_HEADER_SINGLE, ngx_http_qjs_headers_out_handler), + header("Content-Encoding", 0, ngx_http_qjs_headers_out_content_encoding), + header("Content-Length", 0, ngx_http_qjs_headers_out_content_length), + header("Content-Type", 0, ngx_http_qjs_headers_out_content_type), + header("Date", 0, ngx_http_qjs_headers_out_date), + header("Etag", NJS_HEADER_SINGLE, ngx_http_qjs_headers_out_handler), + header("Expires", NJS_HEADER_SINGLE, ngx_http_qjs_headers_out_handler), + header("Last-Modified", 0, ngx_http_qjs_headers_out_last_modified), + header("Location", 0, ngx_http_qjs_headers_out_location), + header("Server", 0, ngx_http_qjs_headers_out_server), + header("Set-Cookie", NJS_HEADER_ARRAY, + ngx_http_qjs_headers_out_handler), + header("Retry-After", NJS_HEADER_SINGLE, + ngx_http_qjs_headers_out_handler), + header("", 0, ngx_http_qjs_headers_out_handler), +#undef header + }; + + for (h = headers_out; h->name.len > 0; h++) { + if (h->name.len == name->len + && ngx_strncasecmp(h->name.data, name->data, name->len) == 0) + { + break; + } + } + + return ((njs_http_qjs_header_handler_t) h->handler)(cx, + r, name, pdesc, value, h->flags | flags); +} + + +static int +ngx_http_qjs_headers_out_own_property(JSContext *cx, + JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop) +{ + int ret; + ngx_str_t name; + ngx_http_request_t *r; + + r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT); + if (r == NULL) { + (void) JS_ThrowInternalError(cx, "\"this\" is not a headers_out" + " object"); + return -1; + } + + name.data = (u_char *) JS_AtomToCString(cx, prop); + if (name.data == NULL) { + return -1; + } + + name.len = ngx_strlen(name.data); + + ret = ngx_http_qjs_headers_out(cx, r, &name, pdesc, NULL, NJS_HEADER_GET); + JS_FreeCString(cx, (char *) name.data); + + return ret; +} + + +static int +ngx_http_qjs_headers_out_set_property(JSContext *cx, + JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, + int flags) +{ + return ngx_http_qjs_headers_out_define_own_property(cx, obj, atom, value, + JS_UNDEFINED, JS_UNDEFINED, flags); +} + + +static int +ngx_http_qjs_headers_out_define_own_property(JSContext *cx, + JSValueConst obj, JSAtom prop, JSValueConst value, JSValueConst getter, + JSValueConst setter, int flags) +{ + int ret; + ngx_str_t name; + ngx_http_request_t *r; + + r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT); + if (r == NULL) { + (void) JS_ThrowInternalError(cx, "\"this\" is not a headers_out" + " object"); + return -1; + } + + if (!JS_IsUndefined(setter) || !JS_IsUndefined(getter)) { + (void) JS_ThrowTypeError(cx, "cannot define getter or setter"); + return -1; + } + + name.data = (u_char *) JS_AtomToCString(cx, prop); + if (name.data == NULL) { + return -1; + } + + name.len = ngx_strlen(name.data); + + if (r->header_sent) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "ignored setting of response header \"%V\" because" + " headers were already sent", &name); + } + + ret = ngx_http_qjs_headers_out(cx, r, &name, NULL, &value, 0); + JS_FreeCString(cx, (char *) name.data); + + return ret; +} + + +static int +ngx_http_qjs_headers_out_delete_property(JSContext *cx, + JSValueConst obj, JSAtom prop) +{ + int ret; + ngx_str_t name; + ngx_http_request_t *r; + + r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT); + if (r == NULL) { + (void) JS_ThrowInternalError(cx, "\"this\" is not a headers_out" + " object"); + return -1; + } + + name.data = (u_char *) JS_AtomToCString(cx, prop); + if (name.data == NULL) { + return -1; + } + + name.len = ngx_strlen(name.data); + + ret = ngx_http_qjs_headers_out(cx, r, &name, NULL, NULL, 0); + JS_FreeCString(cx, (char *) name.data); + + return ret; +} + + +static ngx_int_t +ngx_http_qjs_body_filter(ngx_http_request_t *r, ngx_http_js_loc_conf_t *jlcf, + ngx_http_js_ctx_t *ctx, ngx_chain_t *in) +{ + size_t len; + u_char *p; + JSAtom last_key; + JSValue arguments[3], last; + ngx_int_t rc; + njs_int_t pending; + ngx_buf_t *b; + ngx_chain_t *cl; + JSContext *cx; + ngx_connection_t *c; + + c = r->connection; + cx = ctx->engine->u.qjs.ctx; + + arguments[0] = ngx_qjs_arg(ctx->args[0]); + + last_key = JS_NewAtom(cx, "last"); + if (last_key == JS_ATOM_NULL) { + return NGX_ERROR; + } + + while (in != NULL) { + ctx->buf = in->buf; + b = ctx->buf; + + if (!ctx->done) { + len = b->last - b->pos; + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + return NJS_ERROR; + } + + if (len) { + ngx_memcpy(p, b->pos, len); + } + + arguments[1] = ngx_qjs_prop(cx, jlcf->buffer_type, p, len); + if (JS_IsException(arguments[1])) { + JS_FreeAtom(cx, last_key); + return NGX_ERROR; + } + + last = JS_NewBool(cx, b->last_buf); + + arguments[2] = JS_NewObject(cx); + if (JS_IsException(arguments[2])) { + JS_FreeAtom(cx, last_key); + JS_FreeValue(cx, arguments[1]); + return NGX_ERROR; + } + + if (JS_SetProperty(cx, arguments[2], last_key, last) < 0) { + JS_FreeAtom(cx, last_key); + JS_FreeValue(cx, arguments[1]); + JS_FreeValue(cx, arguments[2]); + return NGX_ERROR; + } + + pending = ngx_js_ctx_pending(ctx); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http js body call \"%V\"", &jlcf->body_filter); + + rc = ctx->engine->call((ngx_js_ctx_t *) ctx, &jlcf->body_filter, + (njs_opaque_value_t *) &arguments[0], 3); + + JS_FreeAtom(cx, last_key); + JS_FreeValue(cx, arguments[1]); + JS_FreeValue(cx, arguments[2]); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (!pending && rc == NGX_AGAIN) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "async operation inside \"%V\" body filter", + &jlcf->body_filter); + return NGX_ERROR; + } + + ctx->buf->pos = ctx->buf->last; + + } else { + cl = ngx_alloc_chain_link(c->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + + *ctx->last_out = cl; + ctx->last_out = &cl->next; + } + + in = in->next; + } + + return NGX_OK; +} + + +static ngx_http_request_t * +ngx_http_qjs_request(JSValueConst val) +{ + ngx_http_qjs_request_t *req; + + req = JS_GetOpaque(val, NGX_QJS_CLASS_ID_HTTP_REQUEST); + if (req == NULL) { + return NULL; + } + + return req->request; +} + + +static JSValue +ngx_http_qjs_request_make(JSContext *cx, ngx_int_t proto_id, + ngx_http_request_t *r) +{ + JSValue request; + ngx_http_qjs_request_t *req; + + request = JS_NewObjectClass(cx, proto_id); + if (JS_IsException(request)) { + return JS_EXCEPTION; + } + + req = js_malloc(cx, sizeof(ngx_http_qjs_request_t)); + if (req == NULL) { + return JS_ThrowOutOfMemory(cx); + } + + req->request = r; + req->args = JS_UNDEFINED; + req->request_body = JS_UNDEFINED; + req->response_body = JS_UNDEFINED; + + JS_SetOpaque(request, req); + + return request; +} + + +static void +ngx_http_qjs_request_finalizer(JSRuntime *rt, JSValue val) +{ + ngx_http_qjs_request_t *req; + + req = JS_GetOpaque(val, NGX_QJS_CLASS_ID_HTTP_REQUEST); + if (req == NULL) { + return; + } + + JS_FreeValueRT(rt, req->args); + JS_FreeValueRT(rt, req->request_body); + JS_FreeValueRT(rt, req->response_body); + + js_free_rt(rt, req); +} + + +static ngx_engine_t * +ngx_engine_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, + njs_int_t proto_id, void *external) +{ + JSValue proto; + JSContext *cx; + ngx_engine_t *engine; + ngx_http_js_ctx_t *hctx; + + engine = ngx_qjs_clone(ctx, cf, external); + if (engine == NULL) { + return NULL; + } + + cx = engine->u.qjs.ctx; + + if (!JS_IsRegisteredClass(JS_GetRuntime(cx), + NGX_QJS_CLASS_ID_HTTP_REQUEST)) + { + if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_HTTP_REQUEST, + &ngx_http_qjs_request_class) < 0) + { + return NULL; + } + + proto = JS_NewObject(cx); + if (JS_IsException(proto)) { + return NULL; + } + + JS_SetPropertyFunctionList(cx, proto, ngx_http_qjs_ext_request, + njs_nitems(ngx_http_qjs_ext_request)); + + JS_SetClassProto(cx, NGX_QJS_CLASS_ID_HTTP_REQUEST, proto); + + if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_HTTP_PERIODIC, + &ngx_http_qjs_periodic_class) < 0) + { + return NULL; + } + + proto = JS_NewObject(cx); + if (JS_IsException(proto)) { + return NULL; + } + + JS_SetPropertyFunctionList(cx, proto, ngx_http_qjs_ext_periodic, + njs_nitems(ngx_http_qjs_ext_periodic)); + + JS_SetClassProto(cx, NGX_QJS_CLASS_ID_HTTP_PERIODIC, proto); + + if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_HTTP_VARS, + &ngx_http_qjs_variables_class) < 0) + { + return NULL; + } + + if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_HTTP_HEADERS_IN, + &ngx_http_qjs_headers_in_class) < 0) + { + return NULL; + } + + if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT, + &ngx_http_qjs_headers_out_class) < 0) + { + return NULL; + } + } + + hctx = (ngx_http_js_ctx_t *) ctx; + hctx->body_filter = ngx_http_qjs_body_filter; + + if (proto_id == ngx_http_js_request_proto_id) { + proto_id = NGX_QJS_CLASS_ID_HTTP_REQUEST; + + } else if (proto_id == ngx_http_js_periodic_session_proto_id) { + proto_id = NGX_QJS_CLASS_ID_HTTP_PERIODIC; + } + + ngx_qjs_arg(hctx->args[0]) = ngx_http_qjs_request_make(cx, proto_id, + external); + if (JS_IsException(ngx_qjs_arg(hctx->args[0]))) { + return NULL; + } + + return engine; +} + +#endif + + +static ngx_int_t +ngx_http_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf) +{ + ngx_engine_opts_t options; + ngx_js_main_conf_t *jmcf; + + memset(&options, 0, sizeof(ngx_engine_opts_t)); + + options.engine = conf->type; + + jmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_js_module); + ngx_http_js_uptr[NGX_JS_MAIN_CONF_INDEX] = (uintptr_t) jmcf; + + if (conf->type == NGX_ENGINE_NJS) { + options.u.njs.metas = &ngx_http_js_metas; + options.u.njs.addons = njs_http_js_addon_modules; + options.clone = ngx_engine_njs_clone; + } + +#if (NJS_HAVE_QUICKJS) + else if (conf->type == NGX_ENGINE_QJS) { + options.u.qjs.metas = ngx_http_js_uptr; + options.u.qjs.addons = njs_http_qjs_addon_modules; + options.clone = ngx_engine_qjs_clone; + } +#endif + return ngx_js_init_conf_vm(cf, conf, &options); } diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index ce4988f9..5fe3dc84 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -8,6 +8,7 @@ #include #include +#include #include "ngx_js.h" @@ -57,6 +58,53 @@ static ngx_int_t ngx_engine_njs_string(ngx_engine_t *e, static void ngx_engine_njs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *conf); +#if (NJS_HAVE_QUICKJS) +static ngx_int_t ngx_engine_qjs_init(ngx_engine_t *engine, + ngx_engine_opts_t *opts); +static ngx_int_t ngx_engine_qjs_compile(ngx_js_loc_conf_t *conf, ngx_log_t *log, + u_char *start, size_t size); +static ngx_int_t ngx_engine_qjs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname, + njs_opaque_value_t *args, njs_uint_t nargs); +static void *ngx_engine_qjs_external(ngx_engine_t *engine); +static ngx_int_t ngx_engine_qjs_pending(ngx_engine_t *engine); +static ngx_int_t ngx_engine_qjs_string(ngx_engine_t *e, + njs_opaque_value_t *value, ngx_str_t *str); + +static JSValue ngx_qjs_ext_set_timeout(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv, int immediate); +static JSValue ngx_qjs_ext_clear_timeout(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); + +static JSValue ngx_qjs_ext_build(JSContext *cx, JSValueConst this_val); +static JSValue ngx_qjs_ext_conf_file_path(JSContext *cx, JSValueConst this_val); +static JSValue ngx_qjs_ext_conf_prefix(JSContext *cx, JSValueConst this_val); +static JSValue ngx_qjs_ext_constant_integer(JSContext *cx, + JSValueConst this_val, int magic); +static JSValue ngx_qjs_ext_error_log_path(JSContext *cx, JSValueConst this_val); +static JSValue ngx_qjs_ext_log(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv, int level); +static JSValue ngx_qjs_ext_console_time(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue ngx_qjs_ext_console_time_end(JSContext *cx, + JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue ngx_qjs_ext_prefix(JSContext *cx, JSValueConst this_val); +static JSValue ngx_qjs_ext_worker_id(JSContext *cx, JSValueConst this_val); + +static void ngx_qjs_console_finalizer(JSRuntime *rt, JSValue val); + +static JSModuleDef *ngx_qjs_module_loader(JSContext *ctx, + const char *module_name, void *opaque); +static int ngx_qjs_unhandled_rejection(ngx_js_ctx_t *ctx); +static void ngx_qjs_rejection_tracker(JSContext *ctx, JSValueConst promise, + JSValueConst reason, JS_BOOL is_handled, void *opaque); + +static JSValue ngx_qjs_value(JSContext *cx, const ngx_str_t *path); +static ngx_int_t ngx_qjs_dump_obj(ngx_engine_t *e, JSValueConst val, + ngx_str_t *dst); + +static JSModuleDef *ngx_qjs_core_init(JSContext *cx, const char *name); +#endif + static njs_int_t ngx_js_ext_build(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); static njs_int_t ngx_js_ext_conf_file_path(njs_vm_t *vm, @@ -377,6 +425,57 @@ njs_module_t *njs_js_addon_modules_shared[] = { static njs_int_t ngx_js_console_proto_id; +#if (NJS_HAVE_QUICKJS) + +static const JSCFunctionListEntry ngx_qjs_ext_ngx[] = { + JS_CGETSET_DEF("build", ngx_qjs_ext_build, NULL), + JS_CGETSET_DEF("conf_prefix", ngx_qjs_ext_conf_prefix, NULL), + JS_CGETSET_DEF("conf_file_path", ngx_qjs_ext_conf_file_path, NULL), + JS_CGETSET_MAGIC_DEF("ERR", ngx_qjs_ext_constant_integer, NULL, + NGX_LOG_ERR), + JS_CGETSET_DEF("error_log_path", ngx_qjs_ext_error_log_path, NULL), + JS_CGETSET_MAGIC_DEF("INFO", ngx_qjs_ext_constant_integer, NULL, + NGX_LOG_INFO), + JS_CFUNC_MAGIC_DEF("log", 1, ngx_qjs_ext_log, 0), + JS_CGETSET_DEF("prefix", ngx_qjs_ext_prefix, NULL), + JS_PROP_STRING_DEF("version", NGINX_VERSION, JS_PROP_C_W_E), + JS_PROP_INT32_DEF("version_number", nginx_version, JS_PROP_C_W_E), + JS_CGETSET_MAGIC_DEF("WARN", ngx_qjs_ext_constant_integer, NULL, + NGX_LOG_WARN), + JS_CGETSET_DEF("worker_id", ngx_qjs_ext_worker_id, NULL), +}; + + +static const JSCFunctionListEntry ngx_qjs_ext_console[] = { + JS_CFUNC_MAGIC_DEF("error", 1, ngx_qjs_ext_log, NGX_LOG_ERR), + JS_CFUNC_MAGIC_DEF("info", 1, ngx_qjs_ext_log, NGX_LOG_INFO), + JS_CFUNC_MAGIC_DEF("log", 1, ngx_qjs_ext_log, NGX_LOG_INFO), + JS_CFUNC_DEF("time", 1, ngx_qjs_ext_console_time), + JS_CFUNC_DEF("timeEnd", 1, ngx_qjs_ext_console_time_end), + JS_CFUNC_MAGIC_DEF("warn", 1, ngx_qjs_ext_log, NGX_LOG_WARN), +}; + + +static const JSCFunctionListEntry ngx_qjs_ext_global[] = { + JS_CFUNC_MAGIC_DEF("setTimeout", 1, ngx_qjs_ext_set_timeout, 0), + JS_CFUNC_MAGIC_DEF("setImmediate", 1, ngx_qjs_ext_set_timeout, 1), + JS_CFUNC_DEF("clearTimeout", 1, ngx_qjs_ext_clear_timeout), +}; + + +static JSClassDef ngx_qjs_console_class = { + "Console", + .finalizer = ngx_qjs_console_finalizer, +}; + + +qjs_module_t ngx_qjs_ngx_module = { + .name = "ngx", + .init = ngx_qjs_core_init, +}; + +#endif + static ngx_engine_t * ngx_create_engine(ngx_engine_opts_t *opts) { @@ -415,6 +514,25 @@ ngx_create_engine(ngx_engine_opts_t *opts) : ngx_engine_njs_destroy; break; +#if (NJS_HAVE_QUICKJS) + case NGX_ENGINE_QJS: + rc = ngx_engine_qjs_init(engine, opts); + if (rc != NGX_OK) { + return NULL; + } + + engine->name = "QuickJS"; + engine->type = NGX_ENGINE_QJS; + engine->compile = ngx_engine_qjs_compile; + engine->call = ngx_engine_qjs_call; + engine->external = ngx_engine_qjs_external; + engine->pending = ngx_engine_qjs_pending; + engine->string = ngx_engine_qjs_string; + engine->destroy = opts->destroy ? opts->destroy + : ngx_engine_qjs_destroy; + break; +#endif + default: return NULL; } @@ -493,212 +611,1592 @@ ngx_engine_njs_compile(ngx_js_loc_conf_t *conf, ngx_log_t *log, u_char *start, if (value != NULL) { i = njs_value_number(value) - 1; - if (i < conf->imports->nelts) { - import = conf->imports->elts; - ngx_log_error(NGX_LOG_EMERG, log, 0, - "%*s, included in %s:%ui", text.length, - text.start, import[i].file, import[i].line); - return NGX_ERROR; - } - } - } + if (i < conf->imports->nelts) { + import = conf->imports->elts; + ngx_log_error(NGX_LOG_EMERG, log, 0, + "%*s, included in %s:%ui", text.length, + text.start, import[i].file, import[i].line); + return NGX_ERROR; + } + } + } + + ngx_log_error(NGX_LOG_EMERG, log, 0, "%*s", text.length, text.start); + return NGX_ERROR; + } + + if (start != end) { + ngx_log_error(NGX_LOG_EMERG, log, 0, + "extra characters in js script: \"%*s\"", + end - start, start); + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_engine_t * +ngx_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external) +{ + njs_vm_t *vm; + njs_int_t rc; + njs_str_t key; + ngx_str_t exception; + ngx_uint_t i; + ngx_engine_t *engine; + njs_opaque_value_t retval; + ngx_js_named_path_t *preload; + + vm = njs_vm_clone(cf->engine->u.njs.vm, external); + if (vm == NULL) { + return NULL; + } + + engine = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(ngx_engine_t)); + if (engine == NULL) { + return NULL; + } + + memcpy(engine, cf->engine, sizeof(ngx_engine_t)); + engine->pool = njs_vm_memory_pool(vm); + engine->u.njs.vm = vm; + + /* bind objects from preload vm */ + + if (cf->preload_objects != NGX_CONF_UNSET_PTR) { + preload = cf->preload_objects->elts; + + for (i = 0; i < cf->preload_objects->nelts; i++) { + key.start = preload[i].name.data; + key.length = preload[i].name.len; + + rc = njs_vm_value(cf->preload_vm, &key, njs_value_arg(&retval)); + if (rc != NJS_OK) { + return NULL; + } + + rc = njs_vm_bind(vm, &key, njs_value_arg(&retval), 0); + if (rc != NJS_OK) { + return NULL; + } + } + } + + if (njs_vm_start(vm, njs_value_arg(&retval)) == NJS_ERROR) { + ngx_js_exception(vm, &exception); + + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception); + + return NULL; + } + + return engine; +} + + +static ngx_int_t +ngx_engine_njs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname, + njs_opaque_value_t *args, njs_uint_t nargs) +{ + njs_vm_t *vm; + njs_int_t ret; + njs_str_t name; + ngx_str_t exception; + njs_function_t *func; + + name.start = fname->data; + name.length = fname->len; + + vm = ctx->engine->u.njs.vm; + + func = njs_vm_function(vm, &name); + if (func == NULL) { + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "js function \"%V\" not found", fname); + return NGX_ERROR; + } + + ret = njs_vm_invoke(vm, func, njs_value_arg(args), nargs, + njs_value_arg(&ctx->retval)); + if (ret == NJS_ERROR) { + ngx_js_exception(vm, &exception); + + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "js exception: %V", &exception); + + return NGX_ERROR; + } + + for ( ;; ) { + ret = njs_vm_execute_pending_job(vm); + if (ret <= NJS_OK) { + if (ret == NJS_ERROR) { + ngx_js_exception(vm, &exception); + + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "js job exception: %V", &exception); + return NGX_ERROR; + } + + break; + } + } + + if (ngx_js_unhandled_rejection(ctx)) { + ngx_js_exception(vm, &exception); + + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception); + return NGX_ERROR; + } + + return njs_rbtree_is_empty(&ctx->waiting_events) ? NGX_OK : NGX_AGAIN; +} + + +static void * +ngx_engine_njs_external(ngx_engine_t *engine) +{ + return njs_vm_external_ptr(engine->u.njs.vm); +} + +static ngx_int_t +ngx_engine_njs_pending(ngx_engine_t *e) +{ + return njs_vm_pending(e->u.njs.vm); +} + + +static ngx_int_t +ngx_engine_njs_string(ngx_engine_t *e, njs_opaque_value_t *value, + ngx_str_t *str) +{ + ngx_int_t rc; + njs_str_t s; + + rc = ngx_js_string(e->u.njs.vm, njs_value_arg(value), &s); + + str->data = s.start; + str->len = s.length; + + return rc; +} + + +static void +ngx_engine_njs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, + ngx_js_loc_conf_t *conf) +{ + ngx_js_event_t *event; + njs_rbtree_node_t *node; + + if (ctx != NULL) { + node = njs_rbtree_min(&ctx->waiting_events); + + while (njs_rbtree_is_there_successor(&ctx->waiting_events, node)) { + event = (ngx_js_event_t *) ((u_char *) node + - offsetof(ngx_js_event_t, node)); + + if (event->destructor != NULL) { + event->destructor(event); + } + + node = njs_rbtree_node_successor(&ctx->waiting_events, node); + } + } + + njs_vm_destroy(e->u.njs.vm); + + /* + * when ctx !=NULL e->pool is vm pool, in such case it is destroyed + * by njs_vm_destroy(). + */ + + if (ctx == NULL) { + njs_mp_destroy(e->pool); + } +} + + +#if (NJS_HAVE_QUICKJS) + +static ngx_int_t +ngx_engine_qjs_init(ngx_engine_t *engine, ngx_engine_opts_t *opts) +{ + JSRuntime *rt; + + rt = JS_NewRuntime(); + if (rt == NULL) { + return NGX_ERROR; + } + + engine->u.qjs.ctx = qjs_new_context(rt, opts->u.qjs.addons); + if (engine->u.qjs.ctx == NULL) { + return NGX_ERROR; + } + + JS_SetRuntimeOpaque(rt, opts->u.qjs.metas); + JS_SetContextOpaque(engine->u.qjs.ctx, opts->u.qjs.addons); + + JS_SetModuleLoaderFunc(rt, NULL, ngx_qjs_module_loader, opts->conf); + + return NGX_OK; +} + + +static ngx_int_t +ngx_engine_qjs_compile(ngx_js_loc_conf_t *conf, ngx_log_t *log, u_char *start, + size_t size) +{ + JSValue code; + ngx_str_t text; + JSContext *cx; + ngx_engine_t *engine; + ngx_js_code_entry_t *pc; + + engine = conf->engine; + cx = engine->u.qjs.ctx; + + code = JS_Eval(cx, (char *) start, size, "
", + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + + if (JS_IsException(code)) { + ngx_qjs_exception(engine, &text); + ngx_log_error(NGX_LOG_EMERG, log, 0, "js compile %V", &text); + return NGX_ERROR; + } + + pc = njs_arr_add(engine->precompiled); + if (pc == NULL) { + JS_FreeValue(cx, code); + ngx_log_error(NGX_LOG_EMERG, log, 0, "njs_arr_add() failed"); + return NGX_ERROR; + } + + pc->code = JS_WriteObject(cx, &pc->code_size, code, JS_WRITE_OBJ_BYTECODE); + if (pc->code == NULL) { + JS_FreeValue(cx, code); + ngx_log_error(NGX_LOG_EMERG, log, 0, "JS_WriteObject() failed"); + return NGX_ERROR; + } + + JS_FreeValue(cx, code); + + return NGX_OK; +} + + +static JSValue +js_std_await(JSContext *ctx, JSValue obj) +{ + int state, err; + JSValue ret; + JSContext *ctx1; + + for (;;) { + state = JS_PromiseState(ctx, obj); + if (state == JS_PROMISE_FULFILLED) { + ret = JS_PromiseResult(ctx, obj); + JS_FreeValue(ctx, obj); + break; + + } else if (state == JS_PROMISE_REJECTED) { + ret = JS_Throw(ctx, JS_PromiseResult(ctx, obj)); + JS_FreeValue(ctx, obj); + break; + + } else if (state == JS_PROMISE_PENDING) { + err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); + if (err < 0) { + /* js_std_dump_error(ctx1); */ + } + + } else { + /* not a promise */ + ret = obj; + break; + } + } + + return ret; +} + + +ngx_engine_t * +ngx_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external) +{ + JSValue rv; + njs_mp_t *mp; + uint32_t i, length; + JSRuntime *rt; + ngx_str_t exception; + JSContext *cx; + ngx_engine_t *engine; + ngx_js_code_entry_t *pc; + + mp = njs_mp_fast_create(2 * getpagesize(), 128, 512, 16); + if (mp == NULL) { + return NULL; + } + + engine = njs_mp_alloc(mp, sizeof(ngx_engine_t)); + if (engine == NULL) { + return NULL; + } + + memcpy(engine, cf->engine, sizeof(ngx_engine_t)); + engine->pool = mp; + + if (cf->reuse_queue != NULL) { + engine->u.qjs.ctx = ngx_js_queue_pop(cf->reuse_queue); + if (engine->u.qjs.ctx != NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "js reused context: %p", engine->u.qjs.ctx); + JS_SetContextOpaque(engine->u.qjs.ctx, external); + return engine; + } + } + + rt = JS_NewRuntime(); + if (rt == NULL) { + return NULL; + } + + JS_SetRuntimeOpaque(rt, JS_GetRuntimeOpaque( + JS_GetRuntime(cf->engine->u.qjs.ctx))); + + cx = qjs_new_context(rt, JS_GetContextOpaque(cf->engine->u.qjs.ctx)); + if (cx == NULL) { + JS_FreeRuntime(rt); + return NULL; + } + + engine->u.qjs.ctx = cx; + JS_SetContextOpaque(cx, external); + + JS_SetHostPromiseRejectionTracker(rt, ngx_qjs_rejection_tracker, ctx); + + + /* TODO: bind objects from preload vm */ + + rv = JS_UNDEFINED; + pc = engine->precompiled->start; + length = engine->precompiled->items; + + for (i = 0; i < length; i++) { + rv = JS_ReadObject(cx, pc[i].code, pc[i].code_size, + JS_READ_OBJ_BYTECODE); + if (JS_IsException(rv)) { + ngx_qjs_exception(engine, &exception); + + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "js load module exception: %V", &exception); + goto destroy; + } + } + + if (JS_ResolveModule(cx, rv) < 0) { + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js resolve module failed"); + goto destroy; + } + + rv = JS_EvalFunction(cx, rv); + + if (JS_IsException(rv)) { + ngx_qjs_exception(engine, &exception); + + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js eval exception: %V", + &exception); + goto destroy; + } + + rv = js_std_await(cx, rv); + if (JS_IsException(rv)) { + ngx_qjs_exception(engine, &exception); + + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js eval exception: %V", + &exception); + goto destroy; + } + + JS_FreeValue(cx, rv); + + return engine; + +destroy: + + JS_FreeContext(cx); + JS_FreeRuntime(rt); + njs_mp_destroy(mp); + + return NULL; +} + + +static ngx_int_t +ngx_engine_qjs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname, + njs_opaque_value_t *args, njs_uint_t nargs) +{ + int rc; + JSValue fn, val; + ngx_str_t exception; + JSRuntime *rt; + JSContext *cx, *cx1; + + cx = ctx->engine->u.qjs.ctx; + + fn = ngx_qjs_value(cx, fname); + if (!JS_IsFunction(cx, fn)) { + JS_FreeValue(cx, fn); + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js function \"%V\" not found", + fname); + + return NGX_ERROR; + } + + val = JS_Call(cx, fn, JS_UNDEFINED, nargs, &ngx_qjs_arg(args[0])); + JS_FreeValue(cx, fn); + if (JS_IsException(val)) { + ngx_qjs_exception(ctx->engine, &exception); + + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "js call exception: %V", &exception); + + return NGX_ERROR; + } + + JS_FreeValue(cx, ngx_qjs_arg(ctx->retval)); + ngx_qjs_arg(ctx->retval) = val; + + rt = JS_GetRuntime(cx); + + for ( ;; ) { + rc = JS_ExecutePendingJob(rt, &cx1); + if (rc <= 0) { + if (rc == -1) { + ngx_qjs_exception(ctx->engine, &exception); + + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "js job exception: %V", &exception); + + return NGX_ERROR; + } + + break; + } + } + + if (ngx_qjs_unhandled_rejection(ctx)) { + ngx_qjs_exception(ctx->engine, &exception); + + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception); + return NGX_ERROR; + } + + return njs_rbtree_is_empty(&ctx->waiting_events) ? NGX_OK : NGX_AGAIN; +} + + +static void * +ngx_engine_qjs_external(ngx_engine_t *e) +{ + return JS_GetContextOpaque(e->u.qjs.ctx); +} + + +static ngx_int_t +ngx_engine_qjs_pending(ngx_engine_t *e) +{ + return JS_IsJobPending(JS_GetRuntime(e->u.qjs.ctx)); +} + + +static ngx_int_t +ngx_engine_qjs_string(ngx_engine_t *e, njs_opaque_value_t *value, + ngx_str_t *str) +{ + return ngx_qjs_dump_obj(e, ngx_qjs_arg(*value), str); +} + + +static void +ngx_js_cleanup_reuse_ctx(void *data) +{ + JSRuntime *rt; + JSContext *cx; + + ngx_js_queue_t *reuse = data; + + for ( ;; ) { + cx = ngx_js_queue_pop(reuse); + if (cx == NULL) { + break; + } + + rt = JS_GetRuntime(cx); + JS_FreeContext(cx); + JS_FreeRuntime(rt); + } +} + + +void +ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, + ngx_js_loc_conf_t *conf) +{ + uint32_t i, length; + JSRuntime *rt; + JSContext *cx; + JSClassID class_id; + ngx_qjs_event_t *event; + ngx_js_opaque_t *opaque; + njs_rbtree_node_t *node; + ngx_pool_cleanup_t *cln; + ngx_js_code_entry_t *pc; + ngx_js_rejected_promise_t *rejected_promise; + + cx = e->u.qjs.ctx; + + if (ctx != NULL) { + node = njs_rbtree_min(&ctx->waiting_events); + + while (njs_rbtree_is_there_successor(&ctx->waiting_events, node)) { + event = (ngx_qjs_event_t *) ((u_char *) node + - offsetof(ngx_qjs_event_t, node)); + + if (event->destructor != NULL) { + event->destructor(event); + } + + node = njs_rbtree_node_successor(&ctx->waiting_events, node); + } + + if (ctx->rejected_promises != NULL) { + rejected_promise = ctx->rejected_promises->start; + + for (i = 0; i < ctx->rejected_promises->items; i++) { + JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].promise)); + JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].message)); + } + } + + class_id = JS_GetClassID(ngx_qjs_arg(ctx->args[0])); + opaque = JS_GetOpaque(ngx_qjs_arg(ctx->args[0]), class_id); + opaque->external = NULL; + + JS_FreeValue(cx, ngx_qjs_arg(ctx->args[0])); + JS_FreeValue(cx, ngx_qjs_arg(ctx->retval)); + + } else if (e->precompiled != NULL) { + pc = e->precompiled->start; + length = e->precompiled->items; + + for (i = 0; i < length; i++) { + js_free(cx, pc[i].code); + } + } + + njs_mp_destroy(e->pool); + + if (conf != NULL && conf->reuse != 0) { + if (conf->reuse_queue == NULL) { + conf->reuse_queue = ngx_js_queue_create(ngx_cycle->pool, + conf->reuse); + if (conf->reuse_queue == NULL) { + goto free_ctx; + } + + cln = ngx_pool_cleanup_add(ngx_cycle->pool, 0); + if (cln == NULL) { + goto free_ctx; + } + + cln->handler = ngx_js_cleanup_reuse_ctx; + cln->data = conf->reuse_queue; + } + + if (ngx_js_queue_push(conf->reuse_queue, cx) != NGX_OK) { + goto free_ctx; + } + + return; + } + +free_ctx: + + rt = JS_GetRuntime(cx); + JS_FreeContext(cx); + JS_FreeRuntime(rt); +} + + +static JSValue +ngx_qjs_value(JSContext *cx, const ngx_str_t *path) +{ + u_char *start, *p, *end; + JSAtom key; + size_t size; + JSValue value, rv; + + start = path->data; + end = start + path->len; + + value = JS_GetGlobalObject(cx); + + for ( ;; ) { + p = njs_strlchr(start, end, '.'); + + size = ((p != NULL) ? p : end) - start; + if (size == 0) { + JS_FreeValue(cx, value); + return JS_ThrowTypeError(cx, "empty path element"); + } + + key = JS_NewAtomLen(cx, (char *) start, size); + if (key == JS_ATOM_NULL) { + JS_FreeValue(cx, value); + return JS_ThrowInternalError(cx, "could not create atom"); + } + + rv = JS_GetProperty(cx, value, key); + JS_FreeAtom(cx, key); + if (JS_IsException(rv)) { + JS_FreeValue(cx, value); + return JS_EXCEPTION; + } + + JS_FreeValue(cx, value); + + if (p == NULL) { + break; + } + + start = p + 1; + value = rv; + } + + return rv; +} + + +static ngx_int_t +ngx_qjs_dump_obj(ngx_engine_t *e, JSValueConst val, ngx_str_t *dst) +{ + size_t len, byte_offset, byte_length; + u_char *start, *p; + JSValue buffer, stack; + ngx_str_t str, stack_str; + JSContext *cx; + + if (JS_IsNullOrUndefined(val)) { + dst->data = NULL; + dst->len = 0; + return NGX_OK; + } + + cx = e->u.qjs.ctx; + + buffer = JS_GetTypedArrayBuffer(cx, val, &byte_offset, &byte_length, NULL); + if (!JS_IsException(buffer)) { + start = JS_GetArrayBuffer(cx, &dst->len, buffer); + + JS_FreeValue(cx, buffer); + + if (start != NULL) { + start += byte_offset; + dst->len = byte_length; + + dst->data = njs_mp_alloc(e->pool, dst->len); + if (dst->data == NULL) { + return NGX_ERROR; + } + + memcpy(dst->data, start, dst->len); + return NGX_OK; + } + } + + str.data = (u_char *) JS_ToCString(cx, val); + if (str.data != NULL) { + str.len = ngx_strlen(str.data); + + stack = JS_GetPropertyStr(cx, val, "stack"); + + stack_str.len = 0; + stack_str.data = NULL; + + if (!JS_IsException(stack) && !JS_IsUndefined(stack)) { + stack_str.data = (u_char *) JS_ToCString(cx, stack); + if (stack_str.data != NULL) { + stack_str.len = ngx_strlen(stack_str.data); + } + } + + len = str.len; + + if (stack_str.len != 0) { + len += stack_str.len + njs_length("\n"); + } + + start = njs_mp_alloc(e->pool, len); + if (start == NULL) { + JS_FreeCString(cx, (char *) str.data); + JS_FreeValue(cx, stack); + return NGX_ERROR; + } + + p = ngx_cpymem(start, str.data, str.len); + + if (stack_str.len != 0) { + *p++ = '\n'; + (void) ngx_cpymem(p, stack_str.data, stack_str.len); + JS_FreeCString(cx, (char *) stack_str.data); + } + + JS_FreeCString(cx, (char *) str.data); + JS_FreeValue(cx, stack); + + } else { + len = njs_length("[exception]"); + + start = njs_mp_alloc(e->pool, len); + if (start == NULL) { + return NGX_ERROR; + } + + memcpy(start, "[exception]", len); + } + + dst->data = start; + dst->len = len; + + return NGX_OK; +} + + +ngx_int_t +ngx_qjs_call(ngx_js_ctx_t *ctx, JSValue fn, JSValue *argv, int argc) +{ + int rc; + JSValue ret; + ngx_str_t exception; + JSRuntime *rt; + JSContext *cx, *cx1; + + cx = ctx->engine->u.qjs.ctx; + + ret = JS_Call(cx, fn, JS_UNDEFINED, argc, argv); + if (JS_IsException(ret)) { + ngx_qjs_exception(ctx->engine, &exception); + + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "js call exception: %V", &exception); + + return NGX_ERROR; + } + + JS_FreeValue(cx, ret); + + rt = JS_GetRuntime(cx); + + for ( ;; ) { + rc = JS_ExecutePendingJob(rt, &cx1); + if (rc <= 0) { + if (rc == -1) { + ngx_qjs_exception(ctx->engine, &exception); + + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "js job exception: %V", &exception); + + return NGX_ERROR; + } + + break; + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_qjs_exception(ngx_engine_t *e, ngx_str_t *s) +{ + JSValue exception; + + exception = JS_GetException(e->u.qjs.ctx); + if (ngx_qjs_dump_obj(e, exception, s) != NGX_OK) { + return NGX_ERROR; + } + + JS_FreeValue(e->u.qjs.ctx, exception); + + return NGX_OK; +} + + +ngx_int_t +ngx_qjs_integer(JSContext *cx, JSValueConst val, ngx_int_t *n) +{ + double num; + + if (JS_ToFloat64(cx, &num, val)) { + return NGX_ERROR; + } + + if (isinf(num) || isnan(num)) { + (void) JS_ThrowTypeError(cx, "invalid number"); + return NGX_ERROR; + } + + *n = num; + + return NGX_OK; +} + + +ngx_int_t +ngx_qjs_string(ngx_engine_t *e, JSValueConst val, ngx_str_t *dst) +{ + size_t len, byte_offset, byte_length; + u_char *start; + JSValue buffer; + JSContext *cx; + const char *str; + + if (JS_IsNullOrUndefined(val)) { + dst->data = NULL; + dst->len = 0; + return NGX_OK; + } + + cx = e->u.qjs.ctx; + + buffer = JS_GetTypedArrayBuffer(cx, val, &byte_offset, &byte_length, NULL); + if (!JS_IsException(buffer)) { + start = JS_GetArrayBuffer(cx, &dst->len, buffer); + + JS_FreeValue(cx, buffer); + + if (start != NULL) { + start += byte_offset; + dst->len = byte_length; + + dst->data = njs_mp_alloc(e->pool, dst->len); + if (dst->data == NULL) { + return NGX_ERROR; + } + + memcpy(dst->data, start, dst->len); + return NGX_OK; + } + } + + str = JS_ToCString(cx, val); + if (str == NULL) { + return NGX_ERROR; + } + + len = strlen(str); + + start = njs_mp_alloc(e->pool, len); + if (start == NULL) { + JS_FreeCString(cx, str); + return NGX_ERROR; + } + + memcpy(start, str, len); + + JS_FreeCString(cx, str); + + dst->data = start; + dst->len = len; + + return NGX_OK; +} + + +static void +ngx_qjs_timer_handler(ngx_event_t *ev) +{ + void *external; + JSContext *cx; + ngx_int_t rc; + ngx_js_ctx_t *ctx; + ngx_qjs_event_t *event; + + event = (ngx_qjs_event_t *) ((u_char *) ev - offsetof(ngx_qjs_event_t, ev)); + + cx = event->ctx; + external = JS_GetContextOpaque(cx); + ctx = ngx_qjs_external_ctx(cx, external); + + rc = ngx_qjs_call((ngx_js_ctx_t *) ctx, event->function, event->args, + event->nargs); + + ngx_js_del_event(ctx, event); + + ngx_qjs_external_event_finalize(cx)(external, rc); +} + + +static void +ngx_qjs_clear_timer(ngx_qjs_event_t *event) +{ + int i; + JSContext *cx; + + cx = event->ctx; + + if (event->ev.timer_set) { + ngx_del_timer(&event->ev); + } + + JS_FreeValue(cx, event->function); + + for (i = 0; i < (int) event->nargs; i++) { + JS_FreeValue(cx, event->args[i]); + } +} + + +static JSValue +ngx_qjs_ext_set_timeout(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv, int immediate) +{ + int i, n; + void *external; + uint32_t delay; + ngx_js_ctx_t *ctx; + ngx_qjs_event_t *event; + ngx_connection_t *c; + + if (!JS_IsFunction(cx, argv[0])) { + return JS_ThrowTypeError(cx, "first arg must be a function"); + } + + delay = 0; + + if (!immediate && argc >= 2) { + if (JS_ToUint32(cx, &delay, argv[1]) < 0) { + return JS_EXCEPTION; + } + } + + n = immediate ? 1 : 2; + argc = (argc >= n) ? argc - n : 0; + external = JS_GetContextOpaque(cx); + ctx = ngx_qjs_external_ctx(cx, external); + + event = ngx_pcalloc(ngx_qjs_external_pool(cx, external), + sizeof(ngx_qjs_event_t) + sizeof(JSValue) * argc); + if (event == NULL) { + return JS_ThrowOutOfMemory(cx); + } + + event->ctx = cx; + event->function = JS_DupValue(cx, argv[0]); + event->nargs = argc; + event->args = (JSValue *) &event[1]; + event->destructor = ngx_qjs_clear_timer; + event->fd = ctx->event_id++; + + c = ngx_qjs_external_connection(cx, external); + + event->ev.log = c->log; + event->ev.data = event; + event->ev.handler = ngx_qjs_timer_handler; + + if (event->nargs != 0) { + for (i = 0; i < argc; i++) { + event->args[i] = JS_DupValue(cx, argv[n + i]); + } + } + + ngx_js_add_event(ctx, event); + + ngx_add_timer(&event->ev, delay); + + return JS_NewInt32(cx, event->fd); +} + + +static JSValue +ngx_qjs_ext_clear_timeout(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint32_t id; + ngx_js_ctx_t *ctx; + ngx_qjs_event_t event_lookup, *event; + njs_rbtree_node_t *rb; + + if (JS_ToUint32(cx, &id, argv[0]) < 0) { + return JS_EXCEPTION; + } + + ctx = ngx_qjs_external_ctx(cx, JS_GetContextOpaque(cx)); + event_lookup.fd = id; + + rb = njs_rbtree_find(&ctx->waiting_events, &event_lookup.node); + if (rb == NULL) { + return JS_ThrowReferenceError(cx, "failed to find timer"); + } + + event = (ngx_qjs_event_t *) ((u_char *) rb + - offsetof(ngx_qjs_event_t, node)); + + ngx_js_del_event(ctx, event); + + return JS_UNDEFINED; +} + + +static JSValue +ngx_qjs_ext_build(JSContext *cx, JSValueConst this_val) +{ + return JS_NewStringLen(cx, +#ifdef NGX_BUILD + (char *) NGX_BUILD, + njs_strlen(NGX_BUILD) +#else + (char *) "", + 0 +#endif + ); +} + + +static JSValue +ngx_qjs_ext_conf_prefix(JSContext *cx, JSValueConst this_val) +{ + return JS_NewStringLen(cx, (char *) ngx_cycle->prefix.data, + ngx_cycle->prefix.len); +} + + +static JSValue +ngx_qjs_ext_conf_file_path(JSContext *cx, JSValueConst this_val) +{ + return JS_NewStringLen(cx, (char *) ngx_cycle->conf_file.data, + ngx_cycle->conf_file.len); +} + + +static JSValue +ngx_qjs_ext_constant_integer(JSContext *cx, JSValueConst this_val, int magic) +{ + return JS_NewInt32(cx, magic); +} + + +static JSValue +ngx_qjs_ext_error_log_path(JSContext *cx, JSValueConst this_val) +{ + return JS_NewStringLen(cx, (char *) ngx_cycle->error_log.data, + ngx_cycle->error_log.len); +} + + +static JSValue +ngx_qjs_ext_prefix(JSContext *cx, JSValueConst this_val) +{ + return JS_NewStringLen(cx, (char *) ngx_cycle->prefix.data, + ngx_cycle->prefix.len); +} + + +static JSValue +ngx_qjs_ext_worker_id(JSContext *cx, JSValueConst this_val) +{ + return JS_NewInt32(cx, ngx_worker); +} + + +static void +ngx_qjs_console_finalizer(JSRuntime *rt, JSValue val) +{ + ngx_queue_t *labels, *q, *next; + ngx_js_console_t *console; + ngx_js_timelabel_t *label; + + console = JS_GetOpaque(val, NGX_QJS_CLASS_ID_CONSOLE); + if (console == (void *) 1) { + return; + } + + labels = &console->labels; + q = ngx_queue_head(labels); + + for ( ;; ) { + if (q == ngx_queue_sentinel(labels)) { + break; + } + + next = ngx_queue_next(q); + + label = ngx_queue_data(q, ngx_js_timelabel_t, queue); + ngx_queue_remove(&label->queue); + js_free_rt(rt, label); + + q = next; + } + + js_free_rt(rt, console); +} + + +static JSValue +ngx_qjs_ext_log(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv, int magic) +{ + char *p; + uint32_t level; + ngx_str_t msg; + ngx_js_ctx_t *ctx; + ngx_connection_t *c; + + p = JS_GetContextOpaque(cx); + if (p == NULL) { + return JS_ThrowInternalError(cx, "external is not set"); + } + + level = magic & NGX_JS_LOG_MASK; + + if (level == 0) { + if (JS_ToUint32(cx, &level, argv[0]) < 0) { + return JS_EXCEPTION; + } + + argc--; + argv++; + } + + ctx = ngx_qjs_external_ctx(cx, p); + c = ngx_qjs_external_connection(cx, p); + + for ( ; argc > 0; argc--, argv++) { + if (ngx_qjs_dump_obj(ctx->engine, argv[0], &msg) != NGX_OK) { + return JS_EXCEPTION; + } + + ngx_js_logger(c, level, (u_char *) msg.data, msg.len); + } + + return JS_UNDEFINED; +} + + +static JSValue +ngx_qjs_ext_console_time(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + ngx_str_t name; + ngx_queue_t *labels, *q; + ngx_js_console_t *console; + ngx_connection_t *c; + ngx_js_timelabel_t *label; + + static const ngx_str_t default_label = ngx_string("default"); + + console = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_CONSOLE); + if (console == NULL) { + return JS_ThrowInternalError(cx, "this is not a console object"); + } + + if (console == (void *) 1) { + console = js_malloc(cx, sizeof(ngx_js_console_t)); + if (console == NULL) { + return JS_ThrowOutOfMemory(cx); + } + + ngx_queue_init(&console->labels); + + JS_SetOpaque(this_val, console); + } + + if (!JS_IsUndefined(argv[0])) { + name.data = (u_char *) JS_ToCStringLen(cx, &name.len, argv[0]); + if (name.data == NULL) { + return JS_EXCEPTION; + } + + } else { + name = default_label; + } + + labels = &console->labels; + + for (q = ngx_queue_head(labels); + q != ngx_queue_sentinel(labels); + q = ngx_queue_next(q)) + { + label = ngx_queue_data(q, ngx_js_timelabel_t, queue); + + if (name.len == label->name.length + && ngx_strncmp(name.data, label->name.start, name.len) == 0) + { + c = ngx_qjs_external_connection(cx, JS_GetContextOpaque(cx)); + ngx_log_error(NGX_LOG_INFO, c->log, 0, "js: Timer \"%V\" already" + " exists", &name); + + goto done; + } + } + + label = js_malloc(cx, sizeof(ngx_js_timelabel_t) + name.len); + if (label == NULL) { + if (name.data != default_label.data) { + JS_FreeCString(cx, (char *) name.data); + } + + return JS_ThrowOutOfMemory(cx); + } + + label->name.length = name.len; + label->name.start = (u_char *) label + sizeof(ngx_js_timelabel_t); + memcpy(label->name.start, name.data, name.len); + + label->time = ngx_js_monotonic_time(); + + ngx_queue_insert_tail(&console->labels, &label->queue); + +done: + + if (name.data != default_label.data) { + JS_FreeCString(cx, (char *) name.data); + } + + return JS_UNDEFINED; +} + + +static JSValue +ngx_qjs_ext_console_time_end(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + uint64_t ns, ms; + ngx_str_t name; + ngx_queue_t *labels, *q; + ngx_js_console_t *console; + ngx_connection_t *c; + ngx_js_timelabel_t *label; + + static const ngx_str_t default_label = ngx_string("default"); + + ns = ngx_js_monotonic_time(); + + console = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_CONSOLE); + if (console == NULL) { + return JS_ThrowInternalError(cx, "this is not a console object"); + } + + if (!JS_IsUndefined(argv[0])) { + name.data = (u_char *) JS_ToCStringLen(cx, &name.len, argv[0]); + if (name.data == NULL) { + return JS_EXCEPTION; + } + + } else { + name = default_label; + } + + if (console == (void *) 1) { + goto not_found; + } + + labels = &console->labels; + q = ngx_queue_head(labels); + + for ( ;; ) { + if (q == ngx_queue_sentinel(labels)) { + goto not_found; + } + + label = ngx_queue_data(q, ngx_js_timelabel_t, queue); + + if (name.len == label->name.length + && ngx_strncmp(name.data, label->name.start, name.len) == 0) + { + ngx_queue_remove(&label->queue); + break; + } + + q = ngx_queue_next(q); + } + + ns = ns - label->time; + + js_free(cx, label); + + ms = ns / 1000000; + ns = ns % 1000000; + + c = ngx_qjs_external_connection(cx, JS_GetContextOpaque(cx)); + ngx_log_error(NGX_LOG_INFO, c->log, 0, "js: %V: %uL.%06uLms", + &name, ms, ns); - ngx_log_error(NGX_LOG_EMERG, log, 0, "%*s", text.length, text.start); - return NGX_ERROR; + if (name.data != default_label.data) { + JS_FreeCString(cx, (char *) name.data); } - if (start != end) { - ngx_log_error(NGX_LOG_EMERG, log, 0, - "extra characters in js script: \"%*s\"", - end - start, start); - return NGX_ERROR; + return JS_UNDEFINED; + +not_found: + + c = ngx_qjs_external_connection(cx, JS_GetContextOpaque(cx)); + ngx_log_error(NGX_LOG_INFO, c->log, 0, "js: Timer \"%V\" doesn't exist", + &name); + + if (name.data != default_label.data) { + JS_FreeCString(cx, (char *) name.data); } - return NGX_OK; + return JS_UNDEFINED; } -ngx_engine_t * -ngx_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external) +static JSModuleDef * +ngx_qjs_module_loader(JSContext *cx, const char *module_name, void *opaque) { - njs_vm_t *vm; - njs_int_t rc; - njs_str_t key; - ngx_str_t exception; - ngx_uint_t i; - ngx_engine_t *engine; - njs_opaque_value_t retval; - ngx_js_named_path_t *preload; + JSValue func_val; + njs_int_t ret; + njs_str_t text; + JSModuleDef *m; + njs_module_info_t info; + ngx_js_loc_conf_t *conf; + ngx_js_code_entry_t *pc; - vm = njs_vm_clone(cf->engine->u.njs.vm, external); - if (vm == NULL) { - return NULL; - } + conf = opaque; - engine = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(ngx_engine_t)); - if (engine == NULL) { + njs_memzero(&info, sizeof(njs_module_info_t)); + + info.name.start = (u_char *) module_name; + info.name.length = njs_strlen(module_name); + + ret = ngx_js_module_lookup(conf, &info); + if (ret != NJS_OK) { + JS_ThrowReferenceError(cx, "could not load module filename '%s'", + module_name); return NULL; } - memcpy(engine, cf->engine, sizeof(ngx_engine_t)); - engine->pool = njs_vm_memory_pool(vm); - engine->u.njs.vm = vm; + ret = ngx_js_module_read(conf->engine->pool, info.fd, &text); - /* bind objects from preload vm */ + (void) close(info.fd); - if (cf->preload_objects != NGX_CONF_UNSET_PTR) { - preload = cf->preload_objects->elts; + if (ret != NJS_OK) { + JS_ThrowInternalError(cx, "while reading \"%*s\" module", + (int) info.file.length, info.file.start); + return NULL; + } - for (i = 0; i < cf->preload_objects->nelts; i++) { - key.start = preload[i].name.data; - key.length = preload[i].name.len; + func_val = JS_Eval(cx, (char *) text.start, text.length, module_name, + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); - rc = njs_vm_value(cf->preload_vm, &key, njs_value_arg(&retval)); - if (rc != NJS_OK) { - return NULL; - } + njs_mp_free(conf->engine->pool, text.start); - rc = njs_vm_bind(vm, &key, njs_value_arg(&retval), 0); - if (rc != NJS_OK) { - return NULL; - } - } + if (JS_IsException(func_val)) { + return NULL; } - if (njs_vm_start(vm, njs_value_arg(&retval)) == NJS_ERROR) { - ngx_js_exception(vm, &exception); + if (conf->engine->precompiled == NULL) { + conf->engine->precompiled = njs_arr_create(conf->engine->pool, 4, + sizeof(ngx_js_code_entry_t)); + if (conf->engine->precompiled == NULL) { + JS_FreeValue(cx, func_val); + JS_ThrowOutOfMemory(cx); + return NULL; + } + } - ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception); + pc = njs_arr_add(conf->engine->precompiled); + if (pc == NULL) { + JS_FreeValue(cx, func_val); + JS_ThrowOutOfMemory(cx); + return NULL; + } + pc->code = JS_WriteObject(cx, &pc->code_size, func_val, + JS_WRITE_OBJ_BYTECODE); + if (pc->code == NULL) { + JS_FreeValue(cx, func_val); + JS_ThrowInternalError(cx, "could not write module bytecode"); return NULL; } - return engine; + m = JS_VALUE_GET_PTR(func_val); + JS_FreeValue(cx, func_val); + + return m; } -static ngx_int_t -ngx_engine_njs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname, - njs_opaque_value_t *args, njs_uint_t nargs) +static int +ngx_qjs_unhandled_rejection(ngx_js_ctx_t *ctx) { - njs_vm_t *vm; - njs_int_t ret; - njs_str_t name; - ngx_str_t exception; - njs_function_t *func; + size_t len; + uint32_t i; + JSContext *cx; + const char *str; + ngx_js_rejected_promise_t *rejected_promise; - name.start = fname->data; - name.length = fname->len; + if (ctx->rejected_promises == NULL + || ctx->rejected_promises->items == 0) + { + return 0; + } - vm = ctx->engine->u.njs.vm; + cx = ctx->engine->u.qjs.ctx; + rejected_promise = ctx->rejected_promises->start; - func = njs_vm_function(vm, &name); - if (func == NULL) { - ngx_log_error(NGX_LOG_ERR, ctx->log, 0, - "js function \"%V\" not found", fname); - return NGX_ERROR; + str = JS_ToCStringLen(cx, &len, ngx_qjs_arg(rejected_promise->message)); + if (njs_slow_path(str == NULL)) { + return -1; } - ret = njs_vm_invoke(vm, func, njs_value_arg(args), nargs, - njs_value_arg(&ctx->retval)); - if (ret == NJS_ERROR) { - ngx_js_exception(vm, &exception); - - ngx_log_error(NGX_LOG_ERR, ctx->log, 0, - "js exception: %V", &exception); + JS_ThrowTypeError(cx, "unhandled promise rejection: %*s", (int) len, str); + JS_FreeCString(cx, str); - return NGX_ERROR; + for (i = 0; i < ctx->rejected_promises->items; i++) { + JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].promise)); + JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].message)); } - for ( ;; ) { - ret = njs_vm_execute_pending_job(vm); - if (ret <= NJS_OK) { - if (ret == NJS_ERROR) { - ngx_js_exception(vm, &exception); + njs_arr_destroy(ctx->rejected_promises); + ctx->rejected_promises = NULL; - ngx_log_error(NGX_LOG_ERR, ctx->log, 0, - "js job exception: %V", &exception); - return NGX_ERROR; - } + return 1; +} - break; + +static void +ngx_qjs_rejection_tracker(JSContext *cx, JSValueConst promise, + JSValueConst reason, JS_BOOL is_handled, void *opaque) +{ + void *promise_obj; + uint32_t i, length; + ngx_js_ctx_t *ctx; + ngx_js_rejected_promise_t *rejected_promise; + + ctx = opaque; + + if (is_handled && ctx->rejected_promises != NULL) { + rejected_promise = ctx->rejected_promises->start; + length = ctx->rejected_promises->items; + + promise_obj = JS_VALUE_GET_PTR(promise); + + for (i = 0; i < length; i++) { + if (JS_VALUE_GET_PTR(ngx_qjs_arg(rejected_promise[i].promise)) + == promise_obj) + { + JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].promise)); + JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].message)); + njs_arr_remove(ctx->rejected_promises, &rejected_promise[i]); + + break; + } } + + return; } - if (ngx_js_unhandled_rejection(ctx)) { - ngx_js_exception(vm, &exception); + if (ctx->rejected_promises == NULL) { + if (ctx->engine == NULL) { + /* Do not track rejections during eval stage. The exception + * is lifted by the ngx_qjs_clone() function manually. */ + return; + } - ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception); - return NGX_ERROR; + ctx->rejected_promises = njs_arr_create(ctx->engine->pool, 4, + sizeof(ngx_js_rejected_promise_t)); + if (ctx->rejected_promises == NULL) { + return; + } } - return njs_rbtree_is_empty(&ctx->waiting_events) ? NGX_OK : NGX_AGAIN; + rejected_promise = njs_arr_add(ctx->rejected_promises); + if (rejected_promise == NULL) { + return; + } + + ngx_qjs_arg(rejected_promise->promise) = JS_DupValue(cx, promise); + ngx_qjs_arg(rejected_promise->message) = JS_DupValue(cx, reason); } -static void * -ngx_engine_njs_external(ngx_engine_t *engine) +static JSModuleDef * +ngx_qjs_core_init(JSContext *cx, const char *name) { - return njs_vm_external_ptr(engine->u.njs.vm); -} + int ret; + JSValue global_obj, proto, obj; + JSModuleDef *m; -static ngx_int_t -ngx_engine_njs_pending(ngx_engine_t *e) -{ - return njs_vm_pending(e->u.njs.vm); -} + if (!JS_IsRegisteredClass(JS_GetRuntime(cx), + NGX_QJS_CLASS_ID_CONSOLE)) + { + if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_CONSOLE, + &ngx_qjs_console_class) < 0) + { + return NULL; + } + proto = JS_NewObject(cx); + if (JS_IsException(proto)) { + return NULL; + } -static ngx_int_t -ngx_engine_njs_string(ngx_engine_t *e, njs_opaque_value_t *value, - ngx_str_t *str) -{ - ngx_int_t rc; - njs_str_t s; + JS_SetPropertyFunctionList(cx, proto, ngx_qjs_ext_console, + njs_nitems(ngx_qjs_ext_console)); - rc = ngx_js_string(e->u.njs.vm, njs_value_arg(value), &s); + JS_SetClassProto(cx, NGX_QJS_CLASS_ID_CONSOLE, proto); + } - str->data = s.start; - str->len = s.length; + obj = JS_NewObject(cx); + if (JS_IsException(obj)) { + return NULL; + } - return rc; -} + JS_SetPropertyFunctionList(cx, obj, ngx_qjs_ext_ngx, + njs_nitems(ngx_qjs_ext_ngx)); + global_obj = JS_GetGlobalObject(cx); -static void -ngx_engine_njs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, - ngx_js_loc_conf_t *conf) -{ - ngx_js_event_t *event; - njs_rbtree_node_t *node; + JS_SetPropertyFunctionList(cx, global_obj, ngx_qjs_ext_global, + njs_nitems(ngx_qjs_ext_global)); - if (ctx != NULL) { - node = njs_rbtree_min(&ctx->waiting_events); + ret = JS_SetPropertyStr(cx, global_obj, "ngx", obj); + if (ret < 0) { + JS_FreeValue(cx, global_obj); + return NULL; + } - while (njs_rbtree_is_there_successor(&ctx->waiting_events, node)) { - event = (ngx_js_event_t *) ((u_char *) node - - offsetof(ngx_js_event_t, node)); + obj = JS_NewObjectClass(cx, NGX_QJS_CLASS_ID_CONSOLE); + if (JS_IsException(obj)) { + JS_FreeValue(cx, global_obj); + return NULL; + } - if (event->destructor != NULL) { - event->destructor(event); - } + JS_SetOpaque(obj, (void *) 1); - node = njs_rbtree_node_successor(&ctx->waiting_events, node); - } + ret = JS_SetPropertyStr(cx, global_obj, "console", obj); + if (ret < 0) { + JS_FreeValue(cx, global_obj); + return NULL; } - njs_vm_destroy(e->u.njs.vm); - - /* - * when ctx !=NULL e->pool is vm pool, in such case it is destroyed - * by njs_vm_destroy(). - */ + JS_FreeValue(cx, global_obj); - if (ctx == NULL) { - njs_mp_destroy(e->pool); + m = JS_NewCModule(cx, name, NULL); + if (m == NULL) { + return NULL; } + + return m; } +#endif + ngx_int_t ngx_js_call(njs_vm_t *vm, njs_function_t *func, njs_opaque_value_t *args, @@ -2439,11 +3937,18 @@ ngx_js_create_conf(ngx_conf_t *cf, size_t size) return NULL; } + /* + * set by ngx_pcalloc(): + * + * conf->reuse_queue = NULL; + */ + conf->paths = NGX_CONF_UNSET_PTR; conf->type = NGX_CONF_UNSET_UINT; conf->imports = NGX_CONF_UNSET_PTR; conf->preload_objects = NGX_CONF_UNSET_PTR; + conf->reuse = NGX_CONF_UNSET_SIZE; conf->buffer_size = NGX_CONF_UNSET_SIZE; conf->max_response_body_size = NGX_CONF_UNSET_SIZE; conf->timeout = NGX_CONF_UNSET_MSEC; @@ -2507,6 +4012,7 @@ ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child, ngx_conf_merge_uint_value(conf->type, prev->type, NGX_ENGINE_NJS); ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000); + ngx_conf_merge_size_value(conf->reuse, prev->reuse, 128); ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 16384); ngx_conf_merge_size_value(conf->max_response_body_size, prev->max_response_body_size, 1048576); @@ -2562,3 +4068,59 @@ ngx_js_monotonic_time(void) return (uint64_t) tv.tv_sec * 1000000000 + tv.tv_usec * 1000; #endif } + + +ngx_js_queue_t * +ngx_js_queue_create(ngx_pool_t *pool, ngx_uint_t capacity) +{ + ngx_js_queue_t *queue; + + queue = ngx_pcalloc(pool, sizeof(ngx_js_queue_t)); + if (queue == NULL) { + return NULL; + } + + queue->data = ngx_pcalloc(pool, sizeof(void *) * capacity); + if (queue->data == NULL) { + return NULL; + } + + queue->head = 0; + queue->tail = 0; + queue->size = 0; + queue->capacity = capacity; + + return queue; +} + + +ngx_int_t +ngx_js_queue_push(ngx_js_queue_t *queue, void *item) +{ + if (queue->size >= queue->capacity) { + return NGX_ERROR; + } + + queue->data[queue->tail] = item; + queue->tail = (queue->tail + 1) % queue->capacity; + queue->size++; + + return NGX_OK; +} + + +void * +ngx_js_queue_pop(ngx_js_queue_t *queue) +{ + void *item; + + if (queue->size == 0) { + return NULL; + } + + item = queue->data[queue->head]; + queue->head = (queue->head + 1) % queue->capacity; + queue->size--; + + return item; +} diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index a3bbd541..8b6fbc85 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -19,8 +19,12 @@ #include "ngx_js_fetch.h" #include "ngx_js_shared_dict.h" +#if (NJS_HAVE_QUICKJS) +#include +#endif #define NGX_ENGINE_NJS 1 +#define NGX_ENGINE_QJS 2 #define NGX_JS_UNSET 0 #define NGX_JS_DEPRECATED 1 @@ -37,6 +41,26 @@ #define ngx_js_buffer_type(btype) ((btype) & ~NGX_JS_DEPRECATED) +/* + * This static table solves the problem of a native QuickJS approach + * which uses a static variables of type JSClassID and JS_NewClassID() to + * allocate class ids for custom classes. The static variables approach + * causes a problem when two modules linked with -Wl,-Bsymbolic-functions flag + * are loaded dynamically. + */ + +#define NGX_QJS_CLASS_ID_OFFSET (QJS_CORE_CLASS_ID_LAST) +#define NGX_QJS_CLASS_ID_CONSOLE (NGX_QJS_CLASS_ID_OFFSET + 1) +#define NGX_QJS_CLASS_ID_HTTP_REQUEST (NGX_QJS_CLASS_ID_OFFSET + 2) +#define NGX_QJS_CLASS_ID_HTTP_PERIODIC (NGX_QJS_CLASS_ID_OFFSET + 3) +#define NGX_QJS_CLASS_ID_HTTP_VARS (NGX_QJS_CLASS_ID_OFFSET + 4) +#define NGX_QJS_CLASS_ID_HTTP_HEADERS_IN (NGX_QJS_CLASS_ID_OFFSET + 5) +#define NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT (NGX_QJS_CLASS_ID_OFFSET + 6) +#define NGX_QJS_CLASS_ID_STREAM_SESSION (NGX_QJS_CLASS_ID_OFFSET + 7) +#define NGX_QJS_CLASS_ID_STREAM_PERIODIC (NGX_QJS_CLASS_ID_OFFSET + 8) +#define NGX_QJS_CLASS_ID_STREAM_FLAGS (NGX_QJS_CLASS_ID_OFFSET + 9) +#define NGX_QJS_CLASS_ID_STREAM_VARS (NGX_QJS_CLASS_ID_OFFSET + 10) + typedef struct ngx_js_loc_conf_s ngx_js_loc_conf_t; typedef struct ngx_js_event_s ngx_js_event_t; @@ -76,6 +100,15 @@ struct ngx_js_event_s { }; +typedef struct { + void **data; + ngx_uint_t head; + ngx_uint_t tail; + ngx_uint_t size; + ngx_uint_t capacity; +} ngx_js_queue_t; + + #define NGX_JS_COMMON_MAIN_CONF \ ngx_js_dict_t *dicts; \ ngx_array_t *periodics \ @@ -84,6 +117,8 @@ struct ngx_js_event_s { #define _NGX_JS_COMMON_LOC_CONF \ ngx_uint_t type; \ ngx_engine_t *engine; \ + ngx_uint_t reuse; \ + ngx_js_queue_t *reuse_queue; \ ngx_str_t cwd; \ ngx_array_t *imports; \ ngx_array_t *paths; \ @@ -157,6 +192,11 @@ struct ngx_js_ctx_s { }; +typedef struct { + void *external; +} ngx_js_opaque_t; + + typedef struct ngx_engine_opts_s { unsigned engine; union { @@ -164,6 +204,12 @@ typedef struct ngx_engine_opts_s { njs_vm_meta_t *metas; njs_module_t **addons; } njs; +#if (NJS_HAVE_QUICKJS) + struct { + uintptr_t *metas; + qjs_module_t **addons; + } qjs; +#endif } u; njs_str_t file; @@ -176,11 +222,22 @@ typedef struct ngx_engine_opts_s { } ngx_engine_opts_t; +typedef struct { + u_char *code; + size_t code_size; +} ngx_js_code_entry_t; + + struct ngx_engine_s { union { struct { njs_vm_t *vm; } njs; +#if (NJS_HAVE_QUICKJS) + struct { + JSContext *ctx; + } qjs; +#endif } u; ngx_int_t (*compile)(ngx_js_loc_conf_t *conf, ngx_log_t *lg, @@ -202,6 +259,7 @@ struct ngx_engine_s { unsigned type; const char *name; njs_mp_t *pool; + njs_arr_t *precompiled; }; @@ -246,6 +304,7 @@ void ngx_js_ctx_init(ngx_js_ctx_t *ctx, ngx_log_t *log); #define ngx_js_ctx_external(ctx) \ ((ctx)->engine->external(ctx->engine)) + void ngx_js_ctx_destroy(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *conf); ngx_int_t ngx_js_call(njs_vm_t *vm, njs_function_t *func, njs_opaque_value_t *args, njs_uint_t nargs); @@ -253,6 +312,74 @@ ngx_int_t ngx_js_exception(njs_vm_t *vm, ngx_str_t *s); ngx_engine_t *ngx_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external); +#if (NJS_HAVE_QUICKJS) + +typedef struct ngx_qjs_event_s ngx_qjs_event_t; + +typedef union { + njs_opaque_value_t opaque; + JSValue value; +} ngx_qjs_value_t; + +struct ngx_qjs_event_s { + void *ctx; + JSValue function; + JSValue *args; + ngx_socket_t fd; + NJS_RBTREE_NODE (node); + njs_uint_t nargs; + void (*destructor)(ngx_qjs_event_t *event); + ngx_event_t ev; + void *data; +}; + +#define ngx_qjs_arg(val) (((ngx_qjs_value_t *) &(val))->value) +ngx_engine_t *ngx_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, + void *external); +void ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, + ngx_js_loc_conf_t *conf); +ngx_int_t ngx_qjs_call(ngx_js_ctx_t *ctx, JSValue function, + JSValue *argv, int argc); +ngx_int_t ngx_qjs_exception(ngx_engine_t *e, ngx_str_t *s); +ngx_int_t ngx_qjs_integer(JSContext *cx, JSValueConst val, ngx_int_t *n); +ngx_int_t ngx_qjs_string(ngx_engine_t *e, JSValueConst val, ngx_str_t *str); + +#define ngx_qjs_prop(cx, type, start, len) \ + ((type == NGX_JS_STRING) ? qjs_string_create(cx, start, len) \ + : qjs_buffer_create(cx, (u_char *) start, len)) + +#define ngx_qjs_meta(cx, i) \ + ((uintptr_t *) JS_GetRuntimeOpaque(JS_GetRuntime(cx)))[i] +#define ngx_qjs_external_connection(cx, e) \ + (*((ngx_connection_t **) ((u_char *) (e) + ngx_qjs_meta(cx, 0)))) +#define ngx_qjs_external_pool(cx, e) \ + ((ngx_external_pool_pt) ngx_qjs_meta(cx, 1))(e) +#define ngx_qjs_external_resolver(cx, e) \ + ((ngx_external_resolver_pt) ngx_qjs_meta(vm, 2))(e) +#define ngx_qjs_external_resolver_timeout(cx, e) \ + ((ngx_external_timeout_pt) ngx_qjs_meta(cx, 3))(e) +#define ngx_qjs_external_event_finalize(cx) \ + ((ngx_js_event_finalize_pt) ngx_qjs_meta(cx, 4)) +#define ngx_qjs_external_ssl(cx, e) \ + ((ngx_external_ssl_pt) ngx_qjs_meta(cx, 5))(e) +#define ngx_qjs_external_ssl_verify(cx, e) \ + ((ngx_external_flag_pt) ngx_qjs_meta(cx, 6))(e) +#define ngx_qjs_external_fetch_timeout(cx, e) \ + ((ngx_external_timeout_pt) ngx_qjs_meta(cx, 7))(e) +#define ngx_qjs_external_buffer_size(cx, e) \ + ((ngx_external_size_pt) ngx_qjs_meta(cx, 8))(e) +#define ngx_qjs_external_max_response_buffer_size(cx, e) \ + ((ngx_external_size_pt) ngx_qjs_meta(cx, 9))(e) +#define ngx_qjs_main_conf(cx) \ + ((ngx_js_main_conf_t *) ngx_qjs_meta(cx, NGX_JS_MAIN_CONF_INDEX)) +#define ngx_qjs_external_ctx(cx, e) \ + ((ngx_js_external_ctx_pt) ngx_qjs_meta(cx, 11))(e) + +extern qjs_module_t qjs_zlib_module; +extern qjs_module_t ngx_qjs_ngx_module; + +#endif + njs_int_t ngx_js_ext_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t level, njs_value_t *retval); void ngx_js_log(njs_vm_t *vm, njs_external_ptr_t external, @@ -286,6 +413,10 @@ njs_int_t ngx_js_ext_flags(njs_vm_t *vm, njs_object_prop_t *prop, ngx_int_t ngx_js_string(njs_vm_t *vm, njs_value_t *value, njs_str_t *str); ngx_int_t ngx_js_integer(njs_vm_t *vm, njs_value_t *value, ngx_int_t *n); +ngx_js_queue_t *ngx_js_queue_create(ngx_pool_t *pool, ngx_uint_t capacity); +ngx_int_t ngx_js_queue_push(ngx_js_queue_t *queue, void *item); +void *ngx_js_queue_pop(ngx_js_queue_t *queue); + extern njs_module_t ngx_js_ngx_module; extern njs_module_t njs_webcrypto_module; diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index 565f4e66..98427aae 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -75,6 +75,21 @@ struct ngx_stream_js_ctx_s { }; +#if (NJS_HAVE_QUICKJS) + +typedef struct { + ngx_str_t name; + ngx_uint_t data_type; + ngx_uint_t id; +} ngx_stream_qjs_event_t; + +typedef struct { + ngx_stream_session_t *session; + JSValue callbacks[NGX_JS_EVENT_MAX]; +} ngx_stream_qjs_session_t; + +#endif + #define ngx_stream_pending(ctx) \ (ngx_js_ctx_pending(ctx) || ngx_stream_js_pending_events(ctx)) @@ -128,6 +143,57 @@ static njs_int_t ngx_stream_js_periodic_variables(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); +#if (NJS_HAVE_QUICKJS) + +static JSValue ngx_stream_qjs_ext_to_string_tag(JSContext *cx, + JSValueConst this_val); +static JSValue ngx_stream_qjs_ext_done(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); +static JSValue ngx_stream_qjs_ext_log(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv, int level); +static JSValue ngx_stream_qjs_ext_on(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue ngx_stream_qjs_ext_off(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue ngx_stream_qjs_ext_periodic_to_string_tag(JSContext *cx, + JSValueConst this_val); +static JSValue ngx_stream_qjs_ext_periodic_variables(JSContext *cx, + JSValueConst this_val, int type); +static JSValue ngx_stream_qjs_ext_remote_address(JSContext *cx, + JSValueConst this_val); +static JSValue ngx_stream_qjs_ext_send(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv, int from_upstream); +static JSValue ngx_stream_qjs_ext_set_return_value(JSContext *cx, + JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue ngx_stream_qjs_ext_variables(JSContext *cx, + JSValueConst this_val, int type); +static JSValue ngx_stream_qjs_ext_uint(JSContext *cx, JSValueConst this_val, + int offset); +static JSValue ngx_stream_qjs_ext_flag(JSContext *cx, JSValueConst this_val, + int mask); + +static int ngx_stream_qjs_variables_own_property(JSContext *cx, + JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop); +static int ngx_stream_qjs_variables_set_property(JSContext *cx, + JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, + int flags); +static int ngx_stream_qjs_variables_define_own_property(JSContext *cx, + JSValueConst obj, JSAtom prop, JSValueConst value, JSValueConst getter, + JSValueConst setter, int flags); + +static ngx_int_t ngx_stream_qjs_run_event(ngx_stream_session_t *s, + ngx_stream_js_ctx_t *ctx, ngx_stream_js_ev_t *event, + ngx_uint_t from_upstream); +static ngx_int_t ngx_stream_qjs_body_filter(ngx_stream_session_t *s, + ngx_stream_js_ctx_t *ctx, ngx_chain_t *in, ngx_uint_t from_upstream); + +static ngx_stream_session_t *ngx_stream_qjs_session(JSValueConst val); +static JSValue ngx_stream_qjs_session_make(JSContext *cx, ngx_int_t proto_id, + ngx_stream_session_t *s); +static void ngx_stream_qjs_session_finalizer(JSRuntime *rt, JSValue val); + +#endif + static ngx_pool_t *ngx_stream_js_pool(ngx_stream_session_t *s); static ngx_resolver_t *ngx_stream_js_resolver(ngx_stream_session_t *s); static ngx_msec_t ngx_stream_js_resolver_timeout(ngx_stream_session_t *s); @@ -167,6 +233,9 @@ static ngx_flag_t ngx_stream_js_ssl_verify(ngx_stream_session_t *s); static ngx_conf_bitmask_t ngx_stream_js_engines[] = { { ngx_string("njs"), NGX_ENGINE_NJS }, +#if (NJS_HAVE_QUICKJS) + { ngx_string("qjs"), NGX_ENGINE_QJS }, +#endif { ngx_null_string, 0 } }; @@ -191,6 +260,13 @@ static ngx_command_t ngx_stream_js_commands[] = { offsetof(ngx_stream_js_srv_conf_t, type), &ngx_stream_js_engines }, + { ngx_string("js_context_reuse"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_js_srv_conf_t, reuse), + NULL }, + { ngx_string("js_import"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE13, ngx_js_import, @@ -649,9 +725,9 @@ static njs_vm_meta_t ngx_stream_js_metas = { static ngx_stream_filter_pt ngx_stream_next_filter; -static njs_int_t ngx_stream_js_session_proto_id; -static njs_int_t ngx_stream_js_periodic_session_proto_id; -static njs_int_t ngx_stream_js_session_flags_proto_id; +static njs_int_t ngx_stream_js_session_proto_id = 1; +static njs_int_t ngx_stream_js_periodic_session_proto_id = 2; +static njs_int_t ngx_stream_js_session_flags_proto_id = 3; njs_module_t ngx_js_stream_module = { @@ -682,6 +758,96 @@ njs_module_t *njs_stream_js_addon_modules[] = { NULL, }; +#if (NJS_HAVE_QUICKJS) + +static const JSCFunctionListEntry ngx_stream_qjs_ext_session[] = { + JS_CGETSET_DEF("[Symbol.toStringTag]", ngx_stream_qjs_ext_to_string_tag, + NULL), + JS_CFUNC_MAGIC_DEF("allow", 1, ngx_stream_qjs_ext_done, NGX_OK), + JS_CFUNC_MAGIC_DEF("decline", 1, ngx_stream_qjs_ext_done, -NGX_DECLINED), + JS_CFUNC_MAGIC_DEF("deny", 1, ngx_stream_qjs_ext_done, -NGX_DONE), + JS_CFUNC_MAGIC_DEF("done", 1, ngx_stream_qjs_ext_done, NGX_OK), + JS_CFUNC_MAGIC_DEF("error", 1, ngx_stream_qjs_ext_log, NGX_LOG_ERR), + JS_CFUNC_MAGIC_DEF("log", 1, ngx_stream_qjs_ext_log, NGX_LOG_INFO), + JS_CFUNC_DEF("on", 2, ngx_stream_qjs_ext_on), + JS_CFUNC_DEF("off", 1, ngx_stream_qjs_ext_off), + JS_CGETSET_MAGIC_DEF("rawVariables", ngx_stream_qjs_ext_variables, + NULL, NGX_JS_BUFFER), + JS_CGETSET_DEF("remoteAddress", ngx_stream_qjs_ext_remote_address, NULL), + JS_CFUNC_MAGIC_DEF("send", 2, ngx_stream_qjs_ext_send, NGX_JS_BOOL_UNSET), + JS_CFUNC_MAGIC_DEF("sendDownstream", 1, ngx_stream_qjs_ext_send, + NGX_JS_BOOL_TRUE), + JS_CFUNC_MAGIC_DEF("sendUpstream", 1, ngx_stream_qjs_ext_send, + NGX_JS_BOOL_FALSE), + JS_CFUNC_DEF("setReturnValue", 1, ngx_stream_qjs_ext_set_return_value), + JS_CGETSET_MAGIC_DEF("status", ngx_stream_qjs_ext_uint, NULL, + offsetof(ngx_stream_session_t, status)), + JS_CGETSET_MAGIC_DEF("variables", ngx_stream_qjs_ext_variables, + NULL, NGX_JS_STRING), + JS_CFUNC_MAGIC_DEF("warn", 1, ngx_stream_qjs_ext_log, NGX_LOG_WARN), +}; + + +static const JSCFunctionListEntry ngx_stream_qjs_ext_periodic[] = { + JS_CGETSET_DEF("[Symbol.toStringTag]", + ngx_stream_qjs_ext_periodic_to_string_tag, NULL), + JS_CGETSET_MAGIC_DEF("rawVariables", ngx_stream_qjs_ext_periodic_variables, + NULL, NGX_JS_BUFFER), + JS_CGETSET_MAGIC_DEF("variables", ngx_stream_qjs_ext_periodic_variables, + NULL, NGX_JS_STRING), +}; + + +static const JSCFunctionListEntry ngx_stream_qjs_ext_flags[] = { + JS_CGETSET_MAGIC_DEF("from_upstream", ngx_stream_qjs_ext_flag, NULL, + 2), + JS_CGETSET_MAGIC_DEF("last", ngx_stream_qjs_ext_flag, NULL, 1), +}; + + +static JSClassDef ngx_stream_qjs_session_class = { + "Session", + .finalizer = ngx_stream_qjs_session_finalizer, +}; + + +static JSClassDef ngx_stream_qjs_periodic_class = { + "Periodic", + .finalizer = NULL, +}; + + +static JSClassDef ngx_stream_qjs_flags_class = { + "Stream Flags", + .finalizer = NULL, +}; + + +static JSClassDef ngx_stream_qjs_variables_class = { + "Variables", + .finalizer = NULL, + .exotic = & (JSClassExoticMethods) { + .get_own_property = ngx_stream_qjs_variables_own_property, + .set_property = ngx_stream_qjs_variables_set_property, + .define_own_property = ngx_stream_qjs_variables_define_own_property, + }, +}; + + +qjs_module_t *njs_stream_qjs_addon_modules[] = { + &ngx_qjs_ngx_module, + /* + * Shared addons should be in the same order and the same positions + * in all nginx modules. + */ +#ifdef NJS_HAVE_ZLIB + &qjs_zlib_module, +#endif + NULL, +}; + +#endif + static ngx_int_t ngx_stream_js_access_handler(ngx_stream_session_t *s) @@ -783,7 +949,6 @@ ngx_stream_js_body_filter(ngx_stream_session_t *s, ngx_chain_t *in, { ngx_int_t rc; ngx_chain_t *out; - ngx_connection_t *c; ngx_stream_js_ctx_t *ctx; ngx_stream_js_srv_conf_t *jscf; @@ -792,10 +957,8 @@ ngx_stream_js_body_filter(ngx_stream_session_t *s, ngx_chain_t *in, return ngx_stream_next_filter(s, in, from_upstream); } - c = s->connection; - - ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream js filter u:%ui", - from_upstream); + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream js filter u:%ui", from_upstream); rc = ngx_stream_js_init_vm(s, ngx_stream_js_session_proto_id); @@ -810,7 +973,7 @@ ngx_stream_js_body_filter(ngx_stream_session_t *s, ngx_chain_t *in, ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); if (!ctx->filter) { - ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, "stream js filter call \"%V\"" , &jscf->filter); rc = ctx->engine->call((ngx_js_ctx_t *) ctx, &jscf->filter, @@ -990,9 +1153,9 @@ ngx_stream_js_init_vm(ngx_stream_session_t *s, njs_int_t proto_id) return NGX_ERROR; } - ngx_log_debug2(NGX_LOG_DEBUG_STREAM, ctx->log, 0, - "stream js vm clone: %p from: %p", ctx->engine, - jscf->engine); + ngx_log_debug3(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "stream js vm clone %s: %p from: %p", jscf->engine->name, + ctx->engine, jscf->engine); cln = ngx_pool_cleanup_add(s->connection->pool, 0); if (cln == NULL) { @@ -1806,25 +1969,950 @@ ngx_engine_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, } -static ngx_int_t -ngx_stream_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf) +#if (NJS_HAVE_QUICKJS) + +static JSValue +ngx_stream_qjs_ext_to_string_tag(JSContext *cx, JSValueConst this_val) { - ngx_engine_opts_t options; - ngx_js_main_conf_t *jmcf; + return JS_NewString(cx, "Stream Session"); +} - memset(&options, 0, sizeof(ngx_engine_opts_t)); - options.engine = conf->type; +static JSValue +ngx_stream_qjs_ext_done(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv, int magic) +{ + ngx_int_t status; + ngx_stream_js_ctx_t *ctx; + ngx_stream_session_t *s; - if (conf->type == NGX_ENGINE_NJS) { - jmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_js_module); - ngx_stream_js_uptr[NGX_JS_MAIN_CONF_INDEX] = (uintptr_t) jmcf; + s = ngx_stream_qjs_session(this_val); + if (s == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + } - options.u.njs.metas = &ngx_stream_js_metas; - options.u.njs.addons = njs_stream_js_addon_modules; - options.clone = ngx_engine_njs_clone; + status = (ngx_int_t) magic; + status = -status; + + if (status == NGX_DONE) { + status = NGX_STREAM_FORBIDDEN; + } + + if (!JS_IsUndefined(argv[0])) { + if (ngx_qjs_integer(cx, argv[0], &status) != NGX_OK) { + return JS_EXCEPTION; + } + + if (status < NGX_ABORT || status > NGX_STREAM_SERVICE_UNAVAILABLE) { + return JS_ThrowInternalError(cx, "code is out of range"); + } + } + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); + + if (ctx->filter) { + return JS_ThrowInternalError(cx, "should not be called while " + "filtering"); + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream js set status: %i", status); + + ctx->status = status; + + ngx_stream_js_drop_events(ctx); + + return JS_UNDEFINED; +} + + +static JSValue +ngx_stream_qjs_ext_log(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv, int level) +{ + int n; + const char *msg; + ngx_stream_session_t *s; + + s = ngx_stream_qjs_session(this_val); + if (s == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + } + + for (n = 0; n < argc; n++) { + msg = JS_ToCString(cx, argv[n]); + + ngx_js_logger(s->connection, level, (u_char *) msg, ngx_strlen(msg)); + + JS_FreeCString(cx, msg); + } + + return JS_UNDEFINED; +} + + +static const ngx_stream_qjs_event_t * +ngx_stream_qjs_event(ngx_stream_session_t *s, JSContext *cx, ngx_str_t *event) +{ + ngx_uint_t i, n, type; + ngx_stream_js_ctx_t *ctx; + + static const ngx_stream_qjs_event_t events[] = { + { + ngx_string("upload"), + NGX_JS_STRING, + NGX_JS_EVENT_UPLOAD, + }, + + { + ngx_string("download"), + NGX_JS_STRING, + NGX_JS_EVENT_DOWNLOAD, + }, + + { + ngx_string("upstream"), + NGX_JS_BUFFER, + NGX_JS_EVENT_UPLOAD, + }, + + { + ngx_string("downstream"), + NGX_JS_BUFFER, + NGX_JS_EVENT_DOWNLOAD, + }, + }; + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); + + i = 0; + n = sizeof(events) / sizeof(events[0]); + + while (i < n) { + if (event->len == events[i].name.len + && ngx_memcmp(event->data, events[i].name.data, event->len) + == 0) + { + break; + } + + i++; + } + + if (i == n) { + (void) JS_ThrowInternalError(cx, "unknown event \"%*s\"", + (int) event->len, event->data); + return NULL; + } + + ctx->events[events[i].id].data_type = events[i].data_type; + + for (n = 0; n < NGX_JS_EVENT_MAX; n++) { + type = ctx->events[n].data_type; + if (type != NGX_JS_UNSET && type != events[i].data_type) { + (void) JS_ThrowInternalError(cx, "mixing string and buffer" + " events is not allowed"); + return NULL; + } + } + + return &events[i]; +} + + +static JSValue +ngx_stream_qjs_ext_on(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + ngx_str_t name; + ngx_stream_js_ctx_t *ctx; + ngx_stream_qjs_session_t *ses; + const ngx_stream_qjs_event_t *e; + + ses = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_STREAM_SESSION); + if (ses == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + } + + ctx = ngx_stream_get_module_ctx(ses->session, ngx_stream_js_module); + + if (ngx_qjs_string(ctx->engine, argv[0], &name) != NGX_OK) { + return JS_EXCEPTION; + } + + e = ngx_stream_qjs_event(ses->session, cx, &name); + if (e == NULL) { + return JS_EXCEPTION; + } + + if (JS_IsFunction(cx, ngx_qjs_arg(ctx->events[e->id].function))) { + return JS_ThrowInternalError(cx, "event handler \"%s\" is already set", + name.data); + } + + if (!JS_IsFunction(cx, argv[1])) { + return JS_ThrowTypeError(cx, "callback is not a function"); + } + + ngx_qjs_arg(ctx->events[e->id].function) = argv[1]; + + JS_FreeValue(cx, ses->callbacks[e->id]); + ses->callbacks[e->id] = JS_DupValue(cx, argv[1]); + + return JS_UNDEFINED; +} + + +static JSValue +ngx_stream_qjs_ext_off(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + ngx_str_t name; + ngx_stream_js_ctx_t *ctx; + ngx_stream_session_t *s; + const ngx_stream_qjs_event_t *e; + + s = ngx_stream_qjs_session(this_val); + if (s == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + } + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); + + if (ngx_qjs_string(ctx->engine, argv[0], &name) != NGX_OK) { + return JS_EXCEPTION; + } + + e = ngx_stream_qjs_event(s, cx, &name); + if (e == NULL) { + return JS_EXCEPTION; + } + + ngx_qjs_arg(ctx->events[e->id].function) = JS_NULL; + ctx->events[e->id].data_type = NGX_JS_UNSET; + + return JS_UNDEFINED; +} + + +static JSValue +ngx_stream_qjs_ext_periodic_to_string_tag(JSContext *cx, + JSValueConst this_val) +{ + return JS_NewString(cx, "PeriodicSession"); +} + + +static JSValue +ngx_stream_qjs_ext_periodic_variables(JSContext *cx, + JSValueConst this_val, int type) +{ + JSValue obj; + ngx_stream_qjs_session_t *ses; + + ses = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_STREAM_PERIODIC); + if (ses == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a periodic object"); + } + + obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_STREAM_VARS); + + /* + * Using lowest bit of the pointer to store the buffer type. + */ + type = (type == NGX_JS_BUFFER) ? 1 : 0; + JS_SetOpaque(obj, (void *) ((uintptr_t) ses->session | (uintptr_t) type)); + + return obj; +} + + +static JSValue +ngx_stream_qjs_ext_remote_address(JSContext *cx, JSValueConst this_val) +{ + ngx_connection_t *c; + ngx_stream_session_t *s; + + s = ngx_stream_qjs_session(this_val); + if (s == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + } + + c = s->connection; + + return qjs_string_create(cx, c->addr_text.data, c->addr_text.len); +} + + +static JSValue +ngx_stream_qjs_ext_send(JSContext *cx, JSValueConst this_val, int argc, + JSValueConst *argv, int from_upstream) +{ + JSValue val; + unsigned last_buf, flush; + ngx_str_t buffer; + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_connection_t *c; + ngx_stream_js_ctx_t *ctx; + ngx_stream_session_t *s; + + s = ngx_stream_qjs_session(this_val); + if (s == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + } + + c = s->connection; + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); + + if (!ctx->filter) { + return JS_ThrowInternalError(cx, "cannot send buffer in this handler"); + } + + if (ngx_qjs_string(ctx->engine, argv[0], &buffer) != NGX_OK) { + return JS_EXCEPTION; } + /* + * ctx->buf != NULL when s.send() is called while processing incoming + * data chunks, otherwise s.send() is called asynchronously + */ + + if (ctx->buf != NULL) { + flush = ctx->buf->flush; + last_buf = ctx->buf->last_buf; + + } else { + flush = 0; + last_buf = 0; + } + + if (JS_IsObject(argv[1])) { + val = JS_GetPropertyStr(cx, argv[1], "flush"); + if (JS_IsException(val)) { + return JS_EXCEPTION; + } + + if (!JS_IsUndefined(val)) { + flush = JS_ToBool(cx, val); + JS_FreeValue(cx, val); + } + + val = JS_GetPropertyStr(cx, argv[1], "last"); + if (JS_IsException(val)) { + return JS_EXCEPTION; + } + + if (!JS_IsUndefined(val)) { + last_buf = JS_ToBool(cx, val); + JS_FreeValue(cx, val); + } + + if (from_upstream == NGX_JS_BOOL_UNSET) { + val = JS_GetPropertyStr(cx, argv[1], "from_upstream"); + if (JS_IsException(val)) { + return JS_EXCEPTION; + } + + if (!JS_IsUndefined(val)) { + from_upstream = JS_ToBool(cx, val); + JS_FreeValue(cx, val); + } + + if (from_upstream == NGX_JS_BOOL_UNSET && ctx->buf == NULL) { + return JS_ThrowInternalError(cx, "from_upstream flag is " + "expected when called " + "asynchronously"); + } + } + } + + cl = ngx_chain_get_free_buf(c->pool, &ctx->free); + if (cl == NULL) { + return JS_ThrowInternalError(cx, "memory error"); + } + + b = cl->buf; + + b->flush = flush; + b->last_buf = last_buf; + + b->memory = (buffer.len ? 1 : 0); + b->sync = (buffer.len ? 0 : 1); + b->tag = (ngx_buf_tag_t) &ngx_stream_js_module; + + b->start = buffer.data; + b->end = buffer.data + buffer.len; + + b->pos = b->start; + b->last = b->end; + + if (from_upstream == NGX_JS_BOOL_UNSET) { + *ctx->last_out = cl; + ctx->last_out = &cl->next; + + } else { + + if (ngx_stream_js_next_filter(s, ctx, cl, from_upstream) == NGX_ERROR) { + return JS_ThrowInternalError(cx, "ngx_stream_js_next_filter() " + "failed"); + } + } + + return JS_UNDEFINED; +} + + +static JSValue +ngx_stream_qjs_ext_set_return_value(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + ngx_js_ctx_t *ctx; + ngx_stream_session_t *s; + + s = ngx_stream_qjs_session(this_val); + if (s == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + } + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); + + JS_FreeValue(cx, ngx_qjs_arg(ctx->retval)); + ngx_qjs_arg(ctx->retval) = JS_DupValue(cx, argv[0]); + + return JS_UNDEFINED; +} + + +static JSValue +ngx_stream_qjs_ext_variables(JSContext *cx, JSValueConst this_val, int type) +{ + JSValue obj; + ngx_stream_session_t *s; + + s = ngx_stream_qjs_session(this_val); + if (s == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + } + + obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_STREAM_VARS); + + /* + * Using lowest bit of the pointer to store the buffer type. + */ + type = (type == NGX_JS_BUFFER) ? 1 : 0; + JS_SetOpaque(obj, (void *) ((uintptr_t) s | (uintptr_t) type)); + + return obj; +} + + +static JSValue +ngx_stream_qjs_ext_uint(JSContext *cx, JSValueConst this_val, int offset) +{ + ngx_uint_t *field; + ngx_stream_session_t *s; + + s = ngx_stream_qjs_session(this_val); + if (s == NULL) { + return JS_ThrowInternalError(cx, "\"this\" is not a session object"); + } + + field = (ngx_uint_t *) ((u_char *) s + offset); + + return JS_NewUint32(cx, *field); +} + + +static JSValue +ngx_stream_qjs_ext_flag(JSContext *cx, JSValueConst this_val, int mask) +{ + uintptr_t flags; + + flags = (uintptr_t) JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_STREAM_FLAGS); + + return JS_NewBool(cx, flags & mask); +} + + +static int +ngx_stream_qjs_variables_own_property(JSContext *cx, + JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop) +{ + uint32_t buffer_type; + ngx_str_t name; + ngx_uint_t key; + ngx_stream_session_t *s; + ngx_stream_variable_value_t *vv; + + s = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_STREAM_VARS); + + buffer_type = ((uintptr_t) s & 1) ? NGX_JS_BUFFER : NGX_JS_STRING; + s = (ngx_stream_session_t *) ((uintptr_t) s & ~(uintptr_t) 1); + + if (s == NULL) { + (void) JS_ThrowInternalError(cx, "\"this\" is not a session object"); + return -1; + } + + name.data = (u_char *) JS_AtomToCString(cx, prop); + if (name.data == NULL) { + return -1; + } + + name.len = ngx_strlen(name.data); + + key = ngx_hash_strlow(name.data, name.data, name.len); + + vv = ngx_stream_get_variable(s, &name, key); + JS_FreeCString(cx, (char *) name.data); + if (vv == NULL || vv->not_found) { + return 0; + } + + if (pdesc != NULL) { + pdesc->flags = JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE; + pdesc->getter = JS_UNDEFINED; + pdesc->setter = JS_UNDEFINED; + pdesc->value = ngx_qjs_prop(cx, buffer_type, vv->data, vv->len); + } + + return 1; +} + + +static int +ngx_stream_qjs_variables_set_property(JSContext *cx, JSValueConst obj, + JSAtom prop, JSValueConst value, JSValueConst receiver, int flags) +{ + ngx_str_t name, val; + ngx_uint_t key; + ngx_js_ctx_t *ctx; + ngx_stream_session_t *s; + ngx_stream_variable_t *v; + ngx_stream_variable_value_t *vv; + ngx_stream_core_main_conf_t *cmcf; + + s = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_STREAM_VARS); + + s = (ngx_stream_session_t *) ((uintptr_t) s & ~(uintptr_t) 1); + + if (s == NULL) { + (void) JS_ThrowInternalError(cx, "\"this\" is not a session object"); + return -1; + } + + name.data = (u_char *) JS_AtomToCString(cx, prop); + if (name.data == NULL) { + return -1; + } + + name.len = ngx_strlen(name.data); + + key = ngx_hash_strlow(name.data, name.data, name.len); + + cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module); + + v = ngx_hash_find(&cmcf->variables_hash, key, name.data, name.len); + JS_FreeCString(cx, (char *) name.data); + + if (v == NULL) { + (void) JS_ThrowInternalError(cx, "variable not found"); + return -1; + } + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); + + if (ngx_qjs_string(ctx->engine, value, &val) != NGX_OK) { + return -1; + } + + if (v->set_handler != NULL) { + vv = ngx_pcalloc(s->connection->pool, + sizeof(ngx_stream_variable_value_t)); + if (vv == NULL) { + (void) JS_ThrowOutOfMemory(cx); + return -1; + } + + vv->valid = 1; + vv->not_found = 0; + vv->data = val.data; + vv->len = val.len; + + v->set_handler(s, vv, v->data); + + return 1; + } + + if (!(v->flags & NGX_STREAM_VAR_INDEXED)) { + (void) JS_ThrowTypeError(cx, "variable is not writable"); + return -1; + } + + vv = &s->variables[v->index]; + + vv->valid = 1; + vv->not_found = 0; + + vv->data = ngx_pnalloc(s->connection->pool, val.len); + if (vv->data == NULL) { + vv->valid = 0; + (void) JS_ThrowOutOfMemory(cx); + return -1; + } + + vv->len = val.len; + ngx_memcpy(vv->data, val.data, vv->len); + + return 1; +} + + +static int +ngx_stream_qjs_variables_define_own_property(JSContext *cx, + JSValueConst obj, JSAtom prop, JSValueConst value, JSValueConst getter, + JSValueConst setter, int flags) +{ + if (!JS_IsUndefined(setter) || !JS_IsUndefined(getter)) { + (void) JS_ThrowTypeError(cx, "cannot define getter or setter"); + return -1; + } + + return ngx_stream_qjs_variables_set_property(cx, obj, prop, value, obj, + flags); +} + + +static ngx_int_t +ngx_stream_qjs_run_event(ngx_stream_session_t *s, ngx_stream_js_ctx_t *ctx, + ngx_stream_js_ev_t *event, ngx_uint_t from_upstream) +{ + size_t len; + u_char *p; + JSContext *cx; + ngx_int_t rc; + ngx_str_t exception; + ngx_buf_t *b; + uintptr_t flags; + ngx_connection_t *c; + JSValue argv[2]; + + cx = ctx->engine->u.qjs.ctx; + + if (!JS_IsFunction(cx, ngx_qjs_arg(event->function))) { + return NGX_OK; + } + + c = s->connection; + b = ctx->filter ? ctx->buf : c->buffer; + + len = b ? b->last - b->pos : 0; + + p = ngx_pnalloc(c->pool, len); + if (p == NULL) { + (void) JS_ThrowOutOfMemory(cx); + goto error; + } + + if (len) { + ngx_memcpy(p, b->pos, len); + } + + argv[0] = ngx_qjs_prop(cx, event->data_type, p, len); + if (JS_IsException(argv[0])) { + goto error; + } + + argv[1] = JS_NewObjectClass(cx, NGX_QJS_CLASS_ID_STREAM_FLAGS); + if (JS_IsException(argv[1])) { + JS_FreeValue(cx, argv[0]); + goto error; + } + + flags = from_upstream << 1 | (uintptr_t) (b && b->last_buf); + + JS_SetOpaque(argv[1], (void *) flags); + + rc = ngx_qjs_call((ngx_js_ctx_t *) ctx, ngx_qjs_arg(event->function), + &argv[0], 2); + JS_FreeValue(cx, argv[0]); + JS_FreeValue(cx, argv[1]); + + if (rc == NGX_ERROR) { +error: + ngx_qjs_exception(ctx->engine, &exception); + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %V", + &exception); + + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_qjs_body_filter(ngx_stream_session_t *s, ngx_stream_js_ctx_t *ctx, + ngx_chain_t *in, ngx_uint_t from_upstream) +{ + ngx_int_t rc; + JSContext *cx; + ngx_chain_t *cl; + ngx_stream_js_ev_t *event; + + cx = ctx->engine->u.qjs.ctx; + + while (in != NULL) { + ctx->buf = in->buf; + + event = ngx_stream_event(from_upstream); + + if (JS_IsFunction(cx, ngx_qjs_arg(event->function))) { + rc = ngx_stream_qjs_run_event(s, ctx, event, from_upstream); + if (rc != NGX_OK) { + return NGX_ERROR; + } + + ctx->buf->pos = ctx->buf->last; + + } else { + cl = ngx_alloc_chain_link(s->connection->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = ctx->buf; + + *ctx->last_out = cl; + ctx->last_out = &cl->next; + } + + in = in->next; + } + + return NGX_OK; +} + + +static ngx_stream_session_t * +ngx_stream_qjs_session(JSValueConst val) +{ + ngx_stream_qjs_session_t *ses; + + ses = JS_GetOpaque(val, NGX_QJS_CLASS_ID_STREAM_SESSION); + if (ses == NULL) { + return NULL; + } + + return ses->session; +} + + +static JSValue +ngx_stream_qjs_session_make(JSContext *cx, ngx_int_t proto_id, + ngx_stream_session_t *s) +{ + JSValue session; + ngx_uint_t i; + ngx_stream_qjs_session_t *ses; + + session = JS_NewObjectClass(cx, proto_id); + if (JS_IsException(session)) { + return JS_EXCEPTION; + } + + ses = js_malloc(cx, sizeof(ngx_stream_qjs_session_t)); + if (ses == NULL) { + return JS_ThrowOutOfMemory(cx); + } + + ses->session = s; + + for (i = 0; i < NGX_JS_EVENT_MAX; i++) { + ses->callbacks[i] = JS_UNDEFINED; + } + + JS_SetOpaque(session, ses); + + return session; +} + + +static void +ngx_stream_qjs_session_finalizer(JSRuntime *rt, JSValue val) +{ + ngx_uint_t i; + ngx_stream_qjs_session_t *ses; + + ses = JS_GetOpaque(val, NGX_QJS_CLASS_ID_STREAM_SESSION); + if (ses == NULL) { + return; + } + + for (i = 0; i < NGX_JS_EVENT_MAX; i++) { + JS_FreeValueRT(rt, ses->callbacks[i]); + } + + js_free_rt(rt, ses); +} + + +static ngx_engine_t * +ngx_engine_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, + njs_int_t proto_id, void *external) +{ + JSValue proto; + JSContext *cx; + ngx_engine_t *engine; + ngx_stream_js_ctx_t *sctx; + + engine = ngx_qjs_clone(ctx, cf, external); + if (engine == NULL) { + return NULL; + } + + cx = engine->u.qjs.ctx; + + if (!JS_IsRegisteredClass(JS_GetRuntime(cx), + NGX_QJS_CLASS_ID_STREAM_SESSION)) + { + if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_STREAM_SESSION, + &ngx_stream_qjs_session_class) < 0) + { + return NULL; + } + + proto = JS_NewObject(cx); + if (JS_IsException(proto)) { + return NULL; + } + + JS_SetPropertyFunctionList(cx, proto, ngx_stream_qjs_ext_session, + njs_nitems(ngx_stream_qjs_ext_session)); + + JS_SetClassProto(cx, NGX_QJS_CLASS_ID_STREAM_SESSION, proto); + + if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_STREAM_PERIODIC, + &ngx_stream_qjs_periodic_class) < 0) + { + return NULL; + } + + proto = JS_NewObject(cx); + if (JS_IsException(proto)) { + return NULL; + } + + JS_SetPropertyFunctionList(cx, proto, ngx_stream_qjs_ext_periodic, + njs_nitems(ngx_stream_qjs_ext_periodic)); + + JS_SetClassProto(cx, NGX_QJS_CLASS_ID_STREAM_PERIODIC, proto); + + if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_STREAM_FLAGS, + &ngx_stream_qjs_flags_class) < 0) + { + return NULL; + } + + proto = JS_NewObject(cx); + if (JS_IsException(proto)) { + return NULL; + } + + JS_SetPropertyFunctionList(cx, proto, ngx_stream_qjs_ext_flags, + njs_nitems(ngx_stream_qjs_ext_flags)); + + JS_SetClassProto(cx, NGX_QJS_CLASS_ID_STREAM_FLAGS, proto); + + if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_STREAM_VARS, + &ngx_stream_qjs_variables_class) < 0) + { + return NULL; + } + } + + sctx = (ngx_stream_js_ctx_t *) ctx; + sctx->run_event = ngx_stream_qjs_run_event; + sctx->body_filter = ngx_stream_qjs_body_filter; + + if (proto_id == ngx_stream_js_session_proto_id) { + proto_id = NGX_QJS_CLASS_ID_STREAM_SESSION; + + } else if (proto_id == ngx_stream_js_periodic_session_proto_id) { + proto_id = NGX_QJS_CLASS_ID_STREAM_PERIODIC; + } + + ngx_qjs_arg(ctx->args[0]) = ngx_stream_qjs_session_make(cx, proto_id, + external); + if (JS_IsException(ngx_qjs_arg(ctx->args[0]))) { + return NULL; + } + + return engine; +} + + +static void +ngx_stream_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, + ngx_js_loc_conf_t *conf) +{ + ngx_uint_t i; + JSValue cb; + ngx_stream_qjs_session_t *ses; + + if (ctx != NULL) { + /* + * explicitly freeing the callback functions + * to avoid circular references with the session object. + */ + ses = JS_GetOpaque(ngx_qjs_arg(ctx->args[0]), + NGX_QJS_CLASS_ID_STREAM_SESSION); + if (ses != NULL) { + for (i = 0; i < NGX_JS_EVENT_MAX; i++) { + cb = ses->callbacks[i]; + ses->callbacks[i] = JS_UNDEFINED; + JS_FreeValue(e->u.qjs.ctx, cb); + } + } + } + + ngx_engine_qjs_destroy(e, ctx, conf); +} + +#endif + + +static ngx_int_t +ngx_stream_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf) +{ + ngx_engine_opts_t options; + ngx_js_main_conf_t *jmcf; + + memset(&options, 0, sizeof(ngx_engine_opts_t)); + + options.engine = conf->type; + + jmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_js_module); + ngx_stream_js_uptr[NGX_JS_MAIN_CONF_INDEX] = (uintptr_t) jmcf; + + if (conf->type == NGX_ENGINE_NJS) { + options.u.njs.metas = &ngx_stream_js_metas; + options.u.njs.addons = njs_stream_js_addon_modules; + options.clone = ngx_engine_njs_clone; + } + +#if (NJS_HAVE_QUICKJS) + else if (conf->type == NGX_ENGINE_QJS) { + options.u.qjs.metas = ngx_stream_js_uptr; + options.u.qjs.addons = njs_stream_qjs_addon_modules; + options.clone = ngx_engine_qjs_clone; + options.destroy = ngx_stream_qjs_destroy; + } +#endif + return ngx_js_init_conf_vm(cf, conf, &options); } @@ -2392,7 +3480,6 @@ ngx_stream_js_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_stream_js_srv_conf_t *prev = parent; ngx_stream_js_srv_conf_t *conf = child; - ngx_conf_merge_uint_value(conf->type, prev->type, NGX_ENGINE_NJS); ngx_conf_merge_str_value(conf->access, prev->access, ""); ngx_conf_merge_str_value(conf->preread, prev->preread, ""); ngx_conf_merge_str_value(conf->filter, prev->filter, ""); diff --git a/nginx/t/js_console.t b/nginx/t/js_console.t index fcaac3a6..c9499169 100644 --- a/nginx/t/js_console.t +++ b/nginx/t/js_console.t @@ -41,6 +41,10 @@ http { listen 127.0.0.1:8080; server_name localhost; + location /engine { + js_content test.engine; + } + location /dump { js_content test.dump; } @@ -74,6 +78,10 @@ http { EOF $t->write_file('test.js', <write_file('test.js', <try_run('no njs console')->plan(7); ############################################################################### +my $engine = http_get('/engine'); + http_get('/dump?data=eyJhIjpbMiwzXX0'); http_get('/error?data=IldBS0Ei'); http_get('/info?data=IkJBUiI'); @@ -136,8 +146,16 @@ $t->stop(); like($t->read_file('error.log'), qr/\[error\].*js: WAKA/, 'console.error'); like($t->read_file('error.log'), qr/\[info\].*js: BAR/, 'console.info'); + +SKIP: { + skip "QuickJS has no console.dump() method.", 1 + if $engine =~ /QuickJS$/m; + like($t->read_file('error.log'), qr/\[info\].*js: \{a:\['B','C'\]\}/, 'console.log with object'); + +} + like($t->read_file('error.log'), qr/\[warn\].*js: FOO/, 'console.warn'); like($t->read_file('error.log'), qr/\[info\].*js: foo: \d+\.\d\d\d\d\d\dms/, 'console.time foo'); diff --git a/nginx/t/js_dump.t b/nginx/t/js_dump.t index c00a53a2..a96c2cd4 100644 --- a/nginx/t/js_dump.t +++ b/nginx/t/js_dump.t @@ -42,6 +42,10 @@ http { listen 127.0.0.1:8080; server_name localhost; + location /engine { + js_content test.engine; + } + location /dump { js_content test.dump; } @@ -63,6 +67,10 @@ http { EOF $t->write_file('test.js', <write_file('test.js', <try_run('no njs dump')->plan(3); ############################################################################### +SKIP: { + skip "QuickJS has no njs.dump() method.", 1 + if http_get('/engine') =~ /QuickJS$/m; + like(http( 'GET /dump?v=1&t=x HTTP/1.0' . CRLF . 'Foo: bar' . CRLF @@ -95,6 +107,12 @@ like(http( . 'Host: localhost' . CRLF . CRLF ), qr/method:'GET'/, 'njs.dump(r)'); +} + +TODO: { + local $TODO = 'in QuickJS these are non-enumerable getter/setter props' + if http_get('/engine') =~ /^(QuickJS)$/m; + like(http( 'GET /stringify?v=1&t=x HTTP/1.0' . CRLF . 'Foo: bar' . CRLF @@ -107,4 +125,6 @@ like(http( . 'Host: localhost' . CRLF . CRLF ), qr/"status":201/, 'JSON.stringify(reply)'); +} + ############################################################################### diff --git a/nginx/t/js_engine.t b/nginx/t/js_engine.t new file mode 100644 index 00000000..e188ccea --- /dev/null +++ b/nginx/t/js_engine.t @@ -0,0 +1,140 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, js_engine directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /njs/ { + proxy_pass http://127.0.0.1:8081/; + } + + location /qjs/ { + proxy_pass http://127.0.0.1:8082/; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + js_engine njs; + + location /test { + js_content test.test; + } + + location /override { + js_engine qjs; + js_content test.test; + } + } + + server { + listen 127.0.0.1:8082; + server_name localhost; + + js_engine qjs; + + location /test { + js_content test.test; + } + + location /override { + js_engine njs; + js_content test.test; + } + } +} + +EOF + +$t->write_file('test.js', <try_run('no njs js_engine')->plan(4); + +############################################################################### + +TODO: { +local $TODO = 'not yet' unless has_version('0.8.6'); + +like(http_get('/njs/test'), qr/njs/, 'js_engine njs server'); +like(http_get('/njs/override'), qr/QuickJS/, 'js_engine override'); +like(http_get('/qjs/test'), qr/QuickJS/, 'js_engine qjs server'); +like(http_get('/qjs/override'), qr/njs/, 'js_engine override'); + +} + +$t->stop(); + +############################################################################### + +sub has_version { + my $need = shift; + + http_get('/njs') =~ /^([.0-9]+)$/m; + + my @v = split(/\./, $1); + my ($n, $v); + + for $n (split(/\./, $need)) { + $v = shift @v || 0; + return 0 if $n > $v; + return 1 if $v > $n; + } + + return 1; +} + +############################################################################### diff --git a/nginx/t/js_fetch.t b/nginx/t/js_fetch.t index e0763a6a..320e06f5 100644 --- a/nginx/t/js_fetch.t +++ b/nginx/t/js_fetch.t @@ -52,6 +52,10 @@ http { js_content test.njs; } + location /engine { + js_content test.engine; + } + location /broken { js_content test.broken; } @@ -134,6 +138,10 @@ $t->write_file('test.js', <write_file('test.js', <try_run('no njs.fetch')->plan(36); +$t->try_run('no njs.fetch'); + +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; + +$t->plan(36); $t->run_daemon(\&http_daemon, port(8082)); $t->waitforsocket('127.0.0.1:' . port(8082)); diff --git a/nginx/t/js_fetch_https.t b/nginx/t/js_fetch_https.t index 9d4ebb0a..9a44a339 100644 --- a/nginx/t/js_fetch_https.t +++ b/nginx/t/js_fetch_https.t @@ -48,6 +48,10 @@ http { js_content test.njs; } + location /engine { + js_content test.engine; + } + location /https { js_content test.https; } @@ -102,6 +106,10 @@ $t->write_file('test.js', <write_file('test.js', < r.return(501, e.message)) } - export default {njs: test_njs, https}; + export default {njs: test_njs, https, engine}; EOF my $d = $t->testdir(); @@ -186,7 +194,11 @@ foreach my $name ('default.example.com', '1.example.com') { . $t->read_file('intermediate.crt')); } -$t->try_run('no njs.fetch')->plan(7); +$t->try_run('no njs.fetch'); + +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; + +$t->plan(7); $t->run_daemon(\&dns_daemon, port(8981), $t); $t->waitforfile($t->testdir . '/' . port(8981)); diff --git a/nginx/t/js_fetch_objects.t b/nginx/t/js_fetch_objects.t index d0f47630..1bc88a3d 100644 --- a/nginx/t/js_fetch_objects.t +++ b/nginx/t/js_fetch_objects.t @@ -45,6 +45,10 @@ http { js_content test.njs; } + location /engine { + js_content test.engine; + } + location /headers { js_content test.headers; } @@ -88,6 +92,10 @@ $t->write_file('test.js', <write_file('test.js', <try_run('no njs')->plan(5); +$t->try_run('no njs'); + +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; + +$t->plan(5); ############################################################################### diff --git a/nginx/t/js_fetch_resolver.t b/nginx/t/js_fetch_resolver.t index 8fb6b66f..7cea3386 100644 --- a/nginx/t/js_fetch_resolver.t +++ b/nginx/t/js_fetch_resolver.t @@ -50,6 +50,10 @@ http { js_content test.njs; } + location /engine { + js_content test.engine; + } + location /dns { js_content test.dns; @@ -104,6 +108,10 @@ $t->write_file('test.js', <write_file('test.js', <try_run('no njs.fetch')->plan(5); +$t->try_run('no njs.fetch'); + +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; + +$t->plan(5); $t->run_daemon(\&dns_daemon, port(8981), $t); $t->waitforfile($t->testdir . '/' . port(8981)); diff --git a/nginx/t/js_fetch_timeout.t b/nginx/t/js_fetch_timeout.t index 486656d6..1ac1c7aa 100644 --- a/nginx/t/js_fetch_timeout.t +++ b/nginx/t/js_fetch_timeout.t @@ -47,6 +47,10 @@ http { js_content test.njs; } + location /engine { + js_content test.engine; + } + location /normal_timeout { js_content test.timeout_test; } @@ -80,6 +84,10 @@ $t->write_file('test.js', <write_file('test.js', < { r.return(200); }, 250, r, 0); } - export default {njs: test_njs, timeout_test, normal_reply, delayed_reply}; + export default {njs: test_njs, engine, timeout_test, normal_reply, + delayed_reply}; EOF -$t->try_run('no js_fetch_timeout')->plan(2); +$t->try_run('no js_fetch_timeout'); + +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; + +$t->plan(2); ############################################################################### diff --git a/nginx/t/js_fetch_verify.t b/nginx/t/js_fetch_verify.t index d6bb1d9e..4c97e04d 100644 --- a/nginx/t/js_fetch_verify.t +++ b/nginx/t/js_fetch_verify.t @@ -48,6 +48,10 @@ http { js_content test.njs; } + location /engine { + js_content test.engine; + } + location /https { js_content test.https; } @@ -76,6 +80,10 @@ $t->write_file('test.js', < reply.text()) @@ -83,7 +91,7 @@ $t->write_file('test.js', < r.return(501, e.message)); } - export default {njs: test_njs, https}; + export default {njs: test_njs, engine, https}; EOF $t->write_file('openssl.conf', <try_run('no js_fetch_verify')->plan(2); +$t->try_run('no js_fetch_verify'); + +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; + +$t->plan(2); $t->run_daemon(\&dns_daemon, port(8981), $t); $t->waitforfile($t->testdir . '/' . port(8981)); diff --git a/nginx/t/js_object.t b/nginx/t/js_object.t index 97e778a2..8c154010 100644 --- a/nginx/t/js_object.t +++ b/nginx/t/js_object.t @@ -42,6 +42,10 @@ http { listen 127.0.0.1:8080; server_name localhost; + location /engine { + js_content test.engine; + } + location /to_string { js_content test.to_string; } @@ -75,6 +79,10 @@ http { EOF $t->write_file('test.js', <write_file('test.js', <write_file('test.js', <write_file('test.js', <try_run('no js_periodic')->plan(9); +$t->try_run('no js_periodic'); + +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; + +$t->plan(9); ############################################################################### diff --git a/nginx/t/js_preload_object.t b/nginx/t/js_preload_object.t index 407e97fe..49befd66 100644 --- a/nginx/t/js_preload_object.t +++ b/nginx/t/js_preload_object.t @@ -45,6 +45,10 @@ http { js_import lib.js; js_preload_object lx from l.json; + location /engine { + js_content lib.engine; + } + location /test { js_content lib.test; } @@ -83,6 +87,10 @@ $t->write_file('lib.js', <write_file('lib.js', <write_file('ga.json', '"ga loaded"'); $t->write_file('l.json', '"l loaded"'); $t->write_file('no_suffix', '"no_suffix loaded"'); -$t->try_run('no js_preload_object available')->plan(12); +$t->try_run('no js_preload_object available'); + +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; + +$t->plan(12); ############################################################################### diff --git a/nginx/t/js_shared_dict.t b/nginx/t/js_shared_dict.t index ffc286e2..16128225 100644 --- a/nginx/t/js_shared_dict.t +++ b/nginx/t/js_shared_dict.t @@ -51,6 +51,10 @@ http { js_content test.njs; } + location /engine { + js_content test.engine; + } + location /add { js_content test.add; } @@ -132,6 +136,10 @@ $t->write_file('test.js', <<'EOF'); r.return(200, njs.version); } + function engine(r) { + r.return(200, njs.engine); + } + function convertToValue(dict, v) { if (dict.type == 'number') { return parseInt(v); @@ -257,7 +265,7 @@ $t->write_file('test.js', <<'EOF'); function pop(r) { var dict = ngx.shared[r.args.dict]; - var val = dict.pop(r.args.key); + var val = dict.pop(r.args.key); if (val == '') { val = 'empty'; @@ -302,10 +310,14 @@ $t->write_file('test.js', <<'EOF'); export default { add, capacity, chain, clear, del, free_space, get, has, incr, items, keys, name, njs: test_njs, pop, replace, set, - set_clear, size, zones }; + set_clear, size, zones, engine }; EOF -$t->try_run('no js_shared_dict_zone')->plan(51); +$t->try_run('no js_shared_dict_zone'); + +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; + +$t->plan(51); ############################################################################### diff --git a/nginx/t/stream_js_console.t b/nginx/t/stream_js_console.t index c3c22800..0c253289 100644 --- a/nginx/t/stream_js_console.t +++ b/nginx/t/stream_js_console.t @@ -34,6 +34,21 @@ daemon off; events { } +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /engine { + js_content test.engine; + } + } +} + stream { %%TEST_GLOBALS_STREAM%% @@ -41,7 +56,7 @@ stream { server { - listen 127.0.0.1:8080; + listen 127.0.0.1:8081; js_preread test.log; @@ -49,7 +64,7 @@ stream { } server { - listen 127.0.0.1:8081; + listen 127.0.0.1:8082; js_preread test.timer; @@ -60,6 +75,10 @@ stream { EOF $t->write_file('test.js', < 0) { @@ -85,7 +104,7 @@ $t->write_file('test.js', <run_daemon(\&stream_daemon, port(8090)); @@ -94,14 +113,23 @@ $t->waitforsocket('127.0.0.1:' . port(8090)); ############################################################################### -is(stream('127.0.0.1:' . port(8080))->io('eyJhIjpbIkIiLCJDIl19'), +my $engine = http_get('/engine'); + +is(stream('127.0.0.1:' . port(8081))->io('eyJhIjpbIkIiLCJDIl19'), 'eyJhIjpbIkIiLCJDIl19', 'log test'); -is(stream('127.0.0.1:' . port(8081))->io('timer'), 'timer', 'timer test'); +is(stream('127.0.0.1:' . port(8082))->io('timer'), 'timer', 'timer test'); $t->stop(); +SKIP: { + skip "QuickJS has no console.dump() method.", 1 + if $engine =~ /QuickJS$/m; + like($t->read_file('error.log'), qr/\[info\].*js: \{a:\['B','C'\]\}/, 'console.log with object'); + +} + like($t->read_file('error.log'), qr/\[info\].*js: foo: \d+\.\d\d\d\d\d\dms/, 'console.time foo'); diff --git a/nginx/t/stream_js_exit.t b/nginx/t/stream_js_exit.t index a8bc34ae..01778f0f 100644 --- a/nginx/t/stream_js_exit.t +++ b/nginx/t/stream_js_exit.t @@ -45,6 +45,10 @@ http { location /njs { js_content test.njs; } + + location /engine { + js_content test.engine; + } } } @@ -74,6 +78,10 @@ $t->write_file('test.js', < { var v = s.variables; @@ -95,10 +103,14 @@ $t->write_file('test.js', <try_run('no stream njs available')->plan(2); +$t->try_run('no stream njs available'); + +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; + +$t->plan(2); $t->run_daemon(\&stream_daemon, port(8090)); $t->waitforsocket('127.0.0.1:' . port(8090)); diff --git a/nginx/t/stream_js_fetch.t b/nginx/t/stream_js_fetch.t index 106702dc..c57128a8 100644 --- a/nginx/t/stream_js_fetch.t +++ b/nginx/t/stream_js_fetch.t @@ -46,6 +46,10 @@ http { js_content test.njs; } + location /engine { + js_content test.engine; + } + location /validate { js_content test.validate; } @@ -99,6 +103,10 @@ $t->write_file('test.js', <write_file('test.js', <try_run('no stream njs available')->plan(9); +$t->try_run('no stream njs available'); + +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; + +$t->plan(9); $t->run_daemon(\&stream_daemon, port(8090), port(8091)); $t->waitforsocket('127.0.0.1:' . port(8090)); diff --git a/nginx/t/stream_js_fetch_https.t b/nginx/t/stream_js_fetch_https.t index c49b833b..5d7c5c20 100644 --- a/nginx/t/stream_js_fetch_https.t +++ b/nginx/t/stream_js_fetch_https.t @@ -47,6 +47,10 @@ http { location /njs { js_content test.njs; } + + location /engine { + js_content test.engine; + } } server { @@ -159,6 +163,10 @@ $t->write_file('test.js', <write_file('test.js', <testdir(); @@ -263,7 +271,11 @@ foreach my $name ('default.example.com', '1.example.com') { . $t->read_file('intermediate.crt')); } -$t->try_run('no njs.fetch')->plan(6); +$t->try_run('no njs.fetch'); + +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; + +$t->plan(6); $t->run_daemon(\&dns_daemon, port(8981), $t); $t->waitforfile($t->testdir . '/' . port(8981)); diff --git a/nginx/t/stream_js_fetch_init.t b/nginx/t/stream_js_fetch_init.t index 6de487da..3f6d7262 100644 --- a/nginx/t/stream_js_fetch_init.t +++ b/nginx/t/stream_js_fetch_init.t @@ -58,6 +58,10 @@ http { js_content test.njs; } + location /engine { + js_content test.engine; + } + location /success { return 200; } @@ -73,16 +77,24 @@ $t->write_file('test.js', <try_run('no stream njs available')->plan(1); +$t->try_run('no stream njs available'); + +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; + +$t->plan(1); $t->run_daemon(\&stream_daemon, port(8090)); $t->waitforsocket('127.0.0.1:' . port(8090)); diff --git a/nginx/t/stream_js_object.t b/nginx/t/stream_js_object.t index 504b9348..571d0a87 100644 --- a/nginx/t/stream_js_object.t +++ b/nginx/t/stream_js_object.t @@ -33,35 +33,76 @@ daemon off; events { } +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /engine { + js_content test.engine; + } + } +} + stream { %%TEST_GLOBALS_STREAM%% - js_set $test test.test; - js_import test.js; + js_set $to_string test.to_string; + js_set $define_prop test.define_prop; + js_set $in_operator test.in_operator; + js_set $redefine_proto test.redefine_proto; + js_set $get_own_prop_descs test.get_own_prop_descs; + server { listen 127.0.0.1:8081; - return $test$status; + return $to_string; + } + + server { + listen 127.0.0.1:8082; + return $define_prop$status; + } + + server { + listen 127.0.0.1:8083; + return $in_operator; + } + + server { + listen 127.0.0.1:8084; + return $redefine_proto; + } + + server { + listen 127.0.0.1:8085; + return $get_own_prop_descs; } } EOF $t->write_file('test.js', <v in s.variables) - .toString() === 'true,false'; + return ['status', 'unknown'].map(v=>v in s.variables).toString(); } function redefine_proto(s) { @@ -76,23 +117,27 @@ $t->write_file('test.js', <v(s)); - } - - export default {test}; + export default { engine, to_string, define_prop, in_operator, + redefine_proto, get_own_prop_descs }; EOF -$t->try_run('no njs stream session object')->plan(1); +$t->try_run('no njs stream session object')->plan(5); ############################################################################### -is(stream('127.0.0.1:' . port(8081))->read(), 'true400', 'var set'); +is(stream('127.0.0.1:' . port(8081))->read(), '[object Stream Session]', + 'to_string'); +is(stream('127.0.0.1:' . port(8082))->read(), '400400', 'define_prop'); +is(stream('127.0.0.1:' . port(8083))->read(), 'true,false', 'in_operator'); +is(stream('127.0.0.1:' . port(8084))->read(), 'true', 'redefine_proto'); + +SKIP: { + skip "In QuickJS methods are in the prototype", 1 + if http_get('/engine') =~ /QuickJS$/m; + +is(stream('127.0.0.1:' . port(8085))->read(), 'true', 'get_own_prop_descs'); + +} ############################################################################### diff --git a/nginx/t/stream_js_preload_object.t b/nginx/t/stream_js_preload_object.t index 3c27098d..34ffae2b 100644 --- a/nginx/t/stream_js_preload_object.t +++ b/nginx/t/stream_js_preload_object.t @@ -33,6 +33,21 @@ daemon off; events { } +http { + %%TEST_GLOBALS_HTTP%% + + js_import main.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /engine { + js_content main.engine; + } + } +} + stream { %%TEST_GLOBALS_STREAM%% @@ -104,14 +119,22 @@ $t->write_file('lib.js', <write_file('main.js', <write_file('g.json', '{"a":1, "b":[1,2,"element",4,5], "c":{"prop":[{"a":3}]}}'); -$t->try_run('no js_preload_object available')->plan(2); +$t->try_run('no js_preload_object available'); + +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; + +$t->plan(2); ############################################################################### diff --git a/nginx/t/stream_js_shared_dict.t b/nginx/t/stream_js_shared_dict.t index e8e482f4..0bdfaeb7 100644 --- a/nginx/t/stream_js_shared_dict.t +++ b/nginx/t/stream_js_shared_dict.t @@ -43,6 +43,10 @@ http { location / { return 200; } + + location /engine { + js_content test.engine; + } } } @@ -71,6 +75,10 @@ EOF $t->write_file('test.js', <write_file('test.js', <try_run('no js_shared_dict_zone')->plan(9); +$t->try_run('no js_shared_dict_zone'); + +plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; + +$t->plan(9); $t->run_daemon(\&stream_daemon, port(8090)); $t->waitforsocket('127.0.0.1:' . port(8090)); diff --git a/src/qjs.h b/src/qjs.h index dff5919b..2418e6cd 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -33,6 +33,12 @@ #include +#define QJS_CORE_CLASS_ID_OFFSET 64 +#define QJS_CORE_CLASS_ID_BUFFER (QJS_CORE_CLASS_ID_OFFSET) +#define QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR (QJS_CORE_CLASS_ID_OFFSET + 1) +#define QJS_CORE_CLASS_ID_LAST (QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR) + + typedef JSModuleDef *(*qjs_addon_init_pt)(JSContext *ctx, const char *name); typedef struct { diff --git a/src/qjs_buffer.c b/src/qjs_buffer.c index 06574110..2487c633 100644 --- a/src/qjs_buffer.c +++ b/src/qjs_buffer.c @@ -262,15 +262,12 @@ static JSClassDef qjs_buffer_class = { }; -static JSClassID qjs_buffer_class_id; - #ifndef NJS_HAVE_QUICKJS_NEW_TYPED_ARRAY static JSClassDef qjs_uint8_array_ctor_class = { "Uint8ArrayConstructor", .finalizer = NULL, }; -static JSClassID qjs_uint8_array_ctor_id; #endif @@ -354,7 +351,7 @@ qjs_buffer_ctor(JSContext *ctx, JSValueConst this_val, int argc, return ret; } - proto = JS_GetClassProto(ctx, qjs_buffer_class_id); + proto = JS_GetClassProto(ctx, QJS_CORE_CLASS_ID_BUFFER); JS_SetPrototype(ctx, ret, proto); JS_FreeValue(ctx, proto); @@ -725,7 +722,7 @@ qjs_buffer_is_buffer(JSContext *ctx, JSValueConst this_val, JSValue proto, buffer_proto, ret; proto = JS_GetPrototype(ctx, argv[0]); - buffer_proto = JS_GetClassProto(ctx, qjs_buffer_class_id); + buffer_proto = JS_GetClassProto(ctx, QJS_CORE_CLASS_ID_BUFFER); ret = JS_NewBool(ctx, JS_VALUE_GET_TAG(argv[0]) == JS_TAG_OBJECT && JS_VALUE_GET_OBJ(buffer_proto) == JS_VALUE_GET_OBJ(proto)); @@ -2426,7 +2423,7 @@ qjs_buffer_alloc(JSContext *ctx, size_t size) return ret; } - proto = JS_GetClassProto(ctx, qjs_buffer_class_id); + proto = JS_GetClassProto(ctx, QJS_CORE_CLASS_ID_BUFFER); JS_SetPrototype(ctx, ret, proto); JS_FreeValue(ctx, proto); @@ -2494,7 +2491,7 @@ qjs_new_uint8_array(JSContext *ctx, int argc, JSValueConst *argv) #else JSValue ctor; - ctor = JS_GetClassProto(ctx, qjs_uint8_array_ctor_id); + ctor = JS_GetClassProto(ctx, QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR); ret = JS_CallConstructor(ctx, ctor, argc, argv); JS_FreeValue(ctx, ctor); #endif @@ -2511,8 +2508,8 @@ qjs_buffer_builtin_init(JSContext *ctx) JSValue global_obj, buffer, proto, ctor, ta, ta_proto, symbol, species; JSClassID u8_ta_class_id; - JS_NewClassID(&qjs_buffer_class_id); - JS_NewClass(JS_GetRuntime(ctx), qjs_buffer_class_id, &qjs_buffer_class); + JS_NewClass(JS_GetRuntime(ctx), QJS_CORE_CLASS_ID_BUFFER, + &qjs_buffer_class); global_obj = JS_GetGlobalObject(ctx); @@ -2528,10 +2525,10 @@ qjs_buffer_builtin_init(JSContext *ctx) * We use JS_SetClassProto()/JS_GetClassProto() as a key-value store * for fast value query by class ID without querying the global object. */ - JS_NewClassID(&qjs_uint8_array_ctor_id); - JS_NewClass(JS_GetRuntime(ctx), qjs_uint8_array_ctor_id, + JS_NewClass(JS_GetRuntime(ctx), QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR, &qjs_uint8_array_ctor_class); - JS_SetClassProto(ctx, qjs_uint8_array_ctor_id, JS_DupValue(ctx, ctor)); + JS_SetClassProto(ctx, QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR, + JS_DupValue(ctx, ctor)); #endif ta = JS_CallConstructor(ctx, ctor, 0, NULL); @@ -2543,7 +2540,7 @@ qjs_buffer_builtin_init(JSContext *ctx) JS_SetPrototype(ctx, proto, ta_proto); JS_FreeValue(ctx, ta_proto); - JS_SetClassProto(ctx, qjs_buffer_class_id, proto); + JS_SetClassProto(ctx, QJS_CORE_CLASS_ID_BUFFER, proto); buffer = JS_NewCFunction2(ctx, qjs_buffer, "Buffer", 3, JS_CFUNC_constructor, 0); From noreply at nginx.com Wed Sep 18 04:53:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 18 Sep 2024 04:53:02 +0000 (UTC) Subject: [njs] HTTP: fixed r.subrequest() check for nested subrequests. Message-ID: <20240918045302.4C3314766F@pubserv1.nginx> details: https://github.com/nginx/njs/commit/c2bc8c6501b1712f75ea6a82a153383d3d3d2017 branches: master commit: c2bc8c6501b1712f75ea6a82a153383d3d3d2017 user: Dmitry Volyntsev date: Tue, 17 Sep 2024 19:17:09 -0700 description: HTTP: fixed r.subrequest() check for nested subrequests. The issue was introduced in a14be61c86 (0.8.5). This fixes #783 on Github. --- nginx/ngx_http_js_module.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 4a50a949..dec65198 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -3358,7 +3358,7 @@ ngx_http_js_ext_subrequest(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); - if (r->main != r) { + if (r->subrequest_in_memory) { njs_vm_error(vm, "subrequest can only be created for " "the primary request"); return NJS_ERROR; @@ -5722,7 +5722,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); - if (r->main != r) { + if (r->subrequest_in_memory) { return JS_ThrowTypeError(cx, "subrequest can only be created for " "the primary request"); } From noreply at nginx.com Fri Sep 20 10:44:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 20 Sep 2024 10:44:02 +0000 (UTC) Subject: [nginx] SSL: optional ssl_client_certificate for ssl_verify_client. Message-ID: <20240920104402.8EA5C47691@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/18afcda938cd2d4712d0d083b57161290a5a2d34 branches: master commit: 18afcda938cd2d4712d0d083b57161290a5a2d34 user: Sergey Kandaurov date: Fri, 20 Sep 2024 14:08:42 +0400 description: SSL: optional ssl_client_certificate for ssl_verify_client. Starting from TLSv1.1 (as seen since draft-ietf-tls-rfc2246-bis-00), the "certificate_authorities" field grammar of the CertificateRequest message was redone to allow no distinguished names. In TLSv1.3, with the restructured CertificateRequest message, this can be similarly done by optionally including the "certificate_authorities" extension. This allows to avoid sending DNs at all. In practice, aside from published TLS specifications, all supported SSL/TLS libraries allow to request client certificates with an empty DN list for any protocol version. For instance, when operating in TLSv1, this results in sending the "certificate_authorities" list as a zero-length vector, which corresponds to the TLSv1.1 specification. Such behaviour goes back to SSLeay. The change relaxes the requirement to specify at least one trusted CA certificate in the ssl_client_certificate directive, which resulted in sending DNs of these certificates (closes #142). Instead, all trusted CA certificates can be specified now using the ssl_trusted_certificate directive if needed. A notable difference that certificates specified in ssl_trusted_certificate are always loaded remains (see 3648ba7db). Co-authored-by: Praveen Chaudhary --- src/http/modules/ngx_http_ssl_module.c | 8 ++++++-- src/mail/ngx_mail_ssl_module.c | 8 ++++++-- src/stream/ngx_stream_ssl_module.c | 8 ++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index 1c92d9fa8..abc8d49ab 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -787,9 +787,13 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) if (conf->verify) { - if (conf->client_certificate.len == 0 && conf->verify != 3) { + if (conf->verify != 3 + && conf->client_certificate.len == 0 + && conf->trusted_certificate.len == 0) + { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no ssl_client_certificate for ssl_verify_client"); + "no ssl_client_certificate or " + "ssl_trusted_certificate for ssl_verify_client"); return NGX_CONF_ERROR; } diff --git a/src/mail/ngx_mail_ssl_module.c b/src/mail/ngx_mail_ssl_module.c index aebb4ccb6..b547dc101 100644 --- a/src/mail/ngx_mail_ssl_module.c +++ b/src/mail/ngx_mail_ssl_module.c @@ -450,9 +450,13 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) if (conf->verify) { - if (conf->client_certificate.len == 0 && conf->verify != 3) { + if (conf->verify != 3 + && conf->client_certificate.len == 0 + && conf->trusted_certificate.len == 0) + { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no ssl_client_certificate for ssl_verify_client"); + "no ssl_client_certificate or " + "ssl_trusted_certificate for ssl_verify_client"); return NGX_CONF_ERROR; } diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c index 072e74917..0233a9258 100644 --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -1008,9 +1008,13 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) if (conf->verify) { - if (conf->client_certificate.len == 0 && conf->verify != 3) { + if (conf->verify != 3 + && conf->client_certificate.len == 0 + && conf->trusted_certificate.len == 0) + { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no ssl_client_certificate for ssl_verify_client"); + "no ssl_client_certificate or " + "ssl_trusted_certificate for ssl_verify_client"); return NGX_CONF_ERROR; } From noreply at nginx.com Fri Sep 20 16:58:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 20 Sep 2024 16:58:02 +0000 (UTC) Subject: [nginx] Added new primary README.md file. Message-ID: <20240920165802.4867047694@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/b1e07409b1071564e8dd8c70436cba5da19a1575 branches: master commit: b1e07409b1071564e8dd8c70436cba5da19a1575 user: Michael Vernik date: Sat, 31 Aug 2024 22:42:09 -0700 description: Added new primary README.md file. --- README | 3 - README.md | 230 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ misc/GNUmakefile | 2 +- 3 files changed, 231 insertions(+), 4 deletions(-) diff --git a/README b/README deleted file mode 100644 index 2f68e14e0..000000000 --- a/README +++ /dev/null @@ -1,3 +0,0 @@ - -Documentation is available at http://nginx.org - diff --git a/README.md b/README.md new file mode 100644 index 000000000..b97181e30 --- /dev/null +++ b/README.md @@ -0,0 +1,230 @@ + + + + NGINX Banner + + +NGINX (pronounced "engine x" or "en-jin-eks") is the world's most popular Web Server, high performance Load Balancer, Reverse Proxy, API Gateway and Content Cache. + +NGINX is free and open source software, distributed under the terms of a simplified [2-clause BSD-like license](LICENSE). + +Enterprise distributions, commercial support and training are available from [F5, Inc](https://www.f5.com/products/nginx). + +> [!IMPORTANT] +> The goal of this README is to provide a basic, structured introduction to NGINX for novice users. Please refer to the [full NGINX documentation](https://nginx.org/en/docs/) for detailed information on [installing](https://nginx.org/en/docs/install.html), [building](https://nginx.org/en/docs/configure.html), [configuring](https://nginx.org/en/docs/dirindex.html), [debugging](https://nginx.org/en/docs/debugging_log.html), and more. These documentation pages also contain a more detailed [Beginners Guide](https://nginx.org/en/docs/beginners_guide.html), How-Tos, [Development guide](https://nginx.org/en/docs/dev/development_guide.html), and a complete module and [directive reference](https://nginx.org/en/docs/dirindex.html). + +# Table of contents +- [How it works](#how-it-works) + - [Modules](#modules) + - [Configurations](#configurations) + - [Runtime](#runtime) +- [Downloading and installing](#downloading-and-installing) + - [Stable and Mainline binaries](#stable-and-mainline-binaries) + - [Linux binary installation process](#linux-binary-installation-process) + - [FreeBSD installation process](#freebsd-installation-process) + - [Windows executables](#windows-executables) + - [Dynamic modules](#dynamic-modules) +- [Getting started with NGINX](#getting-started-with-nginx) + - [Installing SSL certificates and enabling TLS encryption](#installing-ssl-certificates-and-enabling-tls-encryption) + - [Load Balancing](#load-balancing) + - [Rate limiting](#rate-limiting) + - [Content caching](#content-caching) +- [Building from source](#building-from-source) + - [Installing dependencies](#installing-dependencies) + - [Cloning the NGINX GitHub repository](#cloning-the-nginx-github-repository) + - [Configuring the build](#configuring-the-build) + - [Compiling](#compiling) + - [Location of binary and installation](#location-of-binary-and-installation) + - [Running and testing the installed binary](#running-and-testing-the-installed-binary) +- [Asking questions and reporting issues](#asking-questions-and-reporting-issues) +- [Contributing code](#contributing-code) +- [Additional help and resources](#additional-help-and-resources) +- [Changelog](#changelog) +- [License](#license) + +# How it works +NGINX is installed software with binary packages available for all major operating systems and Linux distributions. See [Tested OS and Platforms](https://nginx.org/en/#tested_os_and_platforms) for a full list of compatible systems. + +> [!IMPORTANT] +> While nearly all popular Linux-based operating systems are distributed with a community version of nginx, we highly advise installation and usage of official [packages](https://nginx.org/en/linux_packages.html) or sources from this repository. Doing so ensures that you're using the most recent release or source code, including the latest feature-set, fixes and security patches. + +## Modules +NGINX is comprised of individual modules, each extending core functionality by providing additional, configurable features. See "Modules reference" at the bottom of [nginx documentation](https://nginx.org/en/docs/) for a complete list of official modules. + +NGINX modules can be built and distributed as static or dynamic modules. Static modules are defined at build-time, compiled, and distributed in the resulting binaries. See [Dynamic Modules](#dynamic-modules) for more information on how they work, as well as, how to obtain, install, and configure them. + +> [!TIP] +> You can issue the following command to see which static modules your NGINX binaries were built with: +```bash +nginx -V +``` +> See [Configuring the build](#configuring-the-build) for information on how to include specific Static modules into your nginx build. + + +## Configurations +NGINX is highly flexible and configurable. Provisioning the software is achieved via text-based config file(s) accepting parameters called "[Directives](https://nginx.org/en/docs/dirindex.html)". See [Configuration File's Structure](https://nginx.org/en/docs/beginners_guide.html#conf_structure) for a comprehensive description of how NGINX configuration files work. + +> [!NOTE] +> The set of directives available to your distribution of NGINX is dependent on which [modules](#modules) have been made available to it. + +## Runtime +Rather than running in a single, monolithic process, NGINX is architected to scale beyond Operating System process limitations by operating as a collection of processes. They include: +- A "master" process that maintains worker processes, as well as, reads and evaluates configuration files. +- One or more "worker" processes that process data (eg. HTTP requests). + +The number of [worker processes](https://nginx.org/en/docs/ngx_core_module.html#worker_processes) is defined in the configuration file and may be fixed for a given configuration or automatically adjusted to the number of available CPU cores. In most cases, the latter option optimally balances load across available system resources, as NGINX is designed to efficiently distribute work across all worker processes. + +> [!TIP] +> Processes synchronize data through shared memory. For this reason, many NGINX directives require the allocation of shared memory zones. As an example, when configuring [rate limiting](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req), connecting clients may need to be tracked in a [common memory zone](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req_zone) so all worker processes can know how many times a particular client has accessed the server in a span of time. + +# Downloading and installing +Follow these steps to download and install precompiled NGINX binaries. You may also choose to [build NGINX locally from source code](#building-from-source). + +## Stable and Mainline binaries +NGINX binaries are built and distributed in two versions: stable and mainline. Stable binaries are built from stable branches and only contain critical fixes backported from the mainline version. Mainline binaries are built from the [master branch](https://github.com/nginx/nginx/tree/master) and contain the latest features and bugfixes. You'll need to [decide which is appropriate for your purposes](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#choosing-between-a-stable-or-a-mainline-version). + +## Linux binary installation process +The NGINX binary installation process takes advantage of package managers native to specific Linux distributions. For this reason, first-time installations involve adding the official NGINX package repository to your system's package manager. Follow [these steps](https://nginx.org/en/linux_packages.html) to download, verify, and install NGINX binaries using the package manager appropriate for your Linux distribution. + +### Upgrades +Future upgrades to the latest version can be managed using the same package manager without the need to manually download and verify binaries. + +## FreeBSD installation process +For more information on installing NGINX on FreeBSD system, visit https://nginx.org/en/docs/install.html + +## Windows executables +Windows executables for mainline and stable releases can be found on the main [NGINX download page](https://nginx.org/en/download.html). Note that the current implementation of NGINX for Windows is at the Proof-of-Concept stage and should only be used for development and testing purposes. For additional information, please see [nginx for Windows](https://nginx.org/en/docs/windows.html). + +## Dynamic modules +NGINX version 1.9.11 added support for [Dynamic Modules](https://nginx.org/en/docs/ngx_core_module.html#load_module). Unlike Static modules, dynamically built modules can be downloaded, installed, and configured after the core NGINX binaries have been built. [Official dynamic module binaries](https://nginx.org/en/linux_packages.html#dynmodules) are available from the same package repository as the core NGINX binaries described in previous steps. + +> [!TIP] +> [NGINX JavaScript (njs)](https://github.com/nginx/njs), is a popular NGINX dynamic module that enables the extension of core NGINX functionality using familiar JavaScript syntax. + +> [!IMPORTANT] +> If desired, dynamic modules can also be built statically into NGINX at compile time. + +# Getting started with NGINX +For a gentle introduction to NGINX basics, please see our [Beginner’s Guide](https://nginx.org/en/docs/beginners_guide.html). + +## Installing SSL certificates and enabling TLS encryption +See [Configuring HTTPS servers](https://nginx.org/en/docs/http/configuring_https_servers.html) for a quick guide on how to enable secure traffic to your NGINX installation. + +## Load Balancing +For a quick start guide on configuring NGINX as a Load Balancer, please see [Using nginx as HTTP load balancer](https://nginx.org/en/docs/http/load_balancing.html). + +## Rate limiting +See our [Rate Limiting with NGINX](https://blog.nginx.org/blog/rate-limiting-nginx) blog post for an overview of core concepts for provisioning NGINX as an API Gateway. + +## Content caching +See [A Guide to Caching with NGINX and NGINX Plus](https://blog.nginx.org/blog/nginx-caching-guide) blog post for an overview of how to use NGINX as a content cache (e.g. edge server of a content delivery network). + +# Building from source +The following steps can be used to build NGINX from source code available in this repository. + +## Installing dependencies +Most Linux distributions will require several dependencies to be installed in order to build NGINX. The following instructions are specific to the `apt` package manager, widely available on most Ubuntu/Debian distributions and their derivatives. + +> [!TIP] +> It is always a good idea to update your package repository lists prior to installing new packages. +> ```bash +> sudo apt update +> ``` + +### Installing compiler and make utility +Use the following command to install the GNU C compiler and Make utility. + +```bash +sudo apt install gcc make +``` + +### Installing dependency libraries + +```bash +sudo apt install libpcre3-dev zlib1g-dev +``` + +> [!WARNING] +> This is the minimal set of dependency libraries needed to build NGINX with rewriting and gzip capabilities. Other dependencies may be required if you choose to build NGINX with additional modules. Monitor the output of the `configure` command discussed in the following sections for information on which modules may be missing. For example, if you plan to use SSL certificates to encrypt traffic with TLS, you'll need to install the OpenSSL library. To do so, issue the following command. + +>```bash +>sudo apt install libssl-dev + +## Cloning the NGINX GitHub repository +Using your preferred method, clone the NGINX repository into your development directory. See [Cloning a GitHub Repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) for additional help. + +```bash +git clone https://github.com/nginx/nginx.git +``` + +## Configuring the build +Prior to building NGINX, you must run the `configure` script with [appropriate flags](https://nginx.org/en/docs/configure.html). This will generate a Makefile in your NGINX source root directory that can then be used to compile NGINX with [options specified during configuration](https://nginx.org/en/docs/configure.html). + +From the NGINX source code repository's root directory: + +```bash +auto/configure +``` + +> [!IMPORTANT] +> Configuring the build without any flags will compile NGINX with the default set of options. Please refer to https://nginx.org/en/docs/configure.html for a full list of available build configuration options. + +## Compiling +The `configure` script will generate a `Makefile` in the NGINX source root directory upon successful execution. To compile NGINX into a binary, issue the following command from that same directory: + +```bash +make +``` + +## Location of binary and installation +After successful compilation, a binary will be generated at `/objs/nginx`. To install this binary, issue the following command from the source root directory: + +```bash +sudo make install +``` + +> [!IMPORTANT] +> The binary will be installed into the `/usr/local/nginx/` directory. + +## Running and testing the installed binary +To run the installed binary, issue the following command: + +```bash +sudo /usr/local/nginx/sbin/nginx +``` + +You may test NGINX operation using `curl`. + +```bash +curl localhost +``` + +The output of which should start with: + +```html + + + +Welcome to nginx! +``` + +# Asking questions and reporting issues +We encourage you to engage with us. +- [NGINX GitHub Discussions](https://github.com/nginx/nginx/discussions), is the go-to place to start asking questions and sharing your thoughts. +- Our [GitHub Issues](https://github.com/nginx/nginx/issues) page offers space to submit and discuss specific issues, report bugs, and suggest enhancements. + +# Contributing code +Please see the [Contributing](CONTRIBUTING.md) guide for information on how to contribute code. + +# Additional help and resources +- See the [NGINX Community Blog](https://blog.nginx.org/) for more tips, tricks and HOW-TOs related to NGINX and related projects. +- Access [nginx.org](https://nginx.org/), your go-to source for all documentation, information and software related to the NGINX suite of projects. + +# Changelog +See our [changelog](https://nginx.org/en/CHANGES) to keep track of updates. + +# License +[2-clause BSD-like license](LICENSE) + +--- +Additional documentation available at: https://nginx.org/en/docs diff --git a/misc/GNUmakefile b/misc/GNUmakefile index 4b6a12759..fcf34ec34 100644 --- a/misc/GNUmakefile +++ b/misc/GNUmakefile @@ -92,7 +92,7 @@ zip: export sed -i '' -e "s/$$/`printf '\r'`/" $(TEMP)/$(NGINX)/conf/* mv $(TEMP)/$(NGINX)/LICENSE $(TEMP)/$(NGINX)/docs.new - mv $(TEMP)/$(NGINX)/README $(TEMP)/$(NGINX)/docs.new + mv $(TEMP)/$(NGINX)/README.md $(TEMP)/$(NGINX)/docs.new mv $(TEMP)/$(NGINX)/docs/html $(TEMP)/$(NGINX) rm -r $(TEMP)/$(NGINX)/docs From noreply at nginx.com Tue Sep 24 14:59:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 24 Sep 2024 14:59:02 +0000 (UTC) Subject: [nginx] Fixed a typo of bpf makefile debug option. Message-ID: <20240924145902.596E1476D6@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/51857ce40400b48bc8900b9e3930cf7474fa0c41 branches: master commit: 51857ce40400b48bc8900b9e3930cf7474fa0c41 user: tzssangglass date: Mon, 9 Sep 2024 23:22:34 +0800 description: Fixed a typo of bpf makefile debug option. --- src/event/quic/bpf/makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/bpf/makefile b/src/event/quic/bpf/makefile index b4d758f33..277aa3151 100644 --- a/src/event/quic/bpf/makefile +++ b/src/event/quic/bpf/makefile @@ -25,6 +25,6 @@ clean: @rm -f $(RESULT) *.o debug: $(PROGNAME).o - llvm-objdump -S -no-show-raw-insn $< + llvm-objdump -S --no-show-raw-insn $< .DELETE_ON_ERROR: From noreply at nginx.com Tue Sep 24 14:59:03 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 24 Sep 2024 14:59:03 +0000 (UTC) Subject: [unit] Compile with -funsigned-char Message-ID: <20240924145903.40516476D7@pubserv1.nginx> details: https://github.com/nginx/unit/commit/355f038fdea54bb62fab942729b9386591d308b2 branches: master commit: 355f038fdea54bb62fab942729b9386591d308b2 user: Andrew Clayton date: Thu, 10 Nov 2022 19:54:05 +0000 description: Compile with -funsigned-char Due to 'char' (unless explicitly set) being signed or unsigned depending on architecture, e.g on x86 it's signed, while on Arm it's unsigned, this can lead to subtle bugs such if you use a plain char as a byte thinking it's unsigned on all platforms (maybe you live in the world of Arm). What we can do is tell the compiler to treat 'char' as unsigned by default, thus it will be consistent across platforms. Seeing as most of the time it doesn't matter whether char is signed or unsigned, it really only matters when you're dealing with 'bytes', which means it makes sense to default char to unsigned. The Linux Kernel made this change at the end of 2022. This will also allow in the future to convert our u_char's to char's (which will now be unsigned) and pass them directly into the libc functions for example, without the need for casting. Here is what the ISO C standard has to say >From §6.2.5 Types ¶15 The three types char, signed char, and unsigned char are collectively called the character types. The implementation shall define char to have the same range, representation, and behavior as either signed char or unsigned char.[45] and from Footnote 45) CHAR_MIN, defined in , will have one of the values 0 or SCHAR_MIN, and this can be used to distinguish the two options. Irrespective of the choice made, char is a separate type from the other two and is not compatible with either. If you're still unsure why you'd want this change... It was never clear to me, why we used u_char, perhaps that was used as an alternative to -funsigned-char... But that still leaves the potential for bugs with char being unsigned vs signed... Then because we use u_char but often need to pass such things into libc (and perhaps other functions) which normally take a 'char' we need to cast these cases. So this change brings at least two (or more) benefits 1) Removal of potential for char unsigned vs signed bugs. 2) Removal of a bunch of casts. Reducing casting to the bare minimum is good. This helps the compiler to do proper type checking. 3) Readability/maintainability, everything is now just char... What if you want to work with bytes? Well with char being unsigned (everywhere) you can of course use char. However it would be much better to use the uint8_t type for that to clearly signify that intention. Link: Link: Link: Link: Suggested-by: Alejandro Colomar Reviewed-by: Alejandro Colomar Signed-off-by: Andrew Clayton --- auto/cc/test | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/auto/cc/test b/auto/cc/test index 34e4379e..ac4d5f33 100644 --- a/auto/cc/test +++ b/auto/cc/test @@ -67,6 +67,8 @@ case $NXT_CC_NAME in NXT_CFLAGS="$NXT_CFLAGS -fno-strict-overflow" + NXT_CFLAGS="$NXT_CFLAGS -funsigned-char" + NXT_CFLAGS="$NXT_CFLAGS -std=gnu11" NXT_CFLAGS="$NXT_CFLAGS -O" @@ -109,6 +111,8 @@ case $NXT_CC_NAME in NXT_CFLAGS="$NXT_CFLAGS -fno-strict-overflow" + NXT_CFLAGS="$NXT_CFLAGS -funsigned-char" + NXT_CFLAGS="$NXT_CFLAGS -std=gnu11" NXT_CFLAGS="$NXT_CFLAGS -O" From noreply at nginx.com Thu Sep 26 04:47:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 26 Sep 2024 04:47:02 +0000 (UTC) Subject: [njs] HTTP: fixed r.return() with empty string as a body argument. Message-ID: <20240926044702.716FE476FB@pubserv1.nginx> details: https://github.com/nginx/njs/commit/6e6a9c5ffabefbc9cd5d3a04d33f38a697ae0e65 branches: master commit: 6e6a9c5ffabefbc9cd5d3a04d33f38a697ae0e65 user: Dmitry Volyntsev date: Tue, 17 Sep 2024 22:58:31 -0700 description: HTTP: fixed r.return() with empty string as a body argument. --- nginx/ngx_http_js_module.c | 22 ++++++++++++---------- nginx/t/js_return.t | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index dec65198..c34fccbd 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -2704,14 +2704,16 @@ ngx_http_js_ext_return(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - if (ngx_js_string(vm, njs_arg(args, nargs, 2), &text) != NGX_OK) { - njs_vm_error(vm, "failed to convert text"); - return NJS_ERROR; - } - ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); - if (status < NGX_HTTP_BAD_REQUEST || text.length) { + if (status < NGX_HTTP_BAD_REQUEST + || !njs_value_is_null_or_undefined(njs_arg(args, nargs, 2))) + { + if (ngx_js_string(vm, njs_arg(args, nargs, 2), &text) != NGX_OK) { + njs_vm_error(vm, "failed to convert text"); + return NJS_ERROR; + } + ngx_memzero(&cv, sizeof(ngx_http_complex_value_t)); cv.value.data = text.start; @@ -5352,11 +5354,11 @@ ngx_http_qjs_ext_return(JSContext *cx, JSValueConst this_val, ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); - if (ngx_qjs_string(ctx->engine, argv[1], &body) != NGX_OK) { - return JS_ThrowOutOfMemory(cx); - } + if (status < NGX_HTTP_BAD_REQUEST || !JS_IsNullOrUndefined(argv[1])) { + if (ngx_qjs_string(ctx->engine, argv[1], &body) != NGX_OK) { + return JS_ThrowOutOfMemory(cx); + } - if (status < NGX_HTTP_BAD_REQUEST || body.len) { ngx_memzero(&cv, sizeof(ngx_http_complex_value_t)); cv.value.data = body.data; diff --git a/nginx/t/js_return.t b/nginx/t/js_return.t index 2cc32f74..6f8c4a93 100644 --- a/nginx/t/js_return.t +++ b/nginx/t/js_return.t @@ -46,21 +46,29 @@ http { location / { js_content test.returnf; } + + location /njs { + js_content test.njs; + } } } EOF $t->write_file('test.js', <try_run('no njs return')->plan(5); +$t->try_run('no njs return')->plan(6); ############################################################################### @@ -70,4 +78,30 @@ like(http_get('/?c=301&t=path'), qr/ 301 .*Location: path/s, 'return redirect'); like(http_get('/?c=404'), qr/404 Not.*html/s, 'return error page'); like(http_get('/?c=inv'), qr/ 500 /, 'return invalid'); +TODO: { +local $TODO = 'not yet' unless has_version('0.8.6'); + +unlike(http_get('/?c=404&t='), qr/Not.*html/s, 'return empty body'); + +} + +############################################################################### + +sub has_version { + my $need = shift; + + http_get('/njs') =~ /^([.0-9]+)$/m; + + my @v = split(/\./, $1); + my ($n, $v); + + for $n (split(/\./, $need)) { + $v = shift @v || 0; + return 0 if $n > $v; + return 1 if $v > $n; + } + + return 1; +} + ############################################################################### From noreply at nginx.com Thu Sep 26 21:53:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 26 Sep 2024 21:53:02 +0000 (UTC) Subject: [njs] CI: Run a check-pr job on a specific Ubuntu version. Message-ID: <20240926215302.CCD0A47717@pubserv1.nginx> details: https://github.com/nginx/njs/commit/603563f2368820c9014bdeb7693f34e9ae2f98c7 branches: master commit: 603563f2368820c9014bdeb7693f34e9ae2f98c7 user: Konstantin Pavlov date: Thu, 26 Sep 2024 13:41:42 -0700 description: CI: Run a check-pr job on a specific Ubuntu version. GitHub is going to change the latest tag to point to 24.04 soon, so let's be proactive and pin to a specific version. With that change, we'd also need to drop the _FORTIFY_SOURCE definition since it now has a stricter default on Ubuntu 24.04. --- .github/workflows/check-pr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index 7dd2bd57..0160c1fd 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -5,7 +5,7 @@ on: jobs: build: - runs-on: [ ubuntu-latest ] + runs-on: [ ubuntu-24.04 ] steps: - name: checkout v4 uses: actions/checkout at b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 @@ -14,7 +14,7 @@ jobs: 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 export DEB_BUILD_MAINT_OPTIONS="hardening=+all" - export DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" + export DEB_CFLAGS_MAINT_APPEND="-fPIC" export DEB_LDFLAGS_MAINT_APPEND=""-Wl,--as-needed"" echo CC_OPT=$(dpkg-buildflags --get CFLAGS) >> $GITHUB_ENV echo LD_OPT=$(dpkg-buildflags --get LDFLAGS) >> $GITHUB_ENV