acrylblends 0.3.0 → 0.4.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/dist/index.d.ts +6 -1
- package/dist/index.js +84 -5
- package/dist/index.js.map +1 -1
- package/docs/framework/LICENSE +23 -0
- package/docs/framework/README.md +63 -0
- package/docs/framework/UPSTREAM.md +3 -0
- package/docs/framework/api-summary.md +138 -0
- package/docs/framework/concepts.md +105 -0
- package/docs/framework/context.md +129 -0
- package/docs/framework/getting-started.md +146 -0
- package/docs/framework/helpers.md +118 -0
- package/docs/framework/middleware.md +154 -0
- package/docs/framework/migration-from-0-3.md +108 -0
- package/docs/framework/migration-from-dsh-webserver.md +103 -0
- package/docs/framework/node-server.md +75 -0
- package/docs/framework/plugin-lifecycle.md +111 -0
- package/docs/framework/request.md +106 -0
- package/docs/framework/routing.md +188 -0
- package/docs/framework/surfaces.md +71 -0
- package/docs/framework/upstream.json +9 -0
- package/docs/framework/validation.md +135 -0
- package/package.json +4 -2
- package/plugins/acryl-inspect/plugin.json +2 -1
- package/plugins/acryl-inspect/src/report.ts +21 -13
- package/plugins/acryl-inspect/src/service.ts +9 -7
- package/plugins/acryl-inspect/tests/report.spec.ts +13 -8
- package/plugins/acryl-inspect/tests/service.spec.ts +11 -10
- package/plugins/acryl-inspect/upstream.json +2 -2
- package/plugins/acryl-node-server/LICENSE +26 -0
- package/plugins/acryl-node-server/plugin.json +2 -1
- package/plugins/acryl-node-server/src/adapter/conninfo.ts +23 -0
- package/plugins/acryl-node-server/src/adapter/early-hints.ts +55 -0
- package/plugins/acryl-node-server/src/adapter/error.ts +11 -0
- package/plugins/acryl-node-server/src/adapter/headers.ts +249 -0
- package/plugins/acryl-node-server/src/adapter/index.ts +6 -0
- package/plugins/acryl-node-server/src/adapter/listener.ts +540 -0
- package/plugins/acryl-node-server/src/adapter/request.ts +797 -0
- package/plugins/acryl-node-server/src/adapter/response.ts +186 -0
- package/plugins/acryl-node-server/src/adapter/serve-static.ts +238 -0
- package/plugins/acryl-node-server/src/adapter/server.ts +36 -0
- package/plugins/acryl-node-server/src/adapter/types.ts +82 -0
- package/plugins/acryl-node-server/src/adapter/url.ts +42 -0
- package/plugins/acryl-node-server/src/adapter/utils/response/constants.ts +1 -0
- package/plugins/acryl-node-server/src/adapter/utils/response.ts +4 -0
- package/plugins/acryl-node-server/src/adapter/utils/stream.ts +88 -0
- package/plugins/acryl-node-server/src/adapter/utils.ts +96 -0
- package/plugins/acryl-node-server/src/adapter/websocket-types.ts +45 -0
- package/plugins/acryl-node-server/src/adapter/websocket.ts +375 -0
- package/plugins/acryl-node-server/src/index.ts +19 -6
- package/plugins/acryl-node-server/tests/adapter/app.ts +76 -0
- package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +5 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +1 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +1 -0
- package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +34 -0
- package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +248 -0
- package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +457 -0
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +23 -0
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +27 -0
- package/plugins/acryl-node-server/tests/adapter/headers.test.ts +252 -0
- package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +190 -0
- package/plugins/acryl-node-server/tests/adapter/listener.test.ts +727 -0
- package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +189 -0
- package/plugins/acryl-node-server/tests/adapter/request.test.ts +1230 -0
- package/plugins/acryl-node-server/tests/adapter/response.test.ts +285 -0
- package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +849 -0
- package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +790 -0
- package/plugins/acryl-node-server/tests/adapter/server.test.ts +1302 -0
- package/plugins/acryl-node-server/tests/adapter/setup.ts +16 -0
- package/plugins/acryl-node-server/tests/adapter/url.test.ts +76 -0
- package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +23 -0
- package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +75 -0
- package/plugins/acryl-node-server/tests/adapter/utils.test.ts +235 -0
- package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +260 -0
- package/plugins/acryl-node-server/tests/server.spec.ts +44 -8
- package/plugins/acryl-node-server/upstream.json +3 -3
- package/plugins/acryl-router/LICENSE +26 -0
- package/plugins/acryl-router/plugin.json +2 -1
- package/plugins/acryl-router/src/client.ts +2 -0
- package/plugins/acryl-router/src/connection.ts +42 -0
- package/plugins/acryl-router/src/core/app-base.ts +544 -0
- package/plugins/acryl-router/src/core/app.test.ts +3833 -0
- package/plugins/acryl-router/src/core/app.ts +34 -0
- package/plugins/acryl-router/src/core/client/client.test.ts +2313 -0
- package/plugins/acryl-router/src/core/client/client.ts +249 -0
- package/plugins/acryl-router/src/core/client/fetch-result-please.ts +95 -0
- package/plugins/acryl-router/src/core/client/index.ts +17 -0
- package/plugins/acryl-router/src/core/client/types.test.ts +149 -0
- package/plugins/acryl-router/src/core/client/types.ts +393 -0
- package/plugins/acryl-router/src/core/client/utils.test.ts +405 -0
- package/plugins/acryl-router/src/core/client/utils.ts +117 -0
- package/plugins/acryl-router/src/core/compose.test.ts +751 -0
- package/plugins/acryl-router/src/core/compose.ts +73 -0
- package/plugins/acryl-router/src/core/context.test.ts +629 -0
- package/plugins/acryl-router/src/core/context.ts +781 -0
- package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +344 -0
- package/plugins/acryl-router/src/core/helper/accepts/accepts.ts +96 -0
- package/plugins/acryl-router/src/core/helper/accepts/index.ts +6 -0
- package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +55 -0
- package/plugins/acryl-router/src/core/helper/adapter/index.ts +90 -0
- package/plugins/acryl-router/src/core/helper/conninfo/index.ts +6 -0
- package/plugins/acryl-router/src/core/helper/conninfo/types.ts +45 -0
- package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +510 -0
- package/plugins/acryl-router/src/core/helper/cookie/index.ts +146 -0
- package/plugins/acryl-router/src/core/helper/factory/index.test.ts +434 -0
- package/plugins/acryl-router/src/core/helper/factory/index.ts +375 -0
- package/plugins/acryl-router/src/core/helper/html/index.test.ts +78 -0
- package/plugins/acryl-router/src/core/helper/html/index.ts +57 -0
- package/plugins/acryl-router/src/core/helper/route/index.test.ts +259 -0
- package/plugins/acryl-router/src/core/helper/route/index.ts +144 -0
- package/plugins/acryl-router/src/core/helper/streaming/index.ts +9 -0
- package/plugins/acryl-router/src/core/helper/streaming/sse.ts +101 -0
- package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +128 -0
- package/plugins/acryl-router/src/core/helper/streaming/stream.ts +45 -0
- package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +34 -0
- package/plugins/acryl-router/src/core/helper/streaming/text.ts +15 -0
- package/plugins/acryl-router/src/core/helper/streaming/utils.ts +11 -0
- package/plugins/acryl-router/src/core/helper/testing/index.test.ts +42 -0
- package/plugins/acryl-router/src/core/helper/testing/index.ts +28 -0
- package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +124 -0
- package/plugins/acryl-router/src/core/helper/websocket/index.ts +140 -0
- package/plugins/acryl-router/src/core/http-exception.test.ts +45 -0
- package/plugins/acryl-router/src/core/http-exception.ts +77 -0
- package/plugins/acryl-router/src/core/index.ts +53 -0
- package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +434 -0
- package/plugins/acryl-router/src/core/middleware/basic-auth/index.ts +152 -0
- package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +1018 -0
- package/plugins/acryl-router/src/core/middleware/bearer-auth/index.ts +220 -0
- package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +273 -0
- package/plugins/acryl-router/src/core/middleware/body-limit/index.ts +110 -0
- package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +355 -0
- package/plugins/acryl-router/src/core/middleware/combine/index.ts +171 -0
- package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +535 -0
- package/plugins/acryl-router/src/core/middleware/compress/index.ts +142 -0
- package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +43 -0
- package/plugins/acryl-router/src/core/middleware/context-storage/index.ts +58 -0
- package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +532 -0
- package/plugins/acryl-router/src/core/middleware/cors/index.ts +163 -0
- package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +534 -0
- package/plugins/acryl-router/src/core/middleware/csrf/index.ts +150 -0
- package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +21 -0
- package/plugins/acryl-router/src/core/middleware/etag/digest.ts +95 -0
- package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +503 -0
- package/plugins/acryl-router/src/core/middleware/etag/index.ts +135 -0
- package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +217 -0
- package/plugins/acryl-router/src/core/middleware/ip-restriction/index.ts +278 -0
- package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +1135 -0
- package/plugins/acryl-router/src/core/middleware/jwk/index.ts +1 -0
- package/plugins/acryl-router/src/core/middleware/jwk/jwk.ts +195 -0
- package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +48 -0
- package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +802 -0
- package/plugins/acryl-router/src/core/middleware/jwt/index.ts +9 -0
- package/plugins/acryl-router/src/core/middleware/jwt/jwt.ts +189 -0
- package/plugins/acryl-router/src/core/middleware/language/index.test.ts +552 -0
- package/plugins/acryl-router/src/core/middleware/language/index.ts +12 -0
- package/plugins/acryl-router/src/core/middleware/language/language.ts +314 -0
- package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +204 -0
- package/plugins/acryl-router/src/core/middleware/logger/index.ts +95 -0
- package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +292 -0
- package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.ts +141 -0
- package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +221 -0
- package/plugins/acryl-router/src/core/middleware/method-override/index.ts +147 -0
- package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +37 -0
- package/plugins/acryl-router/src/core/middleware/powered-by/index.ts +35 -0
- package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +218 -0
- package/plugins/acryl-router/src/core/middleware/pretty-json/index.ts +67 -0
- package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +168 -0
- package/plugins/acryl-router/src/core/middleware/request-id/index.ts +7 -0
- package/plugins/acryl-router/src/core/middleware/request-id/request-id.ts +59 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +671 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/index.ts +8 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/permissions-policy.ts +101 -0
- package/plugins/acryl-router/src/core/middleware/secure-headers/secure-headers.ts +340 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +450 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/index.ts +135 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +50 -0
- package/plugins/acryl-router/src/core/middleware/serve-static/path.ts +25 -0
- package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +70 -0
- package/plugins/acryl-router/src/core/middleware/timeout/index.ts +58 -0
- package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +310 -0
- package/plugins/acryl-router/src/core/middleware/timing/index.ts +8 -0
- package/plugins/acryl-router/src/core/middleware/timing/timing.ts +256 -0
- package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +381 -0
- package/plugins/acryl-router/src/core/middleware/trailing-slash/index.ts +156 -0
- package/plugins/acryl-router/src/core/request/constants.ts +1 -0
- package/plugins/acryl-router/src/core/request.test.ts +751 -0
- package/plugins/acryl-router/src/core/request.ts +502 -0
- package/plugins/acryl-router/src/core/router/common.case.test.ts +1132 -0
- package/plugins/acryl-router/src/core/router/linear-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +80 -0
- package/plugins/acryl-router/src/core/router/linear-router/router.ts +152 -0
- package/plugins/acryl-router/src/core/router/pattern-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +46 -0
- package/plugins/acryl-router/src/core/router/pattern-router/router.ts +54 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/index.ts +7 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/matcher.ts +33 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/node.ts +170 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +154 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.ts +165 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +265 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.ts +174 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/trie.ts +84 -0
- package/plugins/acryl-router/src/core/router/smart-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +13 -0
- package/plugins/acryl-router/src/core/router/smart-router/router.ts +70 -0
- package/plugins/acryl-router/src/core/router/trie-router/index.ts +6 -0
- package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +896 -0
- package/plugins/acryl-router/src/core/router/trie-router/node.ts +221 -0
- package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +8 -0
- package/plugins/acryl-router/src/core/router/trie-router/router.ts +18 -0
- package/plugins/acryl-router/src/core/router/utils.ts +1 -0
- package/plugins/acryl-router/src/core/router.ts +103 -0
- package/plugins/acryl-router/src/core/types.test.ts +4134 -0
- package/plugins/acryl-router/src/core/types.ts +2782 -0
- package/plugins/acryl-router/src/core/utils/accept.test.ts +299 -0
- package/plugins/acryl-router/src/core/utils/accept.ts +268 -0
- package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +56 -0
- package/plugins/acryl-router/src/core/utils/basic-auth.ts +26 -0
- package/plugins/acryl-router/src/core/utils/body.test.ts +496 -0
- package/plugins/acryl-router/src/core/utils/body.ts +263 -0
- package/plugins/acryl-router/src/core/utils/buffer.test.ts +147 -0
- package/plugins/acryl-router/src/core/utils/buffer.ts +117 -0
- package/plugins/acryl-router/src/core/utils/color.test.ts +23 -0
- package/plugins/acryl-router/src/core/utils/color.ts +56 -0
- package/plugins/acryl-router/src/core/utils/compress.ts +10 -0
- package/plugins/acryl-router/src/core/utils/concurrent.test.ts +69 -0
- package/plugins/acryl-router/src/core/utils/concurrent.ts +55 -0
- package/plugins/acryl-router/src/core/utils/constants.ts +4 -0
- package/plugins/acryl-router/src/core/utils/cookie.test.ts +460 -0
- package/plugins/acryl-router/src/core/utils/cookie.ts +281 -0
- package/plugins/acryl-router/src/core/utils/crypto.test.ts +43 -0
- package/plugins/acryl-router/src/core/utils/crypto.ts +58 -0
- package/plugins/acryl-router/src/core/utils/encode.test.ts +61 -0
- package/plugins/acryl-router/src/core/utils/encode.ts +34 -0
- package/plugins/acryl-router/src/core/utils/filepath.test.ts +78 -0
- package/plugins/acryl-router/src/core/utils/filepath.ts +60 -0
- package/plugins/acryl-router/src/core/utils/handler.ts +15 -0
- package/plugins/acryl-router/src/core/utils/headers.ts +355 -0
- package/plugins/acryl-router/src/core/utils/html.test.ts +16 -0
- package/plugins/acryl-router/src/core/utils/html.ts +182 -0
- package/plugins/acryl-router/src/core/utils/http-status.ts +72 -0
- package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +149 -0
- package/plugins/acryl-router/src/core/utils/ipaddr.ts +374 -0
- package/plugins/acryl-router/src/core/utils/jwt/index.ts +7 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +86 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwa.ts +38 -0
- package/plugins/acryl-router/src/core/utils/jwt/jws.ts +235 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +1788 -0
- package/plugins/acryl-router/src/core/utils/jwt/jwt.ts +292 -0
- package/plugins/acryl-router/src/core/utils/jwt/types.ts +157 -0
- package/plugins/acryl-router/src/core/utils/jwt/utf8.ts +7 -0
- package/plugins/acryl-router/src/core/utils/mime.test.ts +64 -0
- package/plugins/acryl-router/src/core/utils/mime.ts +94 -0
- package/plugins/acryl-router/src/core/utils/stream.test.ts +212 -0
- package/plugins/acryl-router/src/core/utils/stream.ts +107 -0
- package/plugins/acryl-router/src/core/utils/types.test.ts +295 -0
- package/plugins/acryl-router/src/core/utils/types.ts +123 -0
- package/plugins/acryl-router/src/core/utils/url.test.ts +384 -0
- package/plugins/acryl-router/src/core/utils/url.ts +337 -0
- package/plugins/acryl-router/src/core/validator/index.ts +8 -0
- package/plugins/acryl-router/src/core/validator/utils.test.ts +82 -0
- package/plugins/acryl-router/src/core/validator/utils.ts +69 -0
- package/plugins/acryl-router/src/core/validator/validator.test.ts +1488 -0
- package/plugins/acryl-router/src/core/validator/validator.ts +185 -0
- package/plugins/acryl-router/src/helpers/index.ts +13 -3
- package/plugins/acryl-router/src/helpers/schema-validator.ts +35 -0
- package/plugins/acryl-router/src/index.ts +6 -6
- package/plugins/acryl-router/src/middleware/index.ts +25 -6
- package/plugins/acryl-router/src/middleware/loopback-only.ts +53 -14
- package/plugins/acryl-router/src/service.ts +262 -64
- package/plugins/acryl-router/tests/docs-examples.spec.ts +152 -0
- package/plugins/acryl-router/tests/live-install.spec.ts +2 -2
- package/plugins/acryl-router/tests/loader.spec.ts +3 -2
- package/plugins/acryl-router/tests/loopback-only.spec.ts +80 -0
- package/plugins/acryl-router/tests/service.spec.ts +281 -66
- package/plugins/acryl-router/tests/shadow-state.spec.ts +79 -0
- package/plugins/acryl-router/upstream.json +2 -2
- package/plugins/acryl-router-http/plugin.json +2 -1
- package/plugins/acryl-router-http/src/bridge.ts +3 -1
- package/plugins/acryl-router-http/tests/adapter.spec.ts +16 -12
- package/plugins/acryl-router-http/tests/surfaces.spec.ts +12 -10
- package/plugins/acryl-router-http/upstream.json +3 -3
- package/plugins/acryl-state/NOTICE +30 -0
- package/plugins/acryl-state/plugin.json +1 -1
- package/plugins/acryl-state/src/cell.ts +1 -1
- package/plugins/acryl-state/src/service.ts +33 -15
- package/plugins/acryl-state/upstream.json +6 -4
- package/plugins/builder/script.ts +4 -7
- package/plugins/extensions/index.ts +6 -5
- package/plugins/extensions/resolve.ts +28 -3
- package/plugins/openai/prompt.ts +9 -7
- package/plugins/tsconfig.json +5 -4
- package/shared/AGENTS.core.md +54 -0
- package/templates/agent/AGENTS.md +6 -0
- package/templates/agent/README.md +2 -2
- package/templates/agent/blend.yaml +1 -1
- package/templates/agent/src/routes.ts +37 -27
- package/templates/agent/test/model.test.ts +2 -1
- package/templates/agent/tsconfig.json +8 -5
- package/templates/blank/AGENTS.md +5 -0
- package/templates/blank/README.md +1 -1
- package/templates/blank/src/routes.ts +6 -4
- package/templates/blank/tsconfig.json +2 -4
- package/templates/todo/AGENTS.md +5 -0
- package/templates/todo/src/page.ts +1 -1
- package/templates/todo/src/todo.ts +58 -29
- package/templates/todo/tsconfig.json +2 -4
- package/templates/todo/upstream.json +2 -2
- package/plugins/acryl-router/src/compose.ts +0 -21
- package/plugins/acryl-router/src/context.ts +0 -59
- package/plugins/acryl-router/src/helpers/read-body.ts +0 -59
- package/plugins/acryl-router/src/helpers/sse.ts +0 -83
- package/plugins/acryl-router/src/helpers/validate.ts +0 -44
- package/plugins/acryl-router/src/http-error.ts +0 -21
- package/plugins/acryl-router/src/middleware/body-limit.ts +0 -25
- package/plugins/acryl-router/src/middleware/cors.ts +0 -46
- package/plugins/acryl-router/src/middleware/logger.ts +0 -21
- package/plugins/acryl-router/src/middleware/request-id.ts +0 -24
- package/plugins/acryl-router/src/middleware/secure-headers.ts +0 -23
- package/plugins/acryl-router/src/pattern.ts +0 -89
- package/plugins/acryl-router/src/registrar.ts +0 -90
- package/plugins/acryl-router/src/table.ts +0 -156
- package/plugins/acryl-router/src/types.ts +0 -73
- package/plugins/acryl-router/src/util.ts +0 -23
- package/plugins/acryl-router/tests/api.spec.ts +0 -223
- package/plugins/acryl-router/tests/conformance.ts +0 -170
- package/plugins/acryl-router/tests/helpers.spec.ts +0 -172
- package/plugins/acryl-router/tests/middleware.spec.ts +0 -183
- package/plugins/acryl-router/tests/table.spec.ts +0 -47
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import { ServerResponse } from 'node:http'
|
|
2
|
+
import type { IncomingMessage, OutgoingHttpHeaders } from 'node:http'
|
|
3
|
+
import { Http2ServerRequest, constants as h2constants } from 'node:http2'
|
|
4
|
+
import type { Http2ServerResponse } from 'node:http2'
|
|
5
|
+
import type { Writable } from 'node:stream'
|
|
6
|
+
import type { IncomingMessageWithWrapBodyStream } from './request.ts'
|
|
7
|
+
import {
|
|
8
|
+
abortRequest,
|
|
9
|
+
newRequest,
|
|
10
|
+
recordBodyBufferedBeforeDisconnect,
|
|
11
|
+
Request as LightweightRequest,
|
|
12
|
+
wrapBodyStream,
|
|
13
|
+
toRequestError,
|
|
14
|
+
} from './request.ts'
|
|
15
|
+
import { defaultContentType, cacheKey, Response as LightweightResponse } from './response.ts'
|
|
16
|
+
import type { InternalCache } from './response.ts'
|
|
17
|
+
import type { CustomErrorHandler, FetchCallback, HttpBindings } from './types.ts'
|
|
18
|
+
import {
|
|
19
|
+
readWithoutBlocking,
|
|
20
|
+
writeFromReadableStream,
|
|
21
|
+
writeFromReadableStreamDefaultReader,
|
|
22
|
+
buildOutgoingHttpHeaders,
|
|
23
|
+
} from './utils.ts'
|
|
24
|
+
import { X_ALREADY_SENT } from './utils/response/constants.ts'
|
|
25
|
+
|
|
26
|
+
const outgoingEnded = Symbol('outgoingEnded')
|
|
27
|
+
const incomingDraining = Symbol('incomingDraining')
|
|
28
|
+
type OutgoingHasOutgoingEnded = Http2ServerResponse & {
|
|
29
|
+
[outgoingEnded]?: () => void
|
|
30
|
+
}
|
|
31
|
+
type IncomingHasDrainState = (IncomingMessage | Http2ServerRequest) & {
|
|
32
|
+
[incomingDraining]?: boolean
|
|
33
|
+
}
|
|
34
|
+
type Http1ResponseWithContentLength = ServerResponse & {
|
|
35
|
+
_contentLength: number | null
|
|
36
|
+
_hasBody: boolean
|
|
37
|
+
_removedContLen: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const DRAIN_TIMEOUT_MS = 500
|
|
41
|
+
const MAX_DRAIN_BYTES = 64 * 1024 * 1024
|
|
42
|
+
|
|
43
|
+
const drainIncoming = (incoming: IncomingMessage | Http2ServerRequest): void => {
|
|
44
|
+
const incomingWithDrainState = incoming as IncomingHasDrainState
|
|
45
|
+
if (incoming.destroyed || incomingWithDrainState[incomingDraining]) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
incomingWithDrainState[incomingDraining] = true
|
|
49
|
+
|
|
50
|
+
// HTTP/2: streams are multiplexed, so we can close immediately
|
|
51
|
+
// without risking TCP RST racing the response.
|
|
52
|
+
if (incoming instanceof Http2ServerRequest) {
|
|
53
|
+
try {
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
+
;(incoming as any).stream?.close?.(h2constants.NGHTTP2_NO_ERROR)
|
|
56
|
+
} catch {
|
|
57
|
+
// stream may already be closed
|
|
58
|
+
}
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let bytesRead = 0
|
|
63
|
+
const cleanup = () => {
|
|
64
|
+
clearTimeout(timer)
|
|
65
|
+
incoming.off('data', onData)
|
|
66
|
+
incoming.off('end', cleanup)
|
|
67
|
+
incoming.off('error', cleanup)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const forceClose = () => {
|
|
71
|
+
cleanup()
|
|
72
|
+
const socket = incoming.socket
|
|
73
|
+
if (socket && !socket.destroyed) {
|
|
74
|
+
if (typeof socket.destroySoon === 'function') {
|
|
75
|
+
socket.destroySoon()
|
|
76
|
+
} else if (typeof socket.destroy === 'function') {
|
|
77
|
+
socket.destroy()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const timer = setTimeout(forceClose, DRAIN_TIMEOUT_MS)
|
|
83
|
+
timer.unref?.()
|
|
84
|
+
|
|
85
|
+
const onData = (chunk: Buffer) => {
|
|
86
|
+
bytesRead += chunk.length
|
|
87
|
+
if (bytesRead > MAX_DRAIN_BYTES) {
|
|
88
|
+
forceClose()
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
incoming.on('data', onData)
|
|
93
|
+
incoming.on('end', cleanup)
|
|
94
|
+
incoming.on('error', cleanup)
|
|
95
|
+
|
|
96
|
+
incoming.resume()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const makeCloseHandler =
|
|
100
|
+
(
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
|
+
req: any,
|
|
103
|
+
incoming: IncomingMessage | Http2ServerRequest,
|
|
104
|
+
outgoing: ServerResponse | Http2ServerResponse,
|
|
105
|
+
needsBodyCleanup: boolean
|
|
106
|
+
): (() => void) =>
|
|
107
|
+
() => {
|
|
108
|
+
if (incoming.errored) {
|
|
109
|
+
recordBodyBufferedBeforeDisconnect(incoming)
|
|
110
|
+
req[abortRequest](incoming.errored.toString())
|
|
111
|
+
} else if (
|
|
112
|
+
!outgoing.writableFinished ||
|
|
113
|
+
// An HTTP/2 response reports writableFinished once its stream is destroyed, including
|
|
114
|
+
// when the client resets the stream mid-response. `aborted` is only set in that case.
|
|
115
|
+
(incoming instanceof Http2ServerRequest && incoming.aborted)
|
|
116
|
+
) {
|
|
117
|
+
recordBodyBufferedBeforeDisconnect(incoming)
|
|
118
|
+
req[abortRequest]('Client connection prematurely closed.')
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (needsBodyCleanup && !incoming.readableEnded) {
|
|
122
|
+
setTimeout(() => {
|
|
123
|
+
if (!incoming.readableEnded) {
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
drainIncoming(incoming)
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const isImmediateCacheableResponse = (res: Response): boolean => {
|
|
133
|
+
if (!(cacheKey in res)) {
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
138
|
+
const body = ((res as any)[cacheKey] as InternalCache)[1]
|
|
139
|
+
return body === null || typeof body === 'string' || body instanceof Uint8Array
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const handleRequestError = (): Response =>
|
|
143
|
+
new Response(null, {
|
|
144
|
+
status: 400,
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const handleFetchError = (e: unknown): Response =>
|
|
148
|
+
new Response(null, {
|
|
149
|
+
status:
|
|
150
|
+
e instanceof Error && (e.name === 'TimeoutError' || e.constructor.name === 'TimeoutError')
|
|
151
|
+
? 504 // timeout error emits 504 timeout
|
|
152
|
+
: 500,
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// `writeHead` takes the same arguments on both response types; their overloads differ only in what they return, so the union is not
|
|
156
|
+
// callable under the current Node types and each call names one of them.
|
|
157
|
+
const handleResponseError = (e: unknown, outgoing: ServerResponse | Http2ServerResponse) => {
|
|
158
|
+
const err = (e instanceof Error ? e : new Error('unknown error', { cause: e })) as Error & {
|
|
159
|
+
code: string
|
|
160
|
+
}
|
|
161
|
+
if (err.code === 'ERR_STREAM_PREMATURE_CLOSE') {
|
|
162
|
+
console.info('The user aborted a request.')
|
|
163
|
+
} else {
|
|
164
|
+
console.error(e)
|
|
165
|
+
if (!outgoing.headersSent) {
|
|
166
|
+
if (outgoing instanceof ServerResponse) {
|
|
167
|
+
// writeHead() may have failed after the fast path set the original body's length.
|
|
168
|
+
;(outgoing as Http1ResponseWithContentLength)._contentLength = null
|
|
169
|
+
}
|
|
170
|
+
(outgoing as ServerResponse).writeHead(500, { 'Content-Type': 'text/plain' })
|
|
171
|
+
}
|
|
172
|
+
outgoing.end(`Error: ${err.message}`)
|
|
173
|
+
outgoing.destroy(err)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const flushHeaders = (outgoing: ServerResponse | Http2ServerResponse) => {
|
|
178
|
+
// If outgoing is ServerResponse (HTTP/1.1), it requires this to flush headers.
|
|
179
|
+
// However, Http2ServerResponse is sent without this.
|
|
180
|
+
if ('flushHeaders' in outgoing && outgoing.writable) {
|
|
181
|
+
outgoing.flushHeaders()
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Node's HTTP/1 serializer can add Content-Length without adding a header field.
|
|
186
|
+
// These private fields are covered by compatibility tests in CI. Fall back
|
|
187
|
+
// when automatic insertion would omit the header or preserve a framing header.
|
|
188
|
+
const trySetContentLength = (
|
|
189
|
+
outgoing: ServerResponse | Http2ServerResponse,
|
|
190
|
+
status: number,
|
|
191
|
+
length: number
|
|
192
|
+
): boolean => {
|
|
193
|
+
const http1 = outgoing as Http1ResponseWithContentLength
|
|
194
|
+
if (
|
|
195
|
+
http1._contentLength === null &&
|
|
196
|
+
http1._hasBody &&
|
|
197
|
+
http1.useChunkedEncodingByDefault &&
|
|
198
|
+
!http1._removedContLen &&
|
|
199
|
+
status >= 200 &&
|
|
200
|
+
status !== 204 &&
|
|
201
|
+
status !== 304 &&
|
|
202
|
+
!outgoing.hasHeader('content-length') &&
|
|
203
|
+
!outgoing.hasHeader('transfer-encoding') &&
|
|
204
|
+
!outgoing.hasHeader('trailer')
|
|
205
|
+
) {
|
|
206
|
+
http1._contentLength = length
|
|
207
|
+
return true
|
|
208
|
+
}
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const writeDefaultHeaders = (
|
|
213
|
+
outgoing: ServerResponse | Http2ServerResponse,
|
|
214
|
+
status: number,
|
|
215
|
+
length: number
|
|
216
|
+
): void => {
|
|
217
|
+
if (trySetContentLength(outgoing, status, length)) {
|
|
218
|
+
(outgoing as ServerResponse).writeHead(status, { 'Content-Type': defaultContentType })
|
|
219
|
+
} else {
|
|
220
|
+
(outgoing as ServerResponse).writeHead(status, {
|
|
221
|
+
'Content-Type': defaultContentType,
|
|
222
|
+
'Content-Length': length,
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const responseViaCache = async (
|
|
228
|
+
res: Response,
|
|
229
|
+
outgoing: ServerResponse | Http2ServerResponse
|
|
230
|
+
): Promise<undefined | void> => {
|
|
231
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
232
|
+
let [status, body, header] = (res as any)[cacheKey] as InternalCache
|
|
233
|
+
|
|
234
|
+
// Fast path: no custom headers. Let Node add the length when possible.
|
|
235
|
+
if (!header) {
|
|
236
|
+
if (body === null) {
|
|
237
|
+
(outgoing as ServerResponse).writeHead(status)
|
|
238
|
+
outgoing.end()
|
|
239
|
+
} else if (typeof body === 'string') {
|
|
240
|
+
writeDefaultHeaders(outgoing, status, Buffer.byteLength(body))
|
|
241
|
+
outgoing.end(body)
|
|
242
|
+
} else if (body instanceof Uint8Array) {
|
|
243
|
+
writeDefaultHeaders(outgoing, status, body.byteLength)
|
|
244
|
+
outgoing.end(body)
|
|
245
|
+
} else if (body instanceof Blob) {
|
|
246
|
+
writeDefaultHeaders(outgoing, status, body.size)
|
|
247
|
+
outgoing.end(new Uint8Array(await body.arrayBuffer()))
|
|
248
|
+
} else {
|
|
249
|
+
(outgoing as ServerResponse).writeHead(status, { 'Content-Type': defaultContentType })
|
|
250
|
+
flushHeaders(outgoing)
|
|
251
|
+
await writeFromReadableStream(body, outgoing)?.catch(
|
|
252
|
+
(e) => handleResponseError(e, outgoing) as undefined
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
;(outgoing as OutgoingHasOutgoingEnded)[outgoingEnded]?.()
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
let hasContentLength = false
|
|
260
|
+
let plainHeaders = false
|
|
261
|
+
let canAutoLength = true
|
|
262
|
+
if (header instanceof Headers) {
|
|
263
|
+
hasContentLength = header.has('content-length')
|
|
264
|
+
header = buildOutgoingHttpHeaders(header, body === null ? undefined : defaultContentType)
|
|
265
|
+
} else if (Array.isArray(header)) {
|
|
266
|
+
const headerObj = new Headers(header)
|
|
267
|
+
hasContentLength = headerObj.has('content-length')
|
|
268
|
+
header = buildOutgoingHttpHeaders(headerObj, body === null ? undefined : defaultContentType)
|
|
269
|
+
} else {
|
|
270
|
+
plainHeaders = true
|
|
271
|
+
for (const key in header) {
|
|
272
|
+
if (key.length === 14 && key.toLowerCase() === 'content-length') {
|
|
273
|
+
hasContentLength = true
|
|
274
|
+
break
|
|
275
|
+
}
|
|
276
|
+
if (
|
|
277
|
+
(key.length === 17 && key.toLowerCase() === 'transfer-encoding') ||
|
|
278
|
+
(key.length === 7 && key.toLowerCase() === 'trailer')
|
|
279
|
+
) {
|
|
280
|
+
canAutoLength = false
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// in `responseViaCache`, if body is not stream, Transfer-Encoding is considered not chunked
|
|
286
|
+
if (!hasContentLength) {
|
|
287
|
+
let length: number | undefined
|
|
288
|
+
if (typeof body === 'string') {
|
|
289
|
+
length = Buffer.byteLength(body)
|
|
290
|
+
} else if (body instanceof Uint8Array) {
|
|
291
|
+
length = body.byteLength
|
|
292
|
+
} else if (body instanceof Blob) {
|
|
293
|
+
length = body.size
|
|
294
|
+
}
|
|
295
|
+
if (
|
|
296
|
+
length !== undefined &&
|
|
297
|
+
(!plainHeaders || !canAutoLength || !trySetContentLength(outgoing, status, length))
|
|
298
|
+
) {
|
|
299
|
+
if (plainHeaders) {
|
|
300
|
+
header = { ...header }
|
|
301
|
+
}
|
|
302
|
+
header['Content-Length'] = length
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
(outgoing as ServerResponse).writeHead(status, header)
|
|
307
|
+
if (body == null) {
|
|
308
|
+
outgoing.end()
|
|
309
|
+
} else if (typeof body === 'string' || body instanceof Uint8Array) {
|
|
310
|
+
outgoing.end(body)
|
|
311
|
+
} else if (body instanceof Blob) {
|
|
312
|
+
outgoing.end(new Uint8Array(await body.arrayBuffer()))
|
|
313
|
+
} else {
|
|
314
|
+
flushHeaders(outgoing)
|
|
315
|
+
await writeFromReadableStream(body, outgoing)?.catch(
|
|
316
|
+
(e) => handleResponseError(e, outgoing) as undefined
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
;(outgoing as OutgoingHasOutgoingEnded)[outgoingEnded]?.()
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const isPromise = (res: Response | Promise<Response>): res is Promise<Response> =>
|
|
324
|
+
typeof (res as Promise<Response>).then === 'function'
|
|
325
|
+
|
|
326
|
+
const responseViaResponseObject = async (
|
|
327
|
+
res: Response | Promise<Response>,
|
|
328
|
+
outgoing: ServerResponse | Http2ServerResponse,
|
|
329
|
+
options: { errorHandler?: CustomErrorHandler } = {}
|
|
330
|
+
) => {
|
|
331
|
+
if (isPromise(res)) {
|
|
332
|
+
if (options.errorHandler) {
|
|
333
|
+
try {
|
|
334
|
+
res = await res
|
|
335
|
+
} catch (err) {
|
|
336
|
+
const errRes = await options.errorHandler(err)
|
|
337
|
+
if (!errRes) {
|
|
338
|
+
return
|
|
339
|
+
}
|
|
340
|
+
res = errRes
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
res = await res.catch(handleFetchError)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (cacheKey in res) {
|
|
348
|
+
return responseViaCache(res as Response, outgoing)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const resHeaderRecord: OutgoingHttpHeaders = buildOutgoingHttpHeaders(
|
|
352
|
+
res.headers,
|
|
353
|
+
res.body === null ? undefined : defaultContentType
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
if (res.body) {
|
|
357
|
+
const reader = res.body.getReader()
|
|
358
|
+
|
|
359
|
+
const values: Uint8Array[] = []
|
|
360
|
+
let done = false
|
|
361
|
+
let currentReadPromise: Promise<ReadableStreamReadResult<Uint8Array>> | undefined = undefined
|
|
362
|
+
|
|
363
|
+
if (resHeaderRecord['transfer-encoding'] !== 'chunked') {
|
|
364
|
+
// In the case of synchronous responses, usually a maximum of two (or three in special cases) readings is done
|
|
365
|
+
let maxReadCount = 2
|
|
366
|
+
for (let i = 0; i < maxReadCount; i++) {
|
|
367
|
+
currentReadPromise ||= reader.read()
|
|
368
|
+
const chunk = await readWithoutBlocking(currentReadPromise).catch((e) => {
|
|
369
|
+
console.error(e)
|
|
370
|
+
done = true
|
|
371
|
+
})
|
|
372
|
+
if (!chunk) {
|
|
373
|
+
if (i === 1) {
|
|
374
|
+
// XXX: In Node.js v24, some response bodies are not read all the way through until the next task queue,
|
|
375
|
+
// so wait a moment and retry. (e.g. new Blob([new Uint8Array(contents)]) )
|
|
376
|
+
await new Promise((resolve) => setTimeout(resolve))
|
|
377
|
+
maxReadCount = 3
|
|
378
|
+
continue
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Error occurred or currentReadPromise is not yet resolved.
|
|
382
|
+
// If an error occurs, immediately break the loop.
|
|
383
|
+
// If currentReadPromise is not yet resolved, pass it to writeFromReadableStreamDefaultReader.
|
|
384
|
+
break
|
|
385
|
+
}
|
|
386
|
+
currentReadPromise = undefined
|
|
387
|
+
|
|
388
|
+
if (chunk.value) {
|
|
389
|
+
values.push(chunk.value)
|
|
390
|
+
}
|
|
391
|
+
if (chunk.done) {
|
|
392
|
+
done = true
|
|
393
|
+
break
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (done && !('content-length' in resHeaderRecord)) {
|
|
398
|
+
resHeaderRecord['content-length'] = values.reduce((acc, value) => acc + value.length, 0)
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
(outgoing as ServerResponse).writeHead(res.status, resHeaderRecord)
|
|
403
|
+
values.forEach((value) => {
|
|
404
|
+
;(outgoing as Writable).write(value)
|
|
405
|
+
})
|
|
406
|
+
if (done) {
|
|
407
|
+
outgoing.end()
|
|
408
|
+
} else {
|
|
409
|
+
if (values.length === 0) {
|
|
410
|
+
flushHeaders(outgoing)
|
|
411
|
+
}
|
|
412
|
+
await writeFromReadableStreamDefaultReader(reader, outgoing, currentReadPromise)
|
|
413
|
+
}
|
|
414
|
+
} else if (resHeaderRecord[X_ALREADY_SENT]) {
|
|
415
|
+
// do nothing, the response has already been sent
|
|
416
|
+
} else {
|
|
417
|
+
(outgoing as ServerResponse).writeHead(res.status, resHeaderRecord)
|
|
418
|
+
outgoing.end()
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
;(outgoing as OutgoingHasOutgoingEnded)[outgoingEnded]?.()
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export const getRequestListener = (
|
|
425
|
+
fetchCallback: FetchCallback,
|
|
426
|
+
options: {
|
|
427
|
+
hostname?: string
|
|
428
|
+
errorHandler?: CustomErrorHandler
|
|
429
|
+
overrideGlobalObjects?: boolean
|
|
430
|
+
autoCleanupIncoming?: boolean
|
|
431
|
+
} = {}
|
|
432
|
+
) => {
|
|
433
|
+
const autoCleanupIncoming = options.autoCleanupIncoming ?? true
|
|
434
|
+
if (options.overrideGlobalObjects !== false && global.Request !== LightweightRequest) {
|
|
435
|
+
Object.defineProperty(global, 'Request', {
|
|
436
|
+
value: LightweightRequest,
|
|
437
|
+
})
|
|
438
|
+
Object.defineProperty(global, 'Response', {
|
|
439
|
+
value: LightweightResponse,
|
|
440
|
+
})
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return async (
|
|
444
|
+
incoming: IncomingMessage | Http2ServerRequest,
|
|
445
|
+
outgoing: ServerResponse | Http2ServerResponse
|
|
446
|
+
) => {
|
|
447
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
448
|
+
let res, req: any
|
|
449
|
+
let needsBodyCleanup = false
|
|
450
|
+
let closeHandlerAttached = false
|
|
451
|
+
|
|
452
|
+
const ensureCloseHandler = () => {
|
|
453
|
+
if (!req || closeHandlerAttached) {
|
|
454
|
+
return
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
closeHandlerAttached = true
|
|
458
|
+
outgoing.on('close', makeCloseHandler(req, incoming, outgoing, needsBodyCleanup))
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
try {
|
|
462
|
+
// `fetchCallback()` requests a Request object, but global.Request is expensive to generate,
|
|
463
|
+
// so generate a pseudo Request object with only the minimum required information.
|
|
464
|
+
req = newRequest(incoming, options.hostname)
|
|
465
|
+
|
|
466
|
+
// For non-GET/HEAD requests, mark for body stream wrapping and H2 cleanup.
|
|
467
|
+
needsBodyCleanup =
|
|
468
|
+
autoCleanupIncoming && !(incoming.method === 'GET' || incoming.method === 'HEAD')
|
|
469
|
+
if (needsBodyCleanup) {
|
|
470
|
+
;(incoming as IncomingMessageWithWrapBodyStream)[wrapBodyStream] = true
|
|
471
|
+
|
|
472
|
+
if (incoming instanceof Http2ServerRequest) {
|
|
473
|
+
// a Http2ServerResponse instance requires additional processing on exit
|
|
474
|
+
// since outgoing.on('close') is not called even after outgoing.end() is called
|
|
475
|
+
// when the state is incomplete
|
|
476
|
+
;(outgoing as OutgoingHasOutgoingEnded)[outgoingEnded] = () => {
|
|
477
|
+
// incoming is not consumed to the end
|
|
478
|
+
if (!incoming.readableEnded) {
|
|
479
|
+
setTimeout(() => {
|
|
480
|
+
// in the case of a simple POST request, the cleanup process may be done automatically
|
|
481
|
+
// and readableEnded is true at this point. At that point, nothing is done.
|
|
482
|
+
if (!incoming.readableEnded) {
|
|
483
|
+
setTimeout(() => {
|
|
484
|
+
incoming.destroy()
|
|
485
|
+
// a Http2ServerResponse instance will not terminate without also calling outgoing.destroy()
|
|
486
|
+
outgoing.destroy()
|
|
487
|
+
})
|
|
488
|
+
}
|
|
489
|
+
})
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
res = fetchCallback(req, { incoming, outgoing } as HttpBindings) as
|
|
496
|
+
| Response
|
|
497
|
+
| Promise<Response>
|
|
498
|
+
if (!isPromise(res) && isImmediateCacheableResponse(res)) {
|
|
499
|
+
// Synchronous cacheable response — no close listener needed.
|
|
500
|
+
// No I/O events can fire between fetchCallback returning and responseViaCache
|
|
501
|
+
// completing, so abort detection is not needed here.
|
|
502
|
+
if (needsBodyCleanup && !incoming.readableEnded) {
|
|
503
|
+
// Handler returned without consuming the body; drain after the
|
|
504
|
+
// response is flushed so the socket is freed gracefully (avoids
|
|
505
|
+
// TCP RST racing the response for HTTP/1, and RST_STREAM for HTTP/2).
|
|
506
|
+
outgoing.once('finish', () => {
|
|
507
|
+
if (!incoming.readableEnded) {
|
|
508
|
+
drainIncoming(incoming)
|
|
509
|
+
}
|
|
510
|
+
})
|
|
511
|
+
}
|
|
512
|
+
return responseViaCache(res, outgoing)
|
|
513
|
+
}
|
|
514
|
+
ensureCloseHandler()
|
|
515
|
+
} catch (e: unknown) {
|
|
516
|
+
if (!res) {
|
|
517
|
+
if (options.errorHandler) {
|
|
518
|
+
// Async error handler — register close listener so client disconnect aborts the signal.
|
|
519
|
+
ensureCloseHandler()
|
|
520
|
+
res = await options.errorHandler(req ? e : toRequestError(e))
|
|
521
|
+
if (!res) {
|
|
522
|
+
return
|
|
523
|
+
}
|
|
524
|
+
} else if (!req) {
|
|
525
|
+
res = handleRequestError()
|
|
526
|
+
} else {
|
|
527
|
+
res = handleFetchError(e)
|
|
528
|
+
}
|
|
529
|
+
} else {
|
|
530
|
+
return handleResponseError(e, outgoing)
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
try {
|
|
535
|
+
return await responseViaResponseObject(res, outgoing, options)
|
|
536
|
+
} catch (e) {
|
|
537
|
+
return handleResponseError(e, outgoing)
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|