Skip to content

Commit 649185a

Browse files
authored
Dispatch compose (#2795)
* feat: new interceptors API * WIP: compose
1 parent cba8e0f commit 649185a

18 files changed

+374
-504
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,5 @@ fuzz-results-*.json
7979
# Bundle output
8080
undici-fetch.js
8181
/test/imports/undici-import.js
82+
83+
.tap

index.js

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,7 @@ const MockClient = require('./lib/mock/mock-client')
1414
const MockAgent = require('./lib/mock/mock-agent')
1515
const MockPool = require('./lib/mock/mock-pool')
1616
const mockErrors = require('./lib/mock/mock-errors')
17-
const ProxyAgent = require('./lib/proxy-agent')
18-
const RetryAgent = require('./lib/retry-agent')
19-
const RetryHandler = require('./lib/handler/RetryHandler')
2017
const { getGlobalDispatcher, setGlobalDispatcher } = require('./lib/global')
21-
const DecoratorHandler = require('./lib/handler/DecoratorHandler')
22-
const RedirectHandler = require('./lib/handler/RedirectHandler')
2318

2419
Object.assign(Dispatcher.prototype, api)
2520

@@ -28,12 +23,12 @@ module.exports.Client = Client
2823
module.exports.Pool = Pool
2924
module.exports.BalancedPool = BalancedPool
3025
module.exports.Agent = Agent
31-
module.exports.ProxyAgent = ProxyAgent
32-
module.exports.RetryAgent = RetryAgent
33-
module.exports.RetryHandler = RetryHandler
3426

35-
module.exports.DecoratorHandler = DecoratorHandler
36-
module.exports.RedirectHandler = RedirectHandler
27+
module.exports.interceptor = {
28+
redirect: require('./lib/interceptor/redirect'),
29+
retry: require('./lib/interceptor/retry'),
30+
proxy: require('./lib/interceptor/proxy')
31+
}
3732

3833
module.exports.buildConnector = buildConnector
3934
module.exports.errors = errors

lib/api/api-connect.js

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
const { AsyncResource } = require('node:async_hooks')
44
const { InvalidArgumentError, RequestAbortedError, SocketError } = require('../core/errors')
55
const util = require('../core/util')
6-
const RedirectHandler = require('../handler/RedirectHandler')
6+
const redirect = require('../interceptor/redirect')
77
const { addSignal, removeSignal } = require('./abort-signal')
88

99
class ConnectHandler extends AsyncResource {
@@ -91,19 +91,9 @@ function connect (opts, callback) {
9191
}
9292

9393
try {
94-
const connectHandler = new ConnectHandler(opts, callback)
95-
const connectOptions = { ...opts, method: 'CONNECT' }
96-
97-
if (opts?.maxRedirections != null && (!Number.isInteger(opts?.maxRedirections) || opts?.maxRedirections < 0)) {
98-
throw new InvalidArgumentError('maxRedirections must be a positive number')
99-
}
100-
101-
if (opts?.maxRedirections > 0) {
102-
RedirectHandler.buildDispatch(this, opts.maxRedirections)(connectOptions, connectHandler)
103-
return
104-
}
105-
106-
this.dispatch(connectOptions, connectHandler)
94+
this
95+
.compose(redirect(opts))
96+
.dispatch({ ...opts, method: opts?.method || 'CONNECT' }, new ConnectHandler(opts, callback))
10797
} catch (err) {
10898
if (typeof callback !== 'function') {
10999
throw err

lib/api/api-pipeline.js

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const {
1313
RequestAbortedError
1414
} = require('../core/errors')
1515
const util = require('../core/util')
16-
const RedirectHandler = require('../handler/RedirectHandler')
16+
const redirect = require('../interceptor/redirect')
1717
const { addSignal, removeSignal } = require('./abort-signal')
1818

1919
const kResume = Symbol('resume')
@@ -241,15 +241,9 @@ function pipeline (opts, handler) {
241241
try {
242242
const pipelineHandler = new PipelineHandler(opts, handler)
243243

244-
if (opts?.maxRedirections != null && (!Number.isInteger(opts?.maxRedirections) || opts?.maxRedirections < 0)) {
245-
throw new InvalidArgumentError('maxRedirections must be a positive number')
246-
}
247-
248-
if (opts?.maxRedirections > 0) {
249-
RedirectHandler.buildDispatch(this, opts.maxRedirections)({ ...opts, body: pipelineHandler.req }, pipelineHandler)
250-
} else {
251-
this.dispatch({ ...opts, body: pipelineHandler.req }, pipelineHandler)
252-
}
244+
this
245+
.compose(redirect(opts))
246+
.dispatch({ ...opts, body: pipelineHandler.req }, pipelineHandler)
253247

254248
return pipelineHandler.ret
255249
} catch (err) {

lib/api/api-request.js

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const {
77
RequestAbortedError
88
} = require('../core/errors')
99
const util = require('../core/util')
10-
const RedirectHandler = require('../handler/RedirectHandler')
10+
const redirect = require('../interceptor/redirect')
1111
const { getResolveErrorBodyCallback } = require('./util')
1212
const { addSignal, removeSignal } = require('./abort-signal')
1313

@@ -167,18 +167,9 @@ function request (opts, callback) {
167167
}
168168

169169
try {
170-
const handler = new RequestHandler(opts, callback)
171-
172-
if (opts?.maxRedirections != null && (!Number.isInteger(opts?.maxRedirections) || opts?.maxRedirections < 0)) {
173-
throw new InvalidArgumentError('maxRedirections must be a positive number')
174-
}
175-
176-
if (opts?.maxRedirections > 0) {
177-
RedirectHandler.buildDispatch(this, opts.maxRedirections)(opts, handler)
178-
return
179-
}
180-
181-
this.dispatch(opts, handler)
170+
this
171+
.compose(redirect(opts))
172+
.dispatch(opts, new RequestHandler(opts, callback))
182173
} catch (err) {
183174
if (typeof callback !== 'function') {
184175
throw err

lib/api/api-stream.js

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const {
77
RequestAbortedError
88
} = require('../core/errors')
99
const util = require('../core/util')
10-
const RedirectHandler = require('../handler/RedirectHandler')
10+
const redirect = require('../interceptor/redirect')
1111
const { getResolveErrorBodyCallback } = require('./util')
1212
const { AsyncResource } = require('node:async_hooks')
1313
const { addSignal, removeSignal } = require('./abort-signal')
@@ -208,18 +208,9 @@ function stream (opts, factory, callback) {
208208
}
209209

210210
try {
211-
const handler = new StreamHandler(opts, factory, callback)
212-
213-
if (opts?.maxRedirections != null && (!Number.isInteger(opts?.maxRedirections) || opts?.maxRedirections < 0)) {
214-
throw new InvalidArgumentError('maxRedirections must be a positive number')
215-
}
216-
217-
if (opts?.maxRedirections > 0) {
218-
RedirectHandler.buildDispatch(this, opts.maxRedirections)(opts, handler)
219-
return
220-
}
221-
222-
this.dispatch(opts, handler)
211+
this
212+
.compose(redirect(opts))
213+
.dispatch(opts, new StreamHandler(opts, factory, callback))
223214
} catch (err) {
224215
if (typeof callback !== 'function') {
225216
throw err

lib/api/api-upgrade.js

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const assert = require('node:assert')
44
const { AsyncResource } = require('node:async_hooks')
55
const { InvalidArgumentError, RequestAbortedError, SocketError } = require('../core/errors')
66
const util = require('../core/util')
7-
const RedirectHandler = require('../handler/RedirectHandler')
7+
const redirect = require('../interceptor/redirect')
88
const { addSignal, removeSignal } = require('./abort-signal')
99

1010
class UpgradeHandler extends AsyncResource {
@@ -88,23 +88,9 @@ function upgrade (opts, callback) {
8888
}
8989

9090
try {
91-
const upgradeHandler = new UpgradeHandler(opts, callback)
92-
const upgradeOpts = {
93-
...opts,
94-
method: opts.method || 'GET',
95-
upgrade: opts.protocol || 'Websocket'
96-
}
97-
98-
if (opts?.maxRedirections != null && (!Number.isInteger(opts?.maxRedirections) || opts?.maxRedirections < 0)) {
99-
throw new InvalidArgumentError('maxRedirections must be a positive number')
100-
}
101-
102-
if (opts?.maxRedirections > 0) {
103-
RedirectHandler.buildDispatch(this, opts.maxRedirections)(upgradeOpts, upgradeHandler)
104-
return
105-
}
106-
107-
this.dispatch(upgradeOpts, upgradeHandler)
91+
this
92+
.compose(redirect(opts))
93+
.dispatch({ ...opts, method: opts?.method || 'GET', upgrade: opts?.protocol || 'Websocket' }, new UpgradeHandler(opts, callback))
10894
} catch (err) {
10995
if (typeof callback !== 'function') {
11096
throw err

lib/dispatcher.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
const EventEmitter = require('node:events')
44

5+
const kDispatcherVersion = Symbol.for('undici.dispatcher.version')
6+
57
class Dispatcher extends EventEmitter {
8+
[kDispatcherVersion] = 1
9+
610
dispatch () {
711
throw new Error('not implemented')
812
}
@@ -14,6 +18,26 @@ class Dispatcher extends EventEmitter {
1418
destroy () {
1519
throw new Error('not implemented')
1620
}
21+
22+
compose (...interceptors) {
23+
let dispatcher = this
24+
for (const interceptor of interceptors) {
25+
if (interceptor == null) {
26+
continue
27+
}
28+
29+
if (typeof interceptor !== 'function') {
30+
throw new Error('invalid interceptor')
31+
}
32+
33+
dispatcher = interceptor(dispatcher) ?? dispatcher
34+
35+
if (dispatcher[kDispatcherVersion] !== 1) {
36+
throw new Error('invalid dispatcher')
37+
}
38+
}
39+
return dispatcher
40+
}
1741
}
1842

1943
module.exports = Dispatcher

lib/handler/DecoratorHandler.js

Lines changed: 0 additions & 31 deletions
This file was deleted.
Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
'use strict'
22

3-
const { kProxy, kClose, kDestroy } = require('./core/symbols')
3+
const { kProxy, kClose, kDestroy } = require('../core/symbols')
44
const { URL } = require('node:url')
5-
const Agent = require('./agent')
6-
const Pool = require('./pool')
7-
const DispatcherBase = require('./dispatcher-base')
8-
const { InvalidArgumentError, RequestAbortedError } = require('./core/errors')
9-
const buildConnector = require('./core/connect')
5+
const Agent = require('../agent')
6+
const Pool = require('../pool')
7+
const DispatcherBase = require('../dispatcher-base')
8+
const { InvalidArgumentError, RequestAbortedError } = require('../core/errors')
9+
const buildConnector = require('../core/connect')
1010

1111
const kAgent = Symbol('proxy agent')
1212
const kClient = Symbol('proxy client')
@@ -39,10 +39,10 @@ function defaultFactory (origin, opts) {
3939
}
4040

4141
class ProxyAgent extends DispatcherBase {
42-
constructor (opts) {
42+
constructor (dispatcher, opts) {
4343
super(opts)
4444
this[kProxy] = buildProxyOptions(opts)
45-
this[kAgent] = new Agent(opts)
45+
this[kAgent] = dispatcher
4646

4747
if (typeof opts === 'string') {
4848
opts = { uri: opts }
@@ -183,4 +183,24 @@ function throwIfProxyAuthIsSent (headers) {
183183
}
184184
}
185185

186-
module.exports = ProxyAgent
186+
module.exports = (opts) => {
187+
if (typeof opts === 'string') {
188+
opts = { uri: opts }
189+
}
190+
191+
if (!opts || !opts.uri) {
192+
throw new InvalidArgumentError('Proxy opts.uri is mandatory')
193+
}
194+
195+
const { clientFactory = defaultFactory } = opts
196+
197+
if (typeof clientFactory !== 'function') {
198+
throw new InvalidArgumentError('Proxy opts.clientFactory must be a function.')
199+
}
200+
201+
if (opts.auth && opts.token) {
202+
throw new InvalidArgumentError('opts.auth cannot be used in combination with opts.token')
203+
}
204+
205+
return (dispatcher) => new ProxyAgent(dispatcher, opts)
206+
}

0 commit comments

Comments
 (0)