{"id":13511878,"url":"https://github.com/nginx/njs-examples","last_synced_at":"2025-05-15T11:00:21.705Z","repository":{"id":37855353,"uuid":"150008354","full_name":"nginx/njs-examples","owner":"nginx","description":"NGINX JavaScript examples","archived":false,"fork":false,"pushed_at":"2025-04-10T04:43:30.000Z","size":175,"stargazers_count":645,"open_issues_count":11,"forks_count":85,"subscribers_count":20,"default_branch":"master","last_synced_at":"2025-04-13T00:48:18.423Z","etag":null,"topics":["javascript","nginx","njs"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nginx.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-09-23T17:29:27.000Z","updated_at":"2025-04-12T18:49:35.000Z","dependencies_parsed_at":"2022-07-20T01:18:46.659Z","dependency_job_id":"c8f1fbdd-8a2d-411f-a65f-069e60cb310f","html_url":"https://github.com/nginx/njs-examples","commit_stats":{"total_commits":99,"total_committers":6,"mean_commits":16.5,"dds":0.3232323232323232,"last_synced_commit":"5df0d21be7f05e625b10a604b4dc8860fd45e337"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nginx%2Fnjs-examples","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nginx%2Fnjs-examples/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nginx%2Fnjs-examples/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nginx%2Fnjs-examples/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nginx","download_url":"https://codeload.github.com/nginx/njs-examples/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254328384,"owners_count":22052632,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","nginx","njs"],"created_at":"2024-08-01T03:01:15.448Z","updated_at":"2025-05-15T11:00:21.679Z","avatar_url":"https://github.com/nginx.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","Njs Projects"],"sub_categories":["Lua Modules"],"readme":"=========================\nNGINX JavaScript examples\n=========================\n\n.. contents::\n   :depth: 3\n\nIntro\n=====\n\nThis repo contains complete examples for various use cases where `njs \u003chttp://nginx.org/en/docs/njs/\u003e`_ is useful. The document as well as `njs documentation \u003chttp://nginx.org/en/docs/njs/\u003e`_ expects some familiarity with and understanding of nginx. Beginners should refer to the official `admin guide \u003chttps://docs.nginx.com/nginx/admin-guide/\u003e`_.\n\nNote: the examples below work with njs \u003e= `0.7.0 \u003chttp://nginx.org/en/docs/njs/changes.html#njs0.7.0\u003e`_. To see the current version run the following command: ``docker run -i -t nginx:latest /usr/bin/njs -V``.\n\nRunning inside Docker\n---------------------\nPublic nginx docker image contains open source version of nginx. To run examples for NGINX-PLUS, you have to `build \u003chttps://www.nginx.com/blog/deploying-nginx-nginx-plus-docker/\u003e`_ your own docker image.\n\n.. code-block:: shell\n\n  git clone https://github.com/nginx/njs-examples\n  cd njs-examples\n  EXAMPLE='http/hello'\n  docker run --rm --name njs_example  -v $(pwd)/conf/$EXAMPLE.conf:/etc/nginx/nginx.conf:ro -v $(pwd)/njs/:/etc/nginx/njs/:ro -p 80:80 -p 443:443 -d nginx\n  # for NGINX-PLUS examples,\n  # docker run ... -d mynginxplus\n\n  # Stopping.\n  docker stop njs_example\n\nStatus\n------\nWhile njs is in active development it is production ready. Its reliability has been proven by extensive test coverage as well as a good track record with our customers.\n\nnginx compatibility\n-------------------\nAs njs is a `native nginx module \u003chttp://nginx.org/en/docs/dev/development_guide.html#Modules\u003e`_ its compatibility with nginx is high. While it is developed as a separate project, it is routinely tested with latest nginx versions on various platforms and architectures.\n\nPresentation at nginx.conf 2018\n-------------------------------\nhttps://youtu.be/Jc_L6UffFOs\n\nExtending NGINX with Custom Code\n--------------------------------\nhttps://youtu.be/0CVhq4AUU7M\n\nInstallation\n------------\nnjs is available as a part of official nginx docker image as well as an officially supported `packet \u003chttp://nginx.org/en/linux_packages.html\u003e`_ for major linux distributions.\n\nRepository\n----------\nPlease ask questions, report issues, and send patches via official `Github mirror \u003chttps://github.com/nginx/njs\u003e`_.\n\nHTTP\n====\n\nHello world example [http/hello]\n--------------------------------\n\nnginx.conf:\n\n.. code-block:: nginx\n\n  load_module modules/ngx_http_js_module.so;\n\n  events {}\n\n  http {\n    js_path \"/etc/nginx/njs/\";\n\n    js_import utils.js;\n    js_import main from http/hello.js;\n\n    server {\n      listen 80;\n\n      location = /version {\n         js_content utils.version;\n      }\n\n      location / {\n        js_content main.hello;\n      }\n   }\n }\n\nexample.js:\n\n.. code-block:: js\n\n  function hello(r) {\n    r.return(200, \"Hello world!\\n\");\n  }\n\n  export default {hello}\n\nChecking:\n\n.. code-block:: shell\n\n  curl http://localhost/\n  Hello world!\n\n  curl http://localhost/version\n  0.4.1\n\nSetting nginx var as a result of async operation\n------------------------------------------------\n`js_set \u003chttps://nginx.org/en/docs/http/ngx_http_js_module.html#js_set\u003e`_ handler\ndoes not support asynchronous operation (r.subrequest(), ngx.fetch()) because it is\ninvoked in a synchronous context by nginx and is expected to return its result\nright away. Fortunately there are ways to overcome this limitation using other\nnginx modules.\n\nThe examples in this section is provided in order from simple to more advanced.\nThe simplest method are preferred because generally they are more efficient.\n\nUsing auth_request [http/async_var/auth_request]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIn simple cases `auth_request \u003chttp://nginx.org/en/docs/http/ngx_http_auth_request_module.html\u003e`_\nis enough and njs is not required.\n\nSimple case criteria:\n   - request body is not needed to be forwarded\n   - external service returns the desired value extractable as an nginx variable (for example as a response header)\n\nThe following example illustrates this use case using njs ONLY as a fake service.\n$backend variable is populated by auth_request module from a response header of a subrequest.\n\nnginx.conf:\n\n.. code-block:: nginx\n\n    ...\n\n    http {\n      js_path \"/etc/nginx/njs/\";\n\n      js_import main from http/async_var/auth_request.js;\n\n      server {\n          listen 80;\n\n          location /secure/ {\n              auth_request /fetch_upstream;\n              auth_request_set $backend $upstream_http_x_backend;\n\n              proxy_pass http://$backend;\n          }\n\n          location /fetch_upstream {\n              internal;\n\n              proxy_pass http://127.0.0.1:8079;\n              proxy_pass_request_body off;\n              proxy_set_header Content-Length \"\";\n              proxy_set_header X-Original-URI $request_uri;\n          }\n      }\n\n      server {\n          listen 127.0.0.1:8079;\n\n          location / {\n            js_content main.choose_upstream;\n          }\n      }\n\n      server {\n          listen 127.0.0.1:8081;\n          return 200 \"BACKEND A:$uri\\n\";\n      }\n\n      server {\n          listen 127.0.0.1:8082;\n          return 200 \"BACKEND B:$uri\\n\";\n      }\n    }\n\nexample.js:\n\n.. code-block:: js\n\n    import qs from \"querystring\";\n\n    function choose_upstream(r) {\n        let backend;\n        let args = qs.parse(r.headersIn['X-Original-URI'].split('?')[1]);\n\n        switch (args.token) {\n        case 'A':\n            backend = '127.0.0.1:8081';\n            break;\n        case 'B':\n            backend = '127.0.0.1:8082';\n            break;\n        default:\n            r.return(404);\n        }\n\n        r.headersOut['X-backend'] = backend;\n        r.return(200);\n    }\n\n    export default {choose_upstream}\n\nChecking:\n\n.. code-block:: shell\n\n    curl http://localhost/secure/abc?token=A\n    BACKEND A:/secure/abc\n\n    curl http://localhost/secure/abcde?token=B\n    BACKEND B:/secure/abcde\n\nUsing auth_request and js_header_filter [http/async_var/js_header_filter]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n`js_header_filter \u003chttp://nginx.org/en/docs/http/ngx_http_js_module.html#js_header_filter\u003e`_\ncan be used to modify the service response and set an appropriate response header of\nan auth_request subrequest. This case is applicable when a service returns a value which\ncannot be used directly.\n\nnginx.conf:\n\n.. code-block:: nginx\n\n    ...\n\n    http {\n      js_path \"/etc/nginx/njs/\";\n\n      js_import main from http/async_var/js_header_filter.js;\n\n      server {\n          listen 80;\n\n          location /secure/ {\n              auth_request /fetch_upstream;\n              auth_request_set $backend $sent_http_x_backend;\n\n              proxy_pass http://$backend;\n          }\n\n          location /fetch_upstream {\n              internal;\n\n              proxy_pass http://127.0.0.1:8079;\n              proxy_pass_request_body off;\n              proxy_set_header Content-Length \"\";\n              proxy_set_header X-Original-URI $request_uri;\n\n              js_header_filter main.set_upstream;\n          }\n      }\n\n      server {\n          listen 127.0.0.1:8079;\n\n          location / {\n            js_content main.choose_upstream;\n          }\n      }\n\n      server {\n          listen 127.0.0.1:8081;\n          return 200 \"BACKEND A:$uri\\n\";\n      }\n\n      server {\n          listen 127.0.0.1:8082;\n          return 200 \"BACKEND B:$uri\\n\";\n      }\n    }\n\nexample.js:\n\n.. code-block:: js\n\n    import qs from \"querystring\";\n\n    function choose_upstream(r) {\n        let backend;\n        let args = qs.parse(r.headersIn['X-Original-URI'].split('?')[1]);\n\n        switch (args.token) {\n        case 'A':\n            backend = 'B1';\n            break;\n        case 'B':\n            backend = 'B2';\n            break;\n        default:\n            r.return(404);\n        }\n\n        r.headersOut['X-backend'] = backend;\n        r.return(200);\n    }\n\n    function set_upstream(r) {\n        let backend;\n        switch (r.headersOut['X-backend']) {\n        case 'B1':\n            backend = '127.0.0.1:8081';\n            break;\n        case 'B2':\n            backend = '127.0.0.1:8082';\n            break;\n        }\n\n        if (backend) {\n            r.headersOut['X-backend'] = backend;\n        }\n    }\n\n    export default {choose_upstream, set_upstream}\n\nChecking:\n\n.. code-block:: shell\n\n    curl http://localhost/secure/abc?token=A\n    BACKEND A:/secure/abc\n\n    curl http://localhost/secure/abcde?token=B\n    BACKEND B:/secure/abcde\n\nAuthorization\n-------------\n\nGetting arbitrary field from JWT as a nginx variable [http/authorization/jwt]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nnginx.conf:\n\n.. code-block:: nginx\n\n  http {\n    js_path \"/etc/nginx/njs/\";\n\n    js_import utils.js;\n    js_import main from http/authorization/jwt.js;\n\n    js_set $jwt_payload_sub main.jwt_payload_sub;\n\n    server {\n  ...\n        location /jwt {\n            return 200 $jwt_payload_sub;\n        }\n    }\n  }\n\nexample.js:\n\n.. code-block:: js\n\n    function jwt(data) {\n        var parts = data.split('.').slice(0,2)\n            .map(v=\u003eBuffer.from(v, 'base64url').toString())\n            .map(JSON.parse);\n        return { headers:parts[0], payload: parts[1] };\n    }\n\n    function jwt_payload_sub(r) {\n        return jwt(r.headersIn.Authorization.slice(7)).payload.sub;\n    }\n\n    export default {jwt_payload_sub}\n\nChecking:\n\n.. code-block:: shell\n\n  curl 'http://localhost/jwt' -H \"Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImV4cCI6MTU4NDcyMzA4NX0.eyJpc3MiOiJuZ2lueCIsInN1YiI6ImFsaWNlIiwiZm9vIjoxMjMsImJhciI6InFxIiwienl4IjpmYWxzZX0.Kftl23Rvv9dIso1RuZ8uHaJ83BkKmMtTwch09rJtwgk\"\n  alice\n\nGenerating JWT token [http/authorization/gen_hs_jwt]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nnginx.conf:\n\n.. code-block:: nginx\n\n  env JWT_GEN_KEY;\n\n  ...\n\n  http {\n    js_path \"/etc/nginx/njs/\";\n\n    js_import utils.js;\n    js_import main from http/authorization/gen_hs_jwt.js;\n\n    js_set $jwt main.jwt;\n\n    server {\n  ...\n        location /jwt {\n            return 200 $jwt;\n        }\n    }\n  }\n\nexample.js:\n\n.. code-block:: js\n\n    async function generate_hs256_jwt(init_claims, key, valid) {\n        let header = { typ: \"JWT\",  alg: \"HS256\" };\n        let claims = Object.assign(init_claims, {exp: Math.floor(Date.now()/1000) + valid});\n\n        let s = [header, claims].map(JSON.stringify)\n                                .map(v=\u003eBuffer.from(v).toString('base64url'))\n                                .join('.');\n\n        let wc_key = await crypto.subtle.importKey('raw', key, {name: 'HMAC', hash: 'SHA-256'},\n                                                   false, ['sign']);\n        let sign = await crypto.subtle.sign({name: 'HMAC'}, wc_key, s);\n\n        return s + '.' + Buffer.from(sign).toString('base64url');\n    }\n\n    async function jwt(r) {\n        let claims = {\n            iss: \"nginx\",\n            sub: \"alice\",\n            foo: 123,\n            bar: \"qq\",\n            zyx: false\n        };\n\n        let jwtv = await generate_hs256_jwt(claims, process.env.JWT_GEN_KEY, 600);\n        r.setReturnValue(jwtv);\n    }\n\n    export default {jwt}\n\nChecking:\n\n.. code-block:: shell\n\n  docker run --rm --name njs_example -e JWT_GEN_KEY=\"foo\" ...\n\n  curl 'http://localhost/jwt'\n  eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImV4cCI6MTU4NDcyMjk2MH0.eyJpc3MiOiJuZ2lueCIsInN1YiI6ImFsaWNlIiwiZm9vIjoxMjMsImJhciI6InFxIiwienl4IjpmYWxzZX0.GxfKkJSWI4oq5sGBg4aKRAcFeKmiA6v4TR43HbcP2X8\n\n\nSecure link [http/authorization/secure_link_hash]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nProtecting ``/secure/`` location from simple bots and web crawlers.\n\nnginx.conf:\n\n.. code-block:: nginx\n\n  env SECRET_KEY;\n\n  ...\n\n  http {\n    js_path \"/etc/nginx/njs/\";\n\n    js_import main from http/authorization/secure_link_hash.js;\n\n    js_set $new_foo main.create_secure_link;\n    js_set $secret_key key main.secret_key;\n\n    server {\n          listen 80;\n\n          ...\n\n          location /secure/ {\n              error_page 403 = @login;\n\n              secure_link $cookie_foo;\n              secure_link_md5 \"$uri$secret_key\";\n\n              if ($secure_link = \"\") {\n                      return 403;\n              }\n\n              proxy_pass http://localhost:8080;\n          }\n\n          location @login {\n              add_header Set-Cookie \"foo=$new_foo; Max-Age=60\";\n              return 302 $request_uri;\n          }\n      }\n  }\n\nexample.js:\n\n.. code-block:: js\n\n  import crypto from 'crypto';\n\n  function secret_key(r) {\n      return process.env.SECRET_KEY;\n  }\n\n  function create_secure_link(r) {\n      return crypto.createHash('md5')\n                              .update(r.uri).update(process.env.SECRET_KEY)\n                              .digest('base64url');\n  }\n\n  export default {secret_key, create_secure_link}\n\nChecking:\n\n.. code-block:: shell\n\n  docker run --rm --name njs_example -e SECRET_KEY=\" mykey\" ...\n\n  curl http://127.0.0.1/secure/r\n  302\n\n  curl http://127.0.0.1/secure/r -L\n  curl: (47) Maximum (50) redirects followed\n\n  curl http://127.0.0.1/secure/r --cookie-jar cookie.txt\n  302\n\n  curl http://127.0.0.1/secure/r --cookie cookie.txt\n  PASSED\n\nAuthorizing requests using auth_request [http/authorization/auth_request]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. _`auth request`:\n\n`auth_request \u003chttp://nginx.org/en/docs/http/ngx_http_auth_request_module.html\u003e`_\nis generic nginx modules which implements client authorization based on the result of a subrequest.\nCombination of auth_request and njs allows to implement arbitrary authorization logic.\n\nnginx.conf:\n\n.. code-block:: nginx\n\n    ...\n\n    env SECRET_KEY;\n\n    http {\n      js_path \"/etc/nginx/njs/\";\n\n      js_import main from http/authorization/auth_request.js;\n\n      upstream backend {\n          server 127.0.0.1:8081;\n      }\n\n      server {\n          listen 80;\n\n          location /secure/ {\n              auth_request /validate;\n\n              proxy_pass http://backend;\n          }\n\n          location /validate {\n              internal;\n              js_content main.authorize;\n          }\n      }\n\n      server {\n          listen 127.0.0.1:8081;\n          return 200 \"BACKEND:$uri\\n\";\n      }\n    }\n\nexample.js:\n\n.. code-block:: js\n\n    import crypto from 'crypto';\n\n    function authorize(r) {\n        var signature = r.headersIn.Signature;\n\n        if (!signature) {\n            r.error(\"No signature\");\n            r.return(401);\n            return;\n        }\n\n        if (r.method != 'GET') {\n            r.error(`Unsupported method: ${r.method}`);\n            r.return(401);\n            return;\n        }\n\n        var args = r.variables.args;\n\n        var h = crypto.createHmac('sha1', process.env.SECRET_KEY);\n\n        h.update(r.uri).update(args ? args : \"\");\n\n        var req_sig = h.digest(\"base64\");\n\n        if (req_sig != signature) {\n            r.error(`Invalid signature: ${req_sig}\\n`);\n            r.return(401);\n            return;\n        }\n\n        r.return(200);\n    }\n\n    export default {authorize}\n\nChecking:\n\n.. code-block:: shell\n\n  docker run --rm --name njs_example -e SECRET_KEY=\"foo\" ...\n\n  curl http://localhost/secure/B\n  \u003chtml\u003e\n  \u003chead\u003e\u003ctitle\u003e401 Authorization Required\u003c/title\u003e\u003c/head\u003e\n  \u003cbody\u003e\n  \u003ccenter\u003e\u003ch1\u003e401 Authorization Required\u003c/h1\u003e\u003c/center\u003e\n  \u003chr\u003e\u003ccenter\u003enginx/1.19.0\u003c/center\u003e\n  \u003c/body\u003e\n  \u003c/html\u003e\n\n  curl http://localhost/secure/B  -H Signature:fk9WRmw7Rl+NwVAA759+H2Uq\n  \u003chtml\u003e\n  \u003chead\u003e\u003ctitle\u003e401 Authorization Required\u003c/title\u003e\u003c/head\u003e\n  \u003cbody\u003e\n  \u003ccenter\u003e\u003ch1\u003e401 Authorization Required\u003c/h1\u003e\u003c/center\u003e\n  \u003chr\u003e\u003ccenter\u003enginx/1.19.0\u003c/center\u003e\n  \u003c/body\u003e\n  \u003c/html\u003e\n\n  curl http://localhost/secure/B  -H Signature:fk9WRmw7Rl+NwVAA759+H2UqxNs=\n  BACKEND:/secure/B\n\n  docker logs njs_example\n  172.17.0.1 - - [03/Aug/2020:18:22:30 +0000] \"GET /secure/B HTTP/1.1\" 401 179 \"-\" \"curl/7.58.0\"\n  2020/08/03 18:22:47 [error] 28#28: *3 js: No signature\n  172.17.0.1 - - [03/Aug/2020:18:22:47 +0000] \"GET /secure/B HTTP/1.1\" 401 179 \"-\" \"curl/7.58.0\"\n  2020/08/03 18:22:54 [error] 28#28: *4 js: Invalid signature: fk9WRmw7Rl+NwVAA759+H2UqxNs=\n\n  172.17.0.1 - - [03/Aug/2020:18:22:54 +0000] \"GET /secure/B HTTP/1.1\" 401 179 \"-\" \"curl/7.58.0\"\n  127.0.0.1 - - [03/Aug/2020:18:23:00 +0000] \"GET /secure/B HTTP/1.0\" 200 18 \"-\" \"curl/7.58.0\"\n  172.17.0.1 - - [03/Aug/2020:18:23:00 +0000] \"GET /secure/B HTTP/1.1\" 200 18 \"-\" \"curl/7.58.0\"\n\nAuthorizing requests based on request body content [http/authorization/request_body]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n`Authorizing requests using auth_request [http/authorization/auth_request]`_ cannot inspect client request body.\nSometimes inspecting client request body is required, for example to validate POST arguments (application/x-www-form-urlencoded).\n\nnginx.conf:\n\n.. code-block:: nginx\n\n    ...\n\n    env SECRET_KEY;\n\n    http {\n      js_path \"/etc/nginx/njs/\";\n\n      js_import main from http/authorization/request_body.js;\n\n      upstream backend {\n          server 127.0.0.1:8081;\n      }\n\n      server {\n          listen 80;\n\n          location /secure/ {\n              js_content main.authorize;\n          }\n\n          location @app-backend {\n              proxy_pass http://backend;\n          }\n      }\n\n      server {\n          listen 127.0.0.1:8081;\n          return 200 \"BACKEND:$uri\\n\";\n      }\n    }\n\nexample.js:\n\n.. code-block:: js\n\n    import crypto from 'crypto';\n\n    function authorize(r) {\n        var signature = r.headersIn.Signature;\n\n        if (!signature) {\n            r.return(401, \"No signature\\n\");\n            return;\n        }\n\n        var h = crypto.createHmac('sha1', process.env.SECRET_KEY);\n\n        h.update(r.uri);\n\n        switch (r.method) {\n        case 'GET':\n            var args = r.variables.args;\n            h.update(args ? args : \"\");\n            break;\n\n        case 'POST':\n            var body  = r.requestText;\n            if (r.headersIn['Content-Type'] != 'application/x-www-form-urlencoded'\n                || !body.length)\n            {\n                r.return(401, \"Unsupported method\\n\");\n            }\n\n            h.update(body);\n            break;\n\n        default:\n            r.return(401, \"Unsupported method\\n\");\n            return;\n        }\n\n        var req_sig = h.digest(\"base64\");\n\n        if (req_sig != signature) {\n            r.return(401, `Invalid signature: ${req_sig}\\n`);\n            return;\n        }\n\n        r.internalRedirect('@app-backend');\n    }\n\n    export default {authorize}\n\nChecking:\n\n.. code-block:: shell\n\n  docker run --rm --name njs_example -e SECRET_KEY=\"foo\" ...\n\n  curl http://localhost/secure/B\n  No signature\n\n  curl http://localhost/secure/B?a=1 -H Signature:A\n  Invalid signature: YC5iL6aKDnv7XOjknEeDL+P58iw=\n\n  curl http://localhost/secure/B?a=1 -H Signature:YC5iL6aKDnv7XOjknEeDL+P58iw=\n  BACKEND:/secure/B\n\n  curl http://localhost/secure/B -d \"a=1\" -X POST -H Signature:YC5iL6aKDnv7XOjknEeDL+P58iw=\n  BACKEND:/secure/B\n\nCertificates\n------------\n\nReading subject alternative from client certificate [http/certs/subject_alternative]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nAccessing arbitrary fields in client certificates.\n\nnginx.conf:\n\nCertificates are created using the following `guide \u003chttps://jamielinux.com/docs/openssl-certificate-authority/introduction.html\u003e`_.\n\n.. code-block:: nginx\n\n  ...\n\n  http {\n    js_path \"/etc/nginx/njs/\";\n\n    js_import main from http/certs/js/subject_alternative.js;\n\n    js_set $san main.san;\n\n    server {\n          listen 443 ssl;\n\n          server_name www.example.com;\n\n          ssl_password_file /etc/nginx/njs/http/certs/ca/password;\n          ssl_certificate /etc/nginx/njs/http/certs/ca/intermediate/certs/www.example.com.cert.pem;\n          ssl_certificate_key /etc/nginx/njs/http/certs/ca/intermediate/private/www.example.com.key.pem;\n\n          ssl_client_certificate /etc/nginx/njs/http/certs/ca/intermediate/certs/ca-chain.cert.pem;\n          ssl_verify_client on;\n\n          location / {\n              return 200 $san;\n          }\n    }\n  }\n\nexample.js:\n\n.. code-block:: js\n\n    import x509 from 'x509.js';\n\n    function san(r) {\n        var pem_cert = r.variables.ssl_client_raw_cert;\n        if (!pem_cert) {\n            return '{\"error\": \"no client certificate\"}';\n        }\n\n        var cert = x509.parse_pem_cert(pem_cert);\n\n        // subjectAltName oid 2.5.29.17\n        return JSON.stringify(x509.get_oid_value(cert, \"2.5.29.17\")[0]);\n    }\n\n    export default {san};\n\nChecking:\n\n.. code-block:: shell\n\n  openssl x509 -noout -text -in njs/http/certs/ca/intermediate/certs/client.cert.pem | grep 'X509v3 Subject Alternative Name' -A1\n  X509v3 Subject Alternative Name:\n  IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1, DNS:example.com, DNS:www2.example.com\n\n  curl https://localhost/ --insecure --key njs/http/certs/ca/intermediate/private/client.key.pem --cert njs/http/certs/ca/intermediate/certs/client.cert.pem  --pass secretpassword\n  [\"7f000001\",\"00000000000000000000000000000001\",\"example.com\",\"www2.example.com\"]\n\nSecurely serve encrypted traffic without server restarts when certificate or key changes occur. [http/certs/dynamic]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nConfigure NGINX to serve encrypted traffic without server restarts when certificate or key changes occur by using `js_shared_dict_zone \u003chttps://nginx.org/en/docs/http/ngx_http_js_module.html#js_shared_dict_zone\u003e`_ as a cache.\n\nNote: this example below work with njs \u003e= `0.8.0 \u003chttp://nginx.org/en/docs/njs/changes.html#njs0.8.0\u003e`_.\n\nThis example demonstrates:\n\n - Use of `js_set \u003chttps://nginx.org/en/docs/http/ngx_http_js_module.html#js_set\u003e`_ in combination with ``ssl_certificate data:$var;`` to use NJS to resolve value of cert/key during handshake.\n - Use of `js_shared_dict_zone \u003chttps://nginx.org/en/docs/http/ngx_http_js_module.html#js_shared_dict_zone\u003e`_ to store cert/key in memory.\n - Implementation a simple RESTful API to manage ``shared_dict`` to get/set certificate/key files.\n - How to deal with ``Content-Disposition`` while handling file uploads in NJS.\n\nnginx.conf:\n\n.. code-block:: nginx\n\n  ...\n\n  load_module modules/ngx_http_js_module.so;\n  error_log /dev/stdout debug;\n  events {  }\n\n  http {\n    js_path \"/etc/nginx/njs/\";\n    js_import main from http/certs/js/dynamic.js;\n    js_shared_dict_zone zone=kv:1m;\n\n    server {\n      listen 80;\n      listen 443 ssl;\n      server_name www.example.com;\n\n      js_var $shared_dict_zone_name kv;\n      js_var $cert_folder '/tmp/';\n\n      js_set $dynamic_ssl_cert main.js_cert;\n      js_set $dynamic_ssl_key main.js_key;\n\n      ssl_password_file /etc/nginx/njs/http/certs/ca/password;\n      ssl_certificate data:$dynamic_ssl_cert;\n      ssl_certificate_key data:$dynamic_ssl_key;\n\n      location = / {\n        js_content main.info;\n      }\n\n      location /kv {\n        js_content main.kv;\n      }\n\n      location = /clear {\n        js_content main.clear_cache;\n      }\n    }\n\n  }\n\n\nHere we would implement ``js_set`` handlers that reads cert/key from a FS or from `shared_dict`` (used as a cache here):\n\n.. code-block:: js\n\n  function js_cert(r) {\n    if (r.variables['ssl_server_name']) {\n      return read_cert_or_key(r, '.cert.pem');\n    } else {\n      return '';\n    }\n  }\n\n  function js_key(r) {\n    if (r.variables['ssl_server_name']) {\n      return read_cert_or_key(r, '.key.pem');\n    } else {\n      return '';\n    }\n  }\n\n  function joinPaths(...args) {\n    return args.join('/').replace(/\\/+/g, '/');\n  }\n\n  function read_cert_or_key(r, fileExtension) {\n    let data = '';\n    let path = '';\n    const zone = r.variables['shared_dict_zone_name'];\n    let certName = r.variables.ssl_server_name;\n    let prefix = r.variables['cert_folder'] || '/etc/nginx/certs/';\n    path = joinPaths(prefix, certName + fileExtension);\n    r.log(`Resolving ${path}`);\n    const key = ['certs', path].join(':');\n    const cache = zone \u0026\u0026 ngx.shared \u0026\u0026 ngx.shared[zone];\n\n    if (cache) {\n      data = cache.get(key) || '';\n      if (data) {\n        r.log(`Read ${key} from cache`);\n        return data;\n      }\n    }\n    try {\n      data = fs.readFileSync(path, 'utf8');\n      r.log('Read from cache');\n    } catch (e) {\n      data = '';\n      r.log(`Error reading from file:', ${path}, . Error=${e}`);\n    }\n    if (cache \u0026\u0026 data) {\n      try {\n        cache.set(key, data);\n        r.log('Persisted in cache');\n      } catch (e) {\n        const errMsg = `Error writing to shared dict zone: ${zone}. Error=${e}`;\n        r.log(errMsg);\n      }\n    }\n    return data\n  }\n\nThe rest of code can be found in the `njs/http/certs/js/dynamic.js \u003cnjs/http/certs/js/dynamic.js\u003e`_.\n\nChecking:\n\n.. code-block:: shell\n\n  # when started and there is no cert/key it fails to serve HTTPS\n  curl -k --resolve www.example.com:443:127.0.0.1 https://www.example.com:443\n\n  curl http://localhost/\n\n  # Upload cert/key files. file name would be used to form a key for shared_dict\n  curl -iv http://localhost:80/kv -F cert=@njs/http/certs/ca/intermediate/certs/www.example.com.cert.pem -F key=@njs/http/certs/ca/intermediate/private/www.example.com.key.pem\n\n  # Get Certificate from shared_dict:\n  curl http://localhost/kv/www.example.com.cert.pem\n\n  # Get Private Key from shared_dict:\n  curl http://localhost/kv/www.example.com.key.pem\n\n  # now we can test HTTPS again\n  curl -k --resolve www.example.com:443:127.0.0.1 https://www.example.com\n\n  # Clear shared_dict\n  curl http://localhost/clear\n\n\nFetch\n-----\n\nHTTPS fetch example [http/certs/fetch_https]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nnginx.conf:\n\n.. code-block:: nginx\n\n    ...\n\n    http {\n          js_path \"/etc/nginx/njs/\";\n\n          js_import main from http/certs/js/fetch_https.js;\n\n          resolver 1.1.1.1;\n\n          server {\n                listen 80;\n\n                location / {\n                    js_content main.fetch;\n                    js_fetch_trusted_certificate /etc/nginx/njs/http/certs/ISRG_Root_X1.pem;\n                }\n          }\n    }\n\nexample.js:\n\n.. code-block:: js\n\n    async function fetch(r) {\n        let reply = await ngx.fetch('https://nginx.org/');\n        let text = await reply.text();\n        let footer = \"----------NGINX.ORG-----------\";\n\n        r.return(200, `${footer}\\n${text.substring(0, 200)} ...${text.length - 200} left...\\n${footer}`);\n    }\n\n    export default {fetch};\n\nProxying\n--------\n\nSubrequests join [http/join_subrequests]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nCombining the results of several subrequests asynchronously into a single JSON reply.\n\nnginx.conf:\n\n.. code-block:: nginx\n\n  ...\n\n  http {\n    js_path \"/etc/nginx/njs/\";\n\n    js_import utils.js;\n    js_import main from http/join_subrequests.js;\n\n    server {\n          listen 80;\n\n          location /join {\n              js_content main.join;\n          }\n\n          location /foo {\n              proxy_pass http://localhost:8080;\n          }\n\n          location /bar {\n              proxy_pass http://localhost:8090;\n          }\n    }\n }\n\nexample.js:\n\n.. code-block:: js\n\n    async function join(r) {\n        join_subrequests(r, ['/foo', '/bar']);\n    }\n\n    async function join_subrequests(r, subs) {\n        let results = await Promise.all(subs.map(uri =\u003e r.subrequest(uri)));\n\n         let response = results.map(reply =\u003e ({\n            uri:  reply.uri,\n            code: reply.status,\n            body: reply.responseText,\n         }));\n\n        r.return(200, JSON.stringify(response));\n    }\n\n    export default {join};\n\nChecking:\n\n.. code-block:: shell\n\n  curl http://localhost/join\n  [{\"uri\":\"/foo\",\"code\":200,\"body\":\"FOO\"},{\"uri\":\"/bar\",\"code\":200,\"body\":\"BAR\"}]\n\n\nSubrequests chaining [http/subrequests_chaining]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nSubrequests chaining.\n\nnginx.conf:\n\n.. code-block:: nginx\n\n  ...\n\n  http {\n    js_path \"/etc/nginx/njs/\";\n\n    js_import utils.js;\n    js_import main from http/subrequests_chaining.js;\n\n    server {\n          listen 80;\n\n          location / {\n              js_content main.process;\n          }\n\n          location = /auth {\n              internal;\n              proxy_pass http://localhost:8080;\n          }\n\n          location = /backend {\n              internal;\n              proxy_pass http://localhost:8090;\n          }\n    }\n\n    ...\n }\n\nexample.js:\n\n.. code-block:: js\n\n    async function process(r) {\n        try {\n            let reply = await r.subrequest('/auth')\n            let response = JSON.parse((reply.responseText));\n            let token = response['token'];\n\n            if (!token) {\n                throw new Error(\"token is not available\");\n            }\n\n            let backend_reply = await r.subrequest('/backend', `token=${token}`);\n            r.return(backend_reply.status, backend_reply.responseText);\n\n        } catch (e) {\n            r.return(500, e);\n        }\n    }\n\n    function authenticate(r) {\n        let auth = r.headersIn.Authorization;\n        if (auth \u0026\u0026 auth.slice(7) === 'secret') {\n            r.return(200, JSON.stringify({status: \"OK\", token:42}));\n            return;\n        }\n\n        r.return(403, JSON.stringify({status: \"INVALID\"}));\n    }\n\n    export default {process, authenticate};\n\nChecking:\n\n.. code-block:: shell\n\n  curl http://localhost/start -H 'Authorization: Bearer secret'\n  Token is 42\n\n  curl http://localhost/start\n  Error: token is not available\n\n  curl http://localhost/start -H 'Authorization: Bearer secre'\n  Error: token is not available\n\nModifying response\n------------------\n\nModifying or deleting cookies sent by the upstream server [http/response/modify_set_cookie]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nnginx.conf:\n\n.. code-block:: nginx\n\n  ...\n\n  http {\n    js_path \"/etc/nginx/njs/\";\n\n    js_import main from http/response/modify_set_cookie.js;\n\n    server {\n          listen 80;\n\n          location /modify_cookies {\n              js_header_filter main.cookies_filter;\n              proxy_pass http://localhost:8080;\n          }\n    }\n\n    server {\n          listen 8080;\n\n          location /modify_cookies {\n              add_header Set-Cookie \"XXXXXX\";\n              add_header Set-Cookie \"BB\";\n              add_header Set-Cookie \"YYYYYYY\";\n              return 200;\n          }\n    }\n  }\n\nexample.js:\n\n.. code-block:: js\n\n    function cookies_filter(r) {\n        var cookies = r.headersOut['Set-Cookie'];\n        r.headersOut['Set-Cookie'] = cookies.filter(v=\u003ev.length \u003e Number(r.args.len));\n    }\n\n    export default {cookies_filter};\n\nChecking:\n\n.. code-block:: shell\n\n  curl http://localhost/modify_cookies?len=1 -v\n    ...\n  \u003c Set-Cookie: XXXXXX\n  \u003c Set-Cookie: BB\n  \u003c Set-Cookie: YYYYYYY\n\n  curl http://localhost/modify_cookies?len=3 -v\n    ...\n  \u003c Set-Cookie: XXXXXX\n  \u003c Set-Cookie: YYYYYYY\n\nConverting response body characters to lower case [http/response/to_lower_case]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nnginx.conf:\n\n.. code-block:: nginx\n\n  ...\n\n  http {\n    js_path \"/etc/nginx/njs/\";\n\n    js_import main from http/response/to_lower_case.js;\n\n    server {\n          listen 80;\n\n          location / {\n              js_body_filter main.to_lower_case;\n              proxy_pass http://localhost:8080;\n          }\n    }\n\n    server {\n          listen 8080;\n\n          location / {\n              return 200 'Hello World';\n          }\n    }\n  }\n\nexample.js:\n\n.. code-block:: js\n\n    function to_lower_case(r, data, flags) {\n        r.sendBuffer(data.toLowerCase(), flags);\n    }\n\n    export default {to_lower_case};\n\nChecking:\n\n.. code-block:: shell\n\n  curl http://localhost/\n  hello world\n\nLogging\n-------\n\nLogging the Number of Requests Per Client [http/logging/num_requests]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. note:: The `keyval \u003chttp://nginx.org/en/docs/http/ngx_http_keyval_module.html#keyval\u003e`_ and `keyval_zone \u003chttp://nginx.org/en/docs/http/ngx_http_keyval_module.html#keyval_zone\u003e`_ directives are available as part of our `commercial subscription \u003chttps://www.nginx.com/products/nginx/\u003e`_.\n\nIn this example `keyval \u003chttp://nginx.org/en/docs/http/ngx_http_keyval_module.html#keyval\u003e`_ is used to count (accross all nginx workers) the incoming requests from the same ip address.\n\nnginx.conf:\n\n.. code-block:: nginx\n\n  ...\n\n  http {\n    js_path \"/etc/nginx/njs/\";\n\n    js_import main from http/logging/num_requests.js;\n\n    js_set $num_requests http.num_requests;\n\n    keyval_zone zone=foo:10m;\n\n    keyval $remote_addr $foo zone=foo;\n\n    log_format bar '$remote_addr [$time_local] $num_requests';\n\n    access_log logs/access.log bar;\n\n    server {\n          listen 80;\n\n          location / {\n              return 200;\n          }\n    }\n  }\n\nexample.js:\n\n.. code-block:: js\n\n    function num_requests(r) {\n        var n = r.variables.foo;\n        n = n ? Number(n) + 1 : 1;\n        r.variables.foo = n;\n        return n;\n    }\n\n    export default {num_requests};\n\nChecking:\n\n.. code-block:: shell\n\n  curl http://localhost/aa; curl http://localhost/aa; curl http://localhost/aa\n  curl --interface 127.0.0.2 http://localhost/aa; curl --interface 127.0.0.2 http://localhost/aa\n\n  docker logs njs_example\n  127.0.0.1 [22/Nov/2021:16:55:06 +0000] 1\n  127.0.0.1 [22/Nov/2021:16:55:07 +0000] 2\n  127.0.0.1 [22/Nov/2021:16:55:29 +0000] 3\n  127.0.0.2 [22/Nov/2021:18:20:24 +0000] 1\n  127.0.0.2 [22/Nov/2021:18:20:25 +0000] 2\n\nShared Dictionary\n-----------------\n\nHTTP Rate limit[http/rate-limit/simple]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIn this example `js_shared_dict_zone \u003chttps://nginx.org/en/docs/http/ngx_http_js_module.html#js_shared_dict_zone\u003e`_ is used to implement a simple rate limit and can be set in different contexts.\nThe rate limit is implemented using a shared dictionary zone and a simple javascript function that is called for each request and increments the counter for the current window.\nIf the counter exceeds the limit, the function returns the number of seconds until the end of the window. The function is called using\n`js_set \u003chttps://nginx.org/en/docs/http/ngx_http_js_module.html#js_set\u003e`_ and the result is stored in a variable that is used to return a 429 response if the limit is exceeded.\n\nnginx.conf:\n\n.. code-block:: nginx\n\n    http {\n      js_path \"/etc/nginx/njs/\";\n      js_import main from http/rate-limit/simple.js;\n      # optionally set timeout so NJS resets and deletes all data for ratelimit counters\n      js_shared_dict_zone zone=kv:1M timeout=3600s evict;\n\n      server {\n        listen 80;\n        server_name www.example.com;\n        # access_log off;\n        js_var $rl_zone_name kv;          # shared dict zone name; requred variable\n        js_var $rl_windows_ms 30000;      # optional window in miliseconds; default 1 minute window if not set\n        js_var $rl_limit 10;              # optional limit for the window; default 10 requests if not set\n        js_var $rl_key $remote_addr;      # rate limit key; default remote_addr if not set\n        js_set $rl_result main.ratelimit; # call ratelimit function that returns retry-after value if limit is exceeded\n\n        location = / {\n          # test rate limit result\n          if ($rl_result != \"0\") {\n            add_header Retry-After $rl_result always;\n            return 429 \"Too Many Requests.\";\n          }\n          # Your normal processing here\n          return 200 \"hello world\";\n        }\n      }\n    }\n\nexample.js:\n\n.. code-block:: js\n\n    const defaultResponse = \"0\";\n    function ratelimit(r) {\n        const zone = r.variables['rl_zone_name'];\n        const kv = zone \u0026\u0026 ngx.shared \u0026\u0026 ngx.shared[zone];\n        if (!kv) {\n            r.log(`ratelimit: ${zone} js_shared_dict_zone not found`);\n            return defaultResponse;\n        }\n\n        const key = r.variables['rl_key'] || r.variables['remote_addr'];\n        const window = Number(r.variables['rl_windows_ms']) || 60000;\n        const limit = Number(r.variables['rl_limit']) || 10;\n        const now = Date.now();\n\n        let requestData = kv.get(key);\n        if (requestData === undefined || requestData.length === 0) {\n            requestData = { timestamp: now, count: 1 }\n            kv.set(key, JSON.stringify(requestData));\n            return defaultResponse;\n        }\n        try {\n            requestData = JSON.parse(requestData);\n        } catch (e) {\n            requestData = { timestamp: now, count: 1 }\n            kv.set(key, JSON.stringify(requestData));\n            return defaultResponse;\n        }\n        if (!requestData) {\n            requestData = { timestamp: now, count: 1 }\n            kv.set(key, JSON.stringify(requestData));\n            return defaultResponse;\n        }\n        if (now - requestData.timestamp \u003e= window) {\n            requestData.timestamp = now;\n            requestData.count = 1;\n        } else {\n            requestData.count++;\n        }\n        const elapsed = now - requestData.timestamp;\n        r.log(`limit: ${limit} window: ${window} elapsed: ${elapsed}  count: ${requestData.count} timestamp: ${requestData.timestamp}`)\n        let retryAfter = 0;\n        if (requestData.count \u003e limit) {\n            retryAfter = Math.ceil((window - elapsed) / 1000);\n        }\n        kv.set(key, JSON.stringify(requestData));\n        return retryAfter.toString();\n    }\n\n    export default { ratelimit };\n\n\n.. code-block:: shell\n\n  curl http://localhost\n  200 hello world\n\n  curl http://localhost\n  200 hello world\n\n  # 3rd request should fail according to the rate limit $rl_limit=2\n  curl http://localhost\n  429 rate limit exceeded\n\n\nNGINX-PLUS API\n--------------\n\nSetting keyval using a subrequest [http/api/set_keyval]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. note:: The `keyval \u003chttp://nginx.org/en/docs/http/ngx_http_keyval_module.html#keyval\u003e`_, `api \u003chttp://nginx.org/en/docs/http/ngx_http_api_module.html#api\u003e`_ and `keyval_zone \u003chttp://nginx.org/en/docs/http/ngx_http_keyval_module.html#keyval_zone\u003e`_ directives are available as part of our `commercial subscription \u003chttps://www.nginx.com/products/nginx/\u003e`_.\n\nnginx.conf:\n\n.. code-block:: nginx\n\n  ...\n\n  http {\n    js_path \"/etc/nginx/njs/\";\n\n    js_import main from http/api/set_keyval.js;\n\n    keyval_zone zone=foo:10m;\n\n    server {\n          listen 80;\n\n          location /keyval {\n              js_content main.set_keyval;\n          }\n\n          location /api {\n              internal;\n              api write=on;\n          }\n\n          location /api/ro {\n              api;\n          }\n    }\n\nexample.js:\n\n.. code-block:: js\n\n    async function set_keyval(r) {\n        let method = r.args.method ? r.args.method : 'POST';\n        let res = await r.subrequest('/api/7/http/keyvals/foo',\n                                     { method, body: r.requestText});\n\n        if (res.status \u003e= 300) {\n            r.return(res.status, res.responseText);\n            return;\n        }\n\n        r.return(200);\n    }\n\n    export default {set_keyval};\n\nChecking:\n\n.. code-block:: shell\n\n  curl http://localhost/api/ro/7/http/keyvals/foo\n  {}\n  curl http://localhost:8000/keyval -d '{\"a\":1}'\n  OK\n  curl http://localhost/api/ro/7/http/keyvals/foo\n  {\"a\":\"1\"}\n  curl http://localhost:8000/keyval -d '{\"a\":2}'\n  {\"error\":{\"status\":409,\"text\":\"key \\\"a\\\" already exists\",\"code\":\"KeyvalKeyExists\"},\"request_id\":\"cbec775883f6b10f2fe79e27d3f249ce\",\"href\":\"https://nginx.org/en/docs/http/ngx_http_api_module.html\"}\n  curl http://localhost:8000/keyval?method=PATCH -d '{\"a\":2}'\n  OK\n  curl http://localhost:8000/api/ro/7/http/keyvals/foo\n  {\"a\":\"2\"}\n\nStream\n======\n\nAuthorization\n-------------\n\nAuthorizing connections using ngx.fetch() as auth_request [stream/auth_request]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nThe example illustrates the usage of ngx.fetch() as an `auth request`_ analog in\nstream with a very simple TCP-based protocol: a connection starts with a\nmagic prefix \"MAGiK\" followed by a secret 2 bytes. The preread_verify handler\nreads the first part of a connection and sends the secret bytes for verification\nto a HTTP endpoint. Later it decides based upon the endpoint reply whether\nforward the connection to an upstream or reject the connection.\n\nnginx.conf:\n\n.. code-block:: nginx\n\n  stream {\n        js_path \"/etc/nginx/njs/\";\n\n        js_import main from stream/auth_request.js;\n\n        server {\n              listen 80;\n\n              js_preread  main.preread_verify;\n\n              proxy_pass 127.0.0.1:8081;\n        }\n\n        server {\n              listen 8081;\n\n              return BACKEND\\n;\n        }\n  }\n\n  http {\n        js_path \"/etc/nginx/njs/\";\n\n        js_import main from stream/auth_request.js;\n\n        server {\n              listen 8080;\n\n              server_name  aaa;\n\n              location /validate {\n                  js_content main.validate;\n              }\n        }\n  }\n\nexample.js:\n\n.. code-block:: js\n\n  function preread_verify(s) {\n      var collect = '';\n\n      s.on('upload', async function (data, flags) {\n          collect += data;\n\n          if (collect.length \u003e= 5 \u0026\u0026 collect.startsWith('MAGiK')) {\n              s.off('upload');\n              let reply = ngx.fetch('http://127.0.0.1:8080/validate',\n                                    {body: collect.slice(5,7),\n                                     headers: {Host:'aaa'}});\n\n              (reply.status == 200) ? s.done(): s.deny();\n\n          } else if (collect.length) {\n              s.deny();\n          }\n      });\n  }\n\n  function validate(r) {\n          r.return((r.requestText == 'QZ') ? 200 : 403);\n  }\n\n  export default {validate, preread_verify};\n\nChecking:\n\n.. code-block:: shell\n\n  telnet 127.0.0.1 80\n  ...\n  Hi\n  Connection closed by foreign host.\n\n  telnet 127.0.0.1 80\n  ...\n  MAGiKQZ\n  BACKEND\n  Connection closed by foreign host.\n\n  telnet 127.0.0.1 80\n  ...\n  MAGiKQQ\n  Connection closed by foreign host.\n\nRouting\n-------\n\nChoosing upstream in stream based on the underlying protocol [stream/detect_http]\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nnginx.conf:\n\n.. code-block:: nginx\n\n  ...\n\n  stream {\n    js_path \"/etc/nginx/njs/\";\n\n    js_import utils.js;\n    js_import main from stream/detect_http.js;\n\n    js_set $upstream main.upstream_type;\n\n    upstream httpback {\n        server 127.0.0.1:8080;\n    }\n\n    upstream tcpback {\n        server 127.0.0.1:3001;\n    }\n\n    server {\n          listen 80;\n\n          js_preread  main.detect_http;\n\n          proxy_pass $upstream;\n    }\n  }\n\n\nexample.js:\n\n.. code-block:: js\n\n    var is_http = 0;\n\n    function detect_http(s) {\n        s.on('upload', function (data, flags) {\n            var n = data.indexOf('\\r\\n');\n            if (n != -1 \u0026\u0026 data.substr(0, n - 1).endsWith(\" HTTP/1.\")) {\n                is_http = 1;\n            }\n\n            if (data.length || flags.last) {\n                s.done();\n            }\n        });\n    }\n\n    function upstream_type(s) {\n        return is_http ? \"httpback\" : \"tcpback\";\n    }\n\n    export default {detect_http, upstream_type}\n\nChecking:\n\n.. code-block:: shell\n\n  curl http://localhost/\n  HTTPBACK\n\n  telnet 127.0.0.1 80\n  Trying 127.0.0.1...\n  Connected to 127.0.0.1.\n  Escape character is '^]'.\n  TEST\n  TCPBACK\n  Connection closed by foreign host.\n\nMisc\n====\n\nFile IO [misc/file_io]\n----------------------\n\nnginx.conf:\n\n.. code-block:: nginx\n\n    http {\n      js_path \"/etc/nginx/njs/\";\n\n      js_import utils.js;\n      js_import main from misc/file_io.js;\n\n      server {\n            listen 80;\n\n            location /version {\n                js_content utils.version;\n            }\n\n            location /push {\n                js_content main.push;\n            }\n\n            location /flush {\n                js_content main.flush;\n            }\n\n            location /read {\n                js_content main.read;\n            }\n    }\n\nexample.js:\n\n.. code-block:: js\n\n  import fs from 'fs';\n  var STORAGE = \"/tmp/njs_storage\"\n\n  function push(r) {\n          fs.appendFileSync(STORAGE, r.requestText);\n          r.return(200);\n  }\n\n  function flush(r) {\n          fs.writeFileSync(STORAGE, \"\");\n          r.return(200);\n  }\n\n  function read(r) {\n          var data = \"\";\n          try {\n              data = fs.readFileSync(STORAGE);\n          } catch (e) {\n          }\n\n          r.return(200, data);\n  }\n\n  export default {push, flush, read}\n\n.. code-block:: shell\n\n  curl http://localhost/read\n  200 \u003cempty reply\u003e\n\n  curl http://localhost/push -X POST --data 'AAA'\n  200\n\n  curl http://localhost/push -X POST --data 'BBB'\n  200\n\n  curl http://localhost/push -X POST --data 'CCC'\n  200\n\n  curl http://localhost/read\n  200 AAABBBCCC\n\n  curl http://localhost/flush -X POST\n  200\n\n  curl http://localhost/read\n  200 \u003cempty reply\u003e\n\nWebcrypto (AES-GSM) [misc/aes_gsm]\n----------------------------------\n\nnginx.conf:\n\n.. code-block:: nginx\n\n    http {\n      js_path \"/etc/nginx/njs/\";\n\n      js_import main from misc/aes_gsm.js;\n\n      server {\n            listen 80;\n\n            location /encrypt {\n                js_content main.encrypt;\n            }\n\n            location /decrypt {\n                js_content main.decrypt;\n            }\n      }\n    }\n\nexample.js:\n\n.. code-block:: js\n\n    async function encryptUAM(key_in, iv, text) {\n        const alg = { name: 'AES-GCM', iv: iv ? Buffer.from(iv, 'hex')\n                                              : crypto.getRandomValues(new Uint8Array(12)) };\n\n        const sha256 = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(key_in));\n        const key = await crypto.subtle.importKey('raw', sha256, alg, false, ['encrypt']);\n\n        const cipher = await crypto.subtle.encrypt(alg, key, new TextEncoder().encode(text));\n\n        return JSON.stringify({\n            cipher: btoa(String.fromCharCode.apply(null, new Uint8Array(cipher))),\n                iv: btoa(String.fromCharCode.apply(null, new Uint8Array(alg.iv))),\n        });\n    }\n\n    async function decryptUAM(key_in, value) {\n        value = JSON.parse(value);\n\n        ngx.log(ngx.ERR, njs.dump(value))\n        const alg = { name: 'AES-GCM', iv: Buffer.from(value.iv, 'base64') };\n        const sha256 = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(key_in));\n        const key = await crypto.subtle.importKey('raw', sha256, alg, false, ['decrypt']);\n\n        const decrypt = await crypto.subtle.decrypt(alg, key, Buffer.from(value.cipher, 'base64'));\n        ngx.log(ngx.ERR, njs.dump(new Uint8Array(decrypt)))\n        return new TextDecoder().decode(decrypt);\n    }\n\n    async function encrypt(r) {\n        try {\n            let encrypted = await encryptUAM(r.args.key, r.args.iv, r.requestText);\n            r.return(200, encrypted);\n        } catch (e) {\n            r.return(500, `encryption failed with ${e.message}`);\n        }\n    }\n\n    async function decrypt(r) {\n        try {\n            let decrypted = await decryptUAM(r.args.key, r.requestText);\n            r.return(200, decrypted);\n        } catch (e) {\n            r.return(500, `decryption failed with ${e.message}`);\n        }\n    }\n\n    export default {encrypt, decrypt};\n\n.. code-block:: shell\n\n    curl 'http://localhost/encrypt?key=mySecret\u0026iv=000000000000000000000001' -d TEXT-TO-BE-ENCODED\n    {\"cipher\":\"kLKXeb/h1inwXYlP7M504xCD+/1sF4yesCSUc7/OJiyPyw==\",\"iv\":\"AAAAAAAAAAAAAAAB\"}\n\n    curl 'http://localhost/decrypt?key=mySecret' -d '{\"cipher\":\"kLKXeb/h1inwXYlP7M504xCD+/1sF4yesCSUc7/OJiyPyw==\",\"iv\":\"AAAAAAAAAAAAAAAA\"}'\n    decryption failed with EVP_DecryptFinal_ex() failed\n\n    curl 'http://localhost/decrypt?key=mySecre' -d '{\"cipher\":\"kLKXeb/h1inwXYlP7M504xCD+/1sF4yesCSUc7/OJiyPyw==\",\"iv\":\"AAAAAAAAAAAAAAAB\"}'\n    decryption failed with EVP_DecryptFinal_ex() failed\n\n    curl 'http://localhost/decrypt?key=mySecret' -d '{\"cipher\":\"kLKXeb/h1inwXYlP7M504xCD+/1sF4yesCSUc7/OJiyPyw==\",\"iv\":\"AAAAAAAAAAAAAAAB\"}'\n    TEXT-TO-BE-ENCODED\n\nCommand line interface\n======================\n\n.. code-block:: shell\n\n  docker run -i -t nginx:latest /usr/bin/njs\n\n.. code-block:: none\n\n    interactive njs 0.4.1\n\n    v.\u003cTab\u003e -\u003e the properties and prototype methods of v.\n\n    \u003e\u003e globalThis\n    global {\n     console: Console {\n      log: [Function: native],\n      dump: [Function: native],\n      time: [Function: native],\n      timeEnd: [Function: native]\n     },\n     njs: njs {\n      version: '0.4.1'\n     },\n     print: [Function: native],\n     global: [Circular],\n     process: process {\n      argv: [\n       '/usr/bin/njs',\n       ''\n      ],\n      env: {\n       HOSTNAME: '483ac20bb33f',\n       HOME: '/root',\n       PKG_RELEASE: '1~buster',\n       TERM: 'xterm',\n       NGINX_VERSION: '1.19.0',\n       PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',\n       NJS_VERSION: '0.4.1',\n       PWD: '/'\n      }\n     }\n    }\n\nAdditional learning materials\n=============================\n\n* `soulteary/njs-learning-materials \u003chttps://github.com/soulteary/njs-learning-materials\u003e`_\n* `4141done/talks-njs_for_fun \u003chttps://github.com/4141done/talks-njs_for_fun\u003e`_\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnginx%2Fnjs-examples","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnginx%2Fnjs-examples","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnginx%2Fnjs-examples/lists"}