@loopback/rest 4.0.0-alpha.8 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1822 -0
- package/LICENSE +1 -1
- package/README.md +30 -58
- package/dist/body-parsers/body-parser.d.ts +25 -0
- package/dist/body-parsers/body-parser.helpers.d.ts +44 -0
- package/dist/body-parsers/body-parser.helpers.js +102 -0
- package/dist/body-parsers/body-parser.helpers.js.map +1 -0
- package/dist/body-parsers/body-parser.js +159 -0
- package/dist/body-parsers/body-parser.js.map +1 -0
- package/dist/body-parsers/body-parser.json.d.ts +9 -0
- package/dist/body-parsers/body-parser.json.js +43 -0
- package/dist/body-parsers/body-parser.json.js.map +1 -0
- package/dist/body-parsers/body-parser.raw.d.ts +12 -0
- package/dist/body-parsers/body-parser.raw.js +39 -0
- package/dist/body-parsers/body-parser.raw.js.map +1 -0
- package/dist/body-parsers/body-parser.stream.d.ts +12 -0
- package/dist/body-parsers/body-parser.stream.js +28 -0
- package/dist/body-parsers/body-parser.stream.js.map +1 -0
- package/dist/body-parsers/body-parser.text.d.ts +9 -0
- package/dist/body-parsers/body-parser.text.js +38 -0
- package/dist/body-parsers/body-parser.text.js.map +1 -0
- package/dist/body-parsers/body-parser.urlencoded.d.ts +9 -0
- package/dist/body-parsers/body-parser.urlencoded.js +36 -0
- package/dist/body-parsers/body-parser.urlencoded.js.map +1 -0
- package/dist/body-parsers/index.d.ts +8 -0
- package/dist/body-parsers/index.js +16 -0
- package/dist/body-parsers/index.js.map +1 -0
- package/dist/body-parsers/types.d.ts +51 -0
- package/dist/body-parsers/types.js +12 -0
- package/dist/body-parsers/types.js.map +1 -0
- package/dist/coercion/coerce-parameter.d.ts +9 -0
- package/dist/coercion/coerce-parameter.js +166 -0
- package/dist/coercion/coerce-parameter.js.map +1 -0
- package/dist/coercion/utils.d.ts +43 -0
- package/dist/coercion/utils.js +96 -0
- package/dist/coercion/utils.js.map +1 -0
- package/dist/coercion/validator.d.ts +49 -0
- package/dist/coercion/validator.js +85 -0
- package/dist/coercion/validator.js.map +1 -0
- package/dist/http-handler.d.ts +38 -0
- package/dist/http-handler.js +68 -0
- package/dist/http-handler.js.map +1 -0
- package/dist/index.d.ts +36 -1
- package/dist/index.js +40 -6
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +198 -0
- package/dist/keys.js +202 -0
- package/dist/keys.js.map +1 -0
- package/dist/parse-json.d.ts +11 -0
- package/dist/parse-json.js +42 -0
- package/dist/parse-json.js.map +1 -0
- package/dist/parser.d.ts +11 -0
- package/dist/parser.js +76 -0
- package/dist/parser.js.map +1 -0
- package/{dist6/src/providers/find-route.d.ts → dist/providers/find-route.provider.d.ts} +3 -1
- package/dist/providers/find-route.provider.js +36 -0
- package/dist/providers/find-route.provider.js.map +1 -0
- package/dist/providers/index.d.ts +6 -0
- package/dist/providers/index.js +14 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/{src/providers/invoke-method.d.ts → providers/invoke-method.provider.d.ts} +3 -1
- package/dist/providers/invoke-method.provider.js +30 -0
- package/dist/providers/invoke-method.provider.js.map +1 -0
- package/dist/providers/log-error.provider.d.ts +6 -0
- package/dist/providers/log-error.provider.js +21 -0
- package/dist/providers/log-error.provider.js.map +1 -0
- package/dist/providers/parse-params.provider.d.ts +15 -0
- package/dist/providers/parse-params.provider.js +41 -0
- package/dist/providers/parse-params.provider.js.map +1 -0
- package/dist/providers/reject.provider.d.ts +10 -0
- package/dist/providers/reject.provider.js +47 -0
- package/dist/providers/reject.provider.js.map +1 -0
- package/dist/{src/providers/send.d.ts → providers/send.provider.d.ts} +1 -4
- package/dist/{src/providers/send.js → providers/send.provider.js} +4 -6
- package/dist/providers/send.provider.js.map +1 -0
- package/dist/request-context.d.ts +36 -0
- package/dist/request-context.js +104 -0
- package/dist/request-context.js.map +1 -0
- package/dist/rest-http-error.d.ts +37 -0
- package/dist/rest-http-error.js +51 -0
- package/dist/rest-http-error.js.map +1 -0
- package/dist/rest.application.d.ts +232 -0
- package/dist/rest.application.js +174 -0
- package/dist/rest.application.js.map +1 -0
- package/dist/rest.component.d.ts +15 -0
- package/dist/rest.component.js +72 -0
- package/dist/rest.component.js.map +1 -0
- package/dist/rest.server.d.ts +443 -0
- package/dist/rest.server.js +748 -0
- package/dist/rest.server.js.map +1 -0
- package/dist/router/base-route.d.ts +29 -0
- package/dist/router/base-route.js +41 -0
- package/dist/router/base-route.js.map +1 -0
- package/dist/router/controller-route.d.ts +61 -0
- package/dist/router/controller-route.js +160 -0
- package/dist/router/controller-route.js.map +1 -0
- package/dist/router/external-express-routes.d.ts +24 -0
- package/dist/router/external-express-routes.js +90 -0
- package/dist/router/external-express-routes.js.map +1 -0
- package/dist/router/handler-route.d.ts +12 -0
- package/dist/router/handler-route.js +30 -0
- package/dist/router/handler-route.js.map +1 -0
- package/dist/router/index.d.ts +14 -0
- package/dist/router/index.js +25 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/openapi-path.d.ts +14 -0
- package/dist/router/openapi-path.js +64 -0
- package/dist/router/openapi-path.js.map +1 -0
- package/dist/router/redirect-route.d.ts +23 -0
- package/dist/router/redirect-route.js +50 -0
- package/dist/router/redirect-route.js.map +1 -0
- package/dist/router/regexp-router.d.ts +25 -0
- package/dist/router/regexp-router.js +84 -0
- package/dist/router/regexp-router.js.map +1 -0
- package/dist/router/rest-router.d.ts +35 -0
- package/dist/{src/internal-types.js → router/rest-router.js} +2 -2
- package/dist/router/rest-router.js.map +1 -0
- package/dist/router/route-entry.d.ts +46 -0
- package/dist/router/route-entry.js +20 -0
- package/dist/router/route-entry.js.map +1 -0
- package/dist/router/route-sort.d.ts +7 -0
- package/dist/router/route-sort.js +75 -0
- package/dist/router/route-sort.js.map +1 -0
- package/dist/router/router-base.d.ts +42 -0
- package/dist/router/router-base.js +101 -0
- package/dist/router/router-base.js.map +1 -0
- package/dist/router/router-spec.d.ts +3 -0
- package/dist/router/router-spec.js +40 -0
- package/dist/router/router-spec.js.map +1 -0
- package/dist/router/routing-table.d.ts +32 -0
- package/dist/router/routing-table.js +86 -0
- package/dist/router/routing-table.js.map +1 -0
- package/dist/router/trie-router.d.ts +13 -0
- package/dist/router/trie-router.js +55 -0
- package/dist/router/trie-router.js.map +1 -0
- package/dist/router/trie.d.ts +59 -0
- package/dist/router/trie.js +180 -0
- package/dist/router/trie.js.map +1 -0
- package/{dist6/src → dist}/sequence.d.ts +28 -23
- package/dist/sequence.js +112 -0
- package/dist/sequence.js.map +1 -0
- package/dist/spec-enhancers/consolidate.spec-enhancer.d.ts +68 -0
- package/dist/spec-enhancers/consolidate.spec-enhancer.js +145 -0
- package/dist/spec-enhancers/consolidate.spec-enhancer.js.map +1 -0
- package/dist/spec-enhancers/info.spec-enhancer.d.ts +19 -0
- package/dist/spec-enhancers/info.spec-enhancer.js +89 -0
- package/dist/spec-enhancers/info.spec-enhancer.js.map +1 -0
- package/dist/types.d.ts +178 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/dist/validation/ajv-factory.provider.d.ts +12 -0
- package/dist/validation/ajv-factory.provider.js +87 -0
- package/dist/validation/ajv-factory.provider.js.map +1 -0
- package/dist/validation/request-body.validator.d.ts +14 -0
- package/dist/validation/request-body.validator.js +161 -0
- package/dist/validation/request-body.validator.js.map +1 -0
- package/dist/writer.d.ts +9 -0
- package/dist/writer.js +62 -0
- package/dist/writer.js.map +1 -0
- package/package.json +66 -38
- package/src/body-parsers/body-parser.helpers.ts +148 -0
- package/src/body-parsers/body-parser.json.ts +46 -0
- package/src/body-parsers/body-parser.raw.ts +42 -0
- package/src/body-parsers/body-parser.stream.ts +27 -0
- package/src/body-parsers/body-parser.text.ts +44 -0
- package/src/body-parsers/body-parser.ts +208 -0
- package/src/body-parsers/body-parser.urlencoded.ts +42 -0
- package/src/body-parsers/index.ts +13 -0
- package/src/body-parsers/types.ts +60 -0
- package/src/coercion/coerce-parameter.ts +207 -0
- package/src/coercion/utils.ts +103 -0
- package/src/coercion/validator.ts +98 -0
- package/src/http-handler.ts +84 -41
- package/src/index.ts +37 -30
- package/src/keys.ts +273 -20
- package/src/parse-json.ts +42 -0
- package/src/parser.ts +89 -104
- package/src/providers/{find-route.ts → find-route.provider.ts} +10 -7
- package/src/providers/index.ts +7 -9
- package/src/providers/{invoke-method.ts → invoke-method.provider.ts} +8 -5
- package/src/providers/log-error.provider.ts +27 -0
- package/src/providers/parse-params.provider.ts +42 -0
- package/src/providers/reject.provider.ts +44 -0
- package/src/providers/{send.ts → send.provider.ts} +2 -5
- package/src/request-context.ts +123 -0
- package/src/rest-http-error.ts +87 -0
- package/src/rest.application.ts +390 -0
- package/src/rest.component.ts +111 -0
- package/src/rest.server.ts +1192 -0
- package/src/router/base-route.ts +53 -0
- package/src/router/controller-route.ts +241 -0
- package/src/router/external-express-routes.ts +139 -0
- package/src/router/handler-route.ts +44 -0
- package/src/router/index.ts +24 -0
- package/src/router/openapi-path.ts +67 -0
- package/src/router/redirect-route.ts +64 -0
- package/src/router/regexp-router.ts +104 -0
- package/src/router/rest-router.ts +48 -0
- package/src/router/route-entry.ts +74 -0
- package/src/router/route-sort.ts +74 -0
- package/src/router/router-base.ts +124 -0
- package/src/router/router-spec.ts +36 -0
- package/src/router/routing-table.ts +83 -279
- package/src/router/trie-router.ts +57 -0
- package/src/router/trie.ts +233 -0
- package/src/sequence.ts +44 -37
- package/src/spec-enhancers/consolidate.spec-enhancer.ts +182 -0
- package/src/spec-enhancers/info.spec-enhancer.ts +92 -0
- package/src/types.ts +216 -0
- package/src/validation/ajv-factory.provider.ts +94 -0
- package/src/validation/request-body.validator.ts +208 -0
- package/src/writer.ts +41 -68
- package/api-docs/.DS_Store +0 -0
- package/api-docs/apple-touch-icon-114x114-precomposed.png +0 -0
- package/api-docs/apple-touch-icon-144x144-precomposed.png +0 -0
- package/api-docs/apple-touch-icon-57x57-precomposed.png +0 -0
- package/api-docs/apple-touch-icon-72x72-precomposed.png +0 -0
- package/api-docs/apple-touch-icon-precomposed.png +0 -0
- package/api-docs/apple-touch-icon.png +0 -0
- package/api-docs/css/bootstrap.min.css +0 -9
- package/api-docs/css/code-themes/arta.css +0 -158
- package/api-docs/css/code-themes/ascetic.css +0 -50
- package/api-docs/css/code-themes/brown_paper.css +0 -104
- package/api-docs/css/code-themes/brown_papersq.png +0 -0
- package/api-docs/css/code-themes/dark.css +0 -103
- package/api-docs/css/code-themes/default.css +0 -135
- package/api-docs/css/code-themes/far.css +0 -111
- package/api-docs/css/code-themes/github.css +0 -127
- package/api-docs/css/code-themes/googlecode.css +0 -144
- package/api-docs/css/code-themes/idea.css +0 -121
- package/api-docs/css/code-themes/ir_black.css +0 -104
- package/api-docs/css/code-themes/magula.css +0 -121
- package/api-docs/css/code-themes/monokai.css +0 -114
- package/api-docs/css/code-themes/pojoaque.css +0 -104
- package/api-docs/css/code-themes/pojoaque.jpg +0 -0
- package/api-docs/css/code-themes/rainbow.css +0 -114
- package/api-docs/css/code-themes/school_book.css +0 -111
- package/api-docs/css/code-themes/school_book.png +0 -0
- package/api-docs/css/code-themes/sl-theme.css +0 -45
- package/api-docs/css/code-themes/solarized_dark.css +0 -88
- package/api-docs/css/code-themes/solarized_light.css +0 -88
- package/api-docs/css/code-themes/sunburst.css +0 -158
- package/api-docs/css/code-themes/tomorrow-night-blue.css +0 -52
- package/api-docs/css/code-themes/tomorrow-night-bright.css +0 -51
- package/api-docs/css/code-themes/tomorrow-night-eighties.css +0 -51
- package/api-docs/css/code-themes/tomorrow-night.css +0 -52
- package/api-docs/css/code-themes/tomorrow.css +0 -49
- package/api-docs/css/code-themes/vs.css +0 -86
- package/api-docs/css/code-themes/xcode.css +0 -154
- package/api-docs/css/code-themes/zenburn.css +0 -115
- package/api-docs/css/main.css +0 -139
- package/api-docs/favicon.ico +0 -0
- package/api-docs/fonts/0ihfXUL2emPh0ROJezvraLO3LdcAZYWl9Si6vvxL-qU.woff +0 -0
- package/api-docs/fonts/OsJ2DjdpjqFRVUSto6IffLO3LdcAZYWl9Si6vvxL-qU.woff +0 -0
- package/api-docs/fonts/_aijTyevf54tkVDLy-dlnLO3LdcAZYWl9Si6vvxL-qU.woff +0 -0
- package/api-docs/index.html +0 -7082
- package/api-docs/js/main.js +0 -19
- package/api-docs/js/vendor/bootstrap.min.js +0 -6
- package/api-docs/js/vendor/jquery-1.10.1.min.js +0 -6
- package/api-docs/js/vendor/jquery.scrollTo-1.4.3.1.js +0 -218
- package/api-docs/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js +0 -11
- package/dist/src/http-handler.d.ts +0 -19
- package/dist/src/http-handler.js +0 -43
- package/dist/src/http-handler.js.map +0 -1
- package/dist/src/index.d.ts +0 -14
- package/dist/src/index.js +0 -33
- package/dist/src/index.js.map +0 -1
- package/dist/src/internal-types.d.ts +0 -67
- package/dist/src/internal-types.js.map +0 -1
- package/dist/src/keys.d.ts +0 -22
- package/dist/src/keys.js +0 -35
- package/dist/src/keys.js.map +0 -1
- package/dist/src/parser.d.ts +0 -11
- package/dist/src/parser.js +0 -98
- package/dist/src/parser.js.map +0 -1
- package/dist/src/providers/bind-element.d.ts +0 -7
- package/dist/src/providers/bind-element.js +0 -34
- package/dist/src/providers/bind-element.js.map +0 -1
- package/dist/src/providers/find-route.d.ts +0 -9
- package/dist/src/providers/find-route.js +0 -42
- package/dist/src/providers/find-route.js.map +0 -1
- package/dist/src/providers/get-from-context.d.ts +0 -7
- package/dist/src/providers/get-from-context.js +0 -34
- package/dist/src/providers/get-from-context.js.map +0 -1
- package/dist/src/providers/index.d.ts +0 -8
- package/dist/src/providers/index.js +0 -18
- package/dist/src/providers/index.js.map +0 -1
- package/dist/src/providers/invoke-method.js +0 -36
- package/dist/src/providers/invoke-method.js.map +0 -1
- package/dist/src/providers/log-error-provider.d.ts +0 -6
- package/dist/src/providers/log-error-provider.js +0 -17
- package/dist/src/providers/log-error-provider.js.map +0 -1
- package/dist/src/providers/parse-params.d.ts +0 -13
- package/dist/src/providers/parse-params.js +0 -22
- package/dist/src/providers/parse-params.js.map +0 -1
- package/dist/src/providers/reject.d.ts +0 -6
- package/dist/src/providers/reject.js +0 -40
- package/dist/src/providers/reject.js.map +0 -1
- package/dist/src/providers/send.js.map +0 -1
- package/dist/src/rest-component.d.ts +0 -12
- package/dist/src/rest-component.js +0 -50
- package/dist/src/rest-component.js.map +0 -1
- package/dist/src/rest-server.d.ts +0 -211
- package/dist/src/rest-server.js +0 -426
- package/dist/src/rest-server.js.map +0 -1
- package/dist/src/router/metadata.d.ts +0 -150
- package/dist/src/router/metadata.js +0 -410
- package/dist/src/router/metadata.js.map +0 -1
- package/dist/src/router/routing-table.d.ts +0 -68
- package/dist/src/router/routing-table.js +0 -204
- package/dist/src/router/routing-table.js.map +0 -1
- package/dist/src/sequence.d.ts +0 -81
- package/dist/src/sequence.js +0 -104
- package/dist/src/sequence.js.map +0 -1
- package/dist/src/writer.d.ts +0 -17
- package/dist/src/writer.js +0 -87
- package/dist/src/writer.js.map +0 -1
- package/dist6/index.d.ts +0 -1
- package/dist6/index.js +0 -12
- package/dist6/src/http-handler.d.ts +0 -19
- package/dist6/src/http-handler.js +0 -53
- package/dist6/src/http-handler.js.map +0 -1
- package/dist6/src/index.d.ts +0 -14
- package/dist6/src/index.js +0 -33
- package/dist6/src/index.js.map +0 -1
- package/dist6/src/internal-types.d.ts +0 -67
- package/dist6/src/internal-types.js +0 -7
- package/dist6/src/internal-types.js.map +0 -1
- package/dist6/src/keys.d.ts +0 -22
- package/dist6/src/keys.js +0 -35
- package/dist6/src/keys.js.map +0 -1
- package/dist6/src/parser.d.ts +0 -11
- package/dist6/src/parser.js +0 -108
- package/dist6/src/parser.js.map +0 -1
- package/dist6/src/providers/bind-element.d.ts +0 -7
- package/dist6/src/providers/bind-element.js +0 -34
- package/dist6/src/providers/bind-element.js.map +0 -1
- package/dist6/src/providers/find-route.js +0 -42
- package/dist6/src/providers/find-route.js.map +0 -1
- package/dist6/src/providers/get-from-context.d.ts +0 -7
- package/dist6/src/providers/get-from-context.js +0 -34
- package/dist6/src/providers/get-from-context.js.map +0 -1
- package/dist6/src/providers/index.d.ts +0 -8
- package/dist6/src/providers/index.js +0 -18
- package/dist6/src/providers/index.js.map +0 -1
- package/dist6/src/providers/invoke-method.d.ts +0 -7
- package/dist6/src/providers/invoke-method.js +0 -44
- package/dist6/src/providers/invoke-method.js.map +0 -1
- package/dist6/src/providers/log-error-provider.d.ts +0 -6
- package/dist6/src/providers/log-error-provider.js +0 -17
- package/dist6/src/providers/log-error-provider.js.map +0 -1
- package/dist6/src/providers/parse-params.d.ts +0 -13
- package/dist6/src/providers/parse-params.js +0 -22
- package/dist6/src/providers/parse-params.js.map +0 -1
- package/dist6/src/providers/reject.d.ts +0 -6
- package/dist6/src/providers/reject.js +0 -40
- package/dist6/src/providers/reject.js.map +0 -1
- package/dist6/src/providers/send.d.ts +0 -15
- package/dist6/src/providers/send.js +0 -24
- package/dist6/src/providers/send.js.map +0 -1
- package/dist6/src/rest-component.d.ts +0 -12
- package/dist6/src/rest-component.js +0 -50
- package/dist6/src/rest-component.js.map +0 -1
- package/dist6/src/rest-server.d.ts +0 -211
- package/dist6/src/rest-server.js +0 -444
- package/dist6/src/rest-server.js.map +0 -1
- package/dist6/src/router/metadata.d.ts +0 -150
- package/dist6/src/router/metadata.js +0 -410
- package/dist6/src/router/metadata.js.map +0 -1
- package/dist6/src/router/routing-table.d.ts +0 -68
- package/dist6/src/router/routing-table.js +0 -218
- package/dist6/src/router/routing-table.js.map +0 -1
- package/dist6/src/sequence.js +0 -114
- package/dist6/src/sequence.js.map +0 -1
- package/dist6/src/writer.d.ts +0 -17
- package/dist6/src/writer.js +0 -87
- package/dist6/src/writer.js.map +0 -1
- package/index.d.ts +0 -6
- package/index.js +0 -9
- package/src/internal-types.ts +0 -96
- package/src/providers/bind-element.ts +0 -15
- package/src/providers/get-from-context.ts +0 -16
- package/src/providers/log-error-provider.ts +0 -23
- package/src/providers/parse-params.ts +0 -20
- package/src/providers/reject.ts +0 -27
- package/src/rest-component.ts +0 -54
- package/src/rest-server.ts +0 -584
- package/src/router/metadata.ts +0 -517
|
@@ -0,0 +1,1192 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2018,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/rest
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Binding,
|
|
8
|
+
BindingAddress,
|
|
9
|
+
BindingScope,
|
|
10
|
+
Constructor,
|
|
11
|
+
ContextObserver,
|
|
12
|
+
createBindingFromClass,
|
|
13
|
+
filterByKey,
|
|
14
|
+
filterByTag,
|
|
15
|
+
inject,
|
|
16
|
+
Subscription,
|
|
17
|
+
} from '@loopback/context';
|
|
18
|
+
import {Application, CoreBindings, Server} from '@loopback/core';
|
|
19
|
+
import {BaseMiddlewareRegistry, ExpressRequestHandler} from '@loopback/express';
|
|
20
|
+
import {HttpServer, HttpServerOptions} from '@loopback/http-server';
|
|
21
|
+
import {
|
|
22
|
+
getControllerSpec,
|
|
23
|
+
OASEnhancerBindings,
|
|
24
|
+
OASEnhancerService,
|
|
25
|
+
OpenAPIObject,
|
|
26
|
+
OpenApiSpec,
|
|
27
|
+
OperationObject,
|
|
28
|
+
ServerObject,
|
|
29
|
+
} from '@loopback/openapi-v3';
|
|
30
|
+
import assert, {AssertionError} from 'assert';
|
|
31
|
+
import cors from 'cors';
|
|
32
|
+
import debugFactory from 'debug';
|
|
33
|
+
import express, {ErrorRequestHandler} from 'express';
|
|
34
|
+
import {PathParams} from 'express-serve-static-core';
|
|
35
|
+
import {IncomingMessage, ServerResponse} from 'http';
|
|
36
|
+
import {ServerOptions} from 'https';
|
|
37
|
+
import {safeDump} from 'js-yaml';
|
|
38
|
+
import {cloneDeep} from 'lodash';
|
|
39
|
+
import {ServeStaticOptions} from 'serve-static';
|
|
40
|
+
import {writeErrorToResponse} from 'strong-error-handler';
|
|
41
|
+
import {BodyParser, REQUEST_BODY_PARSER_TAG} from './body-parsers';
|
|
42
|
+
import {HttpHandler} from './http-handler';
|
|
43
|
+
import {RestBindings, RestTags} from './keys';
|
|
44
|
+
import {RequestContext} from './request-context';
|
|
45
|
+
import {
|
|
46
|
+
ControllerClass,
|
|
47
|
+
ControllerFactory,
|
|
48
|
+
ControllerInstance,
|
|
49
|
+
ControllerRoute,
|
|
50
|
+
createControllerFactoryForBinding,
|
|
51
|
+
createRoutesForController,
|
|
52
|
+
ExternalExpressRoutes,
|
|
53
|
+
RedirectRoute,
|
|
54
|
+
RestRouterOptions,
|
|
55
|
+
Route,
|
|
56
|
+
RouteEntry,
|
|
57
|
+
RouterSpec,
|
|
58
|
+
RoutingTable,
|
|
59
|
+
} from './router';
|
|
60
|
+
import {assignRouterSpec} from './router/router-spec';
|
|
61
|
+
import {DefaultSequence, SequenceFunction, SequenceHandler} from './sequence';
|
|
62
|
+
import {Request, RequestBodyParserOptions, Response} from './types';
|
|
63
|
+
|
|
64
|
+
const debug = debugFactory('loopback:rest:server');
|
|
65
|
+
|
|
66
|
+
export type HttpRequestListener = (
|
|
67
|
+
req: IncomingMessage,
|
|
68
|
+
res: ServerResponse,
|
|
69
|
+
) => void;
|
|
70
|
+
|
|
71
|
+
export interface HttpServerLike {
|
|
72
|
+
requestHandler: HttpRequestListener;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const SequenceActions = RestBindings.SequenceActions;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A REST API server for use with Loopback.
|
|
79
|
+
* Add this server to your application by importing the RestComponent.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* const app = new MyApplication();
|
|
84
|
+
* app.component(RestComponent);
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* To add additional instances of RestServer to your application, use the
|
|
88
|
+
* `.server` function:
|
|
89
|
+
* ```ts
|
|
90
|
+
* app.server(RestServer, 'nameOfYourServer');
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* By default, one instance of RestServer will be created when the RestComponent
|
|
94
|
+
* is bootstrapped. This instance can be retrieved with
|
|
95
|
+
* `app.getServer(RestServer)`, or by calling `app.get('servers.RestServer')`
|
|
96
|
+
* Note that retrieving other instances of RestServer must be done using the
|
|
97
|
+
* server's name:
|
|
98
|
+
* ```ts
|
|
99
|
+
* const server = await app.getServer('foo')
|
|
100
|
+
* // OR
|
|
101
|
+
* const server = await app.get('servers.foo');
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export class RestServer extends BaseMiddlewareRegistry
|
|
105
|
+
implements Server, HttpServerLike {
|
|
106
|
+
/**
|
|
107
|
+
* Handle incoming HTTP(S) request by invoking the corresponding
|
|
108
|
+
* Controller method via the configured Sequence.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
*
|
|
112
|
+
* ```ts
|
|
113
|
+
* const app = new Application();
|
|
114
|
+
* app.component(RestComponent);
|
|
115
|
+
* // setup controllers, etc.
|
|
116
|
+
*
|
|
117
|
+
* const restServer = await app.getServer(RestServer);
|
|
118
|
+
* const httpServer = http.createServer(restServer.requestHandler);
|
|
119
|
+
* httpServer.listen(3000);
|
|
120
|
+
* ```
|
|
121
|
+
*
|
|
122
|
+
* @param req - The request.
|
|
123
|
+
* @param res - The response.
|
|
124
|
+
*/
|
|
125
|
+
|
|
126
|
+
protected _OASEnhancer: OASEnhancerService;
|
|
127
|
+
public get OASEnhancer(): OASEnhancerService {
|
|
128
|
+
this._setupOASEnhancerIfNeeded();
|
|
129
|
+
return this._OASEnhancer;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
protected _requestHandler: HttpRequestListener;
|
|
133
|
+
public get requestHandler(): HttpRequestListener {
|
|
134
|
+
if (this._requestHandler == null) {
|
|
135
|
+
this._setupRequestHandlerIfNeeded();
|
|
136
|
+
}
|
|
137
|
+
return this._requestHandler;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
public readonly config: RestServerResolvedConfig;
|
|
141
|
+
private _basePath: string;
|
|
142
|
+
|
|
143
|
+
protected _httpHandler: HttpHandler;
|
|
144
|
+
protected get httpHandler(): HttpHandler {
|
|
145
|
+
this._setupHandlerIfNeeded();
|
|
146
|
+
return this._httpHandler;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Context event subscriptions for route related changes
|
|
151
|
+
*/
|
|
152
|
+
private _routesEventSubscription: Subscription;
|
|
153
|
+
|
|
154
|
+
protected _httpServer: HttpServer | undefined;
|
|
155
|
+
|
|
156
|
+
protected _expressApp?: express.Application;
|
|
157
|
+
|
|
158
|
+
get listening(): boolean {
|
|
159
|
+
return this._httpServer ? this._httpServer.listening : false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The base url for the server, including the basePath if set. For example,
|
|
164
|
+
* the value will be 'http://localhost:3000/api' if `basePath` is set to
|
|
165
|
+
* '/api'.
|
|
166
|
+
*/
|
|
167
|
+
get url(): string | undefined {
|
|
168
|
+
let serverUrl = this.rootUrl;
|
|
169
|
+
if (!serverUrl) return serverUrl;
|
|
170
|
+
serverUrl = serverUrl + (this._basePath || '');
|
|
171
|
+
return serverUrl;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The root url for the server without the basePath. For example, the value
|
|
176
|
+
* will be 'http://localhost:3000' regardless of the `basePath`.
|
|
177
|
+
*/
|
|
178
|
+
get rootUrl(): string | undefined {
|
|
179
|
+
return this._httpServer && this._httpServer.url;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
*
|
|
184
|
+
* Creates an instance of RestServer.
|
|
185
|
+
*
|
|
186
|
+
* @param app - The application instance (injected via
|
|
187
|
+
* CoreBindings.APPLICATION_INSTANCE).
|
|
188
|
+
* @param config - The configuration options (injected via
|
|
189
|
+
* RestBindings.CONFIG).
|
|
190
|
+
*
|
|
191
|
+
*/
|
|
192
|
+
constructor(
|
|
193
|
+
@inject(CoreBindings.APPLICATION_INSTANCE) app: Application,
|
|
194
|
+
@inject(RestBindings.CONFIG, {optional: true})
|
|
195
|
+
config: RestServerConfig = {},
|
|
196
|
+
) {
|
|
197
|
+
super(app);
|
|
198
|
+
|
|
199
|
+
this.config = resolveRestServerConfig(config);
|
|
200
|
+
|
|
201
|
+
this.bind(RestBindings.PORT).to(this.config.port);
|
|
202
|
+
this.bind(RestBindings.HOST).to(config.host);
|
|
203
|
+
this.bind(RestBindings.PATH).to(config.path);
|
|
204
|
+
this.bind(RestBindings.PROTOCOL).to(config.protocol ?? 'http');
|
|
205
|
+
this.bind(RestBindings.HTTPS_OPTIONS).to(config as ServerOptions);
|
|
206
|
+
|
|
207
|
+
if (config.requestBodyParser) {
|
|
208
|
+
this.bind(RestBindings.REQUEST_BODY_PARSER_OPTIONS).to(
|
|
209
|
+
config.requestBodyParser,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (config.sequence) {
|
|
214
|
+
this.sequence(config.sequence);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (config.router) {
|
|
218
|
+
this.bind(RestBindings.ROUTER_OPTIONS).to(config.router);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
this.basePath(config.basePath);
|
|
222
|
+
|
|
223
|
+
this.bind(RestBindings.BASE_PATH).toDynamicValue(() => this._basePath);
|
|
224
|
+
this.bind(RestBindings.HANDLER).toDynamicValue(() => this.httpHandler);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
protected _setupOASEnhancerIfNeeded() {
|
|
228
|
+
if (this._OASEnhancer != null) return;
|
|
229
|
+
this.add(
|
|
230
|
+
createBindingFromClass(OASEnhancerService, {
|
|
231
|
+
key: OASEnhancerBindings.OAS_ENHANCER_SERVICE,
|
|
232
|
+
}),
|
|
233
|
+
);
|
|
234
|
+
this._OASEnhancer = this.getSync(OASEnhancerBindings.OAS_ENHANCER_SERVICE);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
protected _setupRequestHandlerIfNeeded() {
|
|
238
|
+
if (this._expressApp != null) return;
|
|
239
|
+
this._expressApp = express();
|
|
240
|
+
this._applyExpressSettings();
|
|
241
|
+
this._requestHandler = this._expressApp;
|
|
242
|
+
|
|
243
|
+
// Allow CORS support for all endpoints so that users
|
|
244
|
+
// can test with online SwaggerUI instance
|
|
245
|
+
this.expressMiddleware(cors, this.config.cors, {
|
|
246
|
+
injectConfiguration: false,
|
|
247
|
+
key: 'middleware.cors',
|
|
248
|
+
group: 'cors',
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Set up endpoints for OpenAPI spec/ui
|
|
252
|
+
this._setupOpenApiSpecEndpoints();
|
|
253
|
+
|
|
254
|
+
// Mount our router & request handler
|
|
255
|
+
this._expressApp.use(this._basePath, (req, res, next) => {
|
|
256
|
+
this._handleHttpRequest(req, res).catch(next);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Mount our error handler
|
|
260
|
+
this._expressApp.use(this._unexpectedErrorHandler());
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Get an Express handler for unexpected errors
|
|
265
|
+
*/
|
|
266
|
+
protected _unexpectedErrorHandler(): ErrorRequestHandler {
|
|
267
|
+
const handleUnExpectedError: ErrorRequestHandler = (
|
|
268
|
+
err,
|
|
269
|
+
req,
|
|
270
|
+
res,
|
|
271
|
+
next,
|
|
272
|
+
) => {
|
|
273
|
+
// Handle errors reported by Express middleware such as CORS
|
|
274
|
+
// First try to use the `REJECT` action
|
|
275
|
+
this.get(SequenceActions.REJECT, {optional: true})
|
|
276
|
+
.then(reject => {
|
|
277
|
+
if (reject) {
|
|
278
|
+
// TODO(rfeng): There is a possibility that the error is thrown
|
|
279
|
+
// from the `REJECT` action in the sequence
|
|
280
|
+
return reject({request: req, response: res}, err);
|
|
281
|
+
}
|
|
282
|
+
// Use strong-error handler directly
|
|
283
|
+
writeErrorToResponse(err, req, res);
|
|
284
|
+
})
|
|
285
|
+
.catch(unexpectedErr => next(unexpectedErr));
|
|
286
|
+
};
|
|
287
|
+
return handleUnExpectedError;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Apply express settings.
|
|
292
|
+
*/
|
|
293
|
+
protected _applyExpressSettings() {
|
|
294
|
+
assertExists(this._expressApp, 'this._expressApp');
|
|
295
|
+
const settings = this.config.expressSettings;
|
|
296
|
+
for (const key in settings) {
|
|
297
|
+
this._expressApp.set(key, settings[key]);
|
|
298
|
+
}
|
|
299
|
+
if (this.config.router && typeof this.config.router.strict === 'boolean') {
|
|
300
|
+
this._expressApp.set('strict routing', this.config.router.strict);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Mount /openapi.json, /openapi.yaml for specs and /swagger-ui, /explorer
|
|
306
|
+
* to redirect to externally hosted API explorer
|
|
307
|
+
*/
|
|
308
|
+
protected _setupOpenApiSpecEndpoints() {
|
|
309
|
+
assertExists(this._expressApp, 'this._expressApp');
|
|
310
|
+
if (this.config.openApiSpec.disabled) return;
|
|
311
|
+
const router = express.Router();
|
|
312
|
+
const mapping = this.config.openApiSpec.endpointMapping!;
|
|
313
|
+
// Serving OpenAPI spec
|
|
314
|
+
for (const p in mapping) {
|
|
315
|
+
this.addOpenApiSpecEndpoint(p, mapping[p], router);
|
|
316
|
+
}
|
|
317
|
+
const explorerPaths = ['/swagger-ui', '/explorer'];
|
|
318
|
+
router.get(explorerPaths, (req, res, next) =>
|
|
319
|
+
this._redirectToSwaggerUI(req, res, next),
|
|
320
|
+
);
|
|
321
|
+
this.expressMiddleware('middleware.apiSpec.defaults', router, {
|
|
322
|
+
group: 'apiSpec',
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Add a new non-controller endpoint hosting a form of the OpenAPI spec.
|
|
328
|
+
*
|
|
329
|
+
* @param path Path at which to host the copy of the OpenAPI
|
|
330
|
+
* @param form Form that should be rendered from that path
|
|
331
|
+
*/
|
|
332
|
+
addOpenApiSpecEndpoint(
|
|
333
|
+
path: string,
|
|
334
|
+
form: OpenApiSpecForm,
|
|
335
|
+
router?: express.Router,
|
|
336
|
+
) {
|
|
337
|
+
if (router == null) {
|
|
338
|
+
const key = `middleware.apiSpec.${path}.${form}`;
|
|
339
|
+
if (this.contains(key)) {
|
|
340
|
+
throw new Error(
|
|
341
|
+
`The path ${path} is already configured for OpenApi hosting`,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
const newRouter = express.Router();
|
|
345
|
+
newRouter.get(path, (req, res) => this._serveOpenApiSpec(req, res, form));
|
|
346
|
+
this.expressMiddleware(
|
|
347
|
+
() => newRouter,
|
|
348
|
+
{},
|
|
349
|
+
{
|
|
350
|
+
injectConfiguration: false,
|
|
351
|
+
key: `middleware.apiSpec.${path}.${form}`,
|
|
352
|
+
group: 'apiSpec',
|
|
353
|
+
},
|
|
354
|
+
);
|
|
355
|
+
} else {
|
|
356
|
+
router.get(path, (req, res) => this._serveOpenApiSpec(req, res, form));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
protected _handleHttpRequest(request: Request, response: Response) {
|
|
361
|
+
return this.httpHandler.handleRequest(request, response);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
protected _setupHandlerIfNeeded() {
|
|
365
|
+
if (this._httpHandler) return;
|
|
366
|
+
|
|
367
|
+
// Watch for binding events
|
|
368
|
+
// See https://github.com/strongloop/loopback-next/issues/433
|
|
369
|
+
const routesObserver: ContextObserver = {
|
|
370
|
+
filter: binding =>
|
|
371
|
+
filterByKey(RestBindings.API_SPEC.key)(binding) ||
|
|
372
|
+
(filterByKey(/^(controllers|routes)\..+/)(binding) &&
|
|
373
|
+
// Exclude controller routes to avoid circular events
|
|
374
|
+
!filterByTag(RestTags.CONTROLLER_ROUTE)(binding)),
|
|
375
|
+
observe: () => {
|
|
376
|
+
// Rebuild the HttpHandler instance whenever a controller/route was
|
|
377
|
+
// added/deleted.
|
|
378
|
+
this._createHttpHandler();
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
this._routesEventSubscription = this.subscribe(routesObserver);
|
|
382
|
+
|
|
383
|
+
this._createHttpHandler();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Create an instance of HttpHandler and populates it with routes
|
|
388
|
+
*/
|
|
389
|
+
private _createHttpHandler() {
|
|
390
|
+
/**
|
|
391
|
+
* Check if there is custom router in the context
|
|
392
|
+
*/
|
|
393
|
+
const router = this.getSync(RestBindings.ROUTER, {optional: true});
|
|
394
|
+
const routingTable = new RoutingTable(router, this._externalRoutes);
|
|
395
|
+
|
|
396
|
+
this._httpHandler = new HttpHandler(this, this.config, routingTable);
|
|
397
|
+
|
|
398
|
+
// Remove controller routes
|
|
399
|
+
for (const b of this.findByTag(RestTags.CONTROLLER_ROUTE)) {
|
|
400
|
+
this.unbind(b.key);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
for (const b of this.find(`${CoreBindings.CONTROLLERS}.*`)) {
|
|
404
|
+
const controllerName = b.key.replace(/^controllers\./, '');
|
|
405
|
+
const ctor = b.valueConstructor;
|
|
406
|
+
if (!ctor) {
|
|
407
|
+
throw new Error(
|
|
408
|
+
`The controller ${controllerName} was not bound via .toClass()`,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
const apiSpec = getControllerSpec(ctor);
|
|
412
|
+
if (!apiSpec) {
|
|
413
|
+
// controller methods are specified through app.api() spec
|
|
414
|
+
debug('Skipping controller %s - no API spec provided', controllerName);
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
debug('Registering controller %s', controllerName);
|
|
419
|
+
if (apiSpec.components) {
|
|
420
|
+
this._httpHandler.registerApiComponents(apiSpec.components);
|
|
421
|
+
}
|
|
422
|
+
const controllerFactory = createControllerFactoryForBinding(b.key);
|
|
423
|
+
const routes = createRoutesForController(
|
|
424
|
+
apiSpec,
|
|
425
|
+
ctor,
|
|
426
|
+
controllerFactory,
|
|
427
|
+
);
|
|
428
|
+
for (const route of routes) {
|
|
429
|
+
const binding = this.bindRoute(route);
|
|
430
|
+
binding
|
|
431
|
+
.tag(RestTags.CONTROLLER_ROUTE)
|
|
432
|
+
.tag({[RestTags.CONTROLLER_BINDING]: b.key});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
for (const b of this.findByTag(RestTags.REST_ROUTE)) {
|
|
437
|
+
// TODO(bajtos) should we support routes defined asynchronously?
|
|
438
|
+
const route = this.getSync<RouteEntry>(b.key);
|
|
439
|
+
this._httpHandler.registerRoute(route);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// TODO(bajtos) should we support API spec defined asynchronously?
|
|
443
|
+
const spec: OpenApiSpec = this.getSync(RestBindings.API_SPEC);
|
|
444
|
+
for (const path in spec.paths) {
|
|
445
|
+
for (const verb in spec.paths[path]) {
|
|
446
|
+
const routeSpec: OperationObject = spec.paths[path][verb];
|
|
447
|
+
this._setupOperation(verb, path, routeSpec);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private _setupOperation(verb: string, path: string, spec: OperationObject) {
|
|
453
|
+
const handler = spec['x-operation'];
|
|
454
|
+
if (typeof handler === 'function') {
|
|
455
|
+
// Remove a field value that cannot be represented in JSON.
|
|
456
|
+
// Start by creating a shallow-copy of the spec, so that we don't
|
|
457
|
+
// modify the original spec object provided by user.
|
|
458
|
+
spec = Object.assign({}, spec);
|
|
459
|
+
delete spec['x-operation'];
|
|
460
|
+
|
|
461
|
+
const route = new Route(verb, path, spec, handler);
|
|
462
|
+
this._httpHandler.registerRoute(route);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const controllerName = spec['x-controller-name'];
|
|
467
|
+
if (typeof controllerName === 'string') {
|
|
468
|
+
const b = this.getBinding(`controllers.${controllerName}`, {
|
|
469
|
+
optional: true,
|
|
470
|
+
});
|
|
471
|
+
if (!b) {
|
|
472
|
+
throw new Error(
|
|
473
|
+
`Unknown controller ${controllerName} used by "${verb} ${path}"`,
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const ctor = b.valueConstructor;
|
|
478
|
+
if (!ctor) {
|
|
479
|
+
throw new Error(
|
|
480
|
+
`The controller ${controllerName} was not bound via .toClass()`,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const controllerFactory = createControllerFactoryForBinding(b.key);
|
|
485
|
+
const route = new ControllerRoute(
|
|
486
|
+
verb,
|
|
487
|
+
path,
|
|
488
|
+
spec,
|
|
489
|
+
ctor,
|
|
490
|
+
controllerFactory,
|
|
491
|
+
);
|
|
492
|
+
this._httpHandler.registerRoute(route);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
throw new Error(
|
|
497
|
+
`There is no handler configured for operation "${verb} ${path}`,
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
private async _serveOpenApiSpec(
|
|
502
|
+
request: Request,
|
|
503
|
+
response: Response,
|
|
504
|
+
specForm?: OpenApiSpecForm,
|
|
505
|
+
) {
|
|
506
|
+
const requestContext = new RequestContext(
|
|
507
|
+
request,
|
|
508
|
+
response,
|
|
509
|
+
this,
|
|
510
|
+
this.config,
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
specForm = specForm ?? {version: '3.0.0', format: 'json'};
|
|
514
|
+
const specObj = await this.getApiSpec(requestContext);
|
|
515
|
+
|
|
516
|
+
if (specForm.format === 'json') {
|
|
517
|
+
const spec = JSON.stringify(specObj, null, 2);
|
|
518
|
+
response.setHeader('content-type', 'application/json; charset=utf-8');
|
|
519
|
+
response.end(spec, 'utf-8');
|
|
520
|
+
} else {
|
|
521
|
+
const yaml = safeDump(specObj, {});
|
|
522
|
+
response.setHeader('content-type', 'text/yaml; charset=utf-8');
|
|
523
|
+
response.end(yaml, 'utf-8');
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
private async _redirectToSwaggerUI(
|
|
527
|
+
request: Request,
|
|
528
|
+
response: Response,
|
|
529
|
+
next: express.NextFunction,
|
|
530
|
+
) {
|
|
531
|
+
const config = this.config.apiExplorer;
|
|
532
|
+
|
|
533
|
+
if (config.disabled) {
|
|
534
|
+
debug('Redirect to swagger-ui was disabled by configuration.');
|
|
535
|
+
next();
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
debug('Redirecting to swagger-ui from %j.', request.originalUrl);
|
|
540
|
+
const requestContext = new RequestContext(
|
|
541
|
+
request,
|
|
542
|
+
response,
|
|
543
|
+
this,
|
|
544
|
+
this.config,
|
|
545
|
+
);
|
|
546
|
+
const protocol = requestContext.requestedProtocol;
|
|
547
|
+
const baseUrl = protocol === 'http' ? config.httpUrl : config.url;
|
|
548
|
+
const openApiUrl = `${requestContext.requestedBaseUrl}/openapi.json`;
|
|
549
|
+
const fullUrl = `${baseUrl}?url=${openApiUrl}`;
|
|
550
|
+
response.redirect(302, fullUrl);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Register a controller class with this server.
|
|
555
|
+
*
|
|
556
|
+
* @param controllerCtor - The controller class
|
|
557
|
+
* (constructor function).
|
|
558
|
+
* @returns The newly created binding, you can use the reference to
|
|
559
|
+
* further modify the binding, e.g. lock the value to prevent further
|
|
560
|
+
* modifications.
|
|
561
|
+
*
|
|
562
|
+
* @example
|
|
563
|
+
* ```ts
|
|
564
|
+
* class MyController {
|
|
565
|
+
* }
|
|
566
|
+
* app.controller(MyController).lock();
|
|
567
|
+
* ```
|
|
568
|
+
*
|
|
569
|
+
*/
|
|
570
|
+
controller(controllerCtor: ControllerClass<ControllerInstance>): Binding {
|
|
571
|
+
return this.bind('controllers.' + controllerCtor.name).toClass(
|
|
572
|
+
controllerCtor,
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Register a new Controller-based route.
|
|
578
|
+
*
|
|
579
|
+
* @example
|
|
580
|
+
* ```ts
|
|
581
|
+
* class MyController {
|
|
582
|
+
* greet(name: string) {
|
|
583
|
+
* return `hello ${name}`;
|
|
584
|
+
* }
|
|
585
|
+
* }
|
|
586
|
+
* app.route('get', '/greet', operationSpec, MyController, 'greet');
|
|
587
|
+
* ```
|
|
588
|
+
*
|
|
589
|
+
* @param verb - HTTP verb of the endpoint
|
|
590
|
+
* @param path - URL path of the endpoint
|
|
591
|
+
* @param spec - The OpenAPI spec describing the endpoint (operation)
|
|
592
|
+
* @param controllerCtor - Controller constructor
|
|
593
|
+
* @param controllerFactory - A factory function to create controller instance
|
|
594
|
+
* @param methodName - The name of the controller method
|
|
595
|
+
*/
|
|
596
|
+
route<I>(
|
|
597
|
+
verb: string,
|
|
598
|
+
path: string,
|
|
599
|
+
spec: OperationObject,
|
|
600
|
+
controllerCtor: ControllerClass<I>,
|
|
601
|
+
controllerFactory: ControllerFactory<I>,
|
|
602
|
+
methodName: string,
|
|
603
|
+
): Binding;
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Register a new route invoking a handler function.
|
|
607
|
+
*
|
|
608
|
+
* @example
|
|
609
|
+
* ```ts
|
|
610
|
+
* function greet(name: string) {
|
|
611
|
+
* return `hello ${name}`;
|
|
612
|
+
* }
|
|
613
|
+
* app.route('get', '/', operationSpec, greet);
|
|
614
|
+
* ```
|
|
615
|
+
*
|
|
616
|
+
* @param verb - HTTP verb of the endpoint
|
|
617
|
+
* @param path - URL path of the endpoint
|
|
618
|
+
* @param spec - The OpenAPI spec describing the endpoint (operation)
|
|
619
|
+
* @param handler - The function to invoke with the request parameters
|
|
620
|
+
* described in the spec.
|
|
621
|
+
*/
|
|
622
|
+
route(
|
|
623
|
+
verb: string,
|
|
624
|
+
path: string,
|
|
625
|
+
spec: OperationObject,
|
|
626
|
+
handler: Function,
|
|
627
|
+
): Binding;
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Register a new generic route.
|
|
631
|
+
*
|
|
632
|
+
* @example
|
|
633
|
+
* ```ts
|
|
634
|
+
* function greet(name: string) {
|
|
635
|
+
* return `hello ${name}`;
|
|
636
|
+
* }
|
|
637
|
+
* const route = new Route('get', '/', operationSpec, greet);
|
|
638
|
+
* app.route(route);
|
|
639
|
+
* ```
|
|
640
|
+
*
|
|
641
|
+
* @param route - The route to add.
|
|
642
|
+
*/
|
|
643
|
+
route(route: RouteEntry): Binding;
|
|
644
|
+
|
|
645
|
+
route<T>(
|
|
646
|
+
routeOrVerb: RouteEntry | string,
|
|
647
|
+
path?: string,
|
|
648
|
+
spec?: OperationObject,
|
|
649
|
+
controllerCtorOrHandler?: ControllerClass<T> | Function,
|
|
650
|
+
controllerFactory?: ControllerFactory<T>,
|
|
651
|
+
methodName?: string,
|
|
652
|
+
): Binding {
|
|
653
|
+
if (typeof routeOrVerb === 'object') {
|
|
654
|
+
const r = routeOrVerb;
|
|
655
|
+
// Encode the path to escape special chars
|
|
656
|
+
return this.bindRoute(r);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (!path) {
|
|
660
|
+
throw new AssertionError({
|
|
661
|
+
message: 'path is required for a controller-based route',
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (!spec) {
|
|
666
|
+
throw new AssertionError({
|
|
667
|
+
message: 'spec is required for a controller-based route',
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (arguments.length === 4) {
|
|
672
|
+
if (!controllerCtorOrHandler) {
|
|
673
|
+
throw new AssertionError({
|
|
674
|
+
message: 'handler function is required for a handler-based route',
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
return this.route(
|
|
678
|
+
new Route(routeOrVerb, path, spec, controllerCtorOrHandler as Function),
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (!controllerCtorOrHandler) {
|
|
683
|
+
throw new AssertionError({
|
|
684
|
+
message: 'controller is required for a controller-based route',
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (!methodName) {
|
|
689
|
+
throw new AssertionError({
|
|
690
|
+
message: 'methodName is required for a controller-based route',
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return this.route(
|
|
695
|
+
new ControllerRoute(
|
|
696
|
+
routeOrVerb,
|
|
697
|
+
path,
|
|
698
|
+
spec,
|
|
699
|
+
controllerCtorOrHandler as ControllerClass<T>,
|
|
700
|
+
controllerFactory,
|
|
701
|
+
methodName,
|
|
702
|
+
),
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
private bindRoute(r: RouteEntry) {
|
|
707
|
+
const namespace = RestBindings.ROUTES;
|
|
708
|
+
const encodedPath = encodeURIComponent(r.path).replace(/\./g, '%2E');
|
|
709
|
+
return this.bind(`${namespace}.${r.verb} ${encodedPath}`)
|
|
710
|
+
.to(r)
|
|
711
|
+
.tag(RestTags.REST_ROUTE)
|
|
712
|
+
.tag({[RestTags.ROUTE_VERB]: r.verb, [RestTags.ROUTE_PATH]: r.path});
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Register a route redirecting callers to a different URL.
|
|
717
|
+
*
|
|
718
|
+
* @example
|
|
719
|
+
* ```ts
|
|
720
|
+
* server.redirect('/explorer', '/explorer/');
|
|
721
|
+
* ```
|
|
722
|
+
*
|
|
723
|
+
* @param fromPath - URL path of the redirect endpoint
|
|
724
|
+
* @param toPathOrUrl - Location (URL path or full URL) where to redirect to.
|
|
725
|
+
* If your server is configured with a custom `basePath`, then the base path
|
|
726
|
+
* is prepended to the target location.
|
|
727
|
+
* @param statusCode - HTTP status code to respond with,
|
|
728
|
+
* defaults to 303 (See Other).
|
|
729
|
+
*/
|
|
730
|
+
redirect(
|
|
731
|
+
fromPath: string,
|
|
732
|
+
toPathOrUrl: string,
|
|
733
|
+
statusCode?: number,
|
|
734
|
+
): Binding {
|
|
735
|
+
return this.route(
|
|
736
|
+
new RedirectRoute(fromPath, this._basePath + toPathOrUrl, statusCode),
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/*
|
|
741
|
+
* Registry of external routes & static assets
|
|
742
|
+
*/
|
|
743
|
+
private _externalRoutes = new ExternalExpressRoutes();
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Mount static assets to the REST server.
|
|
747
|
+
* See https://expressjs.com/en/4x/api.html#express.static
|
|
748
|
+
* @param path - The path(s) to serve the asset.
|
|
749
|
+
* See examples at https://expressjs.com/en/4x/api.html#path-examples
|
|
750
|
+
* @param rootDir - The root directory from which to serve static assets
|
|
751
|
+
* @param options - Options for serve-static
|
|
752
|
+
*/
|
|
753
|
+
static(path: PathParams, rootDir: string, options?: ServeStaticOptions) {
|
|
754
|
+
this._externalRoutes.registerAssets(path, rootDir, options);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Set the OpenAPI specification that defines the REST API schema for this
|
|
759
|
+
* server. All routes, parameter definitions and return types will be defined
|
|
760
|
+
* in this way.
|
|
761
|
+
*
|
|
762
|
+
* Note that this will override any routes defined via decorators at the
|
|
763
|
+
* controller level (this function takes precedent).
|
|
764
|
+
*
|
|
765
|
+
* @param spec - The OpenAPI specification, as an object.
|
|
766
|
+
* @returns Binding for the spec
|
|
767
|
+
*
|
|
768
|
+
*/
|
|
769
|
+
api(spec: OpenApiSpec): Binding {
|
|
770
|
+
return this.bind(RestBindings.API_SPEC).to(spec);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Get the OpenAPI specification describing the REST API provided by
|
|
775
|
+
* this application.
|
|
776
|
+
*
|
|
777
|
+
* This method merges operations (HTTP endpoints) from the following sources:
|
|
778
|
+
* - `app.api(spec)`
|
|
779
|
+
* - `app.controller(MyController)`
|
|
780
|
+
* - `app.route(route)`
|
|
781
|
+
* - `app.route('get', '/greet', operationSpec, MyController, 'greet')`
|
|
782
|
+
*
|
|
783
|
+
* If the optional `requestContext` is provided, then the `servers` list
|
|
784
|
+
* in the returned spec will be updated to work in that context.
|
|
785
|
+
* Specifically:
|
|
786
|
+
* 1. if `config.openApi.setServersFromRequest` is enabled, the servers
|
|
787
|
+
* list will be replaced with the context base url
|
|
788
|
+
* 2. Any `servers` entries with a path of `/` will have that path
|
|
789
|
+
* replaced with `requestContext.basePath`
|
|
790
|
+
*
|
|
791
|
+
* @param requestContext - Optional context to update the `servers` list
|
|
792
|
+
* in the returned spec
|
|
793
|
+
*/
|
|
794
|
+
async getApiSpec(requestContext?: RequestContext): Promise<OpenApiSpec> {
|
|
795
|
+
let spec = await this.get<OpenApiSpec>(RestBindings.API_SPEC);
|
|
796
|
+
const components = this.httpHandler.getApiComponents();
|
|
797
|
+
|
|
798
|
+
// Apply deep clone to prevent getApiSpec() callers from
|
|
799
|
+
// accidentally modifying our internal routing data
|
|
800
|
+
spec.paths = cloneDeep(this.httpHandler.describeApiPaths());
|
|
801
|
+
if (components) {
|
|
802
|
+
const defs = cloneDeep(components);
|
|
803
|
+
spec.components = {...spec.components, ...defs};
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
assignRouterSpec(spec, this._externalRoutes.routerSpec);
|
|
807
|
+
|
|
808
|
+
if (requestContext) {
|
|
809
|
+
spec = this.updateSpecFromRequest(spec, requestContext);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Apply OAS enhancers to the OpenAPI specification
|
|
813
|
+
this.OASEnhancer.spec = spec;
|
|
814
|
+
spec = await this.OASEnhancer.applyAllEnhancers();
|
|
815
|
+
|
|
816
|
+
return spec;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Update or rebuild OpenAPI Spec object to be appropriate for the context of
|
|
821
|
+
* a specific request for the spec, leveraging both app config and request
|
|
822
|
+
* path information.
|
|
823
|
+
*
|
|
824
|
+
* @param spec base spec object from which to start
|
|
825
|
+
* @param requestContext request to use to infer path information
|
|
826
|
+
* @returns Updated or rebuilt spec object to use in the context of the request
|
|
827
|
+
*/
|
|
828
|
+
private updateSpecFromRequest(
|
|
829
|
+
spec: OpenAPIObject,
|
|
830
|
+
requestContext: RequestContext,
|
|
831
|
+
) {
|
|
832
|
+
if (this.config.openApiSpec.setServersFromRequest) {
|
|
833
|
+
spec = Object.assign({}, spec);
|
|
834
|
+
spec.servers = [{url: requestContext.requestedBaseUrl}];
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const basePath = requestContext.basePath;
|
|
838
|
+
if (spec.servers && basePath) {
|
|
839
|
+
for (const s of spec.servers) {
|
|
840
|
+
// Update the default server url to honor `basePath`
|
|
841
|
+
if (s.url === '/') {
|
|
842
|
+
s.url = basePath;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
return spec;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Configure a custom sequence class for handling incoming requests.
|
|
852
|
+
*
|
|
853
|
+
* @example
|
|
854
|
+
* ```ts
|
|
855
|
+
* class MySequence implements SequenceHandler {
|
|
856
|
+
* constructor(
|
|
857
|
+
* @inject('send) public send: Send)) {
|
|
858
|
+
* }
|
|
859
|
+
*
|
|
860
|
+
* public async handle({response}: RequestContext) {
|
|
861
|
+
* send(response, 'hello world');
|
|
862
|
+
* }
|
|
863
|
+
* }
|
|
864
|
+
* ```
|
|
865
|
+
*
|
|
866
|
+
* @param value - The sequence to invoke for each incoming request.
|
|
867
|
+
*/
|
|
868
|
+
public sequence(value: Constructor<SequenceHandler>) {
|
|
869
|
+
this.bind(RestBindings.SEQUENCE).toClass(value);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Configure a custom sequence function for handling incoming requests.
|
|
874
|
+
*
|
|
875
|
+
* @example
|
|
876
|
+
* ```ts
|
|
877
|
+
* app.handler(({request, response}, sequence) => {
|
|
878
|
+
* sequence.send(response, 'hello world');
|
|
879
|
+
* });
|
|
880
|
+
* ```
|
|
881
|
+
*
|
|
882
|
+
* @param handlerFn - The handler to invoke for each incoming request.
|
|
883
|
+
*/
|
|
884
|
+
public handler(handlerFn: SequenceFunction) {
|
|
885
|
+
class SequenceFromFunction extends DefaultSequence {
|
|
886
|
+
async handle(context: RequestContext): Promise<void> {
|
|
887
|
+
return handlerFn(context, this);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
this.sequence(SequenceFromFunction);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Bind a body parser to the server context
|
|
896
|
+
* @param parserClass - Body parser class
|
|
897
|
+
* @param address - Optional binding address
|
|
898
|
+
*/
|
|
899
|
+
bodyParser(
|
|
900
|
+
bodyParserClass: Constructor<BodyParser>,
|
|
901
|
+
address?: BindingAddress<BodyParser>,
|
|
902
|
+
): Binding<BodyParser> {
|
|
903
|
+
const binding = createBodyParserBinding(bodyParserClass, address);
|
|
904
|
+
this.add(binding);
|
|
905
|
+
return binding;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Configure the `basePath` for the rest server
|
|
910
|
+
* @param path - Base path
|
|
911
|
+
*/
|
|
912
|
+
basePath(path = '') {
|
|
913
|
+
if (this._requestHandler != null) {
|
|
914
|
+
throw new Error(
|
|
915
|
+
'Base path cannot be set as the request handler has been created',
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
// Trim leading and trailing `/`
|
|
919
|
+
path = path.replace(/(^\/)|(\/$)/, '');
|
|
920
|
+
if (path) path = '/' + path;
|
|
921
|
+
this._basePath = path;
|
|
922
|
+
this.config.basePath = path;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Start this REST API's HTTP/HTTPS server.
|
|
927
|
+
*/
|
|
928
|
+
async start(): Promise<void> {
|
|
929
|
+
// Set up the Express app if not done yet
|
|
930
|
+
this._setupRequestHandlerIfNeeded();
|
|
931
|
+
// Setup the HTTP handler so that we can verify the configuration
|
|
932
|
+
// of API spec, controllers and routes at startup time.
|
|
933
|
+
this._setupHandlerIfNeeded();
|
|
934
|
+
|
|
935
|
+
const port = await this.get(RestBindings.PORT);
|
|
936
|
+
const host = await this.get(RestBindings.HOST);
|
|
937
|
+
const path = await this.get(RestBindings.PATH);
|
|
938
|
+
const protocol = await this.get(RestBindings.PROTOCOL);
|
|
939
|
+
const httpsOptions = await this.get(RestBindings.HTTPS_OPTIONS);
|
|
940
|
+
|
|
941
|
+
if (this.config.listenOnStart === false) {
|
|
942
|
+
debug(
|
|
943
|
+
'RestServer is not listening as listenOnStart flag is set to false.',
|
|
944
|
+
);
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
const serverOptions = {};
|
|
949
|
+
if (protocol === 'https') Object.assign(serverOptions, httpsOptions);
|
|
950
|
+
Object.assign(serverOptions, {port, host, protocol, path});
|
|
951
|
+
|
|
952
|
+
this._httpServer = new HttpServer(this.requestHandler, serverOptions);
|
|
953
|
+
|
|
954
|
+
await this._httpServer.start();
|
|
955
|
+
|
|
956
|
+
this.bind(RestBindings.PORT).to(this._httpServer.port);
|
|
957
|
+
this.bind(RestBindings.HOST).to(this._httpServer.host);
|
|
958
|
+
this.bind(RestBindings.URL).to(this._httpServer.url);
|
|
959
|
+
debug('RestServer listening at %s', this._httpServer.url);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Stop this REST API's HTTP/HTTPS server.
|
|
964
|
+
*/
|
|
965
|
+
async stop() {
|
|
966
|
+
// Kill the server instance.
|
|
967
|
+
if (!this._httpServer) return;
|
|
968
|
+
await this._httpServer.stop();
|
|
969
|
+
this._httpServer = undefined;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Mount an Express router to expose additional REST endpoints handled
|
|
974
|
+
* via legacy Express-based stack.
|
|
975
|
+
*
|
|
976
|
+
* @param basePath - Path where to mount the router at, e.g. `/` or `/api`.
|
|
977
|
+
* @param router - The Express router to handle the requests.
|
|
978
|
+
* @param spec - A partial OpenAPI spec describing endpoints provided by the
|
|
979
|
+
* router. LoopBack will prepend `basePath` to all endpoints automatically.
|
|
980
|
+
* This argument is optional. You can leave it out if you don't want to
|
|
981
|
+
* document the routes.
|
|
982
|
+
*/
|
|
983
|
+
mountExpressRouter(
|
|
984
|
+
basePath: string,
|
|
985
|
+
router: ExpressRequestHandler,
|
|
986
|
+
spec?: RouterSpec,
|
|
987
|
+
): void {
|
|
988
|
+
this._externalRoutes.mountRouter(basePath, router, spec);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* An assertion type guard for TypeScript to instruct the compiler that the
|
|
994
|
+
* given value is not `null` or `undefined.
|
|
995
|
+
* @param val - A value can be `undefined` or `null`
|
|
996
|
+
* @param name - Name of the value
|
|
997
|
+
*/
|
|
998
|
+
function assertExists<T>(val: T, name: string): asserts val is NonNullable<T> {
|
|
999
|
+
assert(val != null, `The value of ${name} cannot be null or undefined`);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Create a binding for the given body parser class
|
|
1004
|
+
* @param parserClass - Body parser class
|
|
1005
|
+
* @param key - Optional binding address
|
|
1006
|
+
*/
|
|
1007
|
+
export function createBodyParserBinding(
|
|
1008
|
+
parserClass: Constructor<BodyParser>,
|
|
1009
|
+
key?: BindingAddress<BodyParser>,
|
|
1010
|
+
): Binding<BodyParser> {
|
|
1011
|
+
const address =
|
|
1012
|
+
key ?? `${RestBindings.REQUEST_BODY_PARSER}.${parserClass.name}`;
|
|
1013
|
+
return Binding.bind<BodyParser>(address)
|
|
1014
|
+
.toClass(parserClass)
|
|
1015
|
+
.inScope(BindingScope.TRANSIENT)
|
|
1016
|
+
.tag(REQUEST_BODY_PARSER_TAG);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* The form of OpenAPI specs to be served
|
|
1021
|
+
*/
|
|
1022
|
+
export interface OpenApiSpecForm {
|
|
1023
|
+
version?: string;
|
|
1024
|
+
format?: string;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
const OPENAPI_SPEC_MAPPING: {[key: string]: OpenApiSpecForm} = {
|
|
1028
|
+
'/openapi.json': {version: '3.0.0', format: 'json'},
|
|
1029
|
+
'/openapi.yaml': {version: '3.0.0', format: 'yaml'},
|
|
1030
|
+
};
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* Options to customize how OpenAPI specs are served
|
|
1034
|
+
*/
|
|
1035
|
+
export interface OpenApiSpecOptions {
|
|
1036
|
+
/**
|
|
1037
|
+
* Mapping of urls to spec forms, by default:
|
|
1038
|
+
* ```
|
|
1039
|
+
* {
|
|
1040
|
+
* '/openapi.json': {version: '3.0.0', format: 'json'},
|
|
1041
|
+
* '/openapi.yaml': {version: '3.0.0', format: 'yaml'},
|
|
1042
|
+
* }
|
|
1043
|
+
* ```
|
|
1044
|
+
*/
|
|
1045
|
+
endpointMapping?: {[key: string]: OpenApiSpecForm};
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* A flag to force `servers` to be set from the http request for the OpenAPI
|
|
1049
|
+
* spec
|
|
1050
|
+
*/
|
|
1051
|
+
setServersFromRequest?: boolean;
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* Configure servers for OpenAPI spec
|
|
1055
|
+
*/
|
|
1056
|
+
servers?: ServerObject[];
|
|
1057
|
+
/**
|
|
1058
|
+
* Set this flag to disable the endpoint for OpenAPI spec
|
|
1059
|
+
*/
|
|
1060
|
+
disabled?: true;
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Set this flag to `false` to disable OAS schema consolidation. If not set,
|
|
1064
|
+
* the value defaults to `true`.
|
|
1065
|
+
*/
|
|
1066
|
+
consolidate?: boolean;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
export interface ApiExplorerOptions {
|
|
1070
|
+
/**
|
|
1071
|
+
* URL for the hosted API explorer UI
|
|
1072
|
+
* default to https://loopback.io/api-explorer
|
|
1073
|
+
*/
|
|
1074
|
+
url?: string;
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* URL for the API explorer served over `http` protocol to deal with mixed
|
|
1078
|
+
* content security imposed by browsers as the spec is exposed over `http` by
|
|
1079
|
+
* default.
|
|
1080
|
+
* See https://github.com/strongloop/loopback-next/issues/1603
|
|
1081
|
+
*/
|
|
1082
|
+
httpUrl?: string;
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Set this flag to disable the built-in redirect to externally
|
|
1086
|
+
* hosted API Explorer UI.
|
|
1087
|
+
*/
|
|
1088
|
+
disabled?: true;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* RestServer options
|
|
1093
|
+
*/
|
|
1094
|
+
export type RestServerOptions = Partial<RestServerResolvedOptions>;
|
|
1095
|
+
|
|
1096
|
+
export interface RestServerResolvedOptions {
|
|
1097
|
+
port: number;
|
|
1098
|
+
path?: string;
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* Base path for API/static routes
|
|
1102
|
+
*/
|
|
1103
|
+
basePath?: string;
|
|
1104
|
+
cors: cors.CorsOptions;
|
|
1105
|
+
openApiSpec: OpenApiSpecOptions;
|
|
1106
|
+
apiExplorer: ApiExplorerOptions;
|
|
1107
|
+
requestBodyParser?: RequestBodyParserOptions;
|
|
1108
|
+
sequence?: Constructor<SequenceHandler>;
|
|
1109
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1110
|
+
expressSettings: {[name: string]: any};
|
|
1111
|
+
router: RestRouterOptions;
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* Set this flag to `false` to not listen on connections when the REST server
|
|
1115
|
+
* is started. It's useful to mount a LoopBack REST server as a route to the
|
|
1116
|
+
* facade Express application. If not set, the value is default to `true`.
|
|
1117
|
+
*/
|
|
1118
|
+
listenOnStart?: boolean;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Valid configuration for the RestServer constructor.
|
|
1123
|
+
*/
|
|
1124
|
+
export type RestServerConfig = RestServerOptions & HttpServerOptions;
|
|
1125
|
+
|
|
1126
|
+
export type RestServerResolvedConfig = RestServerResolvedOptions &
|
|
1127
|
+
HttpServerOptions;
|
|
1128
|
+
|
|
1129
|
+
const DEFAULT_CONFIG: RestServerResolvedConfig = {
|
|
1130
|
+
port: 3000,
|
|
1131
|
+
openApiSpec: {},
|
|
1132
|
+
apiExplorer: {},
|
|
1133
|
+
cors: {
|
|
1134
|
+
origin: '*',
|
|
1135
|
+
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
|
1136
|
+
preflightContinue: false,
|
|
1137
|
+
optionsSuccessStatus: 204,
|
|
1138
|
+
maxAge: 86400,
|
|
1139
|
+
credentials: true,
|
|
1140
|
+
},
|
|
1141
|
+
expressSettings: {},
|
|
1142
|
+
router: {},
|
|
1143
|
+
listenOnStart: true,
|
|
1144
|
+
};
|
|
1145
|
+
|
|
1146
|
+
function resolveRestServerConfig(
|
|
1147
|
+
config: RestServerConfig,
|
|
1148
|
+
): RestServerResolvedConfig {
|
|
1149
|
+
const result: RestServerResolvedConfig = Object.assign(
|
|
1150
|
+
cloneDeep(DEFAULT_CONFIG),
|
|
1151
|
+
config,
|
|
1152
|
+
);
|
|
1153
|
+
|
|
1154
|
+
// Can't check falsiness, 0 is a valid port.
|
|
1155
|
+
if (result.port == null) {
|
|
1156
|
+
result.port = 3000;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
if (result.host == null) {
|
|
1160
|
+
// Set it to '' so that the http server will listen on all interfaces
|
|
1161
|
+
result.host = undefined;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
if (!result.openApiSpec.endpointMapping) {
|
|
1165
|
+
// mapping may be mutated by addOpenApiSpecEndpoint, be sure that doesn't
|
|
1166
|
+
// pollute the default mapping configuration
|
|
1167
|
+
result.openApiSpec.endpointMapping = cloneDeep(OPENAPI_SPEC_MAPPING);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
result.apiExplorer = normalizeApiExplorerConfig(config.apiExplorer);
|
|
1171
|
+
|
|
1172
|
+
if (result.openApiSpec.disabled) {
|
|
1173
|
+
// Disable apiExplorer if the OpenAPI spec endpoint is disabled
|
|
1174
|
+
result.apiExplorer.disabled = true;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
return result;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function normalizeApiExplorerConfig(
|
|
1181
|
+
input: ApiExplorerOptions | undefined,
|
|
1182
|
+
): ApiExplorerOptions {
|
|
1183
|
+
const config = input ?? {};
|
|
1184
|
+
const url = config.url ?? 'https://explorer.loopback.io';
|
|
1185
|
+
|
|
1186
|
+
config.httpUrl =
|
|
1187
|
+
config.httpUrl ?? config.url ?? 'http://explorer.loopback.io';
|
|
1188
|
+
|
|
1189
|
+
config.url = url;
|
|
1190
|
+
|
|
1191
|
+
return config;
|
|
1192
|
+
}
|