Skip to content

Commit

Permalink
refactor: replace internal redirects with high-level ones
Browse files Browse the repository at this point in the history
  • Loading branch information
metcoder95 committed Feb 17, 2024
1 parent c4d7662 commit 5798f6d
Show file tree
Hide file tree
Showing 11 changed files with 82 additions and 43 deletions.
1 change: 0 additions & 1 deletion docs/api/Agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ Returns: `Agent`
Extends: [`PoolOptions`](Pool.md#parameter-pooloptions)

* **factory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Pool(origin, opts)`
* **maxRedirections** `Integer` - Default: `0`. The number of HTTP redirection to follow unless otherwise specified in `DispatchOptions`.

## Instance Properties

Expand Down
21 changes: 1 addition & 20 deletions lib/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
const { InvalidArgumentError } = require('./core/errors')
const { kClients, kRunning, kClose, kDestroy, kDispatch } = require('./core/symbols')
const DispatcherBase = require('./dispatcher-base')
const RedirectHandler = require('./handler/RedirectHandler')
const Pool = require('./pool')
const Client = require('./client')
const util = require('./core/util')

const kOnConnect = Symbol('onConnect')
const kOnDisconnect = Symbol('onDisconnect')
const kOnConnectionError = Symbol('onConnectionError')
const kMaxRedirections = Symbol('maxRedirections')
const kOnDrain = Symbol('onDrain')
const kFactory = Symbol('factory')
const kOptions = Symbol('options')
Expand All @@ -23,7 +21,7 @@ function defaultFactory (origin, opts) {
}

class Agent extends DispatcherBase {
constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
constructor ({ factory = defaultFactory, connect, ...options } = {}) {
super()

if (typeof factory !== 'function') {
Expand All @@ -34,16 +32,11 @@ class Agent extends DispatcherBase {
throw new InvalidArgumentError('connect must be a function or an object')
}

if (!Number.isInteger(maxRedirections) || maxRedirections < 0) {
throw new InvalidArgumentError('maxRedirections must be a positive number')
}

if (connect && typeof connect !== 'function') {
connect = { ...connect }
}

this[kOptions] = { ...util.deepClone(options), connect }
this[kMaxRedirections] = maxRedirections
this[kFactory] = factory
this[kClients] = new Map()

Expand Down Expand Up @@ -95,18 +88,6 @@ class Agent extends DispatcherBase {
this[kClients].set(key, dispatcher)
}

if (this[kMaxRedirections] > 0) {
return dispatcher.dispatch(
opts,
new RedirectHandler(
dispatcher.dispatch.bind(dispatcher),
this[kMaxRedirections],
opts,
handler
)
)
}

return dispatcher.dispatch(opts, handler)
}

Expand Down
11 changes: 11 additions & 0 deletions lib/api/api-connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const { AsyncResource } = require('node:async_hooks')
const { InvalidArgumentError, RequestAbortedError, SocketError } = require('../core/errors')
const util = require('../core/util')
const RedirectHandler = require('../handler/RedirectHandler')
const { addSignal, removeSignal } = require('./abort-signal')

class ConnectHandler extends AsyncResource {
Expand Down Expand Up @@ -91,6 +92,16 @@ function connect (opts, callback) {

try {
const connectHandler = new ConnectHandler(opts, callback)

if (opts?.maxRedirections != null && (!Number.isInteger(opts?.maxRedirections) || opts?.maxRedirections < 0)) {
throw new InvalidArgumentError('maxRedirections must be a positive number')
}

if (opts?.maxRedirections > 0) {
RedirectHandler.buildDispatch(this, opts.maxRedirections)(opts, connectHandler)
return
}

this.dispatch({ ...opts, method: 'CONNECT' }, connectHandler)
} catch (err) {
if (typeof callback !== 'function') {
Expand Down
17 changes: 14 additions & 3 deletions lib/api/api-pipeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ const {
Duplex,
PassThrough
} = require('node:stream')
const assert = require('node:assert')
const { AsyncResource } = require('node:async_hooks')
const {
InvalidArgumentError,
InvalidReturnValueError,
RequestAbortedError
} = require('../core/errors')
const util = require('../core/util')
const { AsyncResource } = require('node:async_hooks')
const RedirectHandler = require('../handler/RedirectHandler')
const { addSignal, removeSignal } = require('./abort-signal')
const assert = require('node:assert')

const kResume = Symbol('resume')

Expand Down Expand Up @@ -239,7 +240,17 @@ class PipelineHandler extends AsyncResource {
function pipeline (opts, handler) {
try {
const pipelineHandler = new PipelineHandler(opts, handler)
this.dispatch({ ...opts, body: pipelineHandler.req }, pipelineHandler)

if (opts?.maxRedirections != null && (!Number.isInteger(opts?.maxRedirections) || opts?.maxRedirections < 0)) {
throw new InvalidArgumentError('maxRedirections must be a positive number')
}

if (opts?.maxRedirections > 0) {
RedirectHandler.buildDispatch(this, opts.maxRedirections)({ ...opts, body: pipelineHandler.req }, pipelineHandler)
} else {
this.dispatch({ ...opts, body: pipelineHandler.req }, pipelineHandler)
}

return pipelineHandler.ret
} catch (err) {
return new PassThrough().destroy(err)
Expand Down
16 changes: 14 additions & 2 deletions lib/api/api-request.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
'use strict'

const { AsyncResource } = require('node:async_hooks')
const Readable = require('./readable')
const {
InvalidArgumentError,
RequestAbortedError
} = require('../core/errors')
const util = require('../core/util')
const RedirectHandler = require('../handler/RedirectHandler')
const { getResolveErrorBodyCallback } = require('./util')
const { AsyncResource } = require('node:async_hooks')
const { addSignal, removeSignal } = require('./abort-signal')

class RequestHandler extends AsyncResource {
Expand Down Expand Up @@ -166,7 +167,18 @@ function request (opts, callback) {
}

try {
this.dispatch(opts, new RequestHandler(opts, callback))
const handler = new RequestHandler(opts, callback)

if (opts?.maxRedirections != null && (!Number.isInteger(opts?.maxRedirections) || opts?.maxRedirections < 0)) {
throw new InvalidArgumentError('maxRedirections must be a positive number')
}

if (opts?.maxRedirections > 0) {
RedirectHandler.buildDispatch(this, opts.maxRedirections)(opts, handler)
return
}

this.dispatch(opts, handler)
} catch (err) {
if (typeof callback !== 'function') {
throw err
Expand Down
14 changes: 13 additions & 1 deletion lib/api/api-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const {
RequestAbortedError
} = require('../core/errors')
const util = require('../core/util')
const RedirectHandler = require('../handler/RedirectHandler')
const { getResolveErrorBodyCallback } = require('./util')
const { AsyncResource } = require('node:async_hooks')
const { addSignal, removeSignal } = require('./abort-signal')
Expand Down Expand Up @@ -207,7 +208,18 @@ function stream (opts, factory, callback) {
}

try {
this.dispatch(opts, new StreamHandler(opts, factory, callback))
const handler = new StreamHandler(opts, factory, callback)

if (opts?.maxRedirections != null && (!Number.isInteger(opts?.maxRedirections) || opts?.maxRedirections < 0)) {
throw new InvalidArgumentError('maxRedirections must be a positive number')
}

if (opts?.maxRedirections > 0) {
RedirectHandler.buildDispatch(this, opts.maxRedirections)(opts, handler)
return
}

this.dispatch(opts, handler)
} catch (err) {
if (typeof callback !== 'function') {
throw err
Expand Down
20 changes: 16 additions & 4 deletions lib/api/api-upgrade.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
'use strict'

const { InvalidArgumentError, RequestAbortedError, SocketError } = require('../core/errors')
const assert = require('node:assert')
const { AsyncResource } = require('node:async_hooks')
const { InvalidArgumentError, RequestAbortedError, SocketError } = require('../core/errors')
const util = require('../core/util')
const RedirectHandler = require('../handler/RedirectHandler')
const { addSignal, removeSignal } = require('./abort-signal')
const assert = require('node:assert')

class UpgradeHandler extends AsyncResource {
constructor (opts, callback) {
Expand Down Expand Up @@ -88,11 +89,22 @@ function upgrade (opts, callback) {

try {
const upgradeHandler = new UpgradeHandler(opts, callback)
this.dispatch({
const upgradeOpts = {
...opts,
method: opts.method || 'GET',
upgrade: opts.protocol || 'Websocket'
}, upgradeHandler)
}

if (opts?.maxRedirections != null && (!Number.isInteger(opts?.maxRedirections) || opts?.maxRedirections < 0)) {
throw new InvalidArgumentError('maxRedirections must be a positive number')
}

if (opts?.maxRedirections > 0) {
RedirectHandler.buildDispatch(this, opts.maxRedirections)(upgradeOpts, upgradeHandler)
return
}

this.dispatch(upgradeOpts, upgradeHandler)
} catch (err) {
if (typeof callback !== 'function') {
throw err
Expand Down
7 changes: 0 additions & 7 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ const {
kBodyTimeout,
kStrictContentLength,
kConnector,
kMaxRedirections,
kMaxRequests,
kCounter,
kClose,
Expand Down Expand Up @@ -137,7 +136,6 @@ class Client extends DispatcherBase {
tls,
strictContentLength,
maxCachedSessions,
maxRedirections,
connect,
maxRequestsPerClient,
localAddress,
Expand Down Expand Up @@ -206,10 +204,6 @@ class Client extends DispatcherBase {
throw new InvalidArgumentError('connect must be a function or an object')
}

if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
throw new InvalidArgumentError('maxRedirections must be a positive number')
}

if (maxRequestsPerClient != null && (!Number.isInteger(maxRequestsPerClient) || maxRequestsPerClient < 0)) {
throw new InvalidArgumentError('maxRequestsPerClient must be a positive number')
}
Expand Down Expand Up @@ -268,7 +262,6 @@ class Client extends DispatcherBase {
this[kBodyTimeout] = bodyTimeout != null ? bodyTimeout : 300e3
this[kHeadersTimeout] = headersTimeout != null ? headersTimeout : 300e3
this[kStrictContentLength] = strictContentLength == null ? true : strictContentLength
this[kMaxRedirections] = maxRedirections
this[kMaxRequests] = maxRequestsPerClient
this[kClosedResolve] = null
this[kMaxResponseSize] = maxResponseSize > -1 ? maxResponseSize : -1
Expand Down
9 changes: 9 additions & 0 deletions lib/handler/RedirectHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ class BodyAsyncIterable {
}

class RedirectHandler {
static buildDispatch (dispatcher, maxRedirections) {
if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
throw new InvalidArgumentError('maxRedirections must be a positive number')
}

const dispatch = dispatcher.dispatch.bind(dispatcher)
return (opts, originalHandler) => dispatch(opts, new RedirectHandler(dispatch, maxRedirections, opts, originalHandler))
}

constructor (dispatch, maxRedirections, opts, handler) {
if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
throw new InvalidArgumentError('maxRedirections must be a positive number')
Expand Down
7 changes: 3 additions & 4 deletions test/redirect-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ for (const factory of [

const server = await startRedirectingServer()

const { statusCode, headers, body: bodyStream, context: { history } } = await request(t, server, { maxRedirections: 10 }, `http://${server}/300?key=value`)
const { statusCode, headers, body: bodyStream, context: { history } } = await request(t, server, undefined, `http://${server}/300?key=value`, { maxRedirections: 10 })
const body = await bodyStream.text()

t.strictEqual(statusCode, 200)
Expand Down Expand Up @@ -380,10 +380,9 @@ for (const factory of [
t = tspl(t, { plan: 1 })

try {
await request(t, 'localhost', {
await request(t, 'localhost', undefined, 'http://localhost', {
method: 'GET',
maxRedirections: 'INVALID'
}, 'http://localhost', {
method: 'GET'
})

t.fail('Did not throw')
Expand Down
2 changes: 1 addition & 1 deletion test/redirect-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ test('should always have a history with the final URL even if no redirections we
t.strictEqual(body.join(''), `GET /5 key=value :: host@${server} connection@keep-alive`)
})

test('should not follow redirection by default if not using RedirectAgent', async t => {
test('should not follow redirection by default if max redirect = 0', async t => {
t = tspl(t, { plan: 3 })

const body = []
Expand Down

0 comments on commit 5798f6d

Please sign in to comment.