├── .babelrc ├── .chglog ├── CHANGELOG.tpl.md └── config.yml ├── .eslintignore ├── .eslintrc.js ├── .github ├── auto-merge.yml ├── dependabot.yml └── workflows │ ├── dependabot-automerge.yml │ ├── node.js.yml │ └── publish-npm.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __tests__ ├── js-hello.test.js ├── js-status.test.js ├── js-transform.test.js ├── pdk.test.js └── pluing.test.js ├── bin └── kong-js-pluginserver ├── cli.js ├── examples ├── js-goodbye │ ├── index.js │ └── package.json ├── js-graphql │ ├── index.js │ └── package.json ├── js-hello.js ├── js-status.js ├── js-transform.js ├── package.json └── ts-hello.ts ├── kong ├── client │ ├── index.d.ts │ └── tls.d.ts ├── cluster.d.ts ├── ctx │ ├── index.d.ts │ └── shared.d.ts ├── enterprise_edition │ ├── index.d.ts │ └── jwe.d.ts ├── index.d.ts ├── ip.d.ts ├── log.d.ts ├── nginx │ ├── index.d.ts │ └── shared.d.ts ├── node.d.ts ├── plugin.d.ts ├── request.d.ts ├── response.d.ts ├── router.d.ts ├── service │ ├── index.d.ts │ ├── request.d.ts │ └── response.d.ts ├── telemetry.d.ts └── vault.d.ts ├── lib ├── mod.js └── pipe.js ├── listener.js ├── package-lock.json ├── package.json ├── pdk.js ├── plugin_test.js └── server.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "current" 8 | } 9 | } 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.chglog/CHANGELOG.tpl.md: -------------------------------------------------------------------------------- 1 | {{ if .Versions -}} 2 | 3 | ## [Unreleased] 4 | 5 | {{ if .Unreleased.CommitGroups -}} 6 | {{ range .Unreleased.CommitGroups -}} 7 | ### {{ .Title }} 8 | {{ range .Commits -}} 9 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} 10 | {{ end }} 11 | {{ end -}} 12 | {{ end -}} 13 | {{ end -}} 14 | 15 | {{ range .Versions }} 16 | 17 | ## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }} 18 | {{ range .CommitGroups -}} 19 | ### {{ lower .Title }} 20 | {{ range .Commits -}} 21 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} [{{ .Hash.Short }}]({{ $.Info.RepositoryURL }}/commit/{{ .Hash.Long }}) 22 | {{ end }} 23 | {{ end -}} 24 | 25 | {{- if .NoteGroups -}} 26 | {{ range .NoteGroups -}} 27 | ### {{ .Title }} 28 | {{ range .Notes }} 29 | {{ .Body }} 30 | {{ end }} 31 | {{ end -}} 32 | {{ end -}} 33 | {{ end -}} 34 | 35 | {{- if .Versions }} 36 | [Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD 37 | {{ range .Versions -}} 38 | {{ if .Tag.Previous -}} 39 | [{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }} 40 | {{ end -}} 41 | {{ end -}} 42 | {{ end -}} 43 | -------------------------------------------------------------------------------- /.chglog/config.yml: -------------------------------------------------------------------------------- 1 | style: github 2 | template: CHANGELOG.tpl.md 3 | info: 4 | title: CHANGELOG 5 | repository_url: https://github.com/Kong/kong-js-pdk 6 | options: 7 | sort: "semver" 8 | commits: 9 | filters: 10 | Type: 11 | - feat 12 | - fix 13 | - refactor 14 | commit_groups: 15 | title_maps: 16 | feat: Features 17 | fix: Bug Fixes 18 | perf: Performance Improvements 19 | refactor: Code Refactoring 20 | header: 21 | pattern: "^(\\w*)(?:\\([\\w\\$\\.\\-\\*\\s\\/]*\\))?:?\\s(.*)$" 22 | pattern_maps: 23 | - Type 24 | - Subject 25 | notes: 26 | keywords: 27 | - BREAKING CHANGE 28 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: false, 10 | }, 11 | extends: [ 12 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 13 | 'standard' 14 | ], 15 | plugins: [ 16 | ], 17 | // add your custom rules here 18 | rules: { 19 | // allow async-await 20 | 'generator-star-spacing': 'off', 21 | // allow debugger during development 22 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/auto-merge.yml: -------------------------------------------------------------------------------- 1 | - match: 2 | dependency_type: all 3 | update_type: "semver:minor" # includes patch updates! 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: github-actions 9 | directory: / 10 | schedule: 11 | interval: weekly 12 | - package-ecosystem: npm 13 | directory: / 14 | schedule: 15 | interval: weekly 16 | - package-ecosystem: npm 17 | directory: /examples 18 | schedule: 19 | interval: weekly 20 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-automerge.yml: -------------------------------------------------------------------------------- 1 | name: dependabot-auto-merge 2 | 3 | on: 4 | pull_request_target: 5 | 6 | jobs: 7 | auto-merge: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: ahmadnassri/action-dependabot-auto-merge@v2 12 | with: 13 | target: minor 14 | # needs push access to repo 15 | github-token: ${{ secrets.DEPENDABOT_AUTOMERGE_PUSH_TOKEN }} 16 | command: "squash and merge" 17 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | node-version: [12.x, 14.x, 16.x] 18 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | cache: 'npm' 27 | - run: npm i 28 | - run: npm run build --if-present 29 | - run: npm test 30 | - uses: codecov/codecov-action@v3 31 | with: 32 | flags: node-${{ matrix.node-version }} 33 | token: ${{ secrets.CODECOV_TOKEN }} 34 | -------------------------------------------------------------------------------- /.github/workflows/publish-npm.yml: -------------------------------------------------------------------------------- 1 | name: npm-publish 2 | on: 3 | push: 4 | branches: 5 | - master # Change this to your default branch 6 | jobs: 7 | npm-publish: 8 | name: npm-publish 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout repository 12 | uses: actions/checkout@v3 13 | - name: Publish if version has been updated 14 | uses: pascalgn/npm-publish-action@1.3.9 15 | with: # All of theses inputs are optional 16 | tag_name: "%s" 17 | tag_message: "%s" 18 | create_tag: "true" 19 | commit_pattern: "^release: (\\S+)" 20 | workspace: "." 21 | publish_command: "yarn" 22 | publish_args: "--non-interactive" 23 | env: # More info about the environment variables in the README 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Leave this as is, it's automatically generated 25 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} # You need to set this in your repo settings -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | coverage/ 6 | # Editor directories and files 7 | .idea 8 | .vscode 9 | *.suo 10 | *.ntvs* 11 | *.njsproj 12 | *.sln 13 | *.vim 14 | *.swp 15 | 16 | examples/package-lock.json 17 | examples/node_modules 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## [Unreleased] 3 | 4 | 5 | 6 | ## [0.6.0] - 2024-10-10 7 | ### bug fixes 8 | - incorrect doc ([#316](https://github.com/Kong/kong-js-pdk/issues/316)) [ebc91ba](https://github.com/Kong/kong-js-pdk/commit/ebc91ba775bbc28ca81c284f41a5f96eacf130a3) 9 | 10 | 11 | 12 | ## [0.5.5] - 2023-02-15 13 | 14 | 15 | ## [0.5.4] - 2022-11-08 16 | ### bug fixes 17 | - ignore file starts with `.` ([#232](https://github.com/Kong/kong-js-pdk/issues/232)) [72903d5](https://github.com/Kong/kong-js-pdk/commit/72903d59c75056c48ae4e488725d08e9e9ebdfd6) 18 | - function signature ([#162](https://github.com/Kong/kong-js-pdk/issues/162)) [79de00f](https://github.com/Kong/kong-js-pdk/commit/79de00f36b989cdb4fe9979acc3b4be474d328b5) 19 | - function signature for kong/kong[#8623](https://github.com/Kong/kong-js-pdk/issues/8623) ([#161](https://github.com/Kong/kong-js-pdk/issues/161)) [2977f80](https://github.com/Kong/kong-js-pdk/commit/2977f80602db47ba743a97878141f92e14871b17) 20 | - use version defined in package.json ([#148](https://github.com/Kong/kong-js-pdk/issues/148)) [e9090c8](https://github.com/Kong/kong-js-pdk/commit/e9090c849ec71ba03f2c37ef43d1c2ed78b1adee) 21 | 22 | ### features 23 | - add a reference graphQL server plugin [94b2d67](https://github.com/Kong/kong-js-pdk/commit/94b2d67bc8dda23036ca9014c5a5b924b4474792) 24 | 25 | 26 | 27 | ## [0.5.3] - 2022-02-22 28 | ### bug fixes 29 | - fix the assignment of mocked log functions ([#143](https://github.com/Kong/kong-js-pdk/issues/143)) [1b4aa12](https://github.com/Kong/kong-js-pdk/commit/1b4aa124d483b104ff783812fe50c261f4db8ce4) 30 | 31 | 32 | 33 | ## [0.5.2] - 2022-02-17 34 | ### bug fixes 35 | - exit immediately from kong.response.{error,exit} ([#141](https://github.com/Kong/kong-js-pdk/issues/141)) [c0fab3b](https://github.com/Kong/kong-js-pdk/commit/c0fab3b097f590af547eca648e44e00a0e4c4c0e) 36 | - conditionally merging the serviceResponse only if the response.status is undefined ([#131](https://github.com/Kong/kong-js-pdk/issues/131)) [c9eed50](https://github.com/Kong/kong-js-pdk/commit/c9eed50e29bceb59db5a17506dcf086cc906f050) 37 | 38 | 39 | 40 | ## [0.5.1] - 2022-01-25 41 | 42 | 43 | ## [0.5.0] - 2021-12-09 44 | ### bug fixes 45 | - correct error constructor arguments ([#100](https://github.com/Kong/kong-js-pdk/issues/100)) [9375614](https://github.com/Kong/kong-js-pdk/commit/9375614c7c3d5b198ee858a174c065a98b658f95) 46 | 47 | ### features 48 | - allow plugin loading from discrete npm packages ([#101](https://github.com/Kong/kong-js-pdk/issues/101)) [ec76dc8](https://github.com/Kong/kong-js-pdk/commit/ec76dc83132d388aeecca41fb5e756c0b40fbd26) 49 | 50 | 51 | 52 | ## [0.4.4] - 2021-11-22 53 | ### bug fixes 54 | - remove unnecessary promise wrappers ([#81](https://github.com/Kong/kong-js-pdk/issues/81)) [32d2c8b](https://github.com/Kong/kong-js-pdk/commit/32d2c8bc46734bedbd242850fa0c69584c624d22) 55 | - remove unused import and use relative import in bin ([#80](https://github.com/Kong/kong-js-pdk/issues/80)) [65da804](https://github.com/Kong/kong-js-pdk/commit/65da804ab70ab2846b7b9a629281b65e171b0373) 56 | 57 | 58 | 59 | ## [0.4.3] - 2021-10-21 60 | ### bug fixes 61 | - rename msgpack.pack to msgpack.encode ([#77](https://github.com/Kong/kong-js-pdk/issues/77)) [8fc0ff5](https://github.com/Kong/kong-js-pdk/commit/8fc0ff5ef9bcee5d446bdf3b19a5457c98c69f1a) 62 | 63 | 64 | 65 | ## [0.4.2] - 2021-08-13 66 | ### bug fixes 67 | - allow to concat buffer larger than 64k ([#34](https://github.com/Kong/kong-js-pdk/issues/34)) [f0b0ce5](https://github.com/Kong/kong-js-pdk/commit/f0b0ce5f1a2ae5857402a3db931ba1f7b0bb8df0) 68 | - add kong.response.error in plugin_test ([#35](https://github.com/Kong/kong-js-pdk/issues/35)) [4a469f1](https://github.com/Kong/kong-js-pdk/commit/4a469f1d77327890c0bb6217a418bcdc64acbc34) 69 | 70 | 71 | 72 | ## [0.4.0] - 2021-08-06 73 | ### features 74 | - adds Version as named property in GetPluginInfo function in GetPluginInfo function([#20](https://github.com/Kong/kong-js-pdk/issues/20)) [383e44e](https://github.com/Kong/kong-js-pdk/commit/383e44e50ade0a74b390a0a822659591280dae3a) 75 | 76 | 77 | 78 | ## [0.3.4] - 2021-06-15 79 | ### bug fixes 80 | - server to generate correct Step functions ([#11](https://github.com/Kong/kong-js-pdk/issues/11)) [46208f5](https://github.com/Kong/kong-js-pdk/commit/46208f5c5c3968a82bddfd185b47dc8b34d8cb92) 81 | - remove kong.table TS definitions ([#12](https://github.com/Kong/kong-js-pdk/issues/12)) [128bc58](https://github.com/Kong/kong-js-pdk/commit/128bc5850d9cdf4f51124623d394093914326f3e) 82 | 83 | 84 | 85 | ## [0.3.3] - 2021-05-19 86 | ### bug fixes 87 | - reorganize dependencies ([#8](https://github.com/Kong/kong-js-pdk/issues/8)) [167e56c](https://github.com/Kong/kong-js-pdk/commit/167e56c2b1de07efd345bfddafff8ae7201e7a9b) 88 | 89 | 90 | 91 | ## [0.3.2] - 2021-05-19 92 | ### features 93 | - add tooling to test plugin code ([#7](https://github.com/Kong/kong-js-pdk/issues/7)) [82d01a6](https://github.com/Kong/kong-js-pdk/commit/82d01a68885c5b049dc72aaf0a969cc476bbb38d) 94 | 95 | 96 | 97 | ## [0.3.1] - 2021-05-13 98 | ### bug fixes 99 | - use standarlized error for instance not found exception [32960ff](https://github.com/Kong/kong-js-pdk/commit/32960ff1015f2cc85d2ad147d4fc31a1ba543b7d) 100 | 101 | 102 | 103 | ## [0.3.0] - 2021-05-07 104 | ### bug fixes 105 | - popup PDK errors and add response phase [9152fc1](https://github.com/Kong/kong-js-pdk/commit/9152fc187420c66d2421af8ecdffbfe0617ea482) 106 | - indent and style for package.json [883d1d7](https://github.com/Kong/kong-js-pdk/commit/883d1d7778b224cc5a836b78f74f125209f420ad) 107 | 108 | 109 | 110 | ## 0.1.0 - 2021-03-15 111 | ### features 112 | - allow to import TypeScript plugin directly [c375a11](https://github.com/Kong/kong-js-pdk/commit/c375a11587af296ffeca5b103fa6e8c51e79d1a4) 113 | - support write plugin in typescript [f63bb91](https://github.com/Kong/kong-js-pdk/commit/f63bb9182cc422f9a80c89abc59f6725dc6b426c) 114 | 115 | 116 | [Unreleased]: https://github.com/Kong/kong-js-pdk/compare/0.6.0...HEAD 117 | [0.6.0]: https://github.com/Kong/kong-js-pdk/compare/0.5.5...0.6.0 118 | [0.5.5]: https://github.com/Kong/kong-js-pdk/compare/0.5.4...0.5.5 119 | [0.5.4]: https://github.com/Kong/kong-js-pdk/compare/0.5.3...0.5.4 120 | [0.5.3]: https://github.com/Kong/kong-js-pdk/compare/0.5.2...0.5.3 121 | [0.5.2]: https://github.com/Kong/kong-js-pdk/compare/0.5.1...0.5.2 122 | [0.5.1]: https://github.com/Kong/kong-js-pdk/compare/0.5.0...0.5.1 123 | [0.5.0]: https://github.com/Kong/kong-js-pdk/compare/0.4.4...0.5.0 124 | [0.4.4]: https://github.com/Kong/kong-js-pdk/compare/0.4.3...0.4.4 125 | [0.4.3]: https://github.com/Kong/kong-js-pdk/compare/0.4.2...0.4.3 126 | [0.4.2]: https://github.com/Kong/kong-js-pdk/compare/0.4.0...0.4.2 127 | [0.4.0]: https://github.com/Kong/kong-js-pdk/compare/0.3.4...0.4.0 128 | [0.3.4]: https://github.com/Kong/kong-js-pdk/compare/0.3.3...0.3.4 129 | [0.3.3]: https://github.com/Kong/kong-js-pdk/compare/0.3.2...0.3.3 130 | [0.3.2]: https://github.com/Kong/kong-js-pdk/compare/0.3.1...0.3.2 131 | [0.3.1]: https://github.com/Kong/kong-js-pdk/compare/0.3.0...0.3.1 132 | [0.3.0]: https://github.com/Kong/kong-js-pdk/compare/0.1.0...0.3.0 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2021 Kong Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kong-js-pdk 2 | 3 | [![Node.js CI](https://github.com/Kong/kong-js-pdk/actions/workflows/node.js.yml/badge.svg)](https://github.com/Kong/kong-js-pdk/actions/workflows/node.js.yml) 4 | [![NPM](https://img.shields.io/npm/v/kong-pdk)](https://www.npmjs.com/package/kong-pdk) 5 | [![codecov](https://codecov.io/gh/Kong/kong-js-pdk/branch/main/graph/badge.svg?token=OLN3HEOIVP)](https://codecov.io/gh/Kong/kong-js-pdk) 6 | 7 | Plugin server and PDK (Plugin Development Kit) for Javascript language support in Kong. 8 | 9 | Requires Kong >= 2.3.0. 10 | 11 | ## Documentation 12 | 13 | See in [Kong Docs](https://docs.konghq.com/gateway-oss/latest/external-plugins/#developing-javascript-plugins). 14 | 15 | ## TODO 16 | 17 | - Better API design for user land (without async/await?) 18 | - Dedicated server per plugin 19 | - Rewrite with typescript 20 | -------------------------------------------------------------------------------- /__tests__/js-hello.test.js: -------------------------------------------------------------------------------- 1 | const plugin = require('../examples/js-hello'); 2 | 3 | const { 4 | PluginTest, 5 | Request 6 | } = require("../plugin_test") 7 | 8 | 9 | test('Set headers in response', async () => { 10 | let r = new Request() 11 | 12 | r 13 | .useURL("http://example.com") 14 | .useMethod("GET") 15 | .useHeaders({ 16 | "Host": "example.com", 17 | }) 18 | 19 | let t = new PluginTest(r) 20 | 21 | await t.Run(plugin, { 22 | "message": "test", 23 | }) 24 | 25 | expect(t.response.headers.get('x-hello-from-javascript')) 26 | .toBe('Javascript says test to example.com') 27 | }); 28 | -------------------------------------------------------------------------------- /__tests__/js-status.test.js: -------------------------------------------------------------------------------- 1 | const plugin = require('../examples/js-status'); 2 | 3 | const { 4 | PluginTest, 5 | Request 6 | } = require("../plugin_test") 7 | 8 | 9 | test('Should succeed with status 200 when header is present', async () => { 10 | let r = new Request() 11 | 12 | r 13 | .useURL("http://example.com") 14 | .useMethod("GET") 15 | .useHeaders({ 16 | "userId": "test-id", 17 | }) 18 | 19 | let t = new PluginTest(r) 20 | 21 | await t.Run(plugin, { 22 | "message": "test", 23 | }) 24 | 25 | expect(t.response.status).toBe(200) 26 | expect(t.response.headers.get('x-welcome')) 27 | .toBe('Javascript says test to test-id') 28 | }); 29 | 30 | test('Should exit with status 403 when header is missing', async () => { 31 | let r = new Request() 32 | 33 | r 34 | .useURL("http://example.com") 35 | .useMethod("GET") 36 | 37 | let t = new PluginTest(r) 38 | 39 | await t.Run(plugin, { 40 | "message": "test", 41 | }) 42 | 43 | expect(t.response.status).toBe(403) 44 | }); 45 | -------------------------------------------------------------------------------- /__tests__/js-transform.test.js: -------------------------------------------------------------------------------- 1 | const plugin = require('../examples/js-transform'); 2 | 3 | const { 4 | PluginTest, 5 | Request 6 | } = require("../plugin_test") 7 | 8 | 9 | test('Set headers in response', async () => { 10 | let r = new Request() 11 | 12 | r 13 | .useURL("http://example.com") 14 | .useMethod("GET") 15 | .useHeaders({ 16 | "Host": "example.com", 17 | }) 18 | .useBody("all lower case") 19 | 20 | let t = new PluginTest(r) 21 | 22 | await t.Run(plugin, {}) 23 | 24 | expect(t.serviceRequest.body) 25 | .toBe('ALL LOWER CASE') 26 | 27 | expect(t.serviceRequest.url.search) 28 | .toBe('?js-transform=v0.1.0') 29 | 30 | expect(t.response.status) 31 | .toBe(200) 32 | 33 | expect(t.serviceResponse.body) 34 | .toBe("OK") 35 | 36 | expect(t.response.body) 37 | .toBe( 38 | `Response body from upstream: 39 | OK 40 | Body size: 2 41 | `) 42 | 43 | }) 44 | -------------------------------------------------------------------------------- /__tests__/pdk.test.js: -------------------------------------------------------------------------------- 1 | const PDK = require('../pdk') 2 | const PipePair = require('../lib/pipe') 3 | 4 | test('kong js pdk', async () => { 5 | let [_, child] = new PipePair().getPair() 6 | const pdk = new PDK(child) 7 | expect('' + pdk).toBe('[object KongPDK]') 8 | expect(new pdk.Error).toBeInstanceOf(Error) 9 | 10 | { 11 | const error = new pdk.Error('this is an error') 12 | expect('' + error).toBe('PDKError: this is an error') 13 | } 14 | 15 | { 16 | const error = new PDK.Error('this is an error') 17 | expect('' + error).toBe('PDKError: this is an error') 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /__tests__/pluing.test.js: -------------------------------------------------------------------------------- 1 | const plugin = require('../examples/js-goodbye') 2 | 3 | const {promisify} = require('util') 4 | const sleep = promisify(setTimeout) 5 | const { 6 | PluginTest, 7 | Request 8 | } = require('../plugin_test') 9 | 10 | test('plugin interface', async () => { 11 | let r = new Request() 12 | const start = Date.now() / 1000 13 | await sleep(1000) 14 | 15 | r 16 | .useURL("http://example.com") 17 | .useMethod("GET") 18 | .useHeaders({ 19 | "Host": "example.com", 20 | }) 21 | 22 | let t = new PluginTest(r) 23 | const {mod, instance} = await t.Run(plugin, { 24 | "message": "test", 25 | }) 26 | 27 | expect(mod.getLastStartInstanceTime()).toBeGreaterThan(start) 28 | expect(mod.getName()).toBe('goodbye') 29 | expect(mod.getPhases()).toEqual(expect.arrayContaining(['access'])) 30 | expect(mod.getPriority()).toBe(plugin.Priority) 31 | expect(mod.getVersion()).toBe(plugin.Version) 32 | expect(mod.getSchema()).toMatchObject(plugin.Schema) 33 | 34 | expect(instance.getName()).toBe('goodbye') 35 | expect(instance.isExpired(10)).toBe(false) 36 | 37 | const lastCloseInstanceTime = mod.lastCloseInstanceTime + 0 38 | instance.close() 39 | await sleep(1000) 40 | 41 | expect(mod.getLastCloseInstanceTime()).toBeGreaterThan(lastCloseInstanceTime) 42 | 43 | expect(instance.getConfig()).toMatchObject({message: 'test'}) 44 | expect(instance.getStartTime()).toBeLessThan(Date.now()/1000) 45 | 46 | instance.resetExpireTs() 47 | expect(instance.getLastUsedTime()).toBeGreaterThan(0) 48 | }); 49 | -------------------------------------------------------------------------------- /bin/kong-js-pluginserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict' 4 | 5 | let path = require('path') 6 | 7 | const msgpack = require("@msgpack/msgpack") 8 | const logger = require('node-color-log') 9 | 10 | let cli = require('../cli') 11 | let Server = require('../server') 12 | let Listener = require('../listener') 13 | 14 | const opts = cli.parse() 15 | 16 | logger.setLevel(opts.logLevel) 17 | 18 | const ps = new Server(opts.pluginsDirectory, logger) 19 | if (opts.DumpPluginInfo) { 20 | ps.GetPluginInfo(opts.DumpPluginInfo) 21 | .then((v) => { 22 | process.stdout.write(msgpack.encode(v)) 23 | }) 24 | } else if (opts.DumpAllPlugins) { 25 | Promise.all(function *() { 26 | for (const plugin of ps.getPlugins().keys()) { 27 | yield ps.GetPluginInfo(plugin) 28 | } 29 | }()) 30 | .then((values) => { 31 | console.log(JSON.stringify(values)) 32 | }) 33 | } else { 34 | let l = new Listener(ps, path.join(opts.kongPrefix, opts.sockName)) 35 | l.serve() 36 | } 37 | 38 | ps.close() 39 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | Command, 5 | Option 6 | } = require('commander') 7 | 8 | const {version} = require('./package.json') 9 | 10 | const program = new Command() 11 | program.version(version, '--version') 12 | 13 | const logLevel = ["info", "debug"] 14 | 15 | function increaseVerbosity(dummyValue, previous) { 16 | return previous + 1; 17 | } 18 | 19 | function parse(dedicated) { 20 | program 21 | .option('-p, --kong-prefix, -kong-prefix ', 22 | 'unix domain socket path to listen', 23 | '/usr/local/kong') 24 | .option('-v, --verbose', 'verbose logging', increaseVerbosity, 0) 25 | .option('--sock-name ', 'socket name to listen on', 'js_pluginserver.sock') 26 | 27 | if (dedicated) { 28 | program.option('--dump, -dump', 'dump current plugin info into stdout') 29 | } else { 30 | program 31 | .requiredOption('-d, --plugins-directory, -plugins-directory ', 32 | 'plugins directory for .js files') 33 | .option('--dump-plugin-info, -dump-plugin-info ', 34 | 'dump specific plugin info into stdout') 35 | .option('--dump-all-plugins, -dump-all-plugins', 'dump all plugins info into stdout') 36 | } 37 | 38 | program.parse(process.argv) 39 | 40 | let opts = program.opts() 41 | opts.verbose = opts.verbose || 0 42 | opts.logLevel = logLevel[opts.verbose > 1 ? 1 : opts.verbose] 43 | 44 | return opts 45 | } 46 | 47 | module.exports.parse = parse 48 | -------------------------------------------------------------------------------- /examples/js-goodbye/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is an example plugin that add a header to the response 4 | 5 | class KongPlugin { 6 | constructor(config) { 7 | this.config = config 8 | this.message = config.message || 'goodbye' 9 | } 10 | 11 | async access(kong) { 12 | await Promise.all([ 13 | kong.response.setHeader('x-goodbye-from-javascript', `Javascript says ${this.message}`), 14 | kong.response.setHeader('x-javascript-pid', process.pid), 15 | ]) 16 | } 17 | } 18 | 19 | module.exports = { 20 | Plugin: KongPlugin, 21 | Name: 'goodbye', 22 | Schema: [ 23 | { message: { type: 'string' } }, 24 | ], 25 | Version: '0.1.0', 26 | Priority: 0, 27 | } 28 | -------------------------------------------------------------------------------- /examples/js-goodbye/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-goodbye", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC" 12 | } 13 | -------------------------------------------------------------------------------- /examples/js-graphql/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var { graphql, buildSchema } = require('graphql'); 3 | 4 | // Construct a schema, using GraphQL schema language 5 | var schema = buildSchema(` 6 | type Query { 7 | hello: String 8 | } 9 | `); 10 | 11 | // The rootValue provides a resolver function for each API endpoint 12 | var rootValue = { 13 | hello: () => { 14 | return 'Hello world!'; 15 | }, 16 | }; 17 | 18 | // This is an example plugin that add a header to the response 19 | 20 | class KongPlugin { 21 | constructor(config) { 22 | this.config = config 23 | } 24 | 25 | async access(kong) { 26 | let body = await kong.request.getRawBody() 27 | 28 | let response = await graphql({ 29 | schema, 30 | source: body, 31 | rootValue 32 | }) 33 | 34 | await kong.response.exit(200, response) 35 | } 36 | } 37 | 38 | module.exports = { 39 | Plugin: KongPlugin, 40 | Name: 'js-graphql', 41 | Schema: [ 42 | { message: { type: "string" } }, 43 | ], 44 | Version: '0.1.0', 45 | Priority: 0, 46 | } 47 | -------------------------------------------------------------------------------- /examples/js-graphql/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kong-plugin-js-graphql", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "graphql": "^16.4.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/js-hello.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is an example plugin that add a header to the response 4 | 5 | class KongPlugin { 6 | constructor(config) { 7 | this.config = config 8 | } 9 | 10 | async access(kong) { 11 | let host = await kong.request.getHeader("host") 12 | if (host === undefined) { 13 | return await kong.log.err("unable to get header for request") 14 | } 15 | 16 | let message = this.config.message || "hello" 17 | 18 | // the following can be "parallel"ed 19 | await Promise.all([ 20 | kong.response.setHeader("x-hello-from-javascript", "Javascript says " + message + " to " + host), 21 | kong.response.setHeader("x-javascript-pid", process.pid), 22 | ]) 23 | } 24 | } 25 | 26 | module.exports = { 27 | Plugin: KongPlugin, 28 | Schema: [ 29 | { message: { type: "string" } }, 30 | ], 31 | Version: '0.1.0', 32 | Priority: 0, 33 | } 34 | -------------------------------------------------------------------------------- /examples/js-status.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is an example plugin that sets the response status based on the existence of a header 4 | 5 | class KongPlugin { 6 | constructor(config) { 7 | this.config = config 8 | } 9 | 10 | async access(kong) { 11 | let userId = await kong.request.getHeader("userId") 12 | if (!userId) { 13 | return kong.response.exit(403); 14 | } 15 | 16 | let message = this.config.message || "hello" 17 | 18 | await Promise.all([ 19 | kong.response.setHeader("x-welcome", "Javascript says " + message + " to " + userId), 20 | ]) 21 | } 22 | } 23 | 24 | module.exports = { 25 | Plugin: KongPlugin, 26 | Schema: [ 27 | { message: { type: "string" } }, 28 | ], 29 | Version: '0.1.0', 30 | Priority: 0, 31 | } 32 | -------------------------------------------------------------------------------- /examples/js-transform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is an example plugin that appends a string in response body 4 | 5 | class KongPlugin { 6 | constructor(config) { 7 | this.config = config 8 | } 9 | 10 | async access(kong) { 11 | // Only need the following if response handler not exist 12 | // buffered proxying is automatically turned on for plugin 13 | // with a response handler 14 | // await kong.service.request.enableBuffering() 15 | 16 | let requestBody = await kong.request.getRawBody() 17 | // convert it to uppercase 18 | requestBody = requestBody.replace(/(.)/g, function(v) { return v.toUpperCase(); }) 19 | 20 | await Promise.all([ 21 | // append "?js-transform=v0.1.0" to request line to upstream 22 | kong.service.request.setQuery({ 23 | "js-transform": "v0.1.0", 24 | }), 25 | // set the transformed request body to upstream 26 | kong.service.request.setRawBody(requestBody), 27 | ]) 28 | } 29 | 30 | // Note: by defining reponse handler implictly turns on buffered proxying 31 | // on the Route/Service and may break connections like WebSocket 32 | // and has performance penalty 33 | async response(kong) { 34 | if (await kong.response.getSource() == "service") { 35 | let body = await kong.service.response.getRawBody() 36 | 37 | body = "Response body from upstream:\n" + body + "\nBody size: " + body.length + "\n" 38 | 39 | await kong.response.exit(await kong.response.getStatus(), body) 40 | } 41 | } 42 | } 43 | 44 | module.exports = { 45 | Plugin: KongPlugin, 46 | Schema: [{ 47 | message: { 48 | type: "string" 49 | } 50 | }, ], 51 | Version: '0.1.0', 52 | Priority: 0, 53 | } 54 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test": "jest" 4 | }, 5 | "devDependencies": { 6 | "jest": "^29.2.2" 7 | }, 8 | "dependencies": { 9 | "kong-pdk": "file:.." 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/ts-hello.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import kong from "../kong" 4 | 5 | // This is an example plugin that add a header to the response 6 | 7 | class KongPlugin { 8 | config: any; 9 | constructor(config: any) { 10 | this.config = config 11 | } 12 | 13 | async access(kong: kong) { 14 | let host = await kong.request.getHeader("host") 15 | if (host === undefined) { 16 | return await kong.log.err("unable to get header for request") 17 | } 18 | 19 | let message = this.config.message || "hello" 20 | 21 | // the following can be "parallel"ed 22 | await Promise.all([ 23 | kong.response.setHeader("x-hello-from-javascript", "Javascript says " + message + " to " + host), 24 | kong.response.setHeader("x-javascript-pid", process.pid), 25 | ]) 26 | } 27 | } 28 | 29 | module.exports = { 30 | Plugin: KongPlugin, 31 | Name: 'hello', 32 | Schema: [ 33 | { message: { type: "string" } }, 34 | ], 35 | Version: '0.1.0', 36 | Priority: 0, 37 | } 38 | -------------------------------------------------------------------------------- /kong/client/index.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/client.lua 3 | 4 | import type tls from "./tls" 5 | 6 | export default interface client { 7 | 8 | tls: tls; 9 | 10 | /** 11 | * -- assuming `credential` and `consumer` have been set by some authentication code 12 | * kong.client.authenticate(consumer, credentials) 13 | * @param consumer The consumer to set. If no 14 | value is provided, then any existing value will be cleared. 15 | * @param credential The credential to set. If 16 | no value is provided, then any existing value will be cleared. 17 | */ 18 | authenticate(consumer: Array | object, credential: Array | object): Promise; 19 | 20 | /** 21 | * -- assuming `consumer_id` is provided by some code 22 | * kong.client.authenticate_consumer_group_by_consumer_id(consumer_id) 23 | * @param consumer_id The consumer id to use for setting the consumer group. 24 | If no value is provided, the current consumer group is not changed. 25 | */ 26 | authenticateConsumerGroupByConsumerId(consumer_id: string): Promise; 27 | 28 | /** 29 | * local consumer = kong.client.get_consumer() 30 | * if consumer then 31 | * consumer_id = consumer.id 32 | * else 33 | * -- request not authenticated yet, or a credential 34 | * -- without a consumer (external auth) 35 | * end 36 | * @returns The authenticated consumer entity. 37 | */ 38 | getConsumer(): Promise | object>; 39 | 40 | /** 41 | * local group = kong.client.get_consumer_group() 42 | * @returns The authenticated consumer group. Returns `nil` if no 43 | consumer group has been authenticated for the current request. 44 | */ 45 | getConsumerGroup(): Promise | object>; 46 | 47 | /** 48 | * local groups = kong.client.get_consumer_groups() 49 | * @returns The authenticated consumer groups. Returns `nil` if no 50 | consumer groups has been authenticated for the current request. 51 | */ 52 | getConsumerGroups(): Promise | object>; 53 | 54 | /** 55 | * local credential = kong.client.get_credential() 56 | * if credential then 57 | * consumer_id = credential.consumer_id 58 | * else 59 | * -- request not authenticated yet 60 | * end 61 | * @returns The authenticated credential. 62 | */ 63 | getCredential(): Promise; 64 | 65 | /** 66 | * -- Given a client with IP 127.0.0.1 making connection through 67 | * -- a load balancer with IP 10.0.0.1 to Kong answering the request for 68 | * -- https://username:password@example.com:1234/v1/movies 69 | * kong.client.get_forwarded_ip() -- "127.0.0.1" 70 | * -- Note: This example assumes that 10.0.0.1 is one of the trusted IPs, and that 71 | * -- the load balancer adds the right headers matching with the configuration 72 | * -- of `real_ip_header`, e.g. `proxy_protocol`. 73 | * @returns The remote IP address of the client making the request, 74 | considering forwarded addresses. 75 | */ 76 | getForwardedIp(): Promise; 77 | 78 | /** 79 | * -- [client]:40000 <-> 80:[balancer]:30000 <-> 80:[kong]:20000 <-> 80:[service] 80 | * kong.client.get_forwarded_port() -- 40000 81 | * -- Note: This example assumes that [balancer] is one of the trusted IPs, and that 82 | * -- the load balancer adds the right headers matching with the configuration 83 | * -- of `real_ip_header`, e.g. `proxy_protocol`. 84 | * @returns The remote client port, considering forwarded ports. 85 | */ 86 | getForwardedPort(): Promise; 87 | 88 | /** 89 | * -- Given a client with IP 127.0.0.1 making connection through 90 | * -- a load balancer with IP 10.0.0.1 to Kong answering the request for 91 | * -- https://example.com:1234/v1/movies 92 | * kong.client.get_ip() -- "10.0.0.1" 93 | * @returns The remote IP address of the client making the request. 94 | */ 95 | getIp(): Promise; 96 | 97 | /** 98 | * -- [client]:40000 <-> 80:[balancer]:30000 <-> 80:[kong]:20000 <-> 80:[service] 99 | * kong.client.get_port() -- 30000 100 | * @returns The remote client port. 101 | */ 102 | getPort(): Promise; 103 | 104 | /** 105 | * kong.client.get_protocol() -- "http" 106 | * @param allow_terminated? If set, the `X-Forwarded-Proto` header is checked when checking for HTTPS. 107 | * @returns Can be one of `"http"`, `"https"`, `"tcp"`, `"tls"` or `nil`. 108 | * @returns `nil` if successful, or an error message if it fails. 109 | */ 110 | getProtocol(allow_terminated?: boolean): Promise<[ret_1: string, err: string]>; 111 | 112 | /** 113 | * local consumer_id = "john_doe" 114 | * local consumer = kong.client.load_consumer(consumer_id, true) 115 | * @param consumer_id The consumer ID to look up. 116 | * @param search_by_username? If truthy, 117 | and if the consumer is not found by ID, 118 | then a second search by username will be performed. 119 | * @returns Consumer entity or `nil`. 120 | * @returns `nil` if successful, or an error message if it fails. 121 | */ 122 | loadConsumer(consumer_id: string, search_by_username?: boolean): Promise<[ret_1: Array | object, err: string]>; 123 | 124 | /** 125 | * -- assuming `group` is provided by some code 126 | * kong.client.set_authenticated_consumer_group(group) 127 | * @param group The consumer group to set. If no 128 | value is provided, then any existing value will be cleared. 129 | this value should be a table with metadata of the group like its `id` and `name`. 130 | */ 131 | setAuthenticatedConsumerGroup(group: Array | object): Promise; 132 | 133 | /** 134 | * kong.client.set_authenticated_consumer_groups({ 135 | * { 136 | * id = "fed2bf38-10c4-404e-8d45-a2b0f521464d", 137 | * name = "my-group", 138 | * }, 139 | * { 140 | * id = "736bb9d9-98f2-46d5-97fc-d7361d9488ee", 141 | * name = "my-other-group", 142 | * } 143 | * }) 144 | * @param groups The consumer groups to set. If no 145 | value is provided, then any existing value will be cleared. 146 | This value should be a sequence-like table of tables, with each item 147 | having at least an `id` and a `name`. 148 | */ 149 | setAuthenticatedConsumerGroups(groups: Array | object): Promise; 150 | 151 | } 152 | -------------------------------------------------------------------------------- /kong/client/tls.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/client/tls.lua 3 | 4 | 5 | export default interface tls { 6 | 7 | 8 | /** 9 | * local res, err = kong.client.tls.disable_session_reuse() 10 | * if not res then 11 | * -- do something with err 12 | * end 13 | * @returns Returns `true` if successful, `nil` if it fails. 14 | * @returns Returns `nil` if successful, or an error message if it fails. 15 | */ 16 | disableSessionReuse(): Promise<[ret_1: boolean, err: string]>; 17 | 18 | /** 19 | * local cert, err = kong.client.tls.get_full_client_certificate_chain() 20 | * if err then 21 | * -- do something with err 22 | * end 23 | * if not cert then 24 | * -- client did not complete mTLS 25 | * end 26 | * -- do something with cert 27 | * @returns Returns a PEM-encoded client certificate if the mTLS 28 | handshake was completed, or `nil` if an error occurred or the client did 29 | not present its certificate. 30 | * @returns Returns `nil` if successful, or an error message if it fails. 31 | */ 32 | getFullClientCertificateChain(): Promise<[ret_1: string, err: string]>; 33 | 34 | /** 35 | * local x509_lib = require "resty.openssl.x509" 36 | * local chain_lib = require "resty.openssl.x509.chain" 37 | * local res, err 38 | * local chain = chain_lib.new() 39 | * -- err check 40 | * local x509, err = x509_lib.new(pem_cert, "PEM") 41 | * -- err check 42 | * res, err = chain:add(x509) 43 | * -- err check 44 | * -- `chain.ctx` is the raw data of the chain, i.e. `STACK_OF(X509) *` 45 | * res, err = kong.client.tls.request_client_certificate(chain.ctx) 46 | * if not res then 47 | * -- do something with err 48 | * end 49 | * @param ca_certs? The CA certificate chain opaque pointer 50 | * @returns Returns `true` if successful, or `nil` if it fails. 51 | * @returns Returns `nil` if successful, or an error message if it fails. 52 | */ 53 | requestClientCertificate(ca_certs?: cdata): Promise<[ret_1: boolean, err: string]>; 54 | 55 | /** 56 | * kong.client.tls.set_client_verify("FAILED:unknown CA") 57 | */ 58 | setClientVerify(): Promise; 59 | 60 | } 61 | -------------------------------------------------------------------------------- /kong/cluster.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/cluster.lua 3 | 4 | 5 | export default interface cluster { 6 | 7 | 8 | /** 9 | * local id, err = kong.cluster.get_id() 10 | * if err then 11 | * -- handle error 12 | * end 13 | * if not id then 14 | * -- no cluster ID is available 15 | * end 16 | * -- use id here 17 | * @returns The v4 UUID used by this cluster as its ID. 18 | * @returns An error message. 19 | */ 20 | getId(): Promise<[ret_1: string, ret_2: string]>; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /kong/ctx/index.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/ctx.lua 3 | 4 | import type shared from "./shared" 5 | 6 | export default interface ctx { 7 | 8 | shared: shared; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /kong/ctx/shared.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/ctx/shared.lua 3 | 4 | 5 | export default interface shared { 6 | 7 | 8 | /** 9 | * 10 | * @param k key for the ctx data 11 | * @returns the per-request context data in ngx.ctx 12 | */ 13 | get(k: string): Promise; 14 | 15 | /** 16 | * 17 | * @param k key for the ctx data 18 | * @param v value for the ctx data 19 | */ 20 | set(k: string, v: string): Promise; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /kong/enterprise_edition/index.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/enterprise_edition.lua 3 | 4 | import type jwe from "./jwe" 5 | 6 | export default interface enterpriseEdition { 7 | 8 | jwe: jwe; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /kong/enterprise_edition/jwe.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/enterprise_edition/jwe.lua 3 | 4 | 5 | export default interface jwe { 6 | 7 | 8 | /** 9 | * 10 | * @param token JWE encrypted JWT token 11 | * @returns A table containing JWT token parts decoded, or nil 12 | * @returns Error message, or nil 13 | */ 14 | decode(token: string): Promise<[ret_1: string, ret_2: string]>; 15 | 16 | /** 17 | * 18 | * @param key Private key 19 | * @param token JWE encrypted JWT token 20 | * @returns JWT token payload in plaintext, or nil 21 | * @returns Error message, or nil 22 | */ 23 | decrypt(key: any, token: string): Promise<[ret_1: string, ret_2: string]>; 24 | 25 | /** 26 | * 27 | * @param alg Algorithm used for key management 28 | * @param enc Encryption algorithm used for content encryption 29 | * @param key Public key 30 | * @param plaintext Plaintext 31 | * @param options? Options (optional), default: nil 32 | * @returns JWE encrypted JWT token, or nil 33 | * @returns Error message, or nil 34 | */ 35 | encrypt(alg: string, enc: string, key: any, plaintext: string, options?: Array | object): Promise<[ret_1: string, ret_2: string]>; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /kong/index.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk.lua 3 | 4 | import type client from "./client" 5 | import type cluster from "./cluster" 6 | import type ctx from "./ctx" 7 | import type enterpriseEdition from "./enterpriseEdition" 8 | import type ip from "./ip" 9 | import type log from "./log" 10 | import type nginx from "./nginx" 11 | import type node from "./node" 12 | import type plugin from "./plugin" 13 | import type request from "./request" 14 | import type response from "./response" 15 | import type router from "./router" 16 | import type service from "./service" 17 | import type telemetry from "./telemetry" 18 | import type vault from "./vault" 19 | 20 | export default interface kong { 21 | 22 | client: client; 23 | cluster: cluster; 24 | ctx: ctx; 25 | enterpriseEdition: enterpriseEdition; 26 | ip: ip; 27 | log: log; 28 | nginx: nginx; 29 | node: node; 30 | plugin: plugin; 31 | request: request; 32 | response: response; 33 | router: router; 34 | service: service; 35 | telemetry: telemetry; 36 | vault: vault; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /kong/ip.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/ip.lua 3 | 4 | 5 | export default interface ip { 6 | 7 | 8 | /** 9 | * if kong.ip.is_trusted("1.1.1.1") then 10 | * kong.log("The IP is trusted") 11 | * end 12 | * @param address A string representing an IP address. 13 | * @returns `true` if the IP is trusted, `false` otherwise. 14 | */ 15 | isTrusted(address: string): Promise; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /kong/log.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/log.lua 3 | 4 | 5 | export default interface log { 6 | 7 | 8 | /** 9 | * kong.log.warn("something require attention") 10 | * kong.log.err("something failed: ", err) 11 | * kong.log.alert("something requires immediate action") 12 | * @param ...varargs All params will be concatenated and stringified before being sent to the log. 13 | * @returns Throws an error on invalid inputs. 14 | */ 15 | alert(...varargs: any): Promise; 16 | 17 | /** 18 | * kong.log.warn("something require attention") 19 | * kong.log.err("something failed: ", err) 20 | * kong.log.alert("something requires immediate action") 21 | * @param ...varargs All params will be concatenated and stringified before being sent to the log. 22 | * @returns Throws an error on invalid inputs. 23 | */ 24 | crit(...varargs: any): Promise; 25 | 26 | /** 27 | * kong.log.warn("something require attention") 28 | * kong.log.err("something failed: ", err) 29 | * kong.log.alert("something requires immediate action") 30 | * @param ...varargs All params will be concatenated and stringified before being sent to the log. 31 | * @returns Throws an error on invalid inputs. 32 | */ 33 | debug(...varargs: any): Promise; 34 | 35 | /** 36 | * kong.log.deprecation("hello ", "world") 37 | * kong.log.deprecation("hello ", "world", { after = "2.5.0" }) 38 | * kong.log.deprecation("hello ", "world", { removal = "3.0.0" }) 39 | * kong.log.deprecation("hello ", "world", { after = "2.5.0", removal = "3.0.0" }) 40 | * kong.log.deprecation("hello ", "world", { trace = true }) 41 | * @param ...varargs all params will be concatenated and stringified before being sent to the log 42 | (if the last param is a table, it is considered as a deprecation metadata) 43 | * @returns throws an error on invalid inputs. 44 | */ 45 | deprecation(...varargs: any): Promise; 46 | 47 | /** 48 | * kong.log.warn("something require attention") 49 | * kong.log.err("something failed: ", err) 50 | * kong.log.alert("something requires immediate action") 51 | * @param ...varargs All params will be concatenated and stringified before being sent to the log. 52 | * @returns Throws an error on invalid inputs. 53 | */ 54 | err(...varargs: any): Promise; 55 | 56 | /** 57 | * kong.log.warn("something require attention") 58 | * kong.log.err("something failed: ", err) 59 | * kong.log.alert("something requires immediate action") 60 | * @param ...varargs All params will be concatenated and stringified before being sent to the log. 61 | * @returns Throws an error on invalid inputs. 62 | */ 63 | info(...varargs: any): Promise; 64 | 65 | /** 66 | * kong.log.warn("something require attention") 67 | * kong.log.err("something failed: ", err) 68 | * kong.log.alert("something requires immediate action") 69 | * @param ...varargs All params will be concatenated and stringified before being sent to the log. 70 | * @returns Throws an error on invalid inputs. 71 | */ 72 | notice(...varargs: any): Promise; 73 | 74 | /** 75 | * 76 | */ 77 | serialize(): Promise; 78 | 79 | /** 80 | * -- Adds a new value to the serialized table 81 | * kong.log.set_serialize_value("my_new_value", 1) 82 | * assert(kong.log.serialize().my_new_value == 1) 83 | * -- Value can be a table 84 | * kong.log.set_serialize_value("my", { new = { value = 2 } }) 85 | * assert(kong.log.serialize().my.new.value == 2) 86 | * -- It is possible to change an existing serialized value 87 | * kong.log.set_serialize_value("my_new_value", 3) 88 | * assert(kong.log.serialize().my_new_value == 3) 89 | * -- Unset an existing value by setting it to nil 90 | * kong.log.set_serialize_value("my_new_value", nil) 91 | * assert(kong.log.serialize().my_new_value == nil) 92 | * -- Dots in the key are interpreted as table accesses 93 | * kong.log.set_serialize_value("my.new.value", 4) 94 | * assert(kong.log.serialize().my.new_value == 4) 95 | * @param key The name of the field. 96 | * @param value Value to be set. When a table is used, its keys must be numbers, strings, or booleans, and its values can be numbers, strings, or other tables like itself, recursively. 97 | * @param options Can contain two entries: options.mode can be `set` (the default, always sets), `add` (only add if entry does not already exist) and `replace` (only change value if it already exists). 98 | * @returns The request information table. 99 | */ 100 | setSerializeValue(key: string, value: any, options: Array | object): Promise | object>; 101 | 102 | /** 103 | * kong.log.warn("something require attention") 104 | * kong.log.err("something failed: ", err) 105 | * kong.log.alert("something requires immediate action") 106 | * @param ...varargs All params will be concatenated and stringified before being sent to the log. 107 | * @returns Throws an error on invalid inputs. 108 | */ 109 | warn(...varargs: any): Promise; 110 | 111 | } 112 | -------------------------------------------------------------------------------- /kong/nginx/index.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/nginx.lua 3 | 4 | import type shared from "./shared" 5 | 6 | export default interface nginx { 7 | 8 | shared: shared; 9 | 10 | /** 11 | * 12 | * @param k key for the ctx data 13 | * @returns the per-request context data in ngx.ctx 14 | */ 15 | getCtx(k: string): Promise; 16 | 17 | /** 18 | * local nginx_statistics = kong.nginx.get_statistics() 19 | * @returns Nginx connections and requests statistics 20 | */ 21 | getStatistics(): Promise | object>; 22 | 23 | /** 24 | * 25 | * @returns the subsystem name 26 | */ 27 | getSubsystem(): Promise; 28 | 29 | /** 30 | * 31 | * @returns the TLSv1 version string 32 | */ 33 | getTls1_versionStr(): Promise; 34 | 35 | /** 36 | * 37 | * @returns get NGINX variable value 38 | */ 39 | getVar(): Promise; 40 | 41 | /** 42 | * 43 | * @returns ret_1 44 | */ 45 | reqStartTime(): Promise; 46 | 47 | /** 48 | * 49 | * @param k key for the ctx data 50 | * @param any value for the ctx data 51 | */ 52 | setCtx(k: string, any: string): Promise; 53 | 54 | } 55 | -------------------------------------------------------------------------------- /kong/nginx/shared.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/nginx/shared.lua 3 | 4 | 5 | export default interface shared { 6 | 7 | 8 | /** 9 | * 10 | * @param k key for the ctx data 11 | * @returns the per-request context data in ngx.ctx 12 | */ 13 | get(k: string): Promise; 14 | 15 | /** 16 | * 17 | * @param k key for the ctx data 18 | * @param v value for the ctx data 19 | */ 20 | set(k: string, v: string): Promise; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /kong/node.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/node.lua 3 | 4 | 5 | export default interface node { 6 | 7 | 8 | /** 9 | * local hostname = kong.node.get_hostname() 10 | * @returns The local machine hostname. 11 | */ 12 | getHostname(): Promise; 13 | 14 | /** 15 | * local id = kong.node.get_id() 16 | * @returns The v4 UUID used by this node as its ID. 17 | */ 18 | getId(): Promise; 19 | 20 | /** 21 | * local res = kong.node.get_memory_stats() 22 | * -- res will have the following structure: 23 | * { 24 | * lua_shared_dicts = { 25 | * kong = { 26 | * allocated_slabs = 12288, 27 | * capacity = 24576 28 | * }, 29 | * kong_db_cache = { 30 | * allocated_slabs = 12288, 31 | * capacity = 12288 32 | * } 33 | * }, 34 | * workers_lua_vms = { 35 | * { 36 | * http_allocated_gc = 1102, 37 | * pid = 18004 38 | * }, 39 | * { 40 | * http_allocated_gc = 1102, 41 | * pid = 18005 42 | * } 43 | * } 44 | * } 45 | * local res = kong.node.get_memory_stats("k", 1) 46 | * -- res will have the following structure: 47 | * { 48 | * lua_shared_dicts = { 49 | * kong = { 50 | * allocated_slabs = "12.0 KiB", 51 | * capacity = "24.0 KiB", 52 | * }, 53 | * kong_db_cache = { 54 | * allocated_slabs = "12.0 KiB", 55 | * capacity = "12.0 KiB", 56 | * } 57 | * }, 58 | * workers_lua_vms = { 59 | * { 60 | * http_allocated_gc = "1.1 KiB", 61 | * pid = 18004 62 | * }, 63 | * { 64 | * http_allocated_gc = "1.1 KiB", 65 | * pid = 18005 66 | * } 67 | * } 68 | * } 69 | * @param unit? The unit that memory is reported in. Can be 70 | any of `b/B`, `k/K`, `m/M`, or `g/G` for bytes, kibibytes, mebibytes, 71 | or gibibytes, respectively. Defaults to `b` (bytes). 72 | * @param scale? The number of digits to the right of the decimal 73 | point. Defaults to 2. 74 | * @returns A table containing memory usage statistics for this node. 75 | If `unit` is `b/B` (the default), reported values are Lua numbers. 76 | Otherwise, reported values are strings with the unit as a suffix. 77 | */ 78 | getMemoryStats(unit?: string, scale?: number): Promise | object>; 79 | 80 | } 81 | -------------------------------------------------------------------------------- /kong/plugin.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/plugin.lua 3 | 4 | 5 | export default interface plugin { 6 | 7 | 8 | /** 9 | * kong.plugin.get_id() -- "123e4567-e89b-12d3-a456-426614174000" 10 | * @returns The ID of the running plugin 11 | */ 12 | getId(): Promise; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /kong/request.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/request.lua 3 | 4 | 5 | export default interface request { 6 | 7 | 8 | /** 9 | * local body, err, mimetype = kong.request.get_body() 10 | * body.name -- "John Doe" 11 | * body.age -- "42" 12 | * @param mimetype? The MIME type. 13 | * @param max_args? Sets a limit on the maximum number of parsed 14 | * @param max_allowed_file_size? the max allowed file size to be read from 15 | arguments. 16 | * @returns A table representation of the body. 17 | * @returns An error message. 18 | * @returns mimetype The MIME type used. 19 | */ 20 | getBody(mimetype?: string, max_args?: number, max_allowed_file_size?: number): Promise<[ret_1: Array | object, ret_2: string, ret_3: string]>; 21 | 22 | /** 23 | * kong.request.get_forwarded_host() -- "example.com" 24 | * @returns The forwarded host. 25 | */ 26 | getForwardedHost(): Promise; 27 | 28 | /** 29 | * kong.request.get_forwarded_path() -- /path 30 | * @returns The forwarded path. 31 | */ 32 | getForwardedPath(): Promise; 33 | 34 | /** 35 | * kong.request.get_forwarded_port() -- 1234 36 | * @returns The forwarded port. 37 | */ 38 | getForwardedPort(): Promise; 39 | 40 | /** 41 | * kong.request.get_forwarded_prefix() -- /prefix 42 | * @returns The forwarded path prefix or `nil` if the prefix was 43 | not stripped. 44 | */ 45 | getForwardedPrefix(): Promise; 46 | 47 | /** 48 | * kong.request.get_forwarded_scheme() -- "https" 49 | * @returns The forwarded scheme. 50 | */ 51 | getForwardedScheme(): Promise; 52 | 53 | /** 54 | * -- Given a request with the following headers: 55 | * -- Host: foo.com 56 | * -- X-Custom-Header: bla 57 | * -- X-Another: foo bar 58 | * -- X-Another: baz 59 | * kong.request.get_header("Host") -- "foo.com" 60 | * kong.request.get_header("x-custom-header") -- "bla" 61 | * kong.request.get_header("X-Another") -- "foo bar" 62 | * @param name the name of the header to be returned 63 | * @returns the value of the header or nil if not present 64 | */ 65 | getHeader(name: string): Promise; 66 | 67 | /** 68 | * -- Given a request with the following headers: 69 | * -- Host: foo.com 70 | * -- X-Custom-Header: bla 71 | * -- X-Another: foo bar 72 | * -- X-Another: baz 73 | * local headers = kong.request.get_headers() 74 | * headers.host -- "foo.com" 75 | * headers.x_custom_header -- "bla" 76 | * headers.x_another[1] -- "foo bar" 77 | * headers["X-Another"][2] -- "baz" 78 | * @param max_headers? Sets a limit on the maximum number of 79 | parsed headers. 80 | * @returns The request headers in table form. 81 | */ 82 | getHeaders(max_headers?: number): Promise | object>; 83 | 84 | /** 85 | * -- Given a request to https://example.com:1234/v1/movies 86 | * kong.request.get_host() -- "example.com" 87 | * @returns The hostname. 88 | */ 89 | getHost(): Promise; 90 | 91 | /** 92 | * kong.request.get_http_version() -- 1.1 93 | * @returns The HTTP version as a Lua number. 94 | */ 95 | getHttpVersion(): Promise; 96 | 97 | /** 98 | * kong.request.get_method() -- "GET" 99 | * @returns The request method. 100 | */ 101 | getMethod(): Promise; 102 | 103 | /** 104 | * -- Given a request to https://example.com/t/Abc%20123%C3%B8%2f/parent/..//test/./ 105 | * kong.request.get_path() -- "/t/Abc 123ø%2F/test/" 106 | * @returns the path 107 | */ 108 | getPath(): Promise; 109 | 110 | /** 111 | * -- Given a request to https://example.com:1234/v1/movies?movie=foo 112 | * kong.request.get_path_with_query() -- "/v1/movies?movie=foo" 113 | * @returns The path with the query string. 114 | */ 115 | getPathWithQuery(): Promise; 116 | 117 | /** 118 | * -- Given a request to https://example.com:1234/v1/movies 119 | * kong.request.get_port() -- 1234 120 | * @returns The port. 121 | */ 122 | getPort(): Promise; 123 | 124 | /** 125 | * -- Given a request GET /test?foo=hello%20world&bar=baz&zzz&blo=&bar=bla&bar 126 | * for k, v in pairs(kong.request.get_query()) do 127 | * kong.log.inspect(k, v) 128 | * end 129 | * -- Will print 130 | * -- "foo" "hello world" 131 | * -- "bar" {"baz", "bla", true} 132 | * -- "zzz" true 133 | * -- "blo" "" 134 | * @param max_args? Sets a limit on the maximum number of parsed 135 | arguments. 136 | * @returns A table representation of the query string. 137 | */ 138 | getQuery(max_args?: number): Promise | object>; 139 | 140 | /** 141 | * -- Given a request GET /test?foo=hello%20world&bar=baz&zzz&blo=&bar=bla&bar 142 | * kong.request.get_query_arg("foo") -- "hello world" 143 | * kong.request.get_query_arg("bar") -- "baz" 144 | * kong.request.get_query_arg("zzz") -- true 145 | * kong.request.get_query_arg("blo") -- "" 146 | * @returns The value of the argument. 147 | */ 148 | getQueryArg(): Promise; 149 | 150 | /** 151 | * -- Given a body with payload "Hello, Earth!": 152 | * kong.request.get_raw_body():gsub("Earth", "Mars") -- "Hello, Mars!" 153 | * @returns The plain request body or nil if it does not fit into 154 | the NGINX temporary buffer. 155 | * @returns An error message. 156 | */ 157 | getRawBody(): Promise<[ret_1: Buffer, ret_2: string]>; 158 | 159 | /** 160 | * -- Given a request to https://example.com/t/Abc%20123%C3%B8%2f/parent/..//test/./?movie=foo 161 | * kong.request.get_raw_path() -- "/t/Abc%20123%C3%B8%2f/parent/..//test/./" 162 | * @returns The path. 163 | */ 164 | getRawPath(): Promise; 165 | 166 | /** 167 | * -- Given a request to https://example.com/foo?msg=hello%20world&bla=&bar 168 | * kong.request.get_raw_query() -- "msg=hello%20world&bla=&bar" 169 | * @returns The query component of the request's URL. 170 | */ 171 | getRawQuery(): Promise; 172 | 173 | /** 174 | * -- Given a request to https://example.com:1234/v1/movies 175 | * kong.request.get_scheme() -- "https" 176 | * @returns A string like `"http"` or `"https"`. 177 | */ 178 | getScheme(): Promise; 179 | 180 | /** 181 | * kong.request.get_start_time() -- 1649960273000 182 | * @returns The timestamp 183 | */ 184 | getStartTime(): Promise; 185 | 186 | /** 187 | * local captures = kong.request.get_uri_captures() 188 | * for idx, value in ipairs(captures.unnamed) do 189 | * -- do what you want to captures 190 | * end 191 | * for name, value in pairs(captures.named) do 192 | * -- do what you want to captures 193 | * end 194 | * @returns tables containing unamed and named captures. 195 | */ 196 | getUriCaptures(): Promise | object>; 197 | 198 | } 199 | -------------------------------------------------------------------------------- /kong/response.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/response.lua 3 | 4 | 5 | export default interface response { 6 | 7 | 8 | /** 9 | * kong.response.add_header("Cache-Control", "no-cache") 10 | * kong.response.add_header("Cache-Control", "no-store") 11 | * @param name The header name. 12 | * @param of strings|string|number|boolean value The header value. 13 | * @returns throws an error on invalid input. 14 | */ 15 | addHeader(name: string, of: array): Promise; 16 | 17 | /** 18 | * kong.response.set_header("X-Foo", "foo") 19 | * kong.response.add_header("X-Foo", "bar") 20 | * kong.response.clear_header("X-Foo") 21 | * -- from here onwards, no X-Foo headers will exist in the response 22 | * @param name The name of the header to be cleared 23 | * @returns throws an error on invalid input. 24 | */ 25 | clearHeader(name: string): Promise; 26 | 27 | /** 28 | * return kong.response.error(403, "Access Forbidden", { 29 | * ["Content-Type"] = "text/plain", 30 | * ["WWW-Authenticate"] = "Basic" 31 | * }) 32 | * --- 33 | * return kong.response.error(403, "Access Forbidden") 34 | * --- 35 | * return kong.response.error(403) 36 | * @param status The status to be used (>399). 37 | * @param message? The error message to be used. 38 | * @param headers? The headers to be used. 39 | * @returns throws an error on invalid input. 40 | */ 41 | error(status: number, message?: string, headers?: Array | object): Promise; 42 | 43 | /** 44 | * return kong.response.exit(403, "Access Forbidden", { 45 | * ["Content-Type"] = "text/plain", 46 | * ["WWW-Authenticate"] = "Basic" 47 | * }) 48 | * --- 49 | * return kong.response.exit(403, [[{"message":"Access Forbidden"}]], { 50 | * ["Content-Type"] = "application/json", 51 | * ["WWW-Authenticate"] = "Basic" 52 | * }) 53 | * --- 54 | * return kong.response.exit(403, { message = "Access Forbidden" }, { 55 | * ["WWW-Authenticate"] = "Basic" 56 | * }) 57 | * --- 58 | * -- In L4 proxy mode 59 | * return kong.response.exit(200, "Success") 60 | * @param status The status to be used. 61 | * @param body? The body to be used. 62 | * @param headers? The headers to be used. 63 | * @returns throws an error on invalid input. 64 | */ 65 | exit(status: number, body?: Buffer, headers?: Array | object): Promise; 66 | 67 | /** 68 | * -- Given a response with the following headers: 69 | * -- X-Custom-Header: bla 70 | * -- X-Another: foo bar 71 | * -- X-Another: baz 72 | * kong.response.get_header("x-custom-header") -- "bla" 73 | * kong.response.get_header("X-Another") -- "foo bar" 74 | * kong.response.get_header("X-None") -- nil 75 | * @param name The name of the header. 76 | Header names are case-insensitive and dashes (`-`) can be written as 77 | underscores (`_`). For example, the header `X-Custom-Header` can also be 78 | retrieved as `x_custom_header`. 79 | * @returns The value of the header. 80 | */ 81 | getHeader(name: string): Promise; 82 | 83 | /** 84 | * -- Given an response from the Service with the following headers: 85 | * -- X-Custom-Header: bla 86 | * -- X-Another: foo bar 87 | * -- X-Another: baz 88 | * local headers = kong.response.get_headers() 89 | * headers.x_custom_header -- "bla" 90 | * headers.x_another[1] -- "foo bar" 91 | * headers["X-Another"][2] -- "baz" 92 | * @param max_headers? Limits the number of headers parsed. 93 | * @returns headers A table representation of the headers in the 94 | response. 95 | * @returns err If more headers than `max_headers` were present, 96 | returns a string with the error `"truncated"`. 97 | */ 98 | getHeaders(max_headers?: number): Promise<[ret_1: Array | object, ret_2: string]>; 99 | 100 | /** 101 | * if kong.response.get_source() == "service" then 102 | * kong.log("The response comes from the Service") 103 | * elseif kong.response.get_source() == "error" then 104 | * kong.log("There was an error while processing the request") 105 | * elseif kong.response.get_source() == "exit" then 106 | * kong.log("There was an early exit while processing the request") 107 | * end 108 | * @returns The source. 109 | */ 110 | getSource(): Promise; 111 | 112 | /** 113 | * kong.response.get_status() -- 200 114 | * @returns status The HTTP status code currently set for the 115 | downstream response. 116 | */ 117 | getStatus(): Promise; 118 | 119 | /** 120 | * kong.response.set_header("X-Foo", "value") 121 | * @param name The name of the header 122 | * @param of strings|string|number|boolean value The new value for the header. 123 | * @returns throws an error on invalid input. 124 | */ 125 | setHeader(name: string, of: array): Promise; 126 | 127 | /** 128 | * kong.response.set_headers({ 129 | * ["Bla"] = "boo", 130 | * ["X-Foo"] = "foo3", 131 | * ["Cache-Control"] = { "no-store", "no-cache" } 132 | * }) 133 | * -- Will add the following headers to the response, in this order: 134 | * -- X-Bar: bar1 135 | * -- Bla: boo 136 | * -- Cache-Control: no-store 137 | * -- Cache-Control: no-cache 138 | * -- X-Foo: foo3 139 | * @param headers 140 | * @returns throws an error on invalid input. 141 | */ 142 | setHeaders(headers: Array | object): Promise; 143 | 144 | /** 145 | * kong.response.set_status(404) 146 | * @param status The new status. 147 | * @returns throws an error on invalid input. 148 | */ 149 | setStatus(status: number): Promise; 150 | 151 | } 152 | -------------------------------------------------------------------------------- /kong/router.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/router.lua 3 | 4 | 5 | export default interface router { 6 | 7 | 8 | /** 9 | * local route = kong.router.get_route() 10 | * local protocols = route.protocols 11 | * @returns The `route` entity. 12 | */ 13 | getRoute(): Promise | object>; 14 | 15 | /** 16 | * if kong.router.get_service() then 17 | * -- routed by route & service entities 18 | * else 19 | * -- routed by a route without a service 20 | * end 21 | * @returns The `service` entity. 22 | */ 23 | getService(): Promise | object>; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /kong/service/index.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/service.lua 3 | 4 | import type request from "./request" 5 | import type response from "./response" 6 | 7 | export default interface service { 8 | 9 | request: request; 10 | response: response; 11 | 12 | /** 13 | * kong.service.set_retries(233) 14 | * @param retries 15 | */ 16 | setRetries(retries: number): Promise; 17 | 18 | /** 19 | * kong.service.set_target("service.local", 443) 20 | * kong.service.set_target("192.168.130.1", 80) 21 | * @param host 22 | * @param port 23 | */ 24 | setTarget(host: string, port: number): Promise; 25 | 26 | /** 27 | * kong.service.set_target_retry_callback(function() return "service.local", 443 end) 28 | * @param retry_callback 29 | */ 30 | setTargetRetryCallback(retry_callback: function): Promise; 31 | 32 | /** 33 | * kong.service.set_timeouts(233, 233, 233) 34 | * @param connect_timeout 35 | * @param write_timeout 36 | * @param read_timeout 37 | */ 38 | setTimeouts(connect_timeout: number, write_timeout: number, read_timeout: number): Promise; 39 | 40 | /** 41 | * local ok, err = kong.service.set_tls_verify(true) 42 | * if not ok then 43 | * -- do something with error 44 | * end 45 | * @param on Whether to enable TLS certificate verification for the current request 46 | * @returns `true` if the operation succeeded, `nil` if an error occurred 47 | * @returns An error message describing the error if there was one 48 | */ 49 | setTlsVerify(on: boolean): Promise<[ret_1: boolean, ret_2: string]>; 50 | 51 | /** 52 | * local ok, err = kong.service.set_tls_verify_depth(3) 53 | * if not ok then 54 | * -- do something with error 55 | * end 56 | * @param depth Depth to use when validating. Must be non-negative 57 | * @returns `true` if the operation succeeded, `nil` if an error occurred 58 | * @returns An error message describing the error if there was one 59 | */ 60 | setTlsVerifyDepth(depth: number): Promise<[ret_1: boolean, ret_2: string]>; 61 | 62 | /** 63 | * local ok, err = kong.service.set_upstream("service.prod") 64 | * if not ok then 65 | * kong.log.err(err) 66 | * return 67 | * end 68 | * @param host 69 | * @returns `true` on success, or `nil` if no upstream entities 70 | where found 71 | * @returns An error message describing the error if there was 72 | one. 73 | */ 74 | setUpstream(host: string): Promise<[ret_1: boolean, ret_2: string]>; 75 | 76 | } 77 | -------------------------------------------------------------------------------- /kong/service/request.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/service/request.lua 3 | 4 | 5 | export default interface request { 6 | 7 | 8 | /** 9 | * kong.service.request.add_header("Cache-Control", "no-cache") 10 | * kong.service.request.add_header("Cache-Control", "no-store") 11 | * @param header The header name. Example: "Cache-Control". 12 | * @param of strings|string|number|boolean value The header value. Example: "no-cache". 13 | * @returns throws an error on invalid inputs. 14 | */ 15 | addHeader(header: string, of: array): Promise; 16 | 17 | /** 18 | * kong.service.request.set_header("X-Foo", "foo") 19 | * kong.service.request.add_header("X-Foo", "bar") 20 | * kong.service.request.clear_header("X-Foo") 21 | * -- from here onwards, no X-Foo headers will exist in the request 22 | * @param header The header name. Example: "X-Foo". 23 | * @returns throws an error on invalid inputs. 24 | The function does not throw an error if no header was removed. 25 | */ 26 | clearHeader(header: string): Promise; 27 | 28 | /** 29 | * local ok, err = kong.service.request.disable_tls() 30 | * if not ok then 31 | * -- do something with error 32 | * end 33 | * @returns `true` if the operation succeeded, `nil` if an error occurred. 34 | * @returns An error message describing the error if there was one. 35 | */ 36 | disableTls(): Promise<[ret_1: boolean, ret_2: string]>; 37 | 38 | /** 39 | * kong.service.request.enable_buffering() 40 | * @returns 41 | */ 42 | enableBuffering(): Promise; 43 | 44 | /** 45 | * kong.service.set_header("application/json") 46 | * local ok, err = kong.service.request.set_body({ 47 | * name = "John Doe", 48 | * age = 42, 49 | * numbers = {1, 2, 3} 50 | * }) 51 | * -- Produces the following JSON body: 52 | * -- { "name": "John Doe", "age": 42, "numbers":[1, 2, 3] } 53 | * local ok, err = kong.service.request.set_body({ 54 | * foo = "hello world", 55 | * bar = {"baz", "bla", true}, 56 | * zzz = true, 57 | * blo = "" 58 | * }, "application/x-www-form-urlencoded") 59 | * -- Produces the following body: 60 | * -- bar=baz&bar=bla&bar&blo=&foo=hello%20world&zzz 61 | * @param args A table with data to be converted to the appropriate format 62 | and stored in the body. 63 | * @param mimetype? can be one of: 64 | * @returns `true` on success, `nil` otherwise. 65 | * @returns `nil` on success, an error message in case of error. 66 | Throws an error on invalid inputs. 67 | */ 68 | setBody(args: Array | object, mimetype?: string): Promise<[ret_1: boolean, ret_2: string]>; 69 | 70 | /** 71 | * kong.service.request.set_header("X-Foo", "value") 72 | * @param header The header name. Example: "X-Foo". 73 | * @param of strings|string|boolean|number value The header value. Example: "hello world". 74 | * @returns throws an error on invalid inputs. 75 | */ 76 | setHeader(header: string, of: array): Promise; 77 | 78 | /** 79 | * kong.service.request.set_header("X-Foo", "foo1") 80 | * kong.service.request.add_header("X-Foo", "foo2") 81 | * kong.service.request.set_header("X-Bar", "bar1") 82 | * kong.service.request.set_headers({ 83 | * ["X-Foo"] = "foo3", 84 | * ["Cache-Control"] = { "no-store", "no-cache" }, 85 | * ["Bla"] = "boo" 86 | * }) 87 | * -- Will add the following headers to the request, in this order: 88 | * -- X-Bar: bar1 89 | * -- Bla: boo 90 | * -- Cache-Control: no-store 91 | * -- Cache-Control: no-cache 92 | * -- X-Foo: foo3 93 | * @param headers A table where each key is a string containing a header name 94 | and each value is either a string or an array of strings. 95 | * @returns throws an error on invalid inputs. 96 | */ 97 | setHeaders(headers: Array | object): Promise; 98 | 99 | /** 100 | * kong.service.request.set_method("DELETE") 101 | * @param method The method string, which must be in all 102 | uppercase. Supported values are: `"GET"`, `"HEAD"`, `"PUT"`, `"POST"`, 103 | `"DELETE"`, `"OPTIONS"`, `"MKCOL"`, `"COPY"`, `"MOVE"`, `"PROPFIND"`, 104 | `"PROPPATCH"`, `"LOCK"`, `"UNLOCK"`, `"PATCH"`, or `"TRACE"`. 105 | * @returns throws an error on invalid inputs. 106 | */ 107 | setMethod(method: string): Promise; 108 | 109 | /** 110 | * kong.service.request.set_path("/v2/movies") 111 | * @param path The path string. Special characters and UTF-8 112 | characters are allowed, for example: `"/v2/movies"` or `"/foo/😀"`. 113 | * @returns throws an error on invalid inputs. 114 | */ 115 | setPath(path: string): Promise; 116 | 117 | /** 118 | * kong.service.request.set_query({ 119 | * foo = "hello world", 120 | * bar = {"baz", "bla", true}, 121 | * zzz = true, 122 | * blo = "" 123 | * }) 124 | * -- Produces the following query string: 125 | * -- bar=baz&bar=bla&bar&blo=&foo=hello%20world&zzz 126 | * @param args A table where each key is a string (corresponding to an 127 | argument name), and each value is either a boolean, a string, or an array of 128 | strings or booleans. Any string values given are URL-encoded. 129 | * @returns throws an error on invalid inputs. 130 | */ 131 | setQuery(args: Array | object): Promise; 132 | 133 | /** 134 | * kong.service.request.set_raw_body("Hello, world!") 135 | * @param body The raw body. 136 | * @returns throws an error on invalid inputs. 137 | */ 138 | setRawBody(body: string): Promise; 139 | 140 | /** 141 | * kong.service.request.set_raw_query("zzz&bar=baz&bar=bla&bar&blo=&foo=hello%20world") 142 | * @param query The raw querystring. Example: 143 | `"foo=bar&bla&baz=hello%20world"`. 144 | * @returns throws an error on invalid inputs. 145 | */ 146 | setRawQuery(query: string): Promise; 147 | 148 | /** 149 | * kong.service.request.set_scheme("https") 150 | * @param scheme The scheme to be used. Supported values are `"http"` or `"https"`. 151 | * @returns throws an error on invalid inputs. 152 | */ 153 | setScheme(scheme: string): Promise; 154 | 155 | } 156 | -------------------------------------------------------------------------------- /kong/service/response.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/service/response.lua 3 | 4 | 5 | export default interface response { 6 | 7 | 8 | /** 9 | * -- Plugin needs to call kong.service.request.enable_buffering() on `rewrite` 10 | * -- or `access` phase prior calling this function. 11 | * local body = kong.service.response.get_body() 12 | * @param mimetype? The MIME type of the response (if known). 13 | * @param max_args? Sets a limit on the maximum number of (what?) 14 | that can be parsed. 15 | * @returns The decoded buffered body 16 | */ 17 | getBody(mimetype?: string, max_args?: number): Promise; 18 | 19 | /** 20 | * -- Given a response with the following headers: 21 | * -- X-Custom-Header: bla 22 | * -- X-Another: foo bar 23 | * -- X-Another: baz 24 | * kong.log.inspect(kong.service.response.get_header("x-custom-header")) -- "bla" 25 | * kong.log.inspect(kong.service.response.get_header("X-Another")) -- "foo bar" 26 | * @param name The name of the header. 27 | Header names in are case-insensitive and are normalized to lowercase, and 28 | dashes (`-`) can be written as underscores (`_`); that is, the header 29 | `X-Custom-Header` can also be retrieved as `x_custom_header`. 30 | * @returns The value of the header, or `nil` if a header with 31 | `name` is not found in the response. If a header with the same name is present 32 | multiple times in the response, this function returns the value of the 33 | first occurrence of this header. 34 | */ 35 | getHeader(name: string): Promise; 36 | 37 | /** 38 | * -- Given a response with the following headers: 39 | * -- X-Custom-Header: bla 40 | * -- X-Another: foo bar 41 | * -- X-Another: baz 42 | * local headers = kong.service.response.get_headers() 43 | * if headers then 44 | * kong.log.inspect(headers.x_custom_header) -- "bla" 45 | * kong.log.inspect(headers.x_another[1]) -- "foo bar" 46 | * kong.log.inspect(headers["X-Another"][2]) -- "baz" 47 | * end 48 | * Note that this function returns a proxy table 49 | * which cannot be iterated with `pairs` or used as operand of `#`. 50 | * @param max_headers? Sets a limit on the maximum number of 51 | headers that can be parsed. 52 | * @returns The response headers in table form. 53 | * @returns If more headers than `max_headers` are present, returns 54 | a string with the error `"truncated"`. 55 | */ 56 | getHeaders(max_headers?: number): Promise<[ret_1: Array | object, ret_2: string]>; 57 | 58 | /** 59 | * -- Plugin needs to call kong.service.request.enable_buffering() on `rewrite` 60 | * -- or `access` phase prior calling this function. 61 | * local body = kong.service.response.get_raw_body() 62 | * @returns The raw buffered body. 63 | */ 64 | getRawBody(): Promise; 65 | 66 | /** 67 | * kong.log.inspect(kong.service.response.get_status()) -- 418 68 | * @returns The status code from the response from the Service, or `nil` 69 | if the request was not proxied (that is, if `kong.response.get_source()` returned 70 | anything other than `"service"`). 71 | */ 72 | getStatus(): Promise; 73 | 74 | } 75 | -------------------------------------------------------------------------------- /kong/telemetry.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/telemetry.lua 3 | 4 | 5 | export default interface telemetry { 6 | 7 | 8 | /** 9 | * local attributes = { 10 | * http_method = kong.request.get_method() 11 | * ["node.id"] = kong.node.get_id(), 12 | * hostname = kong.node.get_hostname(), 13 | * } 14 | * local ok, err = kong.telemetry.log("my_plugin", conf, "result", "successful operation", attributes) 15 | * @param plugin_name the name of the plugin 16 | * @param plugin_config the plugin configuration 17 | * @param message_type the type of the log message, useful to categorize 18 | the log entry 19 | * @param message the log message 20 | * @param attributes structured information to be included in the 21 | `attributes` field of the log entry 22 | */ 23 | log(plugin_name: string, plugin_config: Array | object, message_type: string, message: string, attributes: Array | object): Promise; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /kong/vault.d.ts: -------------------------------------------------------------------------------- 1 | // AUTO GENERATED BASED ON Kong 3.8.x, DO NOT EDIT 2 | // Original source path: kong/pdk/vault.lua 3 | 4 | 5 | export default interface vault { 6 | 7 | 8 | /** 9 | * kong.vault.flush() 10 | */ 11 | flush(): Promise; 12 | 13 | /** 14 | * local value, err = kong.vault.get("{vault://env/cert/key}") 15 | * @param reference reference to resolve 16 | * @returns resolved value of the reference 17 | * @returns error message on failure, otherwise `nil` 18 | */ 19 | get(reference: string): Promise<[ret_1: string, ret_2: string]>; 20 | 21 | /** 22 | * 23 | */ 24 | initWorker(): Promise; 25 | 26 | /** 27 | * kong.vault.is_reference("{vault://env/key}") -- true 28 | * kong.vault.is_reference("not a reference") -- false 29 | * @param reference reference to check 30 | * @returns `true` is the passed in reference looks like a reference, otherwise `false` 31 | */ 32 | isReference(reference: string): Promise; 33 | 34 | /** 35 | * local ref, err = kong.vault.parse_reference("{vault://env/cert/key?prefix=SSL_#1}") -- table 36 | * @param reference reference to parse 37 | * @returns a table containing each component of the reference, or `nil` on error 38 | * @returns error message on failure, otherwise `nil` 39 | */ 40 | parseReference(reference: string): Promise<[ret_1: Array | object, ret_2: string]>; 41 | 42 | /** 43 | * local options = kong.vault.update({ 44 | * cert = "-----BEGIN CERTIFICATE-----...", 45 | * key = "-----BEGIN RSA PRIVATE KEY-----...", 46 | * cert_alt = "-----BEGIN CERTIFICATE-----...", 47 | * key_alt = "-----BEGIN EC PRIVATE KEY-----...", 48 | * ["$refs"] = { 49 | * cert = "{vault://aws/cert}", 50 | * key = "{vault://aws/key}", 51 | * cert_alt = "{vault://aws/cert-alt}", 52 | * key_alt = "{vault://aws/key-alt}", 53 | * } 54 | * }) 55 | * -- or 56 | * local options = { 57 | * cert = "-----BEGIN CERTIFICATE-----...", 58 | * key = "-----BEGIN RSA PRIVATE KEY-----...", 59 | * cert_alt = "-----BEGIN CERTIFICATE-----...", 60 | * key_alt = "-----BEGIN EC PRIVATE KEY-----...", 61 | * ["$refs"] = { 62 | * cert = "{vault://aws/cert}", 63 | * key = "{vault://aws/key}", 64 | * cert_alt = "{vault://aws/cert-alt}", 65 | * key_alt = "{vault://aws/key-alt}", 66 | * } 67 | * } 68 | * kong.vault.update(options) 69 | * @param options options containing secrets and references (this function modifies the input options) 70 | * @returns options with updated secret values 71 | */ 72 | update(options: Array | object): Promise | object>; 73 | 74 | /** 75 | * 76 | */ 77 | warmup(): Promise; 78 | 79 | } 80 | -------------------------------------------------------------------------------- /lib/mod.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('ts-node').register(); 4 | const fs = require('fs') 5 | 6 | const phases = ['certificate', 'rewrite', 'log', 'access', 'preread', 'response'] 7 | const noop = () => {} 8 | class Module { 9 | constructor(name, path, literal) { 10 | this.loadTime = Date.now() / 1000 11 | this.mod = null 12 | 13 | if (path) { 14 | this.mod = require(path) 15 | this.mtime = fs.statSync(path).mtime.getTime() / 1000 16 | this.location = path 17 | } else if (literal) { 18 | this.mod = literal 19 | // use mtime of current script instead 20 | this.mtime = fs.statSync(__filename).mtime.getTime() / 1000 21 | } else { 22 | throw new Error('either path or module needs to be passed in') 23 | } 24 | 25 | const plugin_name = (this.mod.Name || name).toLowerCase() 26 | this.path = path 27 | this.cls = this.mod.Plugin 28 | this.name = plugin_name 29 | this.phases = [] 30 | 31 | for (const phase of phases) { 32 | if (typeof this.cls.prototype[phase] == 'function') { 33 | this.phases.push(phase) 34 | } 35 | } 36 | 37 | this.priority = this.mod.Priority || 0 38 | this.version = this.mod.Version 39 | this.schema = this.mod.Schema || [] 40 | 41 | this.lastStartInstanceTime = 0 42 | this.lastCloseInstanceTime = 0 43 | } 44 | 45 | new(config) { 46 | this.lastStartInstanceTime = Date.now() / 1000 47 | return new Instance(this.name, config, this.cls, this.setLastCloseInstanceTime.bind(this)) 48 | } 49 | 50 | setLastCloseInstanceTime() { 51 | this.lastCloseInstanceTime = Date.now() / 1000 52 | } 53 | 54 | getLastCloseInstanceTime() { 55 | return this.lastCloseInstanceTime 56 | } 57 | 58 | getLastStartInstanceTime() { 59 | return this.lastStartInstanceTime 60 | } 61 | 62 | getMTime() { 63 | return this.mtime 64 | } 65 | 66 | getName() { 67 | return this.name 68 | } 69 | 70 | getLoadTime() { 71 | return this.loadTime 72 | } 73 | 74 | getPhases() { 75 | return this.phases 76 | } 77 | 78 | getPriority() { 79 | return this.priority 80 | } 81 | 82 | getVersion() { 83 | return this.version 84 | } 85 | 86 | getSchema() { 87 | return this.schema 88 | } 89 | } 90 | 91 | class Instance { 92 | constructor(name, config, cls, closeCb = noop) { 93 | this.cls = new cls(config) 94 | this.name = name 95 | this.config = config 96 | this.startTime = Date.now() / 1000 97 | this.lastUsedTime = 0 98 | this.closeCb = closeCb 99 | } 100 | 101 | isExpired(ttl = 60) { 102 | const until = Date.now() / 1000 - ttl 103 | return this.startTime < until && this.lastUsedTime < until 104 | } 105 | 106 | resetExpireTs() { 107 | this.lastUsedTime = Date.now() / 1000 108 | return this.cls 109 | } 110 | 111 | close() { 112 | this.closeCb() 113 | } 114 | 115 | executePhase(phase, ctx) { 116 | return this.cls[phase](ctx) 117 | } 118 | 119 | getName() { 120 | return this.name 121 | } 122 | 123 | getConfig() { 124 | return this.config 125 | } 126 | 127 | getStartTime() { 128 | return this.startTime 129 | } 130 | 131 | getLastUsedTime() { 132 | return this.lastUsedTime 133 | } 134 | } 135 | 136 | module.exports = { 137 | Module: Module, 138 | Instance: Instance, 139 | } 140 | -------------------------------------------------------------------------------- /lib/pipe.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // https://stackoverflow.com/questions/47157428/how-to-implement-a-pseudo-blocking-async-queue-in-js-ts 4 | class AsyncBlockingQueue { 5 | constructor() { 6 | // invariant: at least one of the arrays is empty 7 | this.resolvers = []; 8 | this.promises = []; 9 | } 10 | 11 | _add() { 12 | this.promises.push(new Promise(resolve => { 13 | this.resolvers.push(resolve); 14 | })) 15 | } 16 | 17 | enqueue(t) { 18 | if (!this.resolvers.length) this._add(); 19 | this.resolvers.shift()(t); 20 | } 21 | 22 | dequeue() { 23 | if (!this.promises.length) this._add(); 24 | return this.promises.shift(); 25 | } 26 | 27 | // now some utilities: 28 | isEmpty() { // there are no values available 29 | return !this.promises.length; // this.length <= 0 30 | } 31 | 32 | isBlocked() { // it's waiting for values 33 | return !!this.resolvers.length; // this.length < 0 34 | } 35 | 36 | get length() { 37 | return this.promises.length - this.resolvers.length; 38 | } 39 | 40 | [Symbol.asyncIterator]() { 41 | return { 42 | next: () => this.dequeue().then(value => ({ 43 | done: false, 44 | value 45 | })) 46 | }; 47 | } 48 | } 49 | 50 | class Pipe { 51 | constructor (get, put) { 52 | this.get = get 53 | this.put = put 54 | } 55 | } 56 | 57 | class PipePair { 58 | constructor() { 59 | this.qa = new AsyncBlockingQueue() 60 | this.qb = new AsyncBlockingQueue() 61 | 62 | this.a = new Pipe( 63 | () => { return this.qa.dequeue() }, 64 | (v) => { return this.qb.enqueue(v) }, 65 | ) 66 | 67 | this.b = new Pipe( 68 | () => { return this.qb.dequeue() }, 69 | (v) => { return this.qa.enqueue(v) }, 70 | ) 71 | } 72 | 73 | getPair() { 74 | return [this.a, this.b] 75 | } 76 | } 77 | 78 | module.exports = PipePair 79 | -------------------------------------------------------------------------------- /listener.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const net = require('net') 4 | const fs = require('fs') 5 | const {Encoder, Decoder} = require('@msgpack/msgpack') 6 | const TYPE_EXP = /^\[object (.*)\]$/ 7 | const ERR_UNKNOWN = 'Unknown plugin listener error encountered' 8 | const toString = Object.prototype.toString 9 | 10 | function typeOf(value) { 11 | if (!value) return '' 12 | const parts = TYPE_EXP.exec(toString.call(value)) 13 | return parts[1].toLowerCase() 14 | } 15 | 16 | function thenable(obj) { 17 | if (!obj) return false 18 | return (typeof obj.then === 'function' && typeof obj.catch === 'function') 19 | } 20 | 21 | function write_response (client, msgid, response) { 22 | client.write(client.encoder.encode([ 23 | 1, // is response 24 | msgid, 25 | undefined, 26 | response 27 | ])) 28 | } 29 | 30 | function write_error (client, msgid, error) { 31 | client.write(client.encoder.encode([ 32 | 1, // is response 33 | msgid, 34 | errToString(error), 35 | undefined 36 | ])) 37 | } 38 | 39 | function errToString (err) { 40 | if (typeof err === 'string') return err 41 | if ('message' in err) return err.message 42 | if (typeof err.toString === 'function') return err.toString() 43 | return ERR_UNKNOWN 44 | } 45 | 46 | function getStreamDecoder () { 47 | const decoder = new Decoder() 48 | let buffer 49 | 50 | return function (chunk) { 51 | let decoded 52 | try { 53 | let data = chunk 54 | if (buffer !== undefined) { 55 | buffer.push(chunk) 56 | data = Buffer.concat(buffer) 57 | } 58 | decoded = decoder.decode(data) 59 | buffer = undefined 60 | } catch (ex) { 61 | // TODO: less hacky way to detect insufficient data 62 | if (ex.message === 'Insufficient data') { 63 | if (buffer === undefined) { 64 | buffer = [chunk] 65 | } 66 | return 67 | } 68 | 69 | throw ex 70 | } 71 | 72 | return decoded 73 | } 74 | } 75 | 76 | class Listener { 77 | get [Symbol.toStringTag]() { 78 | return 'PluginListener' 79 | } 80 | 81 | constructor(pluginServer, prefix) { 82 | this.ps = pluginServer 83 | this.prefix = prefix 84 | this.logger = pluginServer.getLogger() 85 | } 86 | 87 | serve() { 88 | const listen_path = this.prefix 89 | const logger = this.logger 90 | 91 | try { 92 | fs.unlinkSync(listen_path) 93 | } catch (ex) { 94 | if (ex.code !== 'ENOENT') throw ex 95 | } 96 | 97 | const server = net.createServer((client) => { 98 | client.encoder = new Encoder() 99 | const decodeStream = getStreamDecoder() 100 | 101 | client.on('data', (chunk) => { 102 | let decoded = decodeStream(chunk) 103 | 104 | // partial data received, wait for next chunk 105 | if (!decoded) return 106 | 107 | let [_, msgid, method, args] = decoded 108 | let [ns, cmd] = method.split('.') 109 | if (ns !== 'plugin') { 110 | write_error(client, msgid, `RPC for ${ns} is not supported`) 111 | return 112 | } 113 | 114 | logger.debug(`rpc: #${msgid} method: ${method} args: ${JSON.stringify(args)}`) 115 | if (!this.ps[cmd]) { 116 | const err = `method ${cmd} not implemented` 117 | logger.error(`rpc: #${msgid} ${err}`) 118 | write_error(client, msgid, err) 119 | return 120 | } 121 | 122 | let promise 123 | try { 124 | promise = this.ps[cmd](...args) 125 | } catch (ex) { 126 | logger.error(ex.stack) 127 | write_error(client, msgid, ex) 128 | return 129 | } 130 | 131 | if (!thenable(promise)) { 132 | const err = `${cmd} should return a Promise or thenable object, got ${typeOf(promise)}` 133 | logger.error(`rpc: #${msgid} ${err}`) 134 | write_error(client, msgid, err) 135 | return 136 | } 137 | 138 | promise 139 | .then((ret) => { 140 | write_response(client, msgid, ret) 141 | }) 142 | .catch((err) => { 143 | logger.error(`rpc: # ${msgid} ${err}`) 144 | write_error(client, msgid, err) 145 | }) 146 | }) 147 | }) 148 | 149 | server.listen(listen_path) 150 | logger.info('server started at', listen_path) 151 | return server 152 | } 153 | } 154 | 155 | module.exports = Listener 156 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kong-pdk", 3 | "version": "0.6.0", 4 | "description": "Kong PDK for Javascript and Plugin Server", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "node ./bin/kong-js-pluginserver -d $(pwd)/examples", 8 | "test": "jest --coverage" 9 | }, 10 | "files": [ 11 | "bin", 12 | "kong", 13 | "lib", 14 | "cli.js", 15 | "listener.js", 16 | "pdk.js", 17 | "server.js", 18 | "plugin_test.js" 19 | ], 20 | "author": "Kong", 21 | "license": "Apache-2.0", 22 | "devDependencies": { 23 | "@babel/core": "^7.14.6", 24 | "@babel/preset-env": "^7.14.7", 25 | "jest": "^28.1.0", 26 | "node-fetch": "^2.6.1", 27 | "uuid": "^9.0.0" 28 | }, 29 | "dependencies": { 30 | "@msgpack/msgpack": "^2.7.0", 31 | "@types/node": "^18.11.9", 32 | "commander": "^10.0.0", 33 | "node-color-log": "^10.0.2", 34 | "ts-node": "^10.1.0", 35 | "typescript": "^4.2.4" 36 | }, 37 | "directories": { 38 | "example": "examples", 39 | "lib": "lib", 40 | "bin": "bin" 41 | }, 42 | "repository": { 43 | "type": "git", 44 | "url": "git+https://github.com/Kong/kong-js-pdk.git" 45 | }, 46 | "jest": { 47 | "coveragePathIgnorePatterns": ["/node_modules/", "plugin_test"] 48 | }, 49 | "bugs": { 50 | "url": "https://github.com/Kong/kong-js-pdk/issues" 51 | }, 52 | "bin": { 53 | "kong-js-pluginserver": "./bin/kong-js-pluginserver" 54 | }, 55 | "homepage": "https://github.com/Kong/kong-js-pdk#readme" 56 | } 57 | -------------------------------------------------------------------------------- /pdk.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ERROR_NAME = 'PDKError' 4 | 5 | class PDKError extends Error { 6 | get name () { 7 | return ERROR_NAME 8 | } 9 | 10 | constructor(...args) { 11 | super(...args) 12 | Error.captureStackTrace(this, this.constructor) 13 | } 14 | } 15 | 16 | const bridgeHandler = { 17 | get(target, name) { 18 | // camelCase to underscore_case 19 | const clean_name = name.replace(/[a-z][A-Z]/g, (str) => { 20 | return (str.substring(0, 1) + "_" + str.substring(1)).toLowerCase() 21 | }) 22 | return newBridgeCall(`${target.prefix}.${clean_name}`, target.call) 23 | } 24 | } 25 | 26 | function newBridgeCall(prefix, call) { 27 | function bridgeCall(...args) { 28 | return call(prefix, ...args) 29 | } 30 | bridgeCall.prefix = prefix 31 | bridgeCall.call = call 32 | 33 | return new Proxy(bridgeCall, bridgeHandler); 34 | } 35 | 36 | // those methods never return, instead, they exit from current request immediately 37 | const NON_RETURN_METHODS = new Set([ 38 | "kong.response.exit", 39 | "kong.response.error", 40 | ]) 41 | 42 | function rpcCall(rpcPipe) { 43 | return async (method, ...args) => { 44 | rpcPipe.put({ 45 | "Method": method, 46 | "Args": args, 47 | }) 48 | 49 | if (NON_RETURN_METHODS.has(method)) 50 | return 51 | 52 | const [ret, err] = await rpcPipe.get() 53 | 54 | if (err) { 55 | throw new PDKError(`PDK method ${method} failed: ${err}`) 56 | } 57 | return ret 58 | } 59 | } 60 | 61 | class Kong { 62 | get [Symbol.toStringTag]() { 63 | return 'KongPDK' 64 | } 65 | 66 | get Error() { 67 | return PDKError 68 | } 69 | 70 | static get Error() { 71 | return PDKError 72 | } 73 | 74 | constructor(rpcPipe) { 75 | this.kong = newBridgeCall("kong", rpcCall(rpcPipe)) 76 | } 77 | } 78 | 79 | module.exports = Kong 80 | -------------------------------------------------------------------------------- /plugin_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path') 4 | const { v4: uuidv4 } = require('uuid') 5 | const fetch = require("node-fetch") 6 | const PDK = require(path.join(__dirname, 'pdk')) 7 | const PipePair = require(path.join(__dirname, 'lib', 'pipe')) 8 | const { 9 | Module 10 | } = require(path.join(__dirname, 'lib', 'mod')) 11 | 12 | const MSG_RET = 'ret' 13 | 14 | let noop = function() { 15 | return true 16 | } 17 | 18 | let mockFunctions = { 19 | "kong.client.get_ip": function() { return "1.2.3.4" }, 20 | "kong.client.get_forwarded_ip": function() { return "1.2.3.4" }, 21 | "kong.client.get_port": function() { return 443 }, 22 | "kong.client.get_forwarded_port": function() { return 443 }, 23 | "kong.client.get_credential": function() { return { id: uuidv4(), consumer_id: "123456" } }, 24 | "kong.client.load_consumer": function() { return { id: uuidv4(), username: "Jon Doe" } }, 25 | "kong.client.authenticate": noop, 26 | "kong.client.get_protocol": function() { "https" }, 27 | 28 | "kong.ip.is_trusted": noop, 29 | 30 | "kong.node.get_id": function() { return "a9777ac2-57e6-482b-a3c4-ef3d6ca41a1f" }, 31 | "kong.node.get_memory_stats": function() { 32 | return { 33 | lua_shared_dicts: { 34 | kong: { 35 | allocated_slabs: 12288, 36 | capacity: 24576 37 | }, 38 | kong_db_cache: { 39 | allocated_slabs: 12288, 40 | capacity: 12288 41 | } 42 | }, 43 | workers_lua_vms: [ 44 | { 45 | http_allocated_gc: 1102, 46 | pid: 18004 47 | }, 48 | { 49 | http_allocated_gc: 1102, 50 | pid: 18005 51 | } 52 | ], 53 | } 54 | }, 55 | 56 | "kong.request.get_scheme": function(i) { return i.request.url.protocol.replace(":", "") }, 57 | "kong.request.get_host": function(i) { return i.request.url.hostname }, 58 | "kong.request.get_port": function(i) { return i.request.url.port }, 59 | "kong.request.get_forwarded_scheme": function(i) { return i.request.headers.get("X-Forwarded-Proto") }, 60 | "kong.request.get_forwarded_host": function(i) { return i.request.headers.get("X-Forwarded-Host") }, 61 | "kong.request.get_forwarded_port": function(i) { return i.request.headers.get("X-Forwarded-Port") }, 62 | "kong.request.get_http_version": function() { return "1.1" }, 63 | "kong.request.get_method": function(i) { return i.request.method }, 64 | "kong.request.get_path": function(i) { return i.request.url.pathname }, 65 | "kong.request.get_path_with_query": function(i) { return i.request.url.pathname + i.request.url.search }, 66 | "kong.request.get_raw_query": function(i) { return i.request.url.search }, 67 | "kong.request.get_query_arg": function(i, k) { return new URLSearchParams(i.request.url.search).get(k) }, 68 | "kong.request.get_query": function(i) { return Array.from(new URLSearchParams(i.request.url.search).entries()) }, 69 | "kong.request.get_header": function(i, k) { return i.request.headers.get(k) }, 70 | "kong.request.get_headers": function(i) { return Array.from(i.request.headers.entries()) }, 71 | "kong.request.get_raw_body": function(i) { return i.request.body }, 72 | 73 | "kong.response.get_status": function(i) { return i.response.status }, 74 | "kong.response.get_header": function(i, k) { return i.response.headers.get(k) }, 75 | "kong.response.get_headers": function(i) { return Array.from(i.response.headers.entries()) }, 76 | "kong.response.get_source": function() { return "service" }, 77 | "kong.response.set_status": function(i, status) { i.response.status = status }, 78 | "kong.response.set_header": function(i, k, v) { i.response.headers.set(k, v) }, 79 | "kong.response.add_header": function(i, k, v) { i.response.headers.add(k, v) }, 80 | "kong.response.set_headers": function(i, headers) { 81 | for(let [k, v] in Object.entries(headers)) { 82 | i.response.headers.set(k, v) 83 | } 84 | }, 85 | "kong.response.clear_header": function(i, k) { i.response.headers.delete(k) }, 86 | "kong.response.exit": function(i, status, body, headers) { 87 | i.response.status = status 88 | i.response.body = body 89 | if (headers !== undefined) { 90 | for(let [k, v] in Object.entries(headers)) { 91 | i.response.headers.set(k, v) 92 | } 93 | } 94 | i.setExiting(true) 95 | }, 96 | "kong.response.error": function(i, status, message, headers) { 97 | mockFunctions["kong.response.exit"](i, status, message, headers) 98 | }, 99 | 100 | "kong.router.get_route": function() { return { 101 | id: uuidv4(), 102 | name: "route_66", 103 | protocols: ["http", "tcp"], 104 | paths: ["/v0/left", "/v1/this"], 105 | }}, 106 | "kong.router.get_service": function() { return { 107 | id: uuidv4(), 108 | name: "self_service", 109 | protocol: "http", 110 | path: "/v0/left", 111 | }}, 112 | 113 | "kong.service.set_upstream": noop, 114 | "kong.service.set_target": noop, 115 | "kong.service.request.set_scheme": function(i, protocol) { i.serviceRequest.url.protocol = protocol }, 116 | "kong.service.request.set_path": function(i, path) { i.serviceRequest.url.pathname = path }, 117 | "kong.service.request.set_raw_query": function(i, query) { i.serviceRequest.url.search = query }, 118 | "kong.service.request.set_method": function(i, method) { i.serviceRequest.method = method }, 119 | "kong.service.request.set_query": function(i, args) { 120 | let params = new URLSearchParams(i.serviceRequest.url.search) 121 | for (let [k, v] of Object.entries(args)) { 122 | params.set(k, v) 123 | } 124 | i.serviceRequest.url.search = params.toString() 125 | }, 126 | "kong.service.request.set_header": function(i, k, v) { i.serviceRequest.headers.set(k, v) }, 127 | "kong.service.request.add_header": function(i, k, v) { i.serviceRequest.headers.add(k, v) }, 128 | "kong.service.request.set_headers": function(i, headers) { 129 | for (let [k, v] of Object.entries(headers)) { 130 | i.serviceRequest.headers.set(k, v) 131 | } 132 | }, 133 | "kong.service.request.clear_header": function(i, k) { i.serviceRequest.headers.delete(k) }, 134 | "kong.service.request.set_raw_body": function(i, body) { i.serviceRequest.body = body }, 135 | 136 | "kong.service.response.get_status": function(i) { return i.serviceResponse.status }, 137 | "kong.service.response.get_headers": function(i) { return i.serviceResponse.headers }, 138 | "kong.service.response.get_header": function(i, k) { return i.serviceResponse.headers.get(k) }, 139 | "kong.service.response.get_raw_body": function(i) { return i.serviceResponse.body }, 140 | } 141 | 142 | let logFunctions = [ 143 | "kong.log.alert", "kong.log.crit", "kong.log.err", "kong.log.warn", 144 | "kong.log.notice", "kong.log.info", "kong.log.debug" 145 | ] 146 | 147 | for (let i = 0; i < logFunctions.length; i++) { 148 | const logFunction = logFunctions[i] 149 | mockFunctions[logFunction] = function(...args) { 150 | console.log("Log " + logFunction, ...args) 151 | } 152 | } 153 | 154 | class PluginTest { 155 | constructor(request) { 156 | this.request = request 157 | this.response = new Response() 158 | this.serviceRequest = new Request(request) 159 | let [ch, childCh] = new PipePair().getPair() 160 | this.ch = ch 161 | this.childCh = childCh 162 | this.exiting = false 163 | } 164 | 165 | async Run(pluginToTest, pluginConfig) { 166 | let pluginModule = new Module("TestPlugin", undefined, pluginToTest) 167 | let pluginInstance = pluginModule.new(pluginConfig) 168 | 169 | if(this.request.isHttps || this.request.isTLS){ 170 | await this.executePhase(pluginInstance, "certificate") 171 | } 172 | 173 | if(this.request.isTCP || this.request.isTLS) { 174 | await this.executePhase(pluginInstance, "preread") 175 | } else{ 176 | await this.executePhase(pluginInstance, "access") 177 | await this.executePhase(pluginInstance, "rewrite") 178 | if (!this.exiting) { 179 | this.serviceResponse = this.serviceRequest.toResponse() 180 | this.response.merge(this.serviceResponse) 181 | } 182 | await this.executePhase(pluginInstance, "response") 183 | } 184 | 185 | await this.executePhase(pluginInstance, "log") 186 | return {mod: pluginModule, instance: pluginInstance} 187 | } 188 | 189 | async executePhase(ins, phase) { 190 | // skip phases to mock Kong "early exit" 191 | if (this.exiting && phase != "log") return 192 | // start the consumer to mock RPC functions 193 | this.mockKongPDK() 194 | // if the plugin doesn't implement this phase, ignore 195 | if (ins.cls[phase] === undefined) { 196 | return 197 | } 198 | 199 | await ins.executePhase(phase, new PDK(this.childCh).kong) 200 | this.childCh.put(MSG_RET) 201 | } 202 | 203 | mockKongPDK() { 204 | setImmediate(() => { 205 | new Promise(async () => { 206 | while(1) { 207 | let r = await this.ch.get() 208 | 209 | if (r == MSG_RET) return 210 | 211 | let meth = r.Method 212 | if (mockFunctions[meth] === undefined) { 213 | throw new Error("function " + meth + " is not a valid PDK function") 214 | } 215 | 216 | let ret = mockFunctions[meth](this, ...r.Args) 217 | this.ch.put([ret, undefined]) 218 | } 219 | }) 220 | }) 221 | } 222 | 223 | setExiting(exiting) { 224 | this.exiting = exiting 225 | } 226 | } 227 | 228 | class Request { 229 | url = new URL("http://konghq.com"); 230 | headers = new fetch.Headers(); 231 | method = "GET"; 232 | isHttps = false; 233 | isTCP = false; 234 | isTLS = false; 235 | body = ""; 236 | 237 | constructor(request) { 238 | if (request !== undefined) { 239 | this.useURL(request.url.toString()) 240 | this.useHeaders(Array.from(request.headers.entries())) 241 | this.useMethod(request.method) 242 | this.useBody(request.body) 243 | } 244 | } 245 | 246 | useURL(url) { 247 | this.url = new URL(url) 248 | this.isHttps = this.url.protocol == "https:" 249 | this.isTCP = this.url.protocol == "tcp:" 250 | this.isTLS = this.url.protocol == "tls:" 251 | return this 252 | } 253 | 254 | useHeaders(headers) { 255 | for(let [k, v] of Object.entries(headers)) { 256 | this.headers.set(k, v) 257 | } 258 | return this 259 | } 260 | 261 | useMethod(method) { 262 | this.method = method 263 | return this 264 | } 265 | 266 | useBody(body) { 267 | this.body = body 268 | return this 269 | } 270 | 271 | toResponse() { 272 | let response = new Response(this.url.toString()) 273 | response.status = 200 274 | response.body = "OK" 275 | response.headers = new fetch.Headers(this.headers) 276 | return response 277 | } 278 | } 279 | 280 | class Response { 281 | status; 282 | body = ""; 283 | headers = new fetch.Headers(); 284 | 285 | constructor(response) { 286 | if (response !== undefined) { 287 | this.status = response.status 288 | this.headers = new fetch.Headers(response.headers) 289 | this.body = response.body 290 | } 291 | } 292 | 293 | merge(response) { 294 | for (let [k, v] in response.headers.entries()) { 295 | this.headers.append(k, v) 296 | } 297 | this.body = response.body 298 | this.status = response.status 299 | } 300 | } 301 | 302 | module.exports = { 303 | PluginTest: PluginTest, 304 | Request: Request, 305 | Response: Response, 306 | } 307 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const {Module} = require('./lib/mod') 6 | const PipePair = require('./lib/pipe') 7 | const PDK = require('./pdk') 8 | 9 | const entities = ['Service', 'Consumer', 'Route', 'Plugin', 'Credential', 'MemoryStats'] 10 | const MSG_RET = 'ret' 11 | const ERROR_NAME = 'PluginServerError' 12 | const VALID_EXTENSIONS = new Set([ 13 | '.js', 14 | '.ts', 15 | '.node', 16 | '.cjs', 17 | '' 18 | ]) 19 | 20 | class PluginServerError extends Error { 21 | get name () { 22 | return ERROR_NAME 23 | } 24 | 25 | constructor(...args) { 26 | super(...args) 27 | Error.captureStackTrace(this, this.constructor) 28 | } 29 | } 30 | 31 | class Server { 32 | get Error() { 33 | return PluginServerError 34 | } 35 | 36 | static get Error() { 37 | return PluginServerError 38 | } 39 | 40 | constructor(pluginDir, logger, expireTtl) { 41 | this.pluginDir = pluginDir 42 | this.logger = logger 43 | this.plugins = new Map() 44 | this.instances = new Map() 45 | this.instanceID = 0 46 | this.events = new Map() 47 | this.eventID = 0 48 | 49 | if (pluginDir) { 50 | this.loadPlugins() 51 | } 52 | 53 | this.clearExpiredPluginsTimer = this.clearExpiredPlugins(expireTtl || 60) 54 | } 55 | 56 | loadPlugins() { 57 | if (!this.pluginDir) { 58 | throw new PluginServerError('plugin server is not initialized, call SetPluginDir first') 59 | } 60 | 61 | const files = fs.readdirSync(this.pluginDir) 62 | for (const file of files) { 63 | 64 | if (file.startsWith('.')) continue 65 | if (/node_modules/.test(file)) continue 66 | const file_path = require.resolve(path.join(this.pluginDir, file)) 67 | const {name, ext} = path.parse(file_path) 68 | 69 | if (!name) continue 70 | if (!VALID_EXTENSIONS.has(ext)) continue 71 | 72 | const plugin = this.plugins.get(name) 73 | if (plugin) { 74 | this.logger.warn( 75 | `plugin "${name}" is already loaded from ${plugin.path}, ` + 76 | `trying to load from ${file_path}` 77 | ) 78 | continue 79 | } 80 | 81 | try { 82 | const mod = new Module(name, file_path) 83 | this.plugins.set(mod.name, mod) 84 | this.logger.debug(`loaded plugin "${mod.name}" from ${file_path}`) 85 | } catch (ex) { 86 | this.logger.warn(`error loading plugin "${name}" from ${file_path}: ${ex.stack}`) 87 | } 88 | }; 89 | } 90 | 91 | clearExpiredPlugins(ttl) { 92 | return setInterval(() => { 93 | for (const [id, instance] of this.instances.entries()) { 94 | if (instance.isExpired()) { 95 | this.logger.debug(`cleanup instance #iid of ${instance.name}`) 96 | this.instances.delete(id) 97 | } 98 | } 99 | }, ttl) 100 | } 101 | 102 | close() { 103 | clearInterval(this.clearExpiredPluginsTimer) 104 | } 105 | 106 | async SetPluginDir(dir) { 107 | try { 108 | await fs.promises.stat(dir) 109 | } catch (err) { 110 | if (err.code !== 'ENOENT') throw err 111 | throw new PluginServerError(`${dir} does not exists`) 112 | } 113 | 114 | this.pluginDir = dir 115 | this.loadPlugins() 116 | return 'ok' 117 | } 118 | 119 | // RPC method 120 | async GetStatus() { 121 | const pluginStatus = Object.create(null) 122 | for (const [name, plugin] of this.plugins.entries()) { 123 | const instances = [] 124 | for (const iid in this.instances) { 125 | instances.push(await this.InstanceStatus(iid)) 126 | } 127 | 128 | pluginStatus[name] = { 129 | Name: name, 130 | Modtime: plugin.getMTime(), 131 | LoadTime: plugin.getLoadTime(), 132 | Instances: instances, 133 | LastStartInstance: plugin.getLastStartInstanceTime(), 134 | LastCloseInstance: plugin.getLastCloseInstanceTime(), 135 | } 136 | } 137 | 138 | return { 139 | Pid: process.pid, 140 | Plugins: pluginStatus 141 | } 142 | } 143 | 144 | // RPC method 145 | async GetPluginInfo(name) { 146 | const plugin = this.plugins.get(name) 147 | if (!name || !plugin) { 148 | throw new PluginServerError(`${name} not initizlied`) 149 | } 150 | 151 | return { 152 | Name: name, 153 | Version: plugin.getVersion(), 154 | Phases: plugin.getPhases(), 155 | Priority: plugin.getPriority(), 156 | Schema: { 157 | name: name, 158 | fields: [{ 159 | config: { 160 | type: 'record', 161 | fields: plugin.getSchema(), 162 | } 163 | }], 164 | }, 165 | } 166 | } 167 | 168 | // RPC method 169 | async StartInstance(cfg) { 170 | const name = cfg.Name 171 | const plugin = this.plugins.get(name) 172 | if (!plugin) { 173 | throw new PluginServerError(`${name} not initizlied`) 174 | } 175 | 176 | const config = JSON.parse(cfg.Config) 177 | const iid = this.instanceID++ 178 | this.instances.set(iid, plugin.new(config)) 179 | 180 | this.logger.info(`instance #${iid} of ${name} started`) 181 | 182 | return { 183 | Name: name, 184 | Id: iid, 185 | Config: config, 186 | StartTime: Date.now() / 1000, 187 | } 188 | } 189 | 190 | // RPC method 191 | async InstanceStatus(iid) { 192 | const ins = this.instances.get(iid) 193 | if (!ins) { 194 | // Note: Kong expect the error to start with "no plugin instance" 195 | throw new PluginServerError(`no plugin instance #${iid}`) 196 | } 197 | 198 | return { 199 | Name: ins.getName(), 200 | Id: iid, 201 | Config: ins.getConfig(), 202 | StartTime: ins.getStartTime(), 203 | } 204 | } 205 | 206 | // RPC method 207 | async CloseInstance(iid) { 208 | 209 | let ins = this.instances.get(iid) 210 | if (!ins) { 211 | // Note: Kong expect the error to start with "no plugin instance" 212 | throw new PluginServerError(`no plugin instance #${iid}`) 213 | } 214 | 215 | ins.close() 216 | this.instances.delete(iid) 217 | 218 | return { 219 | Name: ins.getName(), 220 | Id: iid, 221 | Config: ins.getConfig(), 222 | } 223 | } 224 | 225 | // RPC method 226 | async HandleEvent(event) { 227 | const iid = event.InstanceId 228 | const ins = this.instances.get(iid) 229 | if (!ins) { 230 | // Note: Kong expect the error to start with "no plugin instance" 231 | throw new PluginServerError(`no plugin instance #${iid}`) 232 | } 233 | 234 | ins.resetExpireTs() 235 | 236 | const phase = event.EventName 237 | const eid = this.eventID++ 238 | 239 | const [ch, childCh] = new PipePair().getPair() 240 | this.events.set(eid, ch) 241 | 242 | // https://snyk.io/blog/nodejs-how-even-quick-async-functions-can-block-the-event-loop-starve-io/ 243 | setImmediate(async () => { 244 | try { 245 | await ins.executePhase(phase, new PDK(childCh).kong) 246 | } catch(ex){ 247 | this.logger.warn( 248 | `unhandled exception in ${ins.name}.${phase} on instance #${iid}: ${ex}` 249 | ) 250 | } 251 | childCh.put(MSG_RET) 252 | }) 253 | 254 | const r = await ch.get() 255 | ins.resetExpireTs() 256 | 257 | return { 258 | Data: r, 259 | EventId: eid, 260 | } 261 | } 262 | 263 | async step(data, isError) { 264 | const din = data.Data 265 | const eid = data.EventId 266 | const ch = this.events.get(eid) 267 | 268 | if (!ch) { 269 | throw new PluginServerError(`event id ${eid} not found`) 270 | } 271 | 272 | if (isError) { 273 | await ch.put([ undefined, din ]) 274 | } else { 275 | await ch.put([ din, undefined ]) 276 | } 277 | 278 | const ret = await ch.get() 279 | if (ret === MSG_RET) this.events.delete(eid) 280 | return { 281 | Data: ret, 282 | EventId: eid 283 | } 284 | } 285 | 286 | // RPC method 287 | async Step(data) { 288 | return this.step(data, false) 289 | } 290 | 291 | // RPC method 292 | async StepError(err) { 293 | return this.step(err, true) 294 | } 295 | 296 | // RPC method 297 | async StepMultiMap(data) { 298 | return this.step(data, false) 299 | } 300 | 301 | getLogger() { 302 | return this.logger 303 | } 304 | 305 | getPlugins() { 306 | return this.plugins 307 | } 308 | } 309 | 310 | // Generate other RPC methods 311 | for (const entity of entities) { 312 | Server.prototype['Step' + entity] = Server.prototype.Step 313 | } 314 | 315 | 316 | module.exports = Server 317 | --------------------------------------------------------------------------------