-
-
Notifications
You must be signed in to change notification settings - Fork 758
feat: add new dispatch compose #2826
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
560d35b
5337ea0
8019e59
1d54170
051caa1
b45ecad
9048eb5
7656e1b
9f0631e
5588a1d
04c8acb
8ac252d
8c8c064
9d1a6c0
0fd9ea7
53f4cfa
6580d84
1d987a2
d66530f
b932ad2
20d3a33
2d59acc
2f5982e
eb065f7
620f9bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,68 @@ | ||||||||||||||||||||||||||||||||||||||||||
| # Interceptors | ||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Generically I don't think we need a special page for the interceptors. They are more confusing than anything else. Let's only have Having the use specify |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Undici provides a way to intercept requests and responses using interceptors. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Interceptors are a way to modify the request or response before it is sent or received by the original dispatcher, apply custom logic to a network request, or even cancel the request, connect through a proxy for the origin, etc. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Within Undici there are a set of pre-built that can be used, on top of that, you can create your own interceptors. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ## Pre-built interceptors | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ### `proxy` | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| The `proxy` interceptor allows you to connect to a proxy server before connecting to the origin server. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| It accepts the same arguments as the [`ProxyAgent` constructor](./ProxyAgent.md). | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| #### Example - Basic Proxy Interceptor | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ```js | ||||||||||||||||||||||||||||||||||||||||||
| const { Client, interceptors } = require("undici"); | ||||||||||||||||||||||||||||||||||||||||||
| const { proxy } = interceptors; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const client = new Client("http://example.com"); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| client.compose(proxy("http://proxy.com")); | ||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ### `redirect` | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| The `redirect` interceptor allows you to customize the way your dispatcher handles redirects. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| It accepts the same arguments as the [`RedirectHandler` constructor](./RedirectHandler.md). | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| #### Example - Basic Redirect Interceptor | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ```js | ||||||||||||||||||||||||||||||||||||||||||
| const { Client, interceptors } = require("undici"); | ||||||||||||||||||||||||||||||||||||||||||
| const { redirect } = interceptors; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const client = new Client("http://example.com"); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| client.compose(redirect({ maxRedirections: 3, throwOnMaxRedirects: true })); | ||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ### `retry` | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| The `retry` interceptor allows you to customize the way your dispatcher handles retries. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| It accepts the same arguments as the [`RetryHandler` constructor](./RetryHandler.md). | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| #### Example - Basic Redirect Interceptor | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ```js | ||||||||||||||||||||||||||||||||||||||||||
| const { Client, interceptors } = require("undici"); | ||||||||||||||||||||||||||||||||||||||||||
| const { retry } = interceptors; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const client = new Client("http://example.com"); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| client.compose( | ||||||||||||||||||||||||||||||||||||||||||
| retry({ | ||||||||||||||||||||||||||||||||||||||||||
| maxRetries: 3, | ||||||||||||||||||||||||||||||||||||||||||
| minTimeout: 1000, | ||||||||||||||||||||||||||||||||||||||||||
| maxTimeout: 10000, | ||||||||||||||||||||||||||||||||||||||||||
| timeoutFactor: 2, | ||||||||||||||||||||||||||||||||||||||||||
| retryAfter: true, | ||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,11 @@ | ||
| 'use strict' | ||
|
|
||
| const EventEmitter = require('node:events') | ||
|
|
||
| const kDispatcherVersion = Symbol.for('undici.dispatcher.version') | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this the same simbol we use for the global? I think we should use the same. |
||
|
|
||
| class Dispatcher extends EventEmitter { | ||
| [kDispatcherVersion] = 1 | ||
|
|
||
| dispatch () { | ||
| throw new Error('not implemented') | ||
| } | ||
|
|
@@ -14,6 +17,28 @@ class Dispatcher extends EventEmitter { | |
| destroy () { | ||
| throw new Error('not implemented') | ||
| } | ||
|
|
||
| compose (...args) { | ||
| // So we handle [interceptor1, interceptor2] or interceptor1, interceptor2, ... | ||
| const interceptors = Array.isArray(args[0]) ? args[0] : args | ||
| let dispatcher = this | ||
| for (const interceptor of interceptors) { | ||
| if (interceptor == null) { | ||
| continue | ||
| } | ||
|
|
||
| if (typeof interceptor !== 'function') { | ||
|
ronag marked this conversation as resolved.
|
||
| throw new Error('invalid interceptor') | ||
| } | ||
|
|
||
| dispatcher = interceptor(dispatcher) ?? dispatcher | ||
|
ronag marked this conversation as resolved.
Outdated
|
||
|
|
||
| if (dispatcher[kDispatcherVersion] !== 1) { | ||
| throw new Error('invalid dispatcher') | ||
| } | ||
| } | ||
| return dispatcher | ||
| } | ||
| } | ||
|
|
||
| module.exports = Dispatcher | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| 'use strict' | ||
|
|
||
| const { InvalidArgumentError } = require('../core/errors') | ||
| const ProxyAgent = require('../dispatcher/proxy-agent') | ||
| const Dispatcher = require('../dispatcher/dispatcher') | ||
|
|
||
| class ProxyInterceptor extends Dispatcher { | ||
| constructor (dispatcher, opts) { | ||
| if (dispatcher == null) { | ||
| throw new InvalidArgumentError( | ||
| 'Dispatcher instance is mandatory for ProxyInterceptor' | ||
| ) | ||
| } | ||
|
|
||
| if (typeof opts === 'string') { | ||
| opts = { uri: opts } | ||
| } | ||
|
|
||
| if (!opts || (!opts.uri && !(opts instanceof URL))) { | ||
| throw new InvalidArgumentError( | ||
| 'Proxy opts.uri or instance of URL is mandatory' | ||
| ) | ||
| } | ||
|
|
||
| if (opts.auth && opts.token) { | ||
| throw new InvalidArgumentError( | ||
| 'opts.auth cannot be used in combination with opts.token' | ||
| ) | ||
|
ronag marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| super() | ||
| this.dispatcher = dispatcher | ||
| this.agent = new ProxyAgent(opts) | ||
| } | ||
|
|
||
| dispatch (opts, handler) { | ||
| return this.agent.dispatch(opts, handler) | ||
| } | ||
|
|
||
| close () { | ||
| return this.dispatcher.close().then(() => this.agent.close()) | ||
| } | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't an interceptor?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's what I was referring to when I suggested that I couldn't think of any other way without re-working the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will have to investigate this
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It sounds to me like this should not be an interceptor.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's skip this for now so we can land. |
||
|
|
||
| module.exports = ProxyInterceptor | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| 'use strict' | ||
|
|
||
| const { InvalidArgumentError } = require('../core/errors') | ||
|
metcoder95 marked this conversation as resolved.
Outdated
|
||
| const Dispatcher = require('../dispatcher/dispatcher') | ||
| const RedirectHandler = require('../handler/RedirectHandler') | ||
|
|
||
| class RedirectDispatcher extends Dispatcher { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This class is useful as its own export.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can add it as a |
||
| #opts | ||
| #dispatcher | ||
|
|
||
| constructor (dispatcher, opts) { | ||
| super() | ||
|
|
||
| this.#dispatcher = dispatcher | ||
| this.#opts = opts | ||
| } | ||
|
|
||
| dispatch (opts, handler) { | ||
| return this.#dispatcher.dispatch( | ||
| opts, | ||
| new RedirectHandler(this.#dispatcher, opts, this.#opts, handler) | ||
| ) | ||
| } | ||
|
|
||
| close (...args) { | ||
| return this.#dispatcher.close(...args) | ||
| } | ||
|
|
||
| destroy (...args) { | ||
| return this.#dispatcher.destroy(...args) | ||
| } | ||
| } | ||
|
|
||
| module.exports = opts => { | ||
| if (opts?.maxRedirections == null || opts?.maxRedirections === 0) { | ||
| return null | ||
| } | ||
|
|
||
| if (!Number.isInteger(opts.maxRedirections) || opts.maxRedirections < 0) { | ||
| throw new InvalidArgumentError('maxRedirections must be a positive number') | ||
| } | ||
|
|
||
| return dispatcher => new RedirectDispatcher(dispatcher, opts) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| 'use strict' | ||
|
|
||
| const Dispatcher = require('../dispatcher/dispatcher') | ||
| const RetryHandler = require('../handler/RetryHandler') | ||
|
|
||
| class RetryDispatcher extends Dispatcher { | ||
| #dispatcher | ||
| #opts | ||
|
|
||
| constructor (dispatcher, opts) { | ||
| super() | ||
|
|
||
| this.#dispatcher = dispatcher | ||
| this.#opts = opts | ||
| } | ||
|
|
||
| dispatch (opts, handler) { | ||
| return this.#dispatcher.dispatch( | ||
| opts, | ||
| new RetryHandler(this.#dispatcher, opts, this.#opts, handler) | ||
| ) | ||
| } | ||
|
|
||
| close (...args) { | ||
| return this.#dispatcher.close(...args) | ||
| } | ||
|
|
||
| destroy (...args) { | ||
| return this.#dispatcher.destroy(...args) | ||
| } | ||
| } | ||
|
|
||
| module.exports = opts => { | ||
| return dispatcher => new RetryDispatcher(dispatcher, opts) | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.