Skip to content

Commit

Permalink
feat: Add callback context and redirect history 2 (nodejs#769)
Browse files Browse the repository at this point in the history
* feat: Add callback context and redirect history.

* feat: Updated TypeScript types.

* fix: Make sure context exist before modifying it.

* fixup

* fixup

* fixup

* perf: reuse redirect handler

* fixup

* fixup

* fixuP

* fixup

* fixup

* Apply suggestions from code review

* fixup

Co-authored-by: Paolo Insogna <paolo@cowtech.it>
  • Loading branch information
2 people authored and crysmags committed Feb 27, 2024
1 parent 9562f53 commit 82fc585
Show file tree
Hide file tree
Showing 13 changed files with 130 additions and 135 deletions.
2 changes: 1 addition & 1 deletion docs/api/Agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Returns: `Agent`
Extends: [`ClientOptions`](docs/api/Pool.md#parameter-pooloptions)

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

## Instance Properties

Expand Down
5 changes: 4 additions & 1 deletion docs/api/Dispatcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ Returns: `void`

#### Parameter: `DispatchHandlers`

* **onConnect** `(abort: () => void) => void` - Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails.
* **onConnect** `(abort: () => void, context: object) => void` - Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails.
* **onError** `(error: Error) => void` - Invoked when an error has occurred.
* **onUpgrade** `(statusCode: number, headers: Buffer[] | null, socket: Duplex) => void` (optional) - Invoked when request is upgraded. Required if `DispatchOptions.upgrade` is defined or `DispatchOptions.method === 'CONNECT'`.
* **onHeaders** `(statusCode: number, headers: Buffer[] | null, resume: () => void) => boolean` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests.
Expand Down Expand Up @@ -327,6 +327,7 @@ Extends: [`RequestOptions`](#parameter-requestoptions)
* **headers** `IncomingHttpHeaders`
* **opaque** `unknown`
* **body** `stream.Readable`
* **context** `object`

#### Example 1 - Pipeline Echo

Expand Down Expand Up @@ -419,6 +420,7 @@ The `RequestOptions.method` property should not be value `'CONNECT'`.
* **trailers** `Record<string, string>` - This object starts out
as empty and will be mutated to contain trailers after `body` has emitted `'end'`.
* **opaque** `unknown`
* **context** `object`

#### Example 1 - Basic GET Request

Expand Down Expand Up @@ -567,6 +569,7 @@ Returns: `void | Promise<StreamData>` - Only returns a `Promise` if no `callback

* **opaque** `unknown`
* **trailers** `Record<string, string>`
* **context** `object`

#### Example 1 - Basic GET stream request

Expand Down
2 changes: 1 addition & 1 deletion lib/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ class Agent extends Dispatcher {

const { maxRedirections = this[kMaxRedirections] } = opts

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

Expand Down
8 changes: 5 additions & 3 deletions lib/api/api-connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,21 @@ class ConnectHandler extends AsyncResource {
addSignal(this, signal)
}

onConnect (abort) {
onConnect (abort, context) {
if (!this.callback) {
throw new RequestAbortedError()
}

this.abort = abort
this.context = context
}

onHeaders () {
throw new SocketError('bad connect')
}

onUpgrade (statusCode, headers, socket) {
const { callback, opaque } = this
const { callback, opaque, context } = this

removeSignal(this)

Expand All @@ -48,7 +49,8 @@ class ConnectHandler extends AsyncResource {
statusCode,
headers: util.parseHeaders(headers),
socket,
opaque
opaque,
context
})
}

Expand Down
9 changes: 6 additions & 3 deletions lib/api/api-pipeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class PipelineHandler extends AsyncResource {
this.opaque = opaque || null
this.handler = handler
this.abort = null
this.context = null

this.req = new PipelineRequest().on('error', util.nop)

Expand Down Expand Up @@ -137,7 +138,7 @@ class PipelineHandler extends AsyncResource {
addSignal(this, signal)
}

onConnect (abort) {
onConnect (abort, context) {
const { ret, res } = this

assert(!res, 'pipeline cannot be retried')
Expand All @@ -147,10 +148,11 @@ class PipelineHandler extends AsyncResource {
}

this.abort = abort
this.context = context
}

onHeaders (statusCode, headers, resume) {
const { opaque, handler } = this
const { opaque, handler, context } = this

if (statusCode < 200) {
return
Expand All @@ -165,7 +167,8 @@ class PipelineHandler extends AsyncResource {
statusCode,
headers: util.parseHeaders(headers),
opaque,
body: this.res
body: this.res,
context
})
} catch (err) {
this.res.on('error', util.nop)
Expand Down
9 changes: 6 additions & 3 deletions lib/api/api-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class RequestHandler extends AsyncResource {
this.abort = null
this.body = body
this.trailers = {}
this.context = null

if (util.isStream(body)) {
body.on('error', (err) => {
Expand All @@ -75,16 +76,17 @@ class RequestHandler extends AsyncResource {
addSignal(this, signal)
}

onConnect (abort) {
onConnect (abort, context) {
if (!this.callback) {
throw new RequestAbortedError()
}

this.abort = abort
this.context = context
}

onHeaders (statusCode, headers, resume) {
const { callback, opaque, abort } = this
const { callback, opaque, abort, context } = this

if (statusCode < 200) {
return
Expand All @@ -100,7 +102,8 @@ class RequestHandler extends AsyncResource {
headers: util.parseHeaders(headers),
trailers: this.trailers,
opaque,
body
body,
context
})
}

Expand Down
9 changes: 6 additions & 3 deletions lib/api/api-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class StreamHandler extends AsyncResource {
this.callback = callback
this.res = null
this.abort = null
this.context = null
this.trailers = null
this.body = body

Expand All @@ -60,16 +61,17 @@ class StreamHandler extends AsyncResource {
addSignal(this, signal)
}

onConnect (abort) {
onConnect (abort, context) {
if (!this.callback) {
throw new RequestAbortedError()
}

this.abort = abort
this.context = context
}

onHeaders (statusCode, headers, resume) {
const { factory, opaque } = this
const { factory, opaque, context } = this

if (statusCode < 200) {
return
Expand All @@ -79,7 +81,8 @@ class StreamHandler extends AsyncResource {
const res = this.runInAsyncScope(factory, null, {
statusCode,
headers: util.parseHeaders(headers),
opaque
opaque,
context
})

if (
Expand Down
9 changes: 6 additions & 3 deletions lib/api/api-upgrade.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,26 @@ class UpgradeHandler extends AsyncResource {
this.opaque = opaque || null
this.callback = callback
this.abort = null
this.context = null

addSignal(this, signal)
}

onConnect (abort) {
onConnect (abort, context) {
if (!this.callback) {
throw new RequestAbortedError()
}

this.abort = abort
this.context = null
}

onHeaders () {
throw new SocketError('bad upgrade')
}

onUpgrade (statusCode, headers, socket) {
const { callback, opaque } = this
const { callback, opaque, context } = this

assert.strictEqual(statusCode, 101)

Expand All @@ -50,7 +52,8 @@ class UpgradeHandler extends AsyncResource {
this.runInAsyncScope(callback, null, null, {
headers: util.parseHeaders(headers),
socket,
opaque
opaque,
context
})
}

Expand Down
2 changes: 1 addition & 1 deletion lib/core/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class Request {
assert(!this.aborted)
assert(!this.completed)

return this[kHandler].onConnect(abort)
return this[kHandler].onConnect(abort, this.context)
}

onHeaders (statusCode, headers, resume) {
Expand Down
27 changes: 17 additions & 10 deletions lib/handler/redirect.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@
const { InvalidArgumentError } = require('../core/errors')
const util = require('../core/util')

const redirectableStatusCodes = [300, 301, 302, 303, 307, 308]

class RedirectHandler {
constructor (agent, { maxRedirections, ...opts }, handler) {
this.agent = agent
this.location = null
this.abort = null
this.opts = opts
this.redirections = maxRedirections
this.maxRedirections = maxRedirections
this.handler = handler
this.history = []
}

onConnect (abort) {
onConnect (abort, context = {}) {
context.history = this.history

this.abort = abort
this.handler.onConnect(abort)
this.handler.onConnect(abort, context)
}

onUpgrade (statusCode, headers, socket) {
Expand All @@ -27,17 +32,15 @@ class RedirectHandler {
}

onHeaders (statusCode, headers, resume) {
if ([300, 301, 302, 303, 307, 308].indexOf(statusCode) === -1) {
return this.handler.onHeaders(statusCode, headers, resume)
}

this.location = this.redirections === 0 ? null : parseLocation(headers)
this.location = this.history.length >= this.maxRedirections
? null
: parseLocation(statusCode, headers)

if (!this.location) {
return this.handler.onHeaders(statusCode, headers, resume)
}

this.redirections -= 1
this.history.push(new URL(this.opts.path, this.opts.origin).toString())

const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin))
const path = search ? `${pathname}${search}` : pathname
Expand Down Expand Up @@ -102,7 +105,11 @@ class RedirectHandler {
}
}

function parseLocation (headers) {
function parseLocation (statusCode, headers) {
if (redirectableStatusCodes.indexOf(statusCode) === -1) {
return null
}

for (let i = 0; i < headers.length; i += 2) {
if (headers[i].length === 8 && headers[i].toString().toLowerCase() === 'location') {
return headers[i + 1]
Expand Down
Loading

0 comments on commit 82fc585

Please sign in to comment.