@openclaw/slack 2026.9.6 → 2026.9.7
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/assets/icon.png +0 -0
- package/dist/.setup/{account-inspect-DP1Bcwj4.mjs → account-inspect-DWBXpER6.mjs} +1 -1
- package/dist/.setup/{accounts-BBHvg0pY.mjs → accounts-D_N9IqNA.mjs} +23 -1
- package/dist/.setup/{action-runtime-B01d85zt.mjs → action-runtime-DfWvuiZd.mjs} +26 -41
- package/dist/.setup/action-runtime.runtime-Ck4bAWOX.mjs +2 -0
- package/dist/.setup/{actions-DKCtBgCI.mjs → actions-Df0CzcDl.mjs} +88 -175
- package/dist/.setup/{approval-handler.runtime-DMszKn_7.mjs → approval-handler.runtime-Ed6Sw5VK.mjs} +37 -47
- package/dist/.setup/{approval-message-updates-Tb_qRokK.mjs → approval-message-updates-BadvtA7R.mjs} +1 -2
- package/dist/.setup/{channel-CErYZ5zw.mjs → channel-C7bgPSTE.mjs} +181 -273
- package/dist/.setup/channel-meta-C443t-lI.mjs +27 -0
- package/dist/.setup/{channel-type-Dxk8VJUb.mjs → channel-type-ClOttrZ5.mjs} +13 -21
- package/dist/.setup/{channel.setup-BVoUtdPn.mjs → channel.setup-Dh2jzAjN.mjs} +35 -42
- package/dist/.setup/{config-schema-CyL279e7.mjs → config-schema-BZCxmHKG.mjs} +24 -21
- package/dist/.setup/{directory-config-CQTSH45W.mjs → directory-config-BOUWH3hR.mjs} +7 -10
- package/dist/.setup/{directory-live-BCcj89QV.mjs → directory-live-B2O4B_Ho.mjs} +9 -21
- package/dist/.setup/doctor-contract-CRN1Z_c7.mjs +342 -0
- package/dist/.setup/{message-tool-api-C0PTFaNV.mjs → message-tool-api-Cu4lwlGy.mjs} +34 -48
- package/dist/.setup/{provider-C-jPaGt2.mjs → monitor-BPVfkumU.mjs} +2181 -2693
- package/dist/.setup/{outbound-adapter-A7Wdn8ZT.mjs → outbound-adapter-Bv48XUjq.mjs} +10 -21
- package/dist/.setup/{pipeline.runtime-BlPOexmJ.mjs → pipeline.runtime-B1H63Frf.mjs} +311 -457
- package/dist/.setup/{plugin-routes-C3X0eVaK.mjs → plugin-routes-B8tooDLj.mjs} +1 -1
- package/dist/.setup/{policy-BApW53pI.mjs → policy-D-jfM174.mjs} +5 -5
- package/dist/.setup/{probe-DiYIMPJo.mjs → probe-B2Y9qc_z.mjs} +101 -39
- package/dist/.setup/{registry-bbjH7IHX.mjs → registry-CIbMfuf8.mjs} +6 -2
- package/dist/.setup/{relay-source-DfOnjJTE.mjs → relay-source-BpKdjsll.mjs} +10 -12
- package/dist/.setup/{replies-BBAJxUOQ.mjs → replies-BAnUtr2f.mjs} +16 -76
- package/dist/.setup/{reply-blocks-Bm8kf6mK.mjs → reply-blocks-SE1kTE3I.mjs} +27 -154
- package/dist/.setup/{resolve-channels-BXbMBbJD.mjs → resolve-channels-XXBn8UU_.mjs} +4 -5
- package/dist/.setup/{resolve-users-65WqvIqD.mjs → resolve-users-DW0ngD03.mjs} +20 -22
- package/dist/.setup/{scopes-h3YB-mMs.mjs → scopes-CasuDvaa.mjs} +6 -17
- package/dist/.setup/{secret-contract-DxmvbxBF.mjs → secret-contract-BaZcu6P_.mjs} +15 -20
- package/dist/.setup/{secret-contract-api-D9UfYujm.mjs → secret-contract-api-Dp6290Vg.mjs} +1 -1
- package/dist/.setup/{security-audit-BypHqcq5.mjs → security-audit-BqDc-6VL.mjs} +1 -1
- package/dist/.setup/{send-Cp6omkOg.mjs → send-BOVLQi3n.mjs} +84 -171
- package/dist/.setup/{session-status-Cf7AvWOt.mjs → session-status-DscajIsd.mjs} +315 -238
- package/dist/.setup/{setup-plugin-api-dLPkL06L.mjs → setup-plugin-api-DTtbTKNk.mjs} +1 -1
- package/dist/.setup/{setup-surface-D3DLcCxu.mjs → setup-surface-Dn7QgWRE.mjs} +24 -56
- package/dist/.setup/{shared-CJ3arAFL.mjs → shared-qau-uDGl.mjs} +30 -56
- package/dist/.setup/{slash-dispatch.runtime-HFy_XvyO.mjs → slash-dispatch.runtime--AEzLVBF.mjs} +1 -1
- package/dist/.setup/{streaming-compat-B-e0mqM0.mjs → streaming-compat-C5Wut626.mjs} +31 -8
- package/dist/.setup/{target-parsing-DEsPjngB.mjs → target-parsing-FFE26kOV.mjs} +1 -7
- package/dist/.setup/{targets-Cku2KCvg.mjs → targets-C6LZQL1Q.mjs} +1 -1
- package/dist/.setup/{thread-ts-CukseRVL.mjs → thread-ts-Cvt7WD6B.mjs} +0 -1
- package/dist/account-inspect-api.js +1 -1
- package/dist/api.js +18 -17
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/directory-contract-api.js +1 -1
- package/dist/doctor-contract-api.js +1 -1
- package/dist/http-routes-api.js +1 -1
- package/dist/index.js +1 -1
- package/dist/message-tool-api.js +1 -1
- package/dist/runtime-api.js +11 -12
- package/dist/secret-contract-api.js +1 -1
- package/dist/security-contract-api.js +1 -1
- package/dist/setup-entry.js +2 -2
- package/dist/setup-plugin-api.js +1 -1
- package/node_modules/@slack/bolt/node_modules/@types/node/README.md +1 -1
- package/node_modules/@slack/bolt/node_modules/@types/node/child_process.d.ts +1 -1
- package/node_modules/@slack/bolt/node_modules/@types/node/crypto.d.ts +20 -8
- package/node_modules/@slack/bolt/node_modules/@types/node/ffi.d.ts +14 -0
- package/node_modules/@slack/bolt/node_modules/@types/node/http2.d.ts +3 -2
- package/node_modules/@slack/bolt/node_modules/@types/node/net.d.ts +16 -4
- package/node_modules/@slack/bolt/node_modules/@types/node/package.json +2 -2
- package/node_modules/@slack/bolt/node_modules/@types/node/process.d.ts +1 -0
- package/node_modules/@slack/bolt/node_modules/@types/node/quic.d.ts +39 -1
- package/node_modules/@slack/bolt/node_modules/@types/node/stream/iter.d.ts +9 -12
- package/node_modules/@slack/bolt/node_modules/@types/node/test/reporters.d.ts +1 -0
- package/node_modules/@slack/bolt/node_modules/@types/node/test.d.ts +72 -0
- package/node_modules/@slack/bolt/node_modules/@types/node/tls.d.ts +14 -10
- package/node_modules/@slack/bolt/node_modules/@types/node/vm.d.ts +2 -1
- package/node_modules/@slack/bolt/node_modules/@types/node/worker_threads.d.ts +4 -1
- package/node_modules/@slack/web-api/node_modules/@types/node/README.md +1 -1
- package/node_modules/@slack/web-api/node_modules/@types/node/child_process.d.ts +1 -1
- package/node_modules/@slack/web-api/node_modules/@types/node/crypto.d.ts +20 -8
- package/node_modules/@slack/web-api/node_modules/@types/node/ffi.d.ts +14 -0
- package/node_modules/@slack/web-api/node_modules/@types/node/http2.d.ts +3 -2
- package/node_modules/@slack/web-api/node_modules/@types/node/net.d.ts +16 -4
- package/node_modules/@slack/web-api/node_modules/@types/node/package.json +2 -2
- package/node_modules/@slack/web-api/node_modules/@types/node/process.d.ts +1 -0
- package/node_modules/@slack/web-api/node_modules/@types/node/quic.d.ts +39 -1
- package/node_modules/@slack/web-api/node_modules/@types/node/stream/iter.d.ts +9 -12
- package/node_modules/@slack/web-api/node_modules/@types/node/test/reporters.d.ts +1 -0
- package/node_modules/@slack/web-api/node_modules/@types/node/test.d.ts +72 -0
- package/node_modules/@slack/web-api/node_modules/@types/node/tls.d.ts +14 -10
- package/node_modules/@slack/web-api/node_modules/@types/node/vm.d.ts +2 -1
- package/node_modules/@slack/web-api/node_modules/@types/node/worker_threads.d.ts +4 -1
- package/node_modules/get-east-asian-width/index.js +8 -3
- package/node_modules/get-east-asian-width/lookup-data.js +1 -1
- package/node_modules/get-east-asian-width/lookup.js +4 -4
- package/node_modules/get-east-asian-width/package.json +1 -1
- package/node_modules/typebox/build/compile/code.mjs +1 -2
- package/node_modules/typebox/build/compile/validator.mjs +5 -1
- package/node_modules/typebox/build/format/iri.mjs +16 -9
- package/node_modules/typebox/build/guard/emit.mjs +2 -2
- package/node_modules/typebox/build/guard/guard.d.mts +7 -5
- package/node_modules/typebox/build/guard/guard.mjs +14 -9
- package/node_modules/typebox/build/guard/index.mjs +6 -0
- package/node_modules/typebox/build/guard/unicode/unicode.d.mts +6 -0
- package/node_modules/typebox/build/guard/unicode/unicode.mjs +35 -0
- package/node_modules/typebox/build/guard/{string.mjs → unicode/unicode_segment.mjs} +16 -19
- package/node_modules/typebox/build/schema/build.mjs +2 -3
- package/node_modules/typebox/build/schema/engine/_pathing.d.mts +1 -0
- package/node_modules/typebox/build/schema/engine/_pathing.mjs +11 -0
- package/node_modules/typebox/build/schema/engine/_refine.mjs +13 -5
- package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dependencies.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dependentSchemas.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/format.mjs +9 -2
- package/node_modules/typebox/build/schema/engine/patternProperties.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/properties.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/ref.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/ref.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/schema.mjs +24 -24
- package/node_modules/typebox/build/schema/parse.mjs +3 -0
- package/node_modules/typebox/build/schema/pointer/pointer.d.mts +1 -6
- package/node_modules/typebox/build/schema/pointer/pointer.mjs +47 -64
- package/node_modules/typebox/build/system/settings/internal.d.mts +2 -0
- package/node_modules/typebox/build/system/settings/internal.mjs +20 -0
- package/node_modules/typebox/build/system/settings/settings.d.mts +11 -0
- package/node_modules/typebox/build/system/settings/settings.mjs +14 -0
- package/node_modules/typebox/build/type/extends/inference.d.mts +1 -1
- package/node_modules/typebox/build/type/extends/inference.mjs +15 -2
- package/node_modules/typebox/build/type/extends/tuple.d.mts +2 -1
- package/node_modules/typebox/build/type/extends/tuple.mjs +21 -1
- package/node_modules/typebox/build/value/parse/parse.mjs +10 -4
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/typebox/readme.md +3 -1
- package/node_modules/undici/README.md +81 -25
- package/node_modules/undici/docs/docs/api/Agent.md +249 -42
- package/node_modules/undici/docs/docs/api/BalancedPool.md +246 -48
- package/node_modules/undici/docs/docs/api/CacheStorage.md +171 -13
- package/node_modules/undici/docs/docs/api/CacheStore.md +294 -98
- package/node_modules/undici/docs/docs/api/Client.md +407 -162
- package/node_modules/undici/docs/docs/api/ClientStats.md +80 -14
- package/node_modules/undici/docs/docs/api/Connector.md +118 -35
- package/node_modules/undici/docs/docs/api/ContentType.md +65 -25
- package/node_modules/undici/docs/docs/api/Cookies.md +122 -71
- package/node_modules/undici/docs/docs/api/Debug.md +34 -18
- package/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +364 -164
- package/node_modules/undici/docs/docs/api/Dispatcher.md +490 -1055
- package/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +105 -93
- package/node_modules/undici/docs/docs/api/Errors.md +503 -28
- package/node_modules/undici/docs/docs/api/EventSource.md +326 -13
- package/node_modules/undici/docs/docs/api/Fetch.md +681 -30
- package/node_modules/undici/docs/docs/api/GlobalInstallation.md +78 -96
- package/node_modules/undici/docs/docs/api/H2CClient.md +298 -176
- package/node_modules/undici/docs/docs/api/Interceptors.md +418 -0
- package/node_modules/undici/docs/docs/api/MockAgent.md +333 -314
- package/node_modules/undici/docs/docs/api/MockCallHistory.md +272 -98
- package/node_modules/undici/docs/docs/api/MockCallHistoryLog.md +189 -26
- package/node_modules/undici/docs/docs/api/MockClient.md +151 -33
- package/node_modules/undici/docs/docs/api/MockErrors.md +56 -5
- package/node_modules/undici/docs/docs/api/MockPool.md +321 -258
- package/node_modules/undici/docs/docs/api/Pool.md +236 -41
- package/node_modules/undici/docs/docs/api/PoolStats.md +119 -13
- package/node_modules/undici/docs/docs/api/ProxyAgent.md +179 -160
- package/node_modules/undici/docs/docs/api/RedirectHandler.md +238 -55
- package/node_modules/undici/docs/docs/api/RetryAgent.md +113 -27
- package/node_modules/undici/docs/docs/api/RetryHandler.md +161 -79
- package/node_modules/undici/docs/docs/api/RoundRobinPool.md +166 -72
- package/node_modules/undici/docs/docs/api/SnapshotAgent.md +262 -517
- package/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +177 -196
- package/node_modules/undici/docs/docs/api/Util.md +68 -11
- package/node_modules/undici/docs/docs/api/WebSocket.md +475 -80
- package/node_modules/undici/docs/docs/api/api-lifecycle.md +100 -32
- package/node_modules/undici/docs/docs/best-practices/client-certificate.md +2 -0
- package/node_modules/undici/docs/docs/best-practices/crawling.md +3 -1
- package/node_modules/undici/docs/docs/best-practices/migrating-from-v7-to-v8.md +233 -0
- package/node_modules/undici/docs/docs/best-practices/mocking-request.md +9 -7
- package/node_modules/undici/docs/docs/best-practices/proxy.md +3 -1
- package/node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md +10 -8
- package/node_modules/undici/docs/docs/best-practices/writing-tests.md +2 -0
- package/node_modules/undici/docs/docs/getting-started.md +281 -0
- package/node_modules/undici/docs/docs/index.md +784 -0
- package/node_modules/undici/docs/docs/site.json +125 -0
- package/node_modules/undici/docs/docs/type-map.json +79 -0
- package/node_modules/undici/index.js +4 -2
- package/node_modules/undici/lib/api/api-connect.js +13 -11
- package/node_modules/undici/lib/api/api-pipeline.js +26 -13
- package/node_modules/undici/lib/api/api-request.js +51 -21
- package/node_modules/undici/lib/api/api-stream.js +81 -20
- package/node_modules/undici/lib/api/api-upgrade.js +21 -11
- package/node_modules/undici/lib/api/readable.js +45 -9
- package/node_modules/undici/lib/cache/memory-cache-store.js +3 -3
- package/node_modules/undici/lib/cache/sqlite-cache-store.js +3 -1
- package/node_modules/undici/lib/core/connect.js +46 -7
- package/node_modules/undici/lib/core/constants.js +1 -24
- package/node_modules/undici/lib/core/errors.js +22 -2
- package/node_modules/undici/lib/core/request.js +100 -18
- package/node_modules/undici/lib/core/symbols.js +4 -0
- package/node_modules/undici/lib/core/util.js +105 -48
- package/node_modules/undici/lib/dispatcher/agent.js +51 -32
- package/node_modules/undici/lib/dispatcher/balanced-pool.js +23 -27
- package/node_modules/undici/lib/dispatcher/client-h1.js +86 -24
- package/node_modules/undici/lib/dispatcher/client-h2.js +1091 -361
- package/node_modules/undici/lib/dispatcher/client.js +109 -36
- package/node_modules/undici/lib/dispatcher/dispatcher-base.js +19 -6
- package/node_modules/undici/lib/dispatcher/dispatcher.js +15 -5
- package/node_modules/undici/lib/dispatcher/dispatcher1-wrapper.js +110 -0
- package/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +37 -8
- package/node_modules/undici/lib/dispatcher/h2c-client.js +4 -4
- package/node_modules/undici/lib/dispatcher/pool-base.js +28 -10
- package/node_modules/undici/lib/dispatcher/pool.js +30 -5
- package/node_modules/undici/lib/dispatcher/proxy-agent.js +95 -19
- package/node_modules/undici/lib/dispatcher/retry-agent.js +3 -0
- package/node_modules/undici/lib/dispatcher/round-robin-pool.js +32 -10
- package/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +153 -63
- package/node_modules/undici/lib/global.js +37 -14
- package/node_modules/undici/lib/handler/cache-handler.js +49 -7
- package/node_modules/undici/lib/handler/cache-revalidation-handler.js +10 -0
- package/node_modules/undici/lib/handler/decorator-handler.js +8 -3
- package/node_modules/undici/lib/handler/deduplication-handler.js +12 -6
- package/node_modules/undici/lib/handler/redirect-handler.js +66 -66
- package/node_modules/undici/lib/handler/retry-handler.js +191 -50
- package/node_modules/undici/lib/interceptor/cache.js +75 -25
- package/node_modules/undici/lib/interceptor/decompress.js +5 -2
- package/node_modules/undici/lib/interceptor/deduplicate.js +5 -4
- package/node_modules/undici/lib/interceptor/dns.js +12 -1
- package/node_modules/undici/lib/interceptor/redirect.js +3 -3
- package/node_modules/undici/lib/llhttp/llhttp-wasm.js +1 -1
- package/node_modules/undici/lib/llhttp/llhttp_simd-wasm.js +1 -1
- package/node_modules/undici/lib/mock/mock-agent.js +21 -9
- package/node_modules/undici/lib/mock/mock-call-history.js +1 -1
- package/node_modules/undici/lib/mock/mock-interceptor.js +25 -7
- package/node_modules/undici/lib/mock/mock-utils.js +281 -47
- package/node_modules/undici/lib/mock/snapshot-agent.js +25 -7
- package/node_modules/undici/lib/mock/snapshot-recorder.js +38 -3
- package/node_modules/undici/lib/util/cache.js +92 -10
- package/node_modules/undici/lib/util/runtime-features.js +3 -34
- package/node_modules/undici/lib/web/cache/cache.js +6 -8
- package/node_modules/undici/lib/web/cookies/parse.js +150 -146
- package/node_modules/undici/lib/web/cookies/util.js +1 -1
- package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +28 -1
- package/node_modules/undici/lib/web/eventsource/eventsource.js +20 -21
- package/node_modules/undici/lib/web/eventsource/util.js +32 -1
- package/node_modules/undici/lib/web/fetch/body.js +48 -10
- package/node_modules/undici/lib/web/fetch/constants.js +1 -1
- package/node_modules/undici/lib/web/fetch/formdata.js +21 -2
- package/node_modules/undici/lib/web/fetch/index.js +242 -223
- package/node_modules/undici/lib/web/fetch/request.js +35 -6
- package/node_modules/undici/lib/web/fetch/response.js +1 -3
- package/node_modules/undici/lib/web/fetch/util.js +10 -2
- package/node_modules/undici/lib/web/webidl/index.js +5 -7
- package/node_modules/undici/lib/web/websocket/frame.js +1 -7
- package/node_modules/undici/lib/web/websocket/receiver.js +1 -1
- package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +10 -10
- package/node_modules/undici/lib/web/websocket/websocket.js +25 -3
- package/node_modules/undici/package.json +10 -10
- package/node_modules/undici/types/agent.d.ts +0 -2
- package/node_modules/undici/types/client.d.ts +69 -24
- package/node_modules/undici/types/connector.d.ts +1 -0
- package/node_modules/undici/types/cookies.d.ts +1 -1
- package/node_modules/undici/types/dispatcher.d.ts +8 -30
- package/node_modules/undici/types/dispatcher1-wrapper.d.ts +7 -0
- package/node_modules/undici/types/errors.d.ts +10 -0
- package/node_modules/undici/types/fetch.d.ts +5 -1
- package/node_modules/undici/types/formdata.d.ts +0 -6
- package/node_modules/undici/types/h2c-client.d.ts +6 -6
- package/node_modules/undici/types/handlers.d.ts +2 -0
- package/node_modules/undici/types/header.d.ts +5 -0
- package/node_modules/undici/types/index.d.ts +3 -1
- package/node_modules/undici/types/interceptors.d.ts +1 -1
- package/node_modules/undici/types/mock-agent.d.ts +11 -10
- package/node_modules/undici/types/mock-interceptor.d.ts +5 -1
- package/node_modules/undici/types/pool.d.ts +0 -2
- package/node_modules/undici/types/proxy-agent.d.ts +9 -2
- package/node_modules/undici/types/round-robin-pool.d.ts +0 -2
- package/node_modules/undici/types/snapshot-agent.d.ts +4 -0
- package/node_modules/undici/types/socks5-proxy-agent.d.ts +4 -2
- package/node_modules/undici/types/webidl.d.ts +0 -1
- package/openclaw.plugin.json +31 -3
- package/package.json +7 -7
- package/dist/.setup/accounts.runtime-DN-PPocC.mjs +0 -2
- package/dist/.setup/action-runtime.runtime-C8R8CUan.mjs +0 -2
- package/dist/.setup/doctor-contract-CtMUqjvk.mjs +0 -186
- package/dist/.setup/monitor-_Sht7Dqy.mjs +0 -13
- package/dist/.setup/rolldown-runtime-D7D4PA-g.mjs +0 -13
- package/dist/.setup/send.runtime-B4caZU6T.mjs +0 -2
- package/dist/.setup/slash-commands.runtime-BpSH0b3q.mjs +0 -2
- package/dist/.setup/slash-skill-commands.runtime-Bi1JWZHY.mjs +0 -2
- package/node_modules/undici/lib/handler/unwrap-handler.js +0 -106
- package/node_modules/undici/lib/handler/wrap-handler.js +0 -105
- package/node_modules/undici/lib/llhttp/.gitkeep +0 -0
- package/node_modules/undici/lib/util/promise.js +0 -28
- /package/node_modules/typebox/build/guard/{string.d.mts → unicode/unicode_segment.d.mts} +0 -0
|
@@ -1,34 +1,61 @@
|
|
|
1
1
|
# Dispatcher
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<!--introduced_in=v4.0.0-->
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<!--type=module-->
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
<!-- source_link=lib/dispatcher/dispatcher.js -->
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
> Stability: 2 - Stable
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
`Dispatcher` is the core abstraction used to dispatch HTTP requests in undici.
|
|
12
|
+
It extends [`EventEmitter`][] and defines the low-level [`dispatch()`][] contract
|
|
13
|
+
along with the higher-level [`request()`][], [`stream()`][], [`pipeline()`][],
|
|
14
|
+
[`connect()`][], and [`upgrade()`][] methods that every concrete dispatcher
|
|
15
|
+
provides.
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
`Dispatcher` itself is abstract: its [`dispatch()`][], [`close()`][], and
|
|
18
|
+
[`destroy()`][] methods throw `Error: not implemented`. Concrete dispatchers such
|
|
19
|
+
as [`Client`][], [`Pool`][], [`BalancedPool`][], and [`Agent`][] implement the
|
|
20
|
+
contract and are what application code instantiates. Requests are not guaranteed
|
|
21
|
+
to be dispatched in the order in which they are invoked.
|
|
14
22
|
|
|
15
|
-
|
|
23
|
+
```mjs
|
|
24
|
+
import { Dispatcher, Agent } from 'undici'
|
|
16
25
|
|
|
17
|
-
|
|
26
|
+
const dispatcher = new Agent()
|
|
27
|
+
console.log(dispatcher instanceof Dispatcher) // true
|
|
28
|
+
```
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
## Class: `Dispatcher`
|
|
20
31
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
<!-- YAML
|
|
33
|
+
added: v4.0.0
|
|
34
|
+
-->
|
|
35
|
+
|
|
36
|
+
* Extends: {EventEmitter}
|
|
37
|
+
|
|
38
|
+
Base dispatcher class. Concrete dispatchers extend it and implement the
|
|
39
|
+
[`dispatch()`][] method on which the other request helpers are built.
|
|
40
|
+
|
|
41
|
+
### `dispatcher.close([callback])`
|
|
42
|
+
|
|
43
|
+
<!-- YAML
|
|
44
|
+
added: v4.0.0
|
|
45
|
+
-->
|
|
25
46
|
|
|
26
|
-
|
|
47
|
+
* `callback` {Function} (optional) Invoked once the dispatcher is closed.
|
|
48
|
+
* `error` {Error|null}
|
|
49
|
+
* Returns: {Promise} A `Promise` is returned only when `callback` is omitted. It
|
|
50
|
+
resolves with `void` once the dispatcher is closed.
|
|
27
51
|
|
|
28
|
-
|
|
29
|
-
|
|
52
|
+
Closes the dispatcher and gracefully waits for enqueued requests to complete
|
|
53
|
+
before resolving or invoking `callback`.
|
|
54
|
+
|
|
55
|
+
```mjs
|
|
56
|
+
import { createServer } from 'node:http'
|
|
30
57
|
import { Client } from 'undici'
|
|
31
|
-
import { once } from 'events'
|
|
58
|
+
import { once } from 'node:events'
|
|
32
59
|
|
|
33
60
|
const server = createServer((request, response) => {
|
|
34
61
|
response.end('undici')
|
|
@@ -39,10 +66,7 @@ await once(server, 'listening')
|
|
|
39
66
|
const client = new Client(`http://localhost:${server.address().port}`)
|
|
40
67
|
|
|
41
68
|
try {
|
|
42
|
-
const { body } = await client.request({
|
|
43
|
-
path: '/',
|
|
44
|
-
method: 'GET'
|
|
45
|
-
})
|
|
69
|
+
const { body } = await client.request({ path: '/', method: 'GET' })
|
|
46
70
|
body.setEncoding('utf8')
|
|
47
71
|
body.on('data', console.log)
|
|
48
72
|
} catch (error) {}
|
|
@@ -53,40 +77,41 @@ console.log('Client closed')
|
|
|
53
77
|
server.close()
|
|
54
78
|
```
|
|
55
79
|
|
|
56
|
-
### `
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
*
|
|
70
|
-
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
### `dispatcher.connect(options[, callback])`
|
|
81
|
+
|
|
82
|
+
<!-- YAML
|
|
83
|
+
added: v4.0.0
|
|
84
|
+
-->
|
|
85
|
+
|
|
86
|
+
* `options` {Object}
|
|
87
|
+
* `origin` {string|URL} The origin to connect to.
|
|
88
|
+
* `path` {string} The request path.
|
|
89
|
+
* `headers` {UndiciHeaders} Request headers. **Default:** `null`.
|
|
90
|
+
* `signal` {AbortSignal|EventEmitter|null} A signal used to abort the request.
|
|
91
|
+
**Default:** `null`.
|
|
92
|
+
* `opaque` {any} An opaque value passed through to the returned `ConnectData`.
|
|
93
|
+
* `responseHeaders` {string|null} Set to `'raw'` to return the response headers
|
|
94
|
+
as a raw array instead of an object. **Default:** `null`.
|
|
95
|
+
* `callback` {Function} (optional) Invoked when the connection is established.
|
|
96
|
+
* `error` {Error|null}
|
|
97
|
+
* `data` {Object}
|
|
98
|
+
* `statusCode` {number}
|
|
99
|
+
* `headers` {Object} The response headers.
|
|
100
|
+
* `socket` {Duplex} The established socket.
|
|
101
|
+
* `opaque` {any} The `opaque` value passed in `options`.
|
|
102
|
+
* Returns: {Promise} A `Promise` is returned only when `callback` is omitted. It
|
|
103
|
+
resolves with the `data` object described above.
|
|
104
|
+
|
|
105
|
+
Starts two-way communications with the requested resource using the
|
|
106
|
+
[HTTP `CONNECT`][] method.
|
|
107
|
+
|
|
108
|
+
```mjs
|
|
109
|
+
import { createServer } from 'node:http'
|
|
85
110
|
import { Client } from 'undici'
|
|
86
|
-
import { once } from 'events'
|
|
111
|
+
import { once } from 'node:events'
|
|
87
112
|
|
|
88
113
|
const server = createServer((request, response) => {
|
|
89
|
-
throw Error('should never get here')
|
|
114
|
+
throw new Error('should never get here')
|
|
90
115
|
}).listen()
|
|
91
116
|
|
|
92
117
|
server.on('connect', (req, socket, head) => {
|
|
@@ -107,9 +132,7 @@ await once(server, 'listening')
|
|
|
107
132
|
const client = new Client(`http://localhost:${server.address().port}`)
|
|
108
133
|
|
|
109
134
|
try {
|
|
110
|
-
const { socket } = await client.connect({
|
|
111
|
-
path: '/'
|
|
112
|
-
})
|
|
135
|
+
const { socket } = await client.connect({ path: '/' })
|
|
113
136
|
const wanted = 'Body'
|
|
114
137
|
let data = ''
|
|
115
138
|
socket.on('data', d => { data += d })
|
|
@@ -120,35 +143,39 @@ try {
|
|
|
120
143
|
})
|
|
121
144
|
socket.write(wanted)
|
|
122
145
|
socket.end()
|
|
123
|
-
} catch (error) {
|
|
146
|
+
} catch (error) {}
|
|
124
147
|
```
|
|
125
148
|
|
|
126
|
-
### `
|
|
127
|
-
|
|
128
|
-
Destroy the dispatcher abruptly with the given error. All the pending and running requests will be asynchronously aborted and error. Since this operation is asynchronously dispatched there might still be some progress on dispatched requests.
|
|
149
|
+
### `dispatcher.destroy([error[, callback]])`
|
|
129
150
|
|
|
130
|
-
|
|
151
|
+
<!-- YAML
|
|
152
|
+
added: v4.0.0
|
|
153
|
+
-->
|
|
131
154
|
|
|
132
|
-
|
|
155
|
+
* `error` {Error|null} (optional) The error with which pending requests are
|
|
156
|
+
rejected. **Default:** `null`.
|
|
157
|
+
* `callback` {Function} (optional) Invoked once the socket is closed.
|
|
158
|
+
* Returns: {Promise} A `Promise` is returned only when `callback` is omitted. It
|
|
159
|
+
resolves with `void` once the dispatcher is destroyed.
|
|
133
160
|
|
|
134
|
-
|
|
135
|
-
|
|
161
|
+
Destroys the dispatcher abruptly with the given `error`. All pending and running
|
|
162
|
+
requests are asynchronously aborted and rejected. Because the operation is
|
|
163
|
+
dispatched asynchronously, dispatched requests may still make some progress
|
|
164
|
+
before being aborted.
|
|
136
165
|
|
|
137
|
-
|
|
166
|
+
Both arguments are optional, so the method can be called in four ways:
|
|
138
167
|
|
|
139
|
-
```
|
|
168
|
+
```mjs
|
|
140
169
|
dispatcher.destroy() // -> Promise
|
|
141
170
|
dispatcher.destroy(new Error()) // -> Promise
|
|
142
171
|
dispatcher.destroy(() => {}) // -> void
|
|
143
172
|
dispatcher.destroy(new Error(), () => {}) // -> void
|
|
144
173
|
```
|
|
145
174
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
```js
|
|
149
|
-
import { createServer } from 'http'
|
|
175
|
+
```mjs
|
|
176
|
+
import { createServer } from 'node:http'
|
|
150
177
|
import { Client } from 'undici'
|
|
151
|
-
import { once } from 'events'
|
|
178
|
+
import { once } from 'node:events'
|
|
152
179
|
|
|
153
180
|
const server = createServer((request, response) => {
|
|
154
181
|
response.end()
|
|
@@ -159,10 +186,7 @@ await once(server, 'listening')
|
|
|
159
186
|
const client = new Client(`http://localhost:${server.address().port}`)
|
|
160
187
|
|
|
161
188
|
try {
|
|
162
|
-
const request = client.request({
|
|
163
|
-
path: '/',
|
|
164
|
-
method: 'GET'
|
|
165
|
-
})
|
|
189
|
+
const request = client.request({ path: '/', method: 'GET' })
|
|
166
190
|
client.destroy()
|
|
167
191
|
.then(() => {
|
|
168
192
|
console.log('Client destroyed')
|
|
@@ -174,50 +198,111 @@ try {
|
|
|
174
198
|
}
|
|
175
199
|
```
|
|
176
200
|
|
|
177
|
-
### `
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
*
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
*
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
*
|
|
199
|
-
|
|
200
|
-
*
|
|
201
|
-
|
|
202
|
-
*
|
|
203
|
-
|
|
204
|
-
*
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
*
|
|
212
|
-
|
|
213
|
-
*
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
201
|
+
### `dispatcher.dispatch(options, handler)`
|
|
202
|
+
|
|
203
|
+
<!-- YAML
|
|
204
|
+
added: v4.0.0
|
|
205
|
+
-->
|
|
206
|
+
|
|
207
|
+
* `options` {Object}
|
|
208
|
+
* `origin` {string|URL} (optional) The origin to dispatch the request to.
|
|
209
|
+
* `path` {string} The request path.
|
|
210
|
+
* `method` {string} The HTTP method, e.g. `'GET'` or `'POST'`.
|
|
211
|
+
* `body` {string|Buffer|Uint8Array|Readable|FormData|null} The request body.
|
|
212
|
+
**Default:** `null`.
|
|
213
|
+
* `headers` {UndiciHeaders} Request headers. **Default:** `null`.
|
|
214
|
+
* `query` {Object} Query string parameters embedded into the request URL. Both
|
|
215
|
+
keys and values are encoded with `encodeURIComponent`. **Default:** `null`.
|
|
216
|
+
* `idempotent` {boolean} Whether the request can be safely retried. If `false`,
|
|
217
|
+
the request is not sent until all preceding requests in the pipeline have
|
|
218
|
+
completed. **Default:** `true` when `method` is `'HEAD'` or `'GET'`.
|
|
219
|
+
* `blocking` {boolean} Whether the response is expected to block the pipeline.
|
|
220
|
+
When `true`, further pipelining on the same connection is avoided until
|
|
221
|
+
headers have been received. **Default:** `method !== 'HEAD'`.
|
|
222
|
+
* `typeOfService` {number|null} The IP Type of Service (ToS) value for the
|
|
223
|
+
request socket. Must be an integer between `0` and `255`. **Default:** `0`.
|
|
224
|
+
* `upgrade` {boolean|string|null} Upgrades the request, e.g. to `'Websocket'`.
|
|
225
|
+
**Default:** `null`, or the request method when it is `'CONNECT'`.
|
|
226
|
+
* `headersTimeout` {number|null} The time, in milliseconds, the parser waits to
|
|
227
|
+
receive the complete HTTP headers. Defaults to 300 seconds.
|
|
228
|
+
* `bodyTimeout` {number|null} The time, in milliseconds, after which the request
|
|
229
|
+
times out while receiving body data. Monitors the time between body chunks.
|
|
230
|
+
Use `0` to disable it entirely. Defaults to 300 seconds.
|
|
231
|
+
HTTP/1.1 headers/body parser timeouts are not guaranteed to fire with exact
|
|
232
|
+
millisecond precision: delays up to 1000ms use native timers, while larger
|
|
233
|
+
delays use undici's lower-overhead fast timers with a target resolution
|
|
234
|
+
around 500ms.
|
|
235
|
+
* `reset` {boolean} Whether the request should establish a keep-alive
|
|
236
|
+
connection. **Default:** `false`.
|
|
237
|
+
* `expectContinue` {boolean} For HTTP/2, appends the `expect: 100-continue`
|
|
238
|
+
header and halts the request body until a `100 Continue` is received from the
|
|
239
|
+
remote server. **Default:** `false`.
|
|
240
|
+
* `handler` {Object} A set of callbacks invoked over the request lifecycle.
|
|
241
|
+
* `onRequestStart` {Function} Invoked before the request is dispatched on the
|
|
242
|
+
socket. May be invoked multiple times when the request is retried.
|
|
243
|
+
* `controller` {DispatchController}
|
|
244
|
+
* `context` {any}
|
|
245
|
+
* `onRequestUpgrade` {Function} (optional) Invoked when the request is upgraded.
|
|
246
|
+
Required when `options.upgrade` is set or `options.method` is `'CONNECT'`.
|
|
247
|
+
* `controller` {DispatchController}
|
|
248
|
+
* `statusCode` {number}
|
|
249
|
+
* `headers` {Object}
|
|
250
|
+
* `socket` {Duplex}
|
|
251
|
+
* `onResponseStart` {Function} (optional) Invoked when the status code and
|
|
252
|
+
headers have been received. May be invoked multiple times for 1xx
|
|
253
|
+
informational responses. Not used for `upgrade` requests.
|
|
254
|
+
* `controller` {DispatchController}
|
|
255
|
+
* `statusCode` {number}
|
|
256
|
+
* `headers` {Object}
|
|
257
|
+
* `statusMessage` {string} (optional)
|
|
258
|
+
* `onResponseData` {Function} (optional) Invoked when response payload data is
|
|
259
|
+
received. Not used for `upgrade` requests.
|
|
260
|
+
* `controller` {DispatchController}
|
|
261
|
+
* `chunk` {Buffer}
|
|
262
|
+
* `onResponseEnd` {Function} (optional) Invoked when the response payload and
|
|
263
|
+
trailers have been received and the request has completed. Not used for
|
|
264
|
+
`upgrade` requests.
|
|
265
|
+
* `controller` {DispatchController}
|
|
266
|
+
* `trailers` {Object}
|
|
267
|
+
* `onResponseError` {Function} (optional) Invoked when an error occurs. Must not
|
|
268
|
+
throw.
|
|
269
|
+
* `controller` {DispatchController}
|
|
270
|
+
* `error` {Error}
|
|
271
|
+
* `onResponseStarted` {Function} (optional) Invoked when the response is
|
|
272
|
+
received, before headers have been read.
|
|
273
|
+
* `onBodySent` {Function} (optional) Invoked when a chunk of the request body is
|
|
274
|
+
sent. May be invoked multiple times for chunked requests.
|
|
275
|
+
* `chunk` {Buffer}
|
|
276
|
+
* `onRequestSent` {Function} (optional) Invoked after the request body is fully
|
|
277
|
+
sent.
|
|
278
|
+
* Returns: {boolean} `false` when the dispatcher is busy and further `dispatch()`
|
|
279
|
+
calls will make no progress until the [`'drain'`][] event is emitted.
|
|
280
|
+
|
|
281
|
+
This is the low-level API on which all the higher-level methods are built. This
|
|
282
|
+
API is expected to evolve through semver-major versions and is less stable than
|
|
283
|
+
the higher-level methods. It is primarily intended for library developers who
|
|
284
|
+
implement higher-level APIs on top of it.
|
|
285
|
+
|
|
286
|
+
The `controller` passed to each handler is a `DispatchController` with the
|
|
287
|
+
following shape:
|
|
288
|
+
|
|
289
|
+
* `aborted` {boolean} Whether the request has been aborted.
|
|
290
|
+
* `paused` {boolean} Whether the request is paused.
|
|
291
|
+
* `reason` {Error|null} The reason the request was aborted, if any.
|
|
292
|
+
* `rawHeaders` {Buffer[]|string[]|Object|null} The raw response headers,
|
|
293
|
+
preserving duplicates and casing.
|
|
294
|
+
* `rawTrailers` {Buffer[]|string[]|Object|null} The raw response trailers.
|
|
295
|
+
* `abort(reason)` Aborts the request with the given `reason`.
|
|
296
|
+
* `pause()` Pauses the response stream.
|
|
297
|
+
* `resume()` Resumes the response stream.
|
|
298
|
+
|
|
299
|
+
Call `controller.pause()` and `controller.resume()` to apply backpressure rather
|
|
300
|
+
than returning `false` from a handler.
|
|
301
|
+
|
|
302
|
+
```mjs
|
|
303
|
+
import { createServer } from 'node:http'
|
|
219
304
|
import { Client } from 'undici'
|
|
220
|
-
import { once } from 'events'
|
|
305
|
+
import { once } from 'node:events'
|
|
221
306
|
|
|
222
307
|
const server = createServer((request, response) => {
|
|
223
308
|
response.end('Hello, World!')
|
|
@@ -232,169 +317,61 @@ const data = []
|
|
|
232
317
|
client.dispatch({
|
|
233
318
|
path: '/',
|
|
234
319
|
method: 'GET',
|
|
235
|
-
headers: {
|
|
236
|
-
'x-foo': 'bar'
|
|
237
|
-
}
|
|
320
|
+
headers: { 'x-foo': 'bar' }
|
|
238
321
|
}, {
|
|
239
|
-
|
|
322
|
+
onRequestStart: () => {
|
|
240
323
|
console.log('Connected!')
|
|
241
324
|
},
|
|
242
|
-
|
|
325
|
+
onResponseError: (_controller, error) => {
|
|
243
326
|
console.error(error)
|
|
244
327
|
},
|
|
245
|
-
|
|
246
|
-
console.log(`
|
|
328
|
+
onResponseStart: (_controller, statusCode, headers) => {
|
|
329
|
+
console.log(`statusCode: ${statusCode} | headers: ${JSON.stringify(headers)}`)
|
|
247
330
|
},
|
|
248
|
-
|
|
249
|
-
console.log('onData: chunk received')
|
|
331
|
+
onResponseData: (_controller, chunk) => {
|
|
250
332
|
data.push(chunk)
|
|
251
333
|
},
|
|
252
|
-
|
|
253
|
-
console.log(`
|
|
254
|
-
|
|
255
|
-
console.log(`Data: ${res}`)
|
|
334
|
+
onResponseEnd: (_controller, trailers) => {
|
|
335
|
+
console.log(`trailers: ${JSON.stringify(trailers)}`)
|
|
336
|
+
console.log(`Data: ${Buffer.concat(data).toString('utf8')}`)
|
|
256
337
|
client.close()
|
|
257
338
|
server.close()
|
|
258
339
|
}
|
|
259
340
|
})
|
|
260
341
|
```
|
|
261
342
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
343
|
+
### `dispatcher.pipeline(options, handler)`
|
|
344
|
+
|
|
345
|
+
<!-- YAML
|
|
346
|
+
added: v4.0.0
|
|
347
|
+
-->
|
|
348
|
+
|
|
349
|
+
* `options` {Object} Accepts every field of [`dispatcher.request()`][]'s
|
|
350
|
+
`options`, plus:
|
|
351
|
+
* `objectMode` {boolean} Set to `true` when `handler` returns an object stream.
|
|
352
|
+
**Default:** `false`.
|
|
353
|
+
* `handler` {Function} Returns a `Readable` from which the result is read.
|
|
354
|
+
* `data` {Object}
|
|
355
|
+
* `statusCode` {number}
|
|
356
|
+
* `headers` {Object} The response headers.
|
|
357
|
+
* `opaque` {any} The `opaque` value passed in `options`.
|
|
358
|
+
* `body` {Readable} The response body.
|
|
359
|
+
* `context` {Object}
|
|
360
|
+
* Returns: {Readable}
|
|
361
|
+
* Returns: {Duplex} A duplex stream that writes to the request and reads from the
|
|
362
|
+
response.
|
|
363
|
+
|
|
364
|
+
For easy use with [`stream.pipeline()`][]. The `handler` should return a
|
|
365
|
+
`Readable` from which the result is read. Usually it returns the `body` directly,
|
|
366
|
+
unless a transformation is needed based on, for example, `headers` or
|
|
367
|
+
`statusCode`. The `handler` should validate the response and save any required
|
|
368
|
+
state; if there is an error, it should be thrown.
|
|
369
|
+
|
|
370
|
+
```mjs
|
|
371
|
+
import { Readable, Writable, PassThrough, pipeline } from 'node:stream'
|
|
372
|
+
import { createServer } from 'node:http'
|
|
266
373
|
import { Client } from 'undici'
|
|
267
|
-
import { once } from 'events'
|
|
268
|
-
|
|
269
|
-
const server = createServer((request, response) => {
|
|
270
|
-
response.end()
|
|
271
|
-
}).listen()
|
|
272
|
-
|
|
273
|
-
await once(server, 'listening')
|
|
274
|
-
|
|
275
|
-
server.on('upgrade', (request, socket, head) => {
|
|
276
|
-
console.log('Node.js Server - upgrade event')
|
|
277
|
-
socket.write('HTTP/1.1 101 Web Socket Protocol Handshake\r\n')
|
|
278
|
-
socket.write('Upgrade: WebSocket\r\n')
|
|
279
|
-
socket.write('Connection: Upgrade\r\n')
|
|
280
|
-
socket.write('\r\n')
|
|
281
|
-
socket.end()
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
const client = new Client(`http://localhost:${server.address().port}`)
|
|
285
|
-
|
|
286
|
-
client.dispatch({
|
|
287
|
-
path: '/',
|
|
288
|
-
method: 'GET',
|
|
289
|
-
upgrade: 'websocket'
|
|
290
|
-
}, {
|
|
291
|
-
onConnect: () => {
|
|
292
|
-
console.log('Undici Client - onConnect')
|
|
293
|
-
},
|
|
294
|
-
onError: (error) => {
|
|
295
|
-
console.log('onError') // shouldn't print
|
|
296
|
-
},
|
|
297
|
-
onUpgrade: (statusCode, headers, socket) => {
|
|
298
|
-
console.log('Undici Client - onUpgrade')
|
|
299
|
-
console.log(`onUpgrade Headers: ${headers}`)
|
|
300
|
-
socket.on('data', buffer => {
|
|
301
|
-
console.log(buffer.toString('utf8'))
|
|
302
|
-
})
|
|
303
|
-
socket.on('end', () => {
|
|
304
|
-
client.close()
|
|
305
|
-
server.close()
|
|
306
|
-
})
|
|
307
|
-
socket.end()
|
|
308
|
-
}
|
|
309
|
-
})
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
#### Example 3 - Dispatch POST request
|
|
313
|
-
|
|
314
|
-
```js
|
|
315
|
-
import { createServer } from 'http'
|
|
316
|
-
import { Client } from 'undici'
|
|
317
|
-
import { once } from 'events'
|
|
318
|
-
|
|
319
|
-
const server = createServer((request, response) => {
|
|
320
|
-
request.on('data', (data) => {
|
|
321
|
-
console.log(`Request Data: ${data.toString('utf8')}`)
|
|
322
|
-
const body = JSON.parse(data)
|
|
323
|
-
body.message = 'World'
|
|
324
|
-
response.end(JSON.stringify(body))
|
|
325
|
-
})
|
|
326
|
-
}).listen()
|
|
327
|
-
|
|
328
|
-
await once(server, 'listening')
|
|
329
|
-
|
|
330
|
-
const client = new Client(`http://localhost:${server.address().port}`)
|
|
331
|
-
|
|
332
|
-
const data = []
|
|
333
|
-
|
|
334
|
-
client.dispatch({
|
|
335
|
-
path: '/',
|
|
336
|
-
method: 'POST',
|
|
337
|
-
headers: {
|
|
338
|
-
'content-type': 'application/json'
|
|
339
|
-
},
|
|
340
|
-
body: JSON.stringify({ message: 'Hello' })
|
|
341
|
-
}, {
|
|
342
|
-
onConnect: () => {
|
|
343
|
-
console.log('Connected!')
|
|
344
|
-
},
|
|
345
|
-
onError: (error) => {
|
|
346
|
-
console.error(error)
|
|
347
|
-
},
|
|
348
|
-
onHeaders: (statusCode, headers) => {
|
|
349
|
-
console.log(`onHeaders | statusCode: ${statusCode} | headers: ${headers}`)
|
|
350
|
-
},
|
|
351
|
-
onData: (chunk) => {
|
|
352
|
-
console.log('onData: chunk received')
|
|
353
|
-
data.push(chunk)
|
|
354
|
-
},
|
|
355
|
-
onComplete: (trailers) => {
|
|
356
|
-
console.log(`onComplete | trailers: ${trailers}`)
|
|
357
|
-
const res = Buffer.concat(data).toString('utf8')
|
|
358
|
-
console.log(`Response Data: ${res}`)
|
|
359
|
-
client.close()
|
|
360
|
-
server.close()
|
|
361
|
-
}
|
|
362
|
-
})
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
### `Dispatcher.pipeline(options, handler)`
|
|
366
|
-
|
|
367
|
-
For easy use with [stream.pipeline](https://nodejs.org/api/stream.html#streampipelinesource-transforms-destination-options). The `handler` argument should return a `Readable` from which the result will be read. Usually it should just return the `body` argument unless some kind of transformation needs to be performed based on e.g. `headers` or `statusCode`. The `handler` should validate the response and save any required state. If there is an error, it should be thrown. The function returns a `Duplex` which writes to the request and reads from the response.
|
|
368
|
-
|
|
369
|
-
Arguments:
|
|
370
|
-
|
|
371
|
-
* **options** `PipelineOptions`
|
|
372
|
-
* **handler** `(data: PipelineHandlerData) => stream.Readable`
|
|
373
|
-
|
|
374
|
-
Returns: `stream.Duplex`
|
|
375
|
-
|
|
376
|
-
#### Parameter: PipelineOptions
|
|
377
|
-
|
|
378
|
-
Extends: [`RequestOptions`](/docs/docs/api/Dispatcher.md#parameter-requestoptions)
|
|
379
|
-
|
|
380
|
-
* **objectMode** `boolean` (optional) - Default: `false` - Set to `true` if the `handler` will return an object stream.
|
|
381
|
-
|
|
382
|
-
#### Parameter: PipelineHandlerData
|
|
383
|
-
|
|
384
|
-
* **statusCode** `number`
|
|
385
|
-
* **headers** `Record<string, string | string[] | undefined>`
|
|
386
|
-
* **opaque** `unknown`
|
|
387
|
-
* **body** `stream.Readable`
|
|
388
|
-
* **context** `object`
|
|
389
|
-
* **onInfo** `({statusCode: number, headers: Record<string, string | string[]>}) => void | null` (optional) - Default: `null` - Callback collecting all the info headers (HTTP 100-199) received.
|
|
390
|
-
|
|
391
|
-
#### Example 1 - Pipeline Echo
|
|
392
|
-
|
|
393
|
-
```js
|
|
394
|
-
import { Readable, Writable, PassThrough, pipeline } from 'stream'
|
|
395
|
-
import { createServer } from 'http'
|
|
396
|
-
import { Client } from 'undici'
|
|
397
|
-
import { once } from 'events'
|
|
374
|
+
import { once } from 'node:events'
|
|
398
375
|
|
|
399
376
|
const server = createServer((request, response) => {
|
|
400
377
|
request.pipe(response)
|
|
@@ -413,10 +390,7 @@ pipeline(
|
|
|
413
390
|
this.push(null)
|
|
414
391
|
}
|
|
415
392
|
}),
|
|
416
|
-
client.pipeline({
|
|
417
|
-
path: '/',
|
|
418
|
-
method: 'GET'
|
|
419
|
-
}, ({ statusCode, headers, body }) => {
|
|
393
|
+
client.pipeline({ path: '/', method: 'GET' }, ({ statusCode, headers, body }) => {
|
|
420
394
|
console.log(`response received ${statusCode}`)
|
|
421
395
|
console.log('headers', headers)
|
|
422
396
|
return pipeline(body, new PassThrough(), () => {})
|
|
@@ -435,79 +409,72 @@ pipeline(
|
|
|
435
409
|
if (error) {
|
|
436
410
|
console.error(error)
|
|
437
411
|
}
|
|
438
|
-
|
|
439
412
|
client.close()
|
|
440
413
|
server.close()
|
|
441
414
|
}
|
|
442
415
|
)
|
|
443
416
|
```
|
|
444
417
|
|
|
445
|
-
### `
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
*
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
`
|
|
498
|
-
|
|
499
|
-
`
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
Note that body will still be a `Readable` even if it is empty, but attempting to deserialize it with `json()` will result in an exception. Recommended way to ensure there is a body to deserialize is to check if status code is not 204, and `content-type` header starts with `application/json`.
|
|
504
|
-
|
|
505
|
-
#### Example 1 - Basic GET Request
|
|
506
|
-
|
|
507
|
-
```js
|
|
508
|
-
import { createServer } from 'http'
|
|
418
|
+
### `dispatcher.request(options[, callback])`
|
|
419
|
+
|
|
420
|
+
<!-- YAML
|
|
421
|
+
added: v4.0.0
|
|
422
|
+
-->
|
|
423
|
+
|
|
424
|
+
* `options` {Object} Accepts every field of [`dispatcher.dispatch()`][]'s
|
|
425
|
+
`options`, plus:
|
|
426
|
+
* `opaque` {any} A value passed through to the returned response data.
|
|
427
|
+
**Default:** `null`.
|
|
428
|
+
* `signal` {AbortSignal|EventEmitter|null} A signal used to abort the request.
|
|
429
|
+
**Default:** `null`.
|
|
430
|
+
* `onInfo` {Function|null} Invoked for each informational (1xx) response
|
|
431
|
+
received. **Default:** `null`.
|
|
432
|
+
* `info` {Object}
|
|
433
|
+
* `statusCode` {number}
|
|
434
|
+
* `headers` {Object}
|
|
435
|
+
* `responseHeaders` {string|null} Set to `'raw'` to return the response headers
|
|
436
|
+
as a raw array. **Default:** `null`.
|
|
437
|
+
* `highWaterMark` {number} The high water mark of the response body stream.
|
|
438
|
+
**Default:** `65536` (64 KiB).
|
|
439
|
+
* `callback` {Function} (optional) Invoked when the response is received.
|
|
440
|
+
* `error` {Error|null}
|
|
441
|
+
* `data` {Object}
|
|
442
|
+
* `statusCode` {number}
|
|
443
|
+
* `statusText` {string} The status message, e.g. `'OK'` or `'Not Found'`.
|
|
444
|
+
* `headers` {Object} The response headers. All keys are lower-cased.
|
|
445
|
+
* `body` {Readable} The response body, which also implements the
|
|
446
|
+
[body mixin][] from the Fetch Standard.
|
|
447
|
+
* `trailers` {Object} Starts out empty and is mutated to contain the trailers
|
|
448
|
+
after `body` emits `'end'`.
|
|
449
|
+
* `opaque` {any} The `opaque` value passed in `options`.
|
|
450
|
+
* `context` {Object}
|
|
451
|
+
* Returns: {Promise} A `Promise` is returned only when `callback` is omitted. It
|
|
452
|
+
resolves with the `data` object described above.
|
|
453
|
+
|
|
454
|
+
Performs an HTTP request. `options.method` must not be `'CONNECT'`.
|
|
455
|
+
|
|
456
|
+
Non-idempotent requests are not pipelined, to avoid indirect failures. Idempotent
|
|
457
|
+
requests are automatically retried if they fail due to an indirect failure from
|
|
458
|
+
the request at the head of the pipeline; this does not apply to idempotent
|
|
459
|
+
requests with a stream request body. All response bodies must always be fully
|
|
460
|
+
consumed or destroyed.
|
|
461
|
+
|
|
462
|
+
The `body` exposes the following [body mixin][] methods and properties:
|
|
463
|
+
[`.arrayBuffer()`][], [`.blob()`][], [`.bytes()`][], [`.json()`][],
|
|
464
|
+
[`.text()`][], `body`, and `bodyUsed`. A body cannot be consumed twice; for
|
|
465
|
+
example, calling `text()` after `json()` throws a `TypeError`. The body also
|
|
466
|
+
provides `dump({ limit })`, which discards up to `limit` bytes (default
|
|
467
|
+
`131072`) without destroying the socket.
|
|
468
|
+
|
|
469
|
+
The body is always a `Readable`, even when empty. Deserializing an empty body
|
|
470
|
+
with `json()` throws. To guard against this, verify the status code is not `204`
|
|
471
|
+
and the `content-type` header starts with `application/json` before calling
|
|
472
|
+
`json()`.
|
|
473
|
+
|
|
474
|
+
```mjs
|
|
475
|
+
import { createServer } from 'node:http'
|
|
509
476
|
import { Client } from 'undici'
|
|
510
|
-
import { once } from 'events'
|
|
477
|
+
import { once } from 'node:events'
|
|
511
478
|
|
|
512
479
|
const server = createServer((request, response) => {
|
|
513
480
|
response.end('Hello, World!')
|
|
@@ -538,107 +505,28 @@ try {
|
|
|
538
505
|
}
|
|
539
506
|
```
|
|
540
507
|
|
|
541
|
-
|
|
508
|
+
A request can be aborted with an `AbortSignal`, with any `EventEmitter` that
|
|
509
|
+
emits an `'abort'` event, or by destroying the response body:
|
|
542
510
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
```js
|
|
546
|
-
import { createServer } from 'http'
|
|
511
|
+
```mjs
|
|
547
512
|
import { Client } from 'undici'
|
|
548
|
-
import { once } from 'events'
|
|
549
513
|
|
|
550
|
-
const
|
|
551
|
-
response.end('Hello, World!')
|
|
552
|
-
}).listen()
|
|
553
|
-
|
|
554
|
-
await once(server, 'listening')
|
|
555
|
-
|
|
556
|
-
const client = new Client(`http://localhost:${server.address().port}`)
|
|
514
|
+
const client = new Client('http://localhost:3000')
|
|
557
515
|
const abortController = new AbortController()
|
|
558
516
|
|
|
559
517
|
try {
|
|
560
|
-
client.request({
|
|
561
|
-
path: '/',
|
|
562
|
-
method: 'GET',
|
|
563
|
-
signal: abortController.signal
|
|
564
|
-
})
|
|
518
|
+
client.request({ path: '/', method: 'GET', signal: abortController.signal })
|
|
565
519
|
} catch (error) {
|
|
566
|
-
console.error(error) //
|
|
567
|
-
client.close()
|
|
568
|
-
server.close()
|
|
520
|
+
console.error(error) // RequestAbortedError
|
|
569
521
|
}
|
|
570
522
|
|
|
571
523
|
abortController.abort()
|
|
572
524
|
```
|
|
573
525
|
|
|
574
|
-
|
|
526
|
+
When the response body is conditionally read, always fully consume it otherwise:
|
|
575
527
|
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
import { Client } from 'undici'
|
|
579
|
-
import EventEmitter, { once } from 'events'
|
|
580
|
-
|
|
581
|
-
const server = createServer((request, response) => {
|
|
582
|
-
response.end('Hello, World!')
|
|
583
|
-
}).listen()
|
|
584
|
-
|
|
585
|
-
await once(server, 'listening')
|
|
586
|
-
|
|
587
|
-
const client = new Client(`http://localhost:${server.address().port}`)
|
|
588
|
-
const ee = new EventEmitter()
|
|
589
|
-
|
|
590
|
-
try {
|
|
591
|
-
client.request({
|
|
592
|
-
path: '/',
|
|
593
|
-
method: 'GET',
|
|
594
|
-
signal: ee
|
|
595
|
-
})
|
|
596
|
-
} catch (error) {
|
|
597
|
-
console.error(error) // should print an RequestAbortedError
|
|
598
|
-
client.close()
|
|
599
|
-
server.close()
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
ee.emit('abort')
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
Destroying the request or response body will have the same effect.
|
|
606
|
-
|
|
607
|
-
```js
|
|
608
|
-
import { createServer } from 'http'
|
|
609
|
-
import { Client } from 'undici'
|
|
610
|
-
import { once } from 'events'
|
|
611
|
-
|
|
612
|
-
const server = createServer((request, response) => {
|
|
613
|
-
response.end('Hello, World!')
|
|
614
|
-
}).listen()
|
|
615
|
-
|
|
616
|
-
await once(server, 'listening')
|
|
617
|
-
|
|
618
|
-
const client = new Client(`http://localhost:${server.address().port}`)
|
|
619
|
-
|
|
620
|
-
try {
|
|
621
|
-
const { body } = await client.request({
|
|
622
|
-
path: '/',
|
|
623
|
-
method: 'GET'
|
|
624
|
-
})
|
|
625
|
-
body.destroy()
|
|
626
|
-
} catch (error) {
|
|
627
|
-
console.error(error) // should print an RequestAbortedError
|
|
628
|
-
client.close()
|
|
629
|
-
server.close()
|
|
630
|
-
}
|
|
631
|
-
```
|
|
632
|
-
|
|
633
|
-
#### Example 3 - Conditionally reading the body
|
|
634
|
-
|
|
635
|
-
Remember to fully consume the body even in the case when it is not read.
|
|
636
|
-
|
|
637
|
-
```js
|
|
638
|
-
const { body, statusCode } = await client.request({
|
|
639
|
-
path: '/',
|
|
640
|
-
method: 'GET'
|
|
641
|
-
})
|
|
528
|
+
```mjs
|
|
529
|
+
const { body, statusCode } = await client.request({ path: '/', method: 'GET' })
|
|
642
530
|
|
|
643
531
|
if (statusCode === 200) {
|
|
644
532
|
return await body.arrayBuffer()
|
|
@@ -649,40 +537,42 @@ await body.dump()
|
|
|
649
537
|
return null
|
|
650
538
|
```
|
|
651
539
|
|
|
652
|
-
### `
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
*
|
|
661
|
-
*
|
|
662
|
-
*
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
*
|
|
669
|
-
*
|
|
670
|
-
*
|
|
671
|
-
*
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
540
|
+
### `dispatcher.stream(options, factory[, callback])`
|
|
541
|
+
|
|
542
|
+
<!-- YAML
|
|
543
|
+
added: v4.0.0
|
|
544
|
+
-->
|
|
545
|
+
|
|
546
|
+
* `options` {Object} Accepts the same fields as [`dispatcher.request()`][]'s
|
|
547
|
+
`options`.
|
|
548
|
+
* `factory` {Function} Returns the `Writable` to which the response is written.
|
|
549
|
+
* `data` {Object}
|
|
550
|
+
* `statusCode` {number}
|
|
551
|
+
* `headers` {Object} The response headers.
|
|
552
|
+
* `opaque` {any} The `opaque` value passed in `options`.
|
|
553
|
+
* `context` {Object}
|
|
554
|
+
* Returns: {Writable}
|
|
555
|
+
* `callback` {Function} (optional) Invoked when the request has completed.
|
|
556
|
+
* `error` {Error|null}
|
|
557
|
+
* `data` {Object}
|
|
558
|
+
* `opaque` {any} The `opaque` value passed in `options`.
|
|
559
|
+
* `trailers` {Object}
|
|
560
|
+
* Returns: {Promise} A `Promise` is returned only when `callback` is omitted. It
|
|
561
|
+
resolves with the `data` object described above.
|
|
562
|
+
|
|
563
|
+
A faster version of [`dispatcher.request()`][]. The `factory` returns a
|
|
564
|
+
[`Writable`][] to which the response is written, which avoids creating an
|
|
565
|
+
intermediate [`Readable`][] when the caller intends to pipe the response body
|
|
566
|
+
directly to a `Writable`.
|
|
567
|
+
|
|
568
|
+
Use `options.opaque` to avoid creating a closure for the `factory` method. This
|
|
569
|
+
pattern works well with Node.js web frameworks such as [Fastify][].
|
|
570
|
+
|
|
571
|
+
```mjs
|
|
572
|
+
import { createServer } from 'node:http'
|
|
683
573
|
import { Client } from 'undici'
|
|
684
|
-
import { once } from 'events'
|
|
685
|
-
import { Writable } from 'stream'
|
|
574
|
+
import { once } from 'node:events'
|
|
575
|
+
import { Writable } from 'node:stream'
|
|
686
576
|
|
|
687
577
|
const server = createServer((request, response) => {
|
|
688
578
|
response.end('Hello, World!')
|
|
@@ -719,95 +609,38 @@ try {
|
|
|
719
609
|
}
|
|
720
610
|
```
|
|
721
611
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
}, ({ opaque }) => opaque.raw)
|
|
753
|
-
}
|
|
754
|
-
})
|
|
755
|
-
|
|
756
|
-
await fastifyServer.listen()
|
|
757
|
-
|
|
758
|
-
console.log('Fastify Server listening')
|
|
759
|
-
|
|
760
|
-
const fastifyServerUndiciClient = new Client(`http://localhost:${fastifyServer.server.address().port}`)
|
|
761
|
-
|
|
762
|
-
try {
|
|
763
|
-
const { statusCode, body } = await fastifyServerUndiciClient.request({
|
|
764
|
-
path: '/',
|
|
765
|
-
method: 'GET'
|
|
766
|
-
})
|
|
767
|
-
|
|
768
|
-
console.log(`response received ${statusCode}`)
|
|
769
|
-
body.setEncoding('utf8')
|
|
770
|
-
body.on('data', console.log)
|
|
771
|
-
|
|
772
|
-
nodeServerUndiciClient.close()
|
|
773
|
-
fastifyServerUndiciClient.close()
|
|
774
|
-
fastifyServer.close()
|
|
775
|
-
nodeServer.close()
|
|
776
|
-
} catch (error) { }
|
|
777
|
-
```
|
|
778
|
-
|
|
779
|
-
### `Dispatcher.upgrade(options[, callback])`
|
|
780
|
-
|
|
781
|
-
Upgrade to a different protocol. Visit [MDN - HTTP - Protocol upgrade mechanism](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism) for more details.
|
|
782
|
-
|
|
783
|
-
Arguments:
|
|
784
|
-
|
|
785
|
-
* **options** `UpgradeOptions`
|
|
786
|
-
|
|
787
|
-
* **callback** `(error: Error | null, data: UpgradeData) => void` (optional)
|
|
788
|
-
|
|
789
|
-
Returns: `void | Promise<UpgradeData>` - Only returns a `Promise` if no `callback` argument was passed
|
|
790
|
-
|
|
791
|
-
#### Parameter: `UpgradeOptions`
|
|
792
|
-
|
|
793
|
-
* **path** `string`
|
|
794
|
-
* **method** `string` (optional) - Default: `'GET'`
|
|
795
|
-
* **headers** `UndiciHeaders` (optional) - Default: `null`
|
|
796
|
-
* **protocol** `string` (optional) - Default: `'Websocket'` - A string of comma separated protocols, in descending preference order.
|
|
797
|
-
* **signal** `AbortSignal | EventEmitter | null` (optional) - Default: `null`
|
|
798
|
-
|
|
799
|
-
#### Parameter: `UpgradeData`
|
|
800
|
-
|
|
801
|
-
* **headers** `http.IncomingHeaders`
|
|
802
|
-
* **socket** `stream.Duplex`
|
|
803
|
-
* **opaque** `unknown`
|
|
804
|
-
|
|
805
|
-
#### Example 1 - Basic Upgrade Request
|
|
806
|
-
|
|
807
|
-
```js
|
|
808
|
-
import { createServer } from 'http'
|
|
612
|
+
### `dispatcher.upgrade(options[, callback])`
|
|
613
|
+
|
|
614
|
+
<!-- YAML
|
|
615
|
+
added: v4.0.0
|
|
616
|
+
-->
|
|
617
|
+
|
|
618
|
+
* `options` {Object}
|
|
619
|
+
* `path` {string} The request path.
|
|
620
|
+
* `method` {string} The HTTP method. **Default:** `'GET'`.
|
|
621
|
+
* `headers` {UndiciHeaders} Request headers. **Default:** `null`.
|
|
622
|
+
* `protocol` {string} A comma-separated list of protocols, in descending
|
|
623
|
+
preference order. **Default:** `'Websocket'`.
|
|
624
|
+
* `signal` {AbortSignal|EventEmitter|null} A signal used to abort the request.
|
|
625
|
+
**Default:** `null`.
|
|
626
|
+
* `responseHeaders` {string|null} Set to `'raw'` to return the response headers
|
|
627
|
+
as a raw array. **Default:** `null`.
|
|
628
|
+
* `callback` {Function} (optional) Invoked when the upgrade completes.
|
|
629
|
+
* `error` {Error|null}
|
|
630
|
+
* `data` {Object}
|
|
631
|
+
* `headers` {Object} The response headers.
|
|
632
|
+
* `socket` {Duplex} The upgraded socket.
|
|
633
|
+
* `opaque` {any}
|
|
634
|
+
* Returns: {Promise} A `Promise` is returned only when `callback` is omitted. It
|
|
635
|
+
resolves with the `data` object described above.
|
|
636
|
+
|
|
637
|
+
Upgrades to a different protocol. See
|
|
638
|
+
[MDN: Protocol upgrade mechanism][] for more details.
|
|
639
|
+
|
|
640
|
+
```mjs
|
|
641
|
+
import { createServer } from 'node:http'
|
|
809
642
|
import { Client } from 'undici'
|
|
810
|
-
import { once } from 'events'
|
|
643
|
+
import { once } from 'node:events'
|
|
811
644
|
|
|
812
645
|
const server = createServer((request, response) => {
|
|
813
646
|
response.statusCode = 101
|
|
@@ -821,9 +654,7 @@ await once(server, 'listening')
|
|
|
821
654
|
const client = new Client(`http://localhost:${server.address().port}`)
|
|
822
655
|
|
|
823
656
|
try {
|
|
824
|
-
const { headers, socket } = await client.upgrade({
|
|
825
|
-
path: '/',
|
|
826
|
-
})
|
|
657
|
+
const { headers, socket } = await client.upgrade({ path: '/' })
|
|
827
658
|
socket.on('end', () => {
|
|
828
659
|
console.log(`upgrade: ${headers.upgrade}`) // upgrade: Websocket
|
|
829
660
|
client.close()
|
|
@@ -837,109 +668,62 @@ try {
|
|
|
837
668
|
}
|
|
838
669
|
```
|
|
839
670
|
|
|
840
|
-
### `
|
|
841
|
-
|
|
842
|
-
Compose a new dispatcher from the current dispatcher and the given interceptors.
|
|
843
|
-
|
|
844
|
-
> _Notes_:
|
|
845
|
-
> - The order of the interceptors matters. The last interceptor will be the first to be called.
|
|
846
|
-
> - It is important to note that the `interceptor` function should return a function that follows the `Dispatcher.dispatch` signature.
|
|
847
|
-
> - Any fork of the chain of `interceptors` can lead to unexpected results.
|
|
848
|
-
>
|
|
849
|
-
> **Interceptor Stack Visualization:**
|
|
850
|
-
> ```
|
|
851
|
-
> compose([interceptor1, interceptor2, interceptor3])
|
|
852
|
-
>
|
|
853
|
-
> Request Flow:
|
|
854
|
-
> ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
855
|
-
> │ Request │───▶│interceptor3 │───▶│interceptor2 │───▶│interceptor1 │───▶│ dispatcher │
|
|
856
|
-
> └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ .dispatch │
|
|
857
|
-
> ▲ ▲ ▲ └─────────────┘
|
|
858
|
-
> │ │ │ ▲
|
|
859
|
-
> (called first) (called second) (called last) │
|
|
860
|
-
> │
|
|
861
|
-
> ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
862
|
-
> │ Response │◀───│interceptor3 │◀───│interceptor2 │◀───│interceptor1 │◀─────────┘
|
|
863
|
-
> └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
|
|
864
|
-
>
|
|
865
|
-
> The interceptors are composed in reverse order due to function composition.
|
|
866
|
-
> ```
|
|
671
|
+
### `dispatcher.compose(interceptors[, interceptor])`
|
|
867
672
|
|
|
868
|
-
|
|
673
|
+
<!-- YAML
|
|
674
|
+
added: v6.9.0
|
|
675
|
+
-->
|
|
869
676
|
|
|
870
|
-
*
|
|
677
|
+
* `interceptors` {Array|Function} An array of interceptor functions, or the first
|
|
678
|
+
of several interceptors passed as separate arguments.
|
|
679
|
+
* `...interceptor` {Function} Additional interceptors when passed as separate
|
|
680
|
+
arguments.
|
|
681
|
+
* Returns: {Dispatcher} A new dispatcher that applies the interceptors over the
|
|
682
|
+
current dispatcher's [`dispatch()`][] method.
|
|
871
683
|
|
|
872
|
-
|
|
684
|
+
Composes a new dispatcher from the current dispatcher and the given
|
|
685
|
+
interceptors. Each interceptor is a function that takes a `dispatch` method and
|
|
686
|
+
returns a `dispatch`-like function with the same signature
|
|
687
|
+
(`(options, handler)`).
|
|
873
688
|
|
|
874
|
-
|
|
689
|
+
The order of the interceptors matters: the last interceptor in the chain is the
|
|
690
|
+
first to be called. Forking the chain of interceptors can lead to unexpected
|
|
691
|
+
results.
|
|
875
692
|
|
|
876
|
-
|
|
693
|
+
```text
|
|
694
|
+
compose([interceptor1, interceptor2, interceptor3])
|
|
877
695
|
|
|
878
|
-
|
|
696
|
+
Request flow:
|
|
697
|
+
Request -> interceptor3 -> interceptor2 -> interceptor1 -> dispatcher.dispatch
|
|
698
|
+
(called first) (called second) (called last)
|
|
699
|
+
```
|
|
879
700
|
|
|
880
|
-
```
|
|
881
|
-
|
|
701
|
+
```mjs
|
|
702
|
+
import { Client, RedirectHandler } from 'undici'
|
|
882
703
|
|
|
883
704
|
const redirectInterceptor = dispatch => {
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
if (!maxRedirections) {
|
|
888
|
-
return dispatch(opts, handler)
|
|
889
|
-
}
|
|
705
|
+
return (opts, handler) => {
|
|
706
|
+
const { maxRedirections } = opts
|
|
890
707
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
maxRedirections,
|
|
894
|
-
opts,
|
|
895
|
-
handler
|
|
896
|
-
)
|
|
897
|
-
opts = { ...opts, maxRedirections: 0 } // Stop sub dispatcher from also redirecting.
|
|
898
|
-
return dispatch(opts, redirectHandler)
|
|
708
|
+
if (!maxRedirections) {
|
|
709
|
+
return dispatch(opts, handler)
|
|
899
710
|
}
|
|
711
|
+
|
|
712
|
+
const redirectHandler = new RedirectHandler(dispatch, maxRedirections, opts, handler)
|
|
713
|
+
opts = { ...opts, maxRedirections: 0 } // Stop the sub-dispatcher from also redirecting.
|
|
714
|
+
return dispatch(opts, redirectHandler)
|
|
715
|
+
}
|
|
900
716
|
}
|
|
901
717
|
|
|
902
|
-
const client = new Client('http://localhost:3000')
|
|
903
|
-
.compose(redirectInterceptor)
|
|
718
|
+
const client = new Client('http://localhost:3000').compose(redirectInterceptor)
|
|
904
719
|
|
|
905
720
|
await client.request({ path: '/', method: 'GET' })
|
|
906
721
|
```
|
|
907
722
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
```js
|
|
911
|
-
const { Client, RedirectHandler, RetryHandler } = require('undici')
|
|
912
|
-
|
|
913
|
-
const redirectInterceptor = dispatch => {
|
|
914
|
-
return (opts, handler) => {
|
|
915
|
-
const { maxRedirections } = opts
|
|
916
|
-
|
|
917
|
-
if (!maxRedirections) {
|
|
918
|
-
return dispatch(opts, handler)
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
const redirectHandler = new RedirectHandler(
|
|
922
|
-
dispatch,
|
|
923
|
-
maxRedirections,
|
|
924
|
-
opts,
|
|
925
|
-
handler
|
|
926
|
-
)
|
|
927
|
-
opts = { ...opts, maxRedirections: 0 }
|
|
928
|
-
return dispatch(opts, redirectHandler)
|
|
929
|
-
}
|
|
930
|
-
}
|
|
723
|
+
Interceptors can be chained by calling `compose()` repeatedly:
|
|
931
724
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
return dispatch(
|
|
935
|
-
opts,
|
|
936
|
-
new RetryHandler(opts, {
|
|
937
|
-
handler,
|
|
938
|
-
dispatch
|
|
939
|
-
})
|
|
940
|
-
)
|
|
941
|
-
}
|
|
942
|
-
}
|
|
725
|
+
```mjs
|
|
726
|
+
import { Client } from 'undici'
|
|
943
727
|
|
|
944
728
|
const client = new Client('http://localhost:3000')
|
|
945
729
|
.compose(redirectInterceptor)
|
|
@@ -948,445 +732,96 @@ const client = new Client('http://localhost:3000')
|
|
|
948
732
|
await client.request({ path: '/', method: 'GET' })
|
|
949
733
|
```
|
|
950
734
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
The `redirect` interceptor allows you to customize the way your dispatcher handles redirects.
|
|
956
|
-
|
|
957
|
-
It accepts the same arguments as the [`RedirectHandler` constructor](/docs/docs/api/RedirectHandler.md).
|
|
958
|
-
|
|
959
|
-
**Example - Basic Redirect Interceptor**
|
|
960
|
-
|
|
961
|
-
```js
|
|
962
|
-
const { Client, interceptors } = require("undici");
|
|
963
|
-
const { redirect } = interceptors;
|
|
964
|
-
|
|
965
|
-
const client = new Client("http://service.example").compose(
|
|
966
|
-
redirect({ maxRedirections: 3, throwOnMaxRedirect: true })
|
|
967
|
-
);
|
|
968
|
-
client.request({ path: "/" })
|
|
969
|
-
```
|
|
970
|
-
|
|
971
|
-
##### `retry`
|
|
972
|
-
|
|
973
|
-
The `retry` interceptor allows you to customize the way your dispatcher handles retries.
|
|
974
|
-
|
|
975
|
-
It accepts the same arguments as the [`RetryHandler` constructor](/docs/docs/api/RetryHandler.md).
|
|
976
|
-
|
|
977
|
-
**Example - Basic Redirect Interceptor**
|
|
978
|
-
|
|
979
|
-
```js
|
|
980
|
-
const { Client, interceptors } = require("undici");
|
|
981
|
-
const { retry } = interceptors;
|
|
982
|
-
|
|
983
|
-
const client = new Client("http://service.example").compose(
|
|
984
|
-
retry({
|
|
985
|
-
maxRetries: 3,
|
|
986
|
-
minTimeout: 1000,
|
|
987
|
-
maxTimeout: 10000,
|
|
988
|
-
timeoutFactor: 2,
|
|
989
|
-
retryAfter: true,
|
|
990
|
-
})
|
|
991
|
-
);
|
|
992
|
-
```
|
|
993
|
-
|
|
994
|
-
##### `dump`
|
|
995
|
-
|
|
996
|
-
The `dump` interceptor enables you to dump the response body from a request upon a given limit.
|
|
997
|
-
|
|
998
|
-
**Options**
|
|
999
|
-
- `maxSize` - The maximum size (in bytes) of the response body to dump. If the size of the request's body exceeds this value then the connection will be closed. Default: `1048576`.
|
|
1000
|
-
|
|
1001
|
-
> The `Dispatcher#options` also gets extended with the options `dumpMaxSize`, `abortOnDumped`, and `waitForTrailers` which can be used to configure the interceptor at a request-per-request basis.
|
|
1002
|
-
|
|
1003
|
-
**Example - Basic Dump Interceptor**
|
|
1004
|
-
|
|
1005
|
-
```js
|
|
1006
|
-
const { Client, interceptors } = require("undici");
|
|
1007
|
-
const { dump } = interceptors;
|
|
1008
|
-
|
|
1009
|
-
const client = new Client("http://service.example").compose(
|
|
1010
|
-
dump({
|
|
1011
|
-
maxSize: 1024,
|
|
1012
|
-
})
|
|
1013
|
-
);
|
|
1014
|
-
|
|
1015
|
-
// or
|
|
1016
|
-
client.dispatch(
|
|
1017
|
-
{
|
|
1018
|
-
path: "/",
|
|
1019
|
-
method: "GET",
|
|
1020
|
-
dumpMaxSize: 1024,
|
|
1021
|
-
},
|
|
1022
|
-
handler
|
|
1023
|
-
);
|
|
1024
|
-
```
|
|
1025
|
-
|
|
1026
|
-
##### `dns`
|
|
1027
|
-
|
|
1028
|
-
The `dns` interceptor enables you to cache DNS lookups for a given duration, per origin.
|
|
1029
|
-
|
|
1030
|
-
>It is well suited for scenarios where you want to cache DNS lookups to avoid the overhead of resolving the same domain multiple times
|
|
1031
|
-
|
|
1032
|
-
**Options**
|
|
1033
|
-
- `maxTTL` - The maximum time-to-live (in milliseconds) of the DNS cache. It should be a positive integer. Default: `10000`.
|
|
1034
|
-
- Set `0` to disable TTL.
|
|
1035
|
-
- `maxItems` - The maximum number of items to cache. It should be a positive integer. Default: `Infinity`.
|
|
1036
|
-
- `dualStack` - Whether to resolve both IPv4 and IPv6 addresses. Default: `true`.
|
|
1037
|
-
- It will also attempt a happy-eyeballs-like approach to connect to the available addresses in case of a connection failure.
|
|
1038
|
-
- `affinity` - Whether to use IPv4 or IPv6 addresses. Default: `4`.
|
|
1039
|
-
- It can be either `4` or `6`.
|
|
1040
|
-
- It will only take effect if `dualStack` is `false`.
|
|
1041
|
-
- `lookup: (hostname: string, options: LookupOptions, callback: (err: NodeJS.ErrnoException | null, addresses: DNSInterceptorRecord[]) => void) => void` - Custom lookup function. Default: `dns.lookup`.
|
|
1042
|
-
- For more info see [dns.lookup](https://nodejs.org/api/dns.html#dnslookuphostname-options-callback).
|
|
1043
|
-
- `pick: (origin: URL, records: DNSInterceptorRecords, affinity: 4 | 6) => DNSInterceptorRecord` - Custom pick function. Default: `RoundRobin`.
|
|
1044
|
-
- The function should return a single record from the records array.
|
|
1045
|
-
- By default a simplified version of Round Robin is used.
|
|
1046
|
-
- The `records` property can be mutated to store the state of the balancing algorithm.
|
|
1047
|
-
- `storage: DNSStorage` - Custom storage for resolved DNS records
|
|
1048
|
-
|
|
1049
|
-
> The `Dispatcher#options` also gets extended with the options `dns.affinity`, `dns.dualStack`, `dns.lookup` and `dns.pick` which can be used to configure the interceptor at a request-per-request basis.
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
**DNSInterceptorRecord**
|
|
1053
|
-
It represents a DNS record.
|
|
1054
|
-
- `family` - (`number`) The IP family of the address. It can be either `4` or `6`.
|
|
1055
|
-
- `address` - (`string`) The IP address.
|
|
1056
|
-
|
|
1057
|
-
**DNSInterceptorOriginRecords**
|
|
1058
|
-
It represents a map of DNS IP addresses records for a single origin.
|
|
1059
|
-
- `4.ips` - (`DNSInterceptorRecord[] | null`) The IPv4 addresses.
|
|
1060
|
-
- `6.ips` - (`DNSInterceptorRecord[] | null`) The IPv6 addresses.
|
|
1061
|
-
|
|
1062
|
-
**DNSStorage**
|
|
1063
|
-
It represents a storage object for resolved DNS records.
|
|
1064
|
-
- `size` - (`number`) current size of the storage.
|
|
1065
|
-
- `get` - (`(origin: string) => DNSInterceptorOriginRecords | null`) method to get the records for a given origin.
|
|
1066
|
-
- `set` - (`(origin: string, records: DNSInterceptorOriginRecords | null, options: { ttl: number }) => void`) method to set the records for a given origin.
|
|
1067
|
-
- `delete` - (`(origin: string) => void`) method to delete records for a given origin.
|
|
1068
|
-
- `full` - (`() => boolean`) method to check if the storage is full, if returns `true`, DNS lookup will be skipped in this interceptor and new records will not be stored.
|
|
1069
|
-
|
|
1070
|
-
**Example - Basic DNS Interceptor**
|
|
1071
|
-
|
|
1072
|
-
```js
|
|
1073
|
-
const { Client, interceptors } = require("undici");
|
|
1074
|
-
const { dns } = interceptors;
|
|
1075
|
-
|
|
1076
|
-
const client = new Agent().compose([
|
|
1077
|
-
dns({ ...opts })
|
|
1078
|
-
])
|
|
1079
|
-
|
|
1080
|
-
const response = await client.request({
|
|
1081
|
-
origin: `http://localhost:3030`,
|
|
1082
|
-
...requestOpts
|
|
1083
|
-
})
|
|
1084
|
-
```
|
|
1085
|
-
|
|
1086
|
-
**Example - DNS Interceptor and LRU cache as a storage**
|
|
1087
|
-
|
|
1088
|
-
```js
|
|
1089
|
-
const { Client, interceptors } = require("undici");
|
|
1090
|
-
const QuickLRU = require("quick-lru");
|
|
1091
|
-
const { dns } = interceptors;
|
|
1092
|
-
|
|
1093
|
-
const lru = new QuickLRU({ maxSize: 100 });
|
|
1094
|
-
|
|
1095
|
-
const lruAdapter = {
|
|
1096
|
-
get size() {
|
|
1097
|
-
return lru.size;
|
|
1098
|
-
},
|
|
1099
|
-
get(origin) {
|
|
1100
|
-
return lru.get(origin);
|
|
1101
|
-
},
|
|
1102
|
-
set(origin, records, { ttl }) {
|
|
1103
|
-
lru.set(origin, records, { maxAge: ttl });
|
|
1104
|
-
},
|
|
1105
|
-
delete(origin) {
|
|
1106
|
-
lru.delete(origin);
|
|
1107
|
-
},
|
|
1108
|
-
full() {
|
|
1109
|
-
// For LRU cache, we can always store new records,
|
|
1110
|
-
// old records will be evicted automatically
|
|
1111
|
-
return false;
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
const client = new Agent().compose([
|
|
1116
|
-
dns({ storage: lruAdapter })
|
|
1117
|
-
])
|
|
1118
|
-
|
|
1119
|
-
const response = await client.request({
|
|
1120
|
-
origin: `http://localhost:3030`,
|
|
1121
|
-
...requestOpts
|
|
1122
|
-
})
|
|
1123
|
-
```
|
|
1124
|
-
|
|
1125
|
-
##### `responseError`
|
|
1126
|
-
|
|
1127
|
-
The `responseError` interceptor throws an error for responses with status code errors (>= 400).
|
|
1128
|
-
|
|
1129
|
-
**Example**
|
|
1130
|
-
|
|
1131
|
-
```js
|
|
1132
|
-
const { Client, interceptors } = require("undici");
|
|
1133
|
-
const { responseError } = interceptors;
|
|
1134
|
-
|
|
1135
|
-
const client = new Client("http://service.example").compose(
|
|
1136
|
-
responseError()
|
|
1137
|
-
);
|
|
1138
|
-
|
|
1139
|
-
// Will throw a ResponseError for status codes >= 400
|
|
1140
|
-
await client.request({
|
|
1141
|
-
method: "GET",
|
|
1142
|
-
path: "/"
|
|
1143
|
-
});
|
|
1144
|
-
```
|
|
1145
|
-
|
|
1146
|
-
##### `decompress`
|
|
1147
|
-
|
|
1148
|
-
⚠️ The decompress interceptor is experimental and subject to change.
|
|
1149
|
-
|
|
1150
|
-
The `decompress` interceptor automatically decompresses response bodies that are compressed with gzip, deflate, brotli, or zstd compression. It removes the `content-encoding` and `content-length` headers from decompressed responses and supports RFC-9110 compliant multiple encodings.
|
|
1151
|
-
|
|
1152
|
-
**Options**
|
|
1153
|
-
|
|
1154
|
-
- `skipErrorResponses` - Whether to skip decompression for error responses (status codes >= 400). Default: `true`.
|
|
1155
|
-
- `skipStatusCodes` - Array of status codes to skip decompression for. Default: `[204, 304]`.
|
|
1156
|
-
|
|
1157
|
-
**Example - Basic Decompress Interceptor**
|
|
1158
|
-
|
|
1159
|
-
```js
|
|
1160
|
-
const { Client, interceptors } = require("undici");
|
|
1161
|
-
const { decompress } = interceptors;
|
|
1162
|
-
|
|
1163
|
-
const client = new Client("http://service.example").compose(
|
|
1164
|
-
decompress()
|
|
1165
|
-
);
|
|
1166
|
-
|
|
1167
|
-
// Automatically decompresses gzip/deflate/brotli/zstd responses
|
|
1168
|
-
const response = await client.request({
|
|
1169
|
-
method: "GET",
|
|
1170
|
-
path: "/"
|
|
1171
|
-
});
|
|
1172
|
-
```
|
|
1173
|
-
|
|
1174
|
-
**Example - Custom Options**
|
|
1175
|
-
|
|
1176
|
-
```js
|
|
1177
|
-
const { Client, interceptors } = require("undici");
|
|
1178
|
-
const { decompress } = interceptors;
|
|
1179
|
-
|
|
1180
|
-
const client = new Client("http://service.example").compose(
|
|
1181
|
-
decompress({
|
|
1182
|
-
skipErrorResponses: false, // Decompress 5xx responses
|
|
1183
|
-
skipStatusCodes: [204, 304, 201] // Skip these status codes
|
|
1184
|
-
})
|
|
1185
|
-
);
|
|
1186
|
-
```
|
|
1187
|
-
|
|
1188
|
-
**Supported Encodings**
|
|
1189
|
-
|
|
1190
|
-
- `gzip` / `x-gzip` - GZIP compression
|
|
1191
|
-
- `deflate` / `x-compress` - DEFLATE compression
|
|
1192
|
-
- `br` - Brotli compression
|
|
1193
|
-
- `zstd` - Zstandard compression
|
|
1194
|
-
- Multiple encodings (e.g., `gzip, deflate`) are supported per RFC-9110
|
|
1195
|
-
|
|
1196
|
-
**Behavior**
|
|
1197
|
-
|
|
1198
|
-
- Skips decompression for status codes < 200 or >= 400 (configurable)
|
|
1199
|
-
- Skips decompression for 204 No Content and 304 Not Modified by default
|
|
1200
|
-
- Removes `content-encoding` and `content-length` headers when decompressing
|
|
1201
|
-
- Passes through unsupported encodings unchanged
|
|
1202
|
-
- Handles case-insensitive encoding names
|
|
1203
|
-
- Supports streaming decompression without buffering
|
|
1204
|
-
|
|
1205
|
-
##### `Cache Interceptor`
|
|
1206
|
-
|
|
1207
|
-
The `cache` interceptor implements client-side response caching as described in
|
|
1208
|
-
[RFC9111](https://www.rfc-editor.org/rfc/rfc9111.html).
|
|
1209
|
-
|
|
1210
|
-
**Options**
|
|
1211
|
-
|
|
1212
|
-
- `store` - The [`CacheStore`](/docs/docs/api/CacheStore.md) to store and retrieve responses from. Default is [`MemoryCacheStore`](/docs/docs/api/CacheStore.md#memorycachestore).
|
|
1213
|
-
- `methods` - The [**safe** HTTP methods](https://www.rfc-editor.org/rfc/rfc9110#section-9.2.1) to cache the response of.
|
|
1214
|
-
- `cacheByDefault` - The default expiration time to cache responses by if they don't have an explicit expiration and cannot have an heuristic expiry computed. If this isn't present, responses neither with an explicit expiration nor heuristically cacheable will not be cached. Default `undefined`.
|
|
1215
|
-
- `type` - The [type of cache](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching#types_of_caches) for Undici to act as. Can be `shared` or `private`. Default `shared`. `private` implies privately cacheable responses will be cached and potentially shared with other users of your application.
|
|
1216
|
-
|
|
1217
|
-
**Usage with `fetch`**
|
|
1218
|
-
|
|
1219
|
-
```js
|
|
1220
|
-
const { Agent, cacheStores, interceptors, setGlobalDispatcher } = require('undici')
|
|
1221
|
-
|
|
1222
|
-
const client = new Agent().compose(interceptors.cache({
|
|
1223
|
-
store: new cacheStores.MemoryCacheStore({
|
|
1224
|
-
maxSize: 100 * 1024 * 1024, // 100MB
|
|
1225
|
-
maxCount: 1000,
|
|
1226
|
-
maxEntrySize: 5 * 1024 * 1024 // 5MB
|
|
1227
|
-
})
|
|
1228
|
-
}))
|
|
1229
|
-
|
|
1230
|
-
setGlobalDispatcher(client)
|
|
1231
|
-
|
|
1232
|
-
// First request goes to the network and is cached when cache headers allow it.
|
|
1233
|
-
const first = await fetch('https://example.com/data')
|
|
1234
|
-
|
|
1235
|
-
// Second request can be served from cache according to RFC9111 rules.
|
|
1236
|
-
const second = await fetch('https://example.com/data')
|
|
1237
|
-
```
|
|
1238
|
-
|
|
1239
|
-
##### `Deduplicate Interceptor`
|
|
1240
|
-
|
|
1241
|
-
The `deduplicate` interceptor deduplicates concurrent identical requests. When multiple identical requests are made while one is already in-flight, only one request is sent to the origin server, and all waiting handlers receive the same response. This reduces server load and improves performance.
|
|
1242
|
-
|
|
1243
|
-
**Options**
|
|
1244
|
-
|
|
1245
|
-
- `methods` - The [**safe** HTTP methods](https://www.rfc-editor.org/rfc/rfc9110#section-9.2.1) to deduplicate. Default `['GET']`.
|
|
1246
|
-
- `skipHeaderNames` - Header names that, if present in a request, will cause the request to skip deduplication entirely. Useful for headers like `idempotency-key` where presence indicates unique processing. Header name matching is case-insensitive. Default `[]`.
|
|
1247
|
-
- `excludeHeaderNames` - Header names to exclude from the deduplication key. Requests with different values for these headers will still be deduplicated together. Useful for headers like `x-request-id` that vary per request but shouldn't affect deduplication. Header name matching is case-insensitive. Default `[]`.
|
|
1248
|
-
- `maxBufferSize` - Maximum bytes buffered per paused waiting deduplicated handler. If a waiting handler remains paused and exceeds this threshold, it is failed with an abort error to prevent unbounded memory growth. Default `5 * 1024 * 1024`.
|
|
1249
|
-
|
|
1250
|
-
**Usage**
|
|
1251
|
-
|
|
1252
|
-
```js
|
|
1253
|
-
const { Client, interceptors } = require("undici");
|
|
1254
|
-
const { deduplicate, cache } = interceptors;
|
|
1255
|
-
|
|
1256
|
-
// Deduplicate only
|
|
1257
|
-
const client = new Client("http://service.example").compose(
|
|
1258
|
-
deduplicate()
|
|
1259
|
-
);
|
|
1260
|
-
|
|
1261
|
-
// Deduplicate with caching
|
|
1262
|
-
const clientWithCache = new Client("http://service.example").compose(
|
|
1263
|
-
deduplicate(),
|
|
1264
|
-
cache()
|
|
1265
|
-
);
|
|
1266
|
-
```
|
|
1267
|
-
|
|
1268
|
-
Requests are considered identical if they have the same:
|
|
1269
|
-
- Origin
|
|
1270
|
-
- HTTP method
|
|
1271
|
-
- Path
|
|
1272
|
-
- Request headers (excluding any headers specified in `excludeHeaderNames`)
|
|
1273
|
-
|
|
1274
|
-
All deduplicated requests receive the complete response including status code, headers, and body.
|
|
1275
|
-
|
|
1276
|
-
For observability, request deduplication events are published to the `undici:request:pending-requests` [diagnostic channel](/docs/docs/api/DiagnosticsChannel.md#undicirequestpending-requests).
|
|
1277
|
-
|
|
1278
|
-
## Instance Events
|
|
735
|
+
For the full list of built-in interceptors provided by undici, see [Interceptors](Interceptors.md).
|
|
736
|
+
For an example of a custom interceptor that wraps handler callbacks, see
|
|
737
|
+
[Custom interceptors](Interceptors.md#custom-interceptors).
|
|
1279
738
|
|
|
1280
739
|
### Event: `'connect'`
|
|
1281
740
|
|
|
1282
|
-
|
|
741
|
+
<!-- YAML
|
|
742
|
+
added: v4.0.0
|
|
743
|
+
-->
|
|
744
|
+
|
|
745
|
+
* `origin` {URL}
|
|
746
|
+
* `targets` {Array<Dispatcher>}
|
|
1283
747
|
|
|
1284
|
-
|
|
1285
|
-
* **targets** `Array<Dispatcher>`
|
|
748
|
+
Emitted when the dispatcher has connected to the origin.
|
|
1286
749
|
|
|
1287
750
|
### Event: `'disconnect'`
|
|
1288
751
|
|
|
1289
|
-
|
|
752
|
+
<!-- YAML
|
|
753
|
+
added: v4.0.0
|
|
754
|
+
-->
|
|
1290
755
|
|
|
1291
|
-
*
|
|
1292
|
-
*
|
|
1293
|
-
*
|
|
756
|
+
* `origin` {URL}
|
|
757
|
+
* `targets` {Array<Dispatcher>}
|
|
758
|
+
* `error` {Error}
|
|
1294
759
|
|
|
1295
760
|
Emitted when the dispatcher has been disconnected from the origin.
|
|
1296
761
|
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
762
|
+
For HTTP/2, this event is also emitted when the dispatcher receives a
|
|
763
|
+
[GOAWAY frame][], with an error whose message is `HTTP/2: "GOAWAY" frame received`
|
|
764
|
+
and whose code is `UND_ERR_INFO`. Because of the binary-framing nature of the
|
|
765
|
+
protocol, a request may hang if a frame is received between the `HEADER` and
|
|
766
|
+
`DATA` frames. It is recommended to handle this event and close the dispatcher to
|
|
767
|
+
create a new HTTP/2 session.
|
|
1300
768
|
|
|
1301
769
|
### Event: `'connectionError'`
|
|
1302
770
|
|
|
1303
|
-
|
|
771
|
+
<!-- YAML
|
|
772
|
+
added: v4.0.0
|
|
773
|
+
-->
|
|
1304
774
|
|
|
1305
|
-
*
|
|
1306
|
-
*
|
|
1307
|
-
*
|
|
775
|
+
* `origin` {URL}
|
|
776
|
+
* `targets` {Array<Dispatcher>}
|
|
777
|
+
* `error` {Error}
|
|
1308
778
|
|
|
1309
|
-
Emitted when dispatcher fails to connect to
|
|
1310
|
-
origin.
|
|
779
|
+
Emitted when the dispatcher fails to connect to the origin.
|
|
1311
780
|
|
|
1312
781
|
### Event: `'drain'`
|
|
1313
782
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
[
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
]
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
### Example 3 - Iterable
|
|
1361
|
-
|
|
1362
|
-
```js
|
|
1363
|
-
new Headers({
|
|
1364
|
-
'content-length': '123',
|
|
1365
|
-
'content-type': 'text/plain',
|
|
1366
|
-
connection: 'keep-alive',
|
|
1367
|
-
host: 'mysite.com',
|
|
1368
|
-
accept: '*/*'
|
|
1369
|
-
})
|
|
1370
|
-
```
|
|
1371
|
-
or
|
|
1372
|
-
```js
|
|
1373
|
-
new Map([
|
|
1374
|
-
['content-length', '123'],
|
|
1375
|
-
['content-type', 'text/plain'],
|
|
1376
|
-
['connection', 'keep-alive'],
|
|
1377
|
-
['host', 'mysite.com'],
|
|
1378
|
-
['accept', '*/*']
|
|
1379
|
-
])
|
|
1380
|
-
```
|
|
1381
|
-
or
|
|
1382
|
-
```js
|
|
1383
|
-
{
|
|
1384
|
-
*[Symbol.iterator] () {
|
|
1385
|
-
yield ['content-length', '123']
|
|
1386
|
-
yield ['content-type', 'text/plain']
|
|
1387
|
-
yield ['connection', 'keep-alive']
|
|
1388
|
-
yield ['host', 'mysite.com']
|
|
1389
|
-
yield ['accept', '*/*']
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
```
|
|
783
|
+
<!-- YAML
|
|
784
|
+
added: v4.0.0
|
|
785
|
+
-->
|
|
786
|
+
|
|
787
|
+
* `origin` {URL}
|
|
788
|
+
|
|
789
|
+
Emitted when the dispatcher is no longer busy and further [`dispatch()`][] calls
|
|
790
|
+
can make progress.
|
|
791
|
+
|
|
792
|
+
## Pre-built interceptors
|
|
793
|
+
|
|
794
|
+
For the full reference of built-in interceptors (`dump`, `retry`, `redirect`,
|
|
795
|
+
`decompress`, `responseError`, `dns`, `cache`, `deduplicate`) and their options,
|
|
796
|
+
see [Interceptors](Interceptors.md).
|
|
797
|
+
|
|
798
|
+
[Fastify]: https://fastify.dev
|
|
799
|
+
[GOAWAY frame]: https://webconcepts.info/concepts/http2-frame-type/0x7
|
|
800
|
+
[HTTP `CONNECT`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/CONNECT
|
|
801
|
+
[MDN: Protocol upgrade mechanism]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism
|
|
802
|
+
[`.arrayBuffer()`]: https://fetch.spec.whatwg.org/#dom-body-arraybuffer
|
|
803
|
+
[`.blob()`]: https://fetch.spec.whatwg.org/#dom-body-blob
|
|
804
|
+
[`.bytes()`]: https://fetch.spec.whatwg.org/#dom-body-bytes
|
|
805
|
+
[`.json()`]: https://fetch.spec.whatwg.org/#dom-body-json
|
|
806
|
+
[`.text()`]: https://fetch.spec.whatwg.org/#dom-body-text
|
|
807
|
+
[`'drain'`]: #event-drain
|
|
808
|
+
[`Agent`]: Agent.md#class-agent
|
|
809
|
+
[`BalancedPool`]: BalancedPool.md#class-balancedpool
|
|
810
|
+
[`Client`]: Client.md#class-client
|
|
811
|
+
[`EventEmitter`]: https://nodejs.org/api/events.html#class-eventemitter
|
|
812
|
+
[`Pool`]: Pool.md#class-pool
|
|
813
|
+
[`Readable`]: https://nodejs.org/api/stream.html#class-streamreadable
|
|
814
|
+
[`Writable`]: https://nodejs.org/api/stream.html#class-streamwritable
|
|
815
|
+
[`close()`]: #dispatcherclosecallback
|
|
816
|
+
[`connect()`]: #dispatcherconnectoptions-callback
|
|
817
|
+
[`destroy()`]: #dispatcherdestroyerror-callback
|
|
818
|
+
[`dispatch()`]: #dispatcherdispatchoptions-handler
|
|
819
|
+
[`dispatcher.compose()`]: #dispatchercomposeinterceptors-interceptor
|
|
820
|
+
[`dispatcher.dispatch()`]: #dispatcherdispatchoptions-handler
|
|
821
|
+
[`dispatcher.request()`]: #dispatcherrequestoptions-callback
|
|
822
|
+
[`pipeline()`]: #dispatcherpipelineoptions-handler
|
|
823
|
+
[`request()`]: #dispatcherrequestoptions-callback
|
|
824
|
+
[`stream()`]: #dispatcherstreamoptions-factory-callback
|
|
825
|
+
[`stream.pipeline()`]: https://nodejs.org/api/stream.html#streampipelinesource-transforms-destination-options
|
|
826
|
+
[`upgrade()`]: #dispatcherupgradeoptions-callback
|
|
827
|
+
[body mixin]: https://fetch.spec.whatwg.org/#body-mixin
|