Comparing version 4.0.0-alpha.4 to 4.0.0-alpha.5
@@ -22,3 +22,3 @@ # Agent | ||
* **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`. | ||
@@ -25,0 +25,0 @@ ## Instance Properties |
@@ -204,3 +204,3 @@ # Dispatcher | ||
* **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. | ||
@@ -331,2 +331,3 @@ * **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'`. | ||
* **body** `stream.Readable` | ||
* **context** `object` | ||
@@ -424,2 +425,3 @@ #### Example 1 - Pipeline Echo | ||
* **opaque** `unknown` | ||
* **context** `object` | ||
@@ -573,2 +575,3 @@ #### Example 1 - Basic GET Request | ||
* **trailers** `Record<string, string>` | ||
* **context** `object` | ||
@@ -751,2 +754,13 @@ #### Example 1 - Basic GET stream request | ||
### Event: `'connectionError'` | ||
Parameters: | ||
* **origin** `URL` | ||
* **targets** `Array<Dispatcher>` | ||
* **error** `Error` | ||
Emitted when dispatcher fails to connect to | ||
origin. | ||
### Event: `'drain'` | ||
@@ -753,0 +767,0 @@ |
@@ -10,13 +10,13 @@ # Errors | ||
| Error | Error Codes | Description | | ||
| -----------------------------|-----------------------------------|------------------------------------------------| | ||
| `InvalidArgumentError` | `UND_ERR_INVALID_ARG` | passed an invalid argument. | | ||
| `InvalidReturnValueError` | `UND_ERR_INVALID_RETURN_VALUE` | returned an invalid value. | | ||
| `RequestAbortedError` | `UND_ERR_ABORTED` | the request has been aborted by the user | | ||
| `ClientDestroyedError` | `UND_ERR_DESTROYED` | trying to use a destroyed client. | | ||
| `ClientClosedError` | `UND_ERR_CLOSED` | trying to use a closed client. | | ||
| `SocketError` | `UND_ERR_SOCKET` | there is an error with the socket. | | ||
| `NotSupportedError` | `UND_ERR_NOT_SUPPORTED` | encountered unsupported functionality. | | ||
| `ContentLengthMismatchError` | `UND_ERR_CONTENT_LENGTH_MISMATCH`| body does not match content-length header | | ||
| `InformationalError` | `UND_ERR_INFO` | expected error with reason | | ||
| `TrailerMismatchError` | `UND_ERR_TRAILER_MISMATCH` | trailers did not match specification | | ||
| Error | Error Codes | Description | | ||
| ------------------------------------|---------------------------------------|---------------------------------------------------| | ||
| `InvalidArgumentError` | `UND_ERR_INVALID_ARG` | passed an invalid argument. | | ||
| `InvalidReturnValueError` | `UND_ERR_INVALID_RETURN_VALUE` | returned an invalid value. | | ||
| `RequestAbortedError` | `UND_ERR_ABORTED` | the request has been aborted by the user | | ||
| `ClientDestroyedError` | `UND_ERR_DESTROYED` | trying to use a destroyed client. | | ||
| `ClientClosedError` | `UND_ERR_CLOSED` | trying to use a closed client. | | ||
| `SocketError` | `UND_ERR_SOCKET` | there is an error with the socket. | | ||
| `NotSupportedError` | `UND_ERR_NOT_SUPPORTED` | encountered unsupported functionality. | | ||
| `RequestContentLengthMismatchError` | `UND_ERR_REQ_CONTENT_LENGTH_MISMATCH`| request body does not match content-length header | | ||
| `InformationalError` | `UND_ERR_INFO` | expected error with reason | | ||
| `TrailerMismatchError` | `UND_ERR_TRAILER_MISMATCH` | trailers did not match specification | |
@@ -158,3 +158,3 @@ 'use strict' | ||
if (!Number.isInteger(maxRedirections) || maxRedirections < 0) { | ||
if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) { | ||
throw new InvalidArgumentError('maxRedirections must be a positive number') | ||
@@ -161,0 +161,0 @@ } |
@@ -29,3 +29,3 @@ 'use strict' | ||
onConnect (abort) { | ||
onConnect (abort, context) { | ||
if (!this.callback) { | ||
@@ -36,2 +36,3 @@ throw new RequestAbortedError() | ||
this.abort = abort | ||
this.context = context | ||
} | ||
@@ -44,3 +45,3 @@ | ||
onUpgrade (statusCode, headers, socket) { | ||
const { callback, opaque } = this | ||
const { callback, opaque, context } = this | ||
@@ -54,3 +55,4 @@ removeSignal(this) | ||
socket, | ||
opaque | ||
opaque, | ||
context | ||
}) | ||
@@ -57,0 +59,0 @@ } |
@@ -87,2 +87,3 @@ 'use strict' | ||
this.abort = null | ||
this.context = null | ||
@@ -141,3 +142,3 @@ this.req = new PipelineRequest().on('error', util.nop) | ||
onConnect (abort) { | ||
onConnect (abort, context) { | ||
const { ret, res } = this | ||
@@ -152,6 +153,7 @@ | ||
this.abort = abort | ||
this.context = context | ||
} | ||
onHeaders (statusCode, headers, resume) { | ||
const { opaque, handler } = this | ||
const { opaque, handler, context } = this | ||
@@ -171,3 +173,4 @@ if (statusCode < 200) { | ||
opaque, | ||
body: this.res | ||
body: this.res, | ||
context | ||
}) | ||
@@ -174,0 +177,0 @@ } catch (err) { |
@@ -68,2 +68,3 @@ 'use strict' | ||
this.trailers = {} | ||
this.context = null | ||
@@ -79,3 +80,3 @@ if (util.isStream(body)) { | ||
onConnect (abort) { | ||
onConnect (abort, context) { | ||
if (!this.callback) { | ||
@@ -86,6 +87,7 @@ throw new RequestAbortedError() | ||
this.abort = abort | ||
this.context = context | ||
} | ||
onHeaders (statusCode, headers, resume) { | ||
const { callback, opaque, abort } = this | ||
const { callback, opaque, abort, context } = this | ||
@@ -106,3 +108,4 @@ if (statusCode < 200) { | ||
opaque, | ||
body | ||
body, | ||
context | ||
}) | ||
@@ -109,0 +112,0 @@ } |
@@ -51,2 +51,3 @@ 'use strict' | ||
this.abort = null | ||
this.context = null | ||
this.trailers = null | ||
@@ -64,3 +65,3 @@ this.body = body | ||
onConnect (abort) { | ||
onConnect (abort, context) { | ||
if (!this.callback) { | ||
@@ -71,6 +72,7 @@ throw new RequestAbortedError() | ||
this.abort = abort | ||
this.context = context | ||
} | ||
onHeaders (statusCode, headers, resume) { | ||
const { factory, opaque } = this | ||
const { factory, opaque, context } = this | ||
@@ -85,3 +87,4 @@ if (statusCode < 200) { | ||
headers: util.parseHeaders(headers), | ||
opaque | ||
opaque, | ||
context | ||
}) | ||
@@ -88,0 +91,0 @@ |
@@ -26,2 +26,3 @@ 'use strict' | ||
this.abort = null | ||
this.context = null | ||
@@ -31,3 +32,3 @@ addSignal(this, signal) | ||
onConnect (abort) { | ||
onConnect (abort, context) { | ||
if (!this.callback) { | ||
@@ -38,2 +39,3 @@ throw new RequestAbortedError() | ||
this.abort = abort | ||
this.context = null | ||
} | ||
@@ -46,3 +48,3 @@ | ||
onUpgrade (statusCode, headers, socket) { | ||
const { callback, opaque } = this | ||
const { callback, opaque, context } = this | ||
@@ -57,3 +59,4 @@ assert.strictEqual(statusCode, 101) | ||
socket, | ||
opaque | ||
opaque, | ||
context | ||
}) | ||
@@ -60,0 +63,0 @@ } |
@@ -12,3 +12,3 @@ 'use strict' | ||
const { | ||
ContentLengthMismatchError, | ||
RequestContentLengthMismatchError, | ||
TrailerMismatchError, | ||
@@ -27,10 +27,2 @@ InvalidArgumentError, | ||
const { resolve } = require('path') | ||
const { readFileSync } = require('fs') | ||
const constants = require('./llhttp/constants') | ||
const WASM_BUILD = resolve(__dirname, './llhttp/llhttp.wasm') | ||
const EMPTY_BUF = Buffer.alloc(0) | ||
const bin = readFileSync(WASM_BUILD) | ||
const mod = new WebAssembly.Module(bin) | ||
const { | ||
@@ -51,3 +43,5 @@ kUrl, | ||
kConnected, | ||
kConnecting, | ||
kNeedDrain, | ||
kNoRef, | ||
kTLSServerName, | ||
@@ -231,13 +225,3 @@ kTLSSession, | ||
get [kConnected] () { | ||
return ( | ||
this[kSocket] && | ||
!this[kSocket].pending && | ||
// Older versions of Node don't set secureConnecting to false. | ||
(this[kSocket].authorized !== false || | ||
this[kSocket].authorizationError | ||
) && | ||
!this[kSocket].destroyed | ||
) | ||
? 1 | ||
: 0 | ||
return this[kSocket] && !this[kSocket][kConnecting] && !this[kSocket].destroyed ? 1 : 0 | ||
} | ||
@@ -365,3 +349,5 @@ | ||
for (const request of this[kQueue].splice(this[kPendingIdx])) { | ||
const requests = this[kQueue].splice(this[kPendingIdx]) | ||
for (let i = 0; i < requests.length; i++) { | ||
const request = requests[i] | ||
request.onError(err) | ||
@@ -378,4 +364,4 @@ assert(request.aborted) | ||
this[kOnDestroyed] = null | ||
for (const callback of callbacks) { | ||
callback(null, null) | ||
for (let i = 0; i < callbacks.length; i++) { | ||
callbacks[i](null, null) | ||
} | ||
@@ -404,3 +390,22 @@ } | ||
let currentParser = null | ||
let mod, build | ||
const { resolve } = require('path') | ||
const { readFileSync } = require('fs') | ||
const constants = require('./llhttp/constants') | ||
const EMPTY_BUF = Buffer.alloc(0) | ||
try { | ||
build = resolve(__dirname, './llhttp/llhttp_simd.wasm') | ||
const bin = readFileSync(build) | ||
mod = new WebAssembly.Module(bin) | ||
} catch (e) { | ||
// We could check if the error was caused by the simd option not | ||
// being enabled, but the occurring of this other error | ||
// * https://github.com/emscripten-core/emscripten/issues/11495 | ||
// got me to remove that check to avoid breaking Node 12. | ||
build = resolve(__dirname, './llhttp/llhttp.wasm') | ||
const bin = readFileSync(build) | ||
mod = new WebAssembly.Module(bin) | ||
} | ||
const llhttp = new WebAssembly.Instance(mod, { | ||
@@ -412,11 +417,11 @@ env: { | ||
assert.strictEqual(currentParser.ptr, p) | ||
const start = at - currentParser.bufferPtr | ||
const start = at - currentBufferPtr | ||
const end = start + len | ||
return currentParser.onHeaderField(currentParser.bufferRef.slice(start, end)) || 0 | ||
return currentParser.onHeaderField(currentBufferRef.slice(start, end)) || 0 | ||
}, | ||
wasm_on_header_value: (p, at, len) => { | ||
assert.strictEqual(currentParser.ptr, p) | ||
const start = at - currentParser.bufferPtr | ||
const start = at - currentBufferPtr | ||
const end = start + len | ||
return currentParser.onHeaderValue(currentParser.bufferRef.slice(start, end)) || 0 | ||
return currentParser.onHeaderValue(currentBufferRef.slice(start, end)) || 0 | ||
}, | ||
@@ -429,5 +434,5 @@ wasm_on_headers_complete: (p, statusCode, upgrade, shouldKeepAlive) => { | ||
assert.strictEqual(currentParser.ptr, p) | ||
const start = at - currentParser.bufferPtr | ||
const start = at - currentBufferPtr | ||
const end = start + len | ||
return currentParser.onBody(currentParser.bufferRef.slice(start, end)) || 0 | ||
return currentParser.onBody(currentBufferRef.slice(start, end)) || 0 | ||
}, | ||
@@ -443,2 +448,8 @@ wasm_on_message_complete: (p) => { | ||
let currentParser = null | ||
let currentBufferRef = null | ||
let currentBufferSize = 16384 | ||
let currentBufferPtr = llhttp.exports.malloc(currentBufferSize) | ||
let currentBufferView = new Uint8Array(llhttp.exports.memory.buffer, currentBufferPtr, currentBufferSize) | ||
const TIMEOUT_HEADERS = 1 | ||
@@ -454,5 +465,2 @@ const TIMEOUT_BODY = 2 | ||
this.ptr = llhttp.exports.llhttp_alloc(constants.TYPE.RESPONSE) | ||
this.bufferSize = 16384 | ||
this.bufferPtr = llhttp.exports.malloc(this.bufferSize) | ||
this.bufferRef = null | ||
this.client = client | ||
@@ -477,3 +485,11 @@ this.socket = socket | ||
clearTimeout(this.timeout) | ||
this.timeout = value ? setTimeout(onParserTimeout, value, this) : null | ||
if (value) { | ||
this.timeout = setTimeout(onParserTimeout, value, this) | ||
// istanbul ignore else: only for jest | ||
if (this.timeout.unref) { | ||
this.timeout.unref() | ||
} | ||
} else { | ||
this.timeout = null | ||
} | ||
this.timeoutValue = value | ||
@@ -518,13 +534,11 @@ } else if (this.timeout) { | ||
// Be sure the parser buffer can contain `data` | ||
if (data.length > this.bufferSize) { | ||
llhttp.exports.free(this.bufferPtr) | ||
this.bufferSize = Math.ceil(data.length / 4096) * 4096 | ||
this.bufferPtr = llhttp.exports.malloc(this.bufferSize) | ||
if (data.length > currentBufferSize) { | ||
llhttp.exports.free(currentBufferPtr) | ||
currentBufferSize = Math.ceil(data.length / 4096) * 4096 | ||
currentBufferPtr = llhttp.exports.malloc(currentBufferSize) | ||
currentBufferView = new Uint8Array(llhttp.exports.memory.buffer, currentBufferPtr, currentBufferSize) | ||
} | ||
assert(this.bufferSize >= data.length) | ||
// TODO (perf): Can we avoid this copy somehow? | ||
new Uint8Array(llhttp.exports.memory.buffer, this.bufferPtr, this.bufferSize).set(data) | ||
currentBufferView.set(data) | ||
@@ -535,13 +549,13 @@ // Call `execute` on the wasm parser. | ||
// The return value is an error code or `constants.ERROR.OK`. | ||
this.bufferRef = data | ||
currentBufferRef = data | ||
currentParser = this | ||
const ret = llhttp.exports.llhttp_execute(this.ptr, this.bufferPtr, data.length) | ||
const ret = llhttp.exports.llhttp_execute(this.ptr, currentBufferPtr, data.length) | ||
currentParser = null | ||
this.bufferRef = null | ||
currentBufferRef = null | ||
if (ret === constants.ERROR.PAUSED_UPGRADE) { | ||
const offset = llhttp.exports.llhttp_get_error_pos(this.ptr) - this.bufferPtr | ||
const offset = llhttp.exports.llhttp_get_error_pos(this.ptr) - currentBufferPtr | ||
this.onUpgrade(data.slice(offset)) | ||
} else if (ret === constants.ERROR.PAUSED) { | ||
const offset = llhttp.exports.llhttp_get_error_pos(this.ptr) - this.bufferPtr | ||
const offset = llhttp.exports.llhttp_get_error_pos(this.ptr) - currentBufferPtr | ||
this.paused = true | ||
@@ -563,3 +577,2 @@ socket.pause() | ||
assert(this.ptr != null) | ||
assert(this.bufferPtr != null) | ||
assert(currentParser == null) | ||
@@ -570,5 +583,2 @@ | ||
llhttp.exports.free(this.bufferPtr) | ||
this.bufferPtr = null | ||
clearTimeout(this.timeout) | ||
@@ -725,4 +735,5 @@ this.timeout = null | ||
for (let n = 0; n < rawHeaders.length; n += 2) { | ||
const key = rawHeaders[n + 0] | ||
let looking = true | ||
for (let n = 0; n < rawHeaders.length && looking; n += 2) { | ||
const key = rawHeaders[n] | ||
const val = rawHeaders[n + 1] | ||
@@ -732,4 +743,6 @@ | ||
keepAlive = val | ||
looking = !trailers | ||
} else if (!trailers && key.length === 7 && key.toString().toLowerCase() === 'trailer') { | ||
trailers = val | ||
looking = !keepAlive | ||
} | ||
@@ -835,6 +848,7 @@ } | ||
for (const trailer of trailers) { | ||
for (let i = 0; i < trailers.length; i++) { | ||
const trailer = trailers[i] | ||
let found = false | ||
for (let n = 0; n < rawTrailers.length; n += 2) { | ||
const key = rawTrailers[n + 0] | ||
const key = rawTrailers[n] | ||
if (key.length === trailer.length && key.toString().toLowerCase() === trailer.toLowerCase()) { | ||
@@ -910,2 +924,3 @@ found = true | ||
this[kConnecting] = false | ||
client.emit('connect', client[kUrl], [client]) | ||
@@ -935,3 +950,6 @@ resume(client) | ||
// socket error. | ||
for (const request of client[kQueue].splice(client[kRunningIdx])) { | ||
const requests = client[kQueue].splice(client[kRunningIdx]) | ||
for (let i = 0; i < requests.length; i++) { | ||
const request = requests[i] | ||
request.onError(err) | ||
@@ -979,3 +997,5 @@ assert(request.aborted) | ||
// Fail entire queue. | ||
for (const request of client[kQueue].splice(client[kRunningIdx])) { | ||
const requests = client[kQueue].splice(client[kRunningIdx]) | ||
for (let i = 0; i < requests.length; i++) { | ||
const request = requests[i] | ||
request.onError(err) | ||
@@ -997,3 +1017,8 @@ assert(request.aborted) | ||
client.emit('disconnect', client[kUrl], [client], err) | ||
if (this[kConnecting]) { | ||
this[kConnecting] = false | ||
client.emit('connectionError', client[kUrl], [client], err) | ||
} else { | ||
client.emit('disconnect', client[kUrl], [client], err) | ||
} | ||
@@ -1047,2 +1072,4 @@ resume(client) | ||
socket[kNoRef] = false | ||
socket[kConnecting] = true | ||
socket[kWriting] = false | ||
@@ -1096,6 +1123,15 @@ socket[kReset] = false | ||
const socket = client[kSocket] | ||
const connected = client[kConnected] | ||
if (socket) { | ||
if (!connected) { | ||
if (client[kSize] === 0) { | ||
if (!socket[kNoRef] && socket.unref) { | ||
socket.unref() | ||
socket[kNoRef] = true | ||
} | ||
} else if (socket[kNoRef] && socket.ref) { | ||
socket.ref() | ||
socket[kNoRef] = false | ||
} | ||
if (socket[kConnecting]) { | ||
if (socket[kParser].timeoutType !== TIMEOUT_CONNECT) { | ||
@@ -1166,10 +1202,6 @@ socket[kParser].setTimeout(client[kConnectTimeoutValue], TIMEOUT_CONNECT) | ||
if (!connected) { | ||
if (socket.destroyed || socket[kConnecting] || socket[kWriting] || socket[kReset]) { | ||
return | ||
} | ||
if (socket[kWriting] || socket[kReset]) { | ||
return | ||
} | ||
if (client[kRunning] > 0 && !request.idempotent) { | ||
@@ -1266,3 +1298,3 @@ // Non-idempotent request cannot be retried. | ||
if (client[kStrictContentLength]) { | ||
request.onError(new ContentLengthMismatchError()) | ||
request.onError(new RequestContentLengthMismatchError()) | ||
assert(request.aborted) | ||
@@ -1272,3 +1304,3 @@ return false | ||
process.emitWarning(new ContentLengthMismatchError()) | ||
process.emitWarning(new RequestContentLengthMismatchError()) | ||
} | ||
@@ -1287,3 +1319,3 @@ | ||
socket.destroy(new InformationalError('aborted')) | ||
util.destroy(socket, new InformationalError('aborted')) | ||
}) | ||
@@ -1380,7 +1412,7 @@ } catch (err) { | ||
if (client[kStrictContentLength]) { | ||
util.destroy(socket, new ContentLengthMismatchError()) | ||
util.destroy(socket, new RequestContentLengthMismatchError()) | ||
return | ||
} | ||
process.emitWarning(new ContentLengthMismatchError()) | ||
process.emitWarning(new RequestContentLengthMismatchError()) | ||
} | ||
@@ -1435,5 +1467,5 @@ | ||
if (client[kStrictContentLength]) { | ||
err = new ContentLengthMismatchError() | ||
err = new RequestContentLengthMismatchError() | ||
} else { | ||
process.emitWarning(new ContentLengthMismatchError()) | ||
process.emitWarning(new RequestContentLengthMismatchError()) | ||
} | ||
@@ -1440,0 +1472,0 @@ } |
@@ -91,7 +91,7 @@ 'use strict' | ||
class ContentLengthMismatchError extends UndiciError { | ||
class RequestContentLengthMismatchError extends UndiciError { | ||
constructor (message) { | ||
super(message) | ||
Error.captureStackTrace(this, ContentLengthMismatchError) | ||
this.name = 'ContentLengthMismatchError' | ||
Error.captureStackTrace(this, RequestContentLengthMismatchError) | ||
this.name = 'RequestContentLengthMismatchError' | ||
this.message = message || 'Request body length does not match content-length header' | ||
@@ -157,3 +157,3 @@ this.code = 'UND_ERR_CONTENT_LENGTH_MISMATCH' | ||
BodyTimeoutError, | ||
ContentLengthMismatchError, | ||
RequestContentLengthMismatchError, | ||
ConnectTimeoutError, | ||
@@ -160,0 +160,0 @@ TrailerMismatchError, |
@@ -86,7 +86,9 @@ 'use strict' | ||
for (let i = 0; i < headers.length; i += 2) { | ||
processHeader(this, headers[i + 0], headers[i + 1]) | ||
processHeader(this, headers[i], headers[i + 1]) | ||
} | ||
} else if (headers && typeof headers === 'object') { | ||
for (const [key, val] of Object.entries(headers)) { | ||
processHeader(this, key, val) | ||
const keys = Object.keys(headers) | ||
for (let i = 0; i < keys.length; i++) { | ||
const key = keys[i] | ||
processHeader(this, key, headers[key]) | ||
} | ||
@@ -130,3 +132,3 @@ } else if (headers != null) { | ||
return this[kHandler].onConnect(abort) | ||
return this[kHandler].onConnect(abort, this.context) | ||
} | ||
@@ -133,0 +135,0 @@ |
@@ -7,2 +7,3 @@ module.exports = { | ||
kConnect: Symbol('connect'), | ||
kConnecting: Symbol('connecting'), | ||
kConnectTimeoutValue: Symbol('connect timeout value'), | ||
@@ -18,2 +19,3 @@ kKeepAliveDefaultTimeout: Symbol('default keep alive timeout'), | ||
kHost: Symbol('host'), | ||
kNoRef: Symbol('no ref'), | ||
kRunning: Symbol('running'), | ||
@@ -20,0 +22,0 @@ kPending: Symbol('pending'), |
@@ -6,14 +6,20 @@ 'use strict' | ||
const redirectableStatusCodes = [300, 301, 302, 303, 307, 308] | ||
class RedirectHandler { | ||
constructor (agent, opts, handler) { | ||
this.agent = agent | ||
constructor (dispatcher, { maxRedirections, ...opts }, handler) { | ||
this.dispatcher = dispatcher | ||
this.location = null | ||
this.abort = null | ||
this.opts = opts | ||
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) | ||
} | ||
@@ -30,8 +36,6 @@ | ||
onHeaders (statusCode, headers, resume) { | ||
if ([300, 301, 302, 303, 307, 308].indexOf(statusCode) === -1) { | ||
return this.handler.onHeaders(statusCode, headers, resume) | ||
} | ||
this.location = this.history.length >= this.maxRedirections | ||
? null | ||
: parseLocation(statusCode, headers) | ||
this.location = parseLocation(headers) | ||
if (!this.location) { | ||
@@ -41,8 +45,7 @@ return this.handler.onHeaders(statusCode, headers, resume) | ||
this.history.push(new URL(this.opts.path, this.opts.origin)) | ||
const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin)) | ||
const path = search ? `${pathname}${search}` : pathname | ||
this.opts = { ...this.opts } | ||
this.opts.maxRedirections = this.opts.maxRedirections - 1 | ||
// Remove headers referring to the original URL. | ||
@@ -98,3 +101,6 @@ // By default it is Host only, unless it's a 303 (see below), which removes also all Content-* headers. | ||
this.agent.dispatch(this.opts, this.handler) | ||
this.location = null | ||
this.abort = null | ||
this.dispatcher.dispatch(this.opts, this) | ||
} else { | ||
@@ -106,3 +112,7 @@ this.handler.onComplete(trailers) | ||
function parseLocation (headers) { | ||
function parseLocation (statusCode, headers) { | ||
if (redirectableStatusCodes.indexOf(statusCode) === -1) { | ||
return null | ||
} | ||
for (let i = 0; i < headers.length; i += 2) { | ||
@@ -133,5 +143,7 @@ if (headers[i].length === 8 && headers[i].toString().toLowerCase() === 'location') { | ||
} else if (headers && typeof headers === 'object') { | ||
for (const [key, val] of Object.entries(headers)) { | ||
const keys = Object.keys(headers) | ||
for (let i = 0; i < keys.length; i++) { | ||
const key = keys[i] | ||
if (!shouldRemoveHeader(key, removeContent)) { | ||
ret.push(key, val) | ||
ret.push(key, headers[key]) | ||
} | ||
@@ -138,0 +150,0 @@ } |
{ | ||
"name": "undici", | ||
"version": "4.0.0-alpha.4", | ||
"version": "4.0.0-alpha.5", | ||
"description": "An HTTP/1.1 client, written from scratch for Node.js", | ||
@@ -25,3 +25,4 @@ "homepage": "https://undici.nodejs.org", | ||
"files": [ | ||
"index.(js|d.ts)", | ||
"*.d.ts", | ||
"index.js", | ||
"lib", | ||
@@ -41,4 +42,7 @@ "types", | ||
"coverage:ci": "npm run coverage -- --coverage-report=lcovonly", | ||
"prebench": "node -e \"try { require('fs').unlinkSync(require('path').join(require('os').tmpdir(), 'undici.sock')) } catch (_) {}\"", | ||
"bench": "npx concurrently -k -s first \"node benchmarks/server.js\" \"node -e 'setTimeout(() => {}, 1000)' && node benchmarks\"", | ||
"bench": "concurrently -k -s first npm:bench:server npm:bench:run", | ||
"bench:simd": "concurrently -k -s first npm:bench:server npm:bench:run:simd", | ||
"bench:server": "node benchmarks/server.js", | ||
"prebench:run": "node benchmarks/wait.js", | ||
"bench:run": "CONNECTIONS=1 node --experimental-wasm-simd benchmarks/benchmark.js && CONNECTIONS=50 node --experimental-wasm-simd benchmarks/benchmark.js", | ||
"serve:website": "docsify serve .", | ||
@@ -51,4 +55,4 @@ "prepare": "husky install" | ||
"abort-controller": "^3.0.0", | ||
"benchmark": "^2.1.4", | ||
"concurrently": "^6.0.2", | ||
"cronometro": "^0.8.0", | ||
"docsify-cli": "^4.4.2", | ||
@@ -66,3 +70,4 @@ "https-pem": "^2.0.0", | ||
"tap": "^15.0.0", | ||
"tsd": "^0.14.0" | ||
"tsd": "^0.14.0", | ||
"wait-on": "^5.3.0" | ||
}, | ||
@@ -69,0 +74,0 @@ "engines": { |
@@ -20,16 +20,28 @@ # undici | ||
Machine: AMD EPYC 7502P | ||
The benchmark is a simple `hello world` [example](benchmarks/index.js) using a | ||
number of unix sockets (connections) with a pipelining depth of 10 running on Node 16. | ||
The benchmarks have the [simd](https://github.com/WebAssembly/simd) feature enabled. | ||
Node 15 | ||
``` | ||
http - keepalive x 12,028 ops/sec ±2.60% (265 runs sampled) | ||
undici - pipeline x 31,321 ops/sec ±0.77% (276 runs sampled) | ||
undici - request x 36,612 ops/sec ±0.71% (277 runs sampled) | ||
undici - stream x 41,291 ops/sec ±0.90% (268 runs sampled) | ||
undici - dispatch x 47,319 ops/sec ±1.17% (263 runs sampled) | ||
``` | ||
### Connections 1 | ||
The benchmark is a simple `hello world` [example](benchmarks/index.js) using a | ||
single unix socket with pipelining. | ||
| Tests | Samples | Result | Tolerance | Difference with slowest | | ||
|---------------------|---------|---------------|-----------|-------------------------| | ||
| http - no keepalive | 15 | 4.63 req/sec | ± 2.77 % | - | | ||
| http - keepalive | 10 | 4.81 req/sec | ± 2.16 % | + 3.94 % | | ||
| undici - stream | 25 | 62.22 req/sec | ± 2.67 % | + 1244.58 % | | ||
| undici - dispatch | 15 | 64.33 req/sec | ± 2.47 % | + 1290.24 % | | ||
| undici - request | 15 | 66.08 req/sec | ± 2.48 % | + 1327.88 % | | ||
| undici - pipeline | 10 | 66.13 req/sec | ± 1.39 % | + 1329.08 % | | ||
### Connections 50 | ||
| Tests | Samples | Result | Tolerance | Difference with slowest | | ||
|---------------------|---------|------------------|-----------|-------------------------| | ||
| http - no keepalive | 50 | 3546.49 req/sec | ± 2.90 % | - | | ||
| http - keepalive | 15 | 5692.67 req/sec | ± 2.48 % | + 60.52 % | | ||
| undici - pipeline | 25 | 8478.71 req/sec | ± 2.62 % | + 139.07 % | | ||
| undici - request | 20 | 9766.66 req/sec | ± 2.79 % | + 175.39 % | | ||
| undici - stream | 15 | 10109.74 req/sec | ± 2.94 % | + 185.06 % | | ||
| undici - dispatch | 25 | 10949.73 req/sec | ± 2.54 % | + 208.75 % | | ||
## Quick Start | ||
@@ -73,4 +85,2 @@ | ||
`url` may contain pathname. `options` may not contain path. | ||
Calls `options.dispatcher.request(options)`. | ||
@@ -88,2 +98,3 @@ | ||
* **method** `String` - Default: `PUT` if `options.body`, otherwise `GET` | ||
* **maxRedirections** `Integer` - Default: `0` | ||
* **factory** `Dispatcher.stream.factory` | ||
@@ -105,2 +116,3 @@ | ||
* **method** `String` - Default: `PUT` if `options.body`, otherwise `GET` | ||
* **maxRedirections** `Integer` - Default: `0` | ||
* **handler** `Dispatcher.pipeline.handler` | ||
@@ -123,2 +135,3 @@ | ||
* **dispatcher** `Dispatcher` - Default: [getGlobalDispatcher](#undicigetglobaldispatcherdispatcher) | ||
* **maxRedirections** `Integer` - Default: `0` | ||
* **callback** `(err: Error | null, data: ConnectData | null) => void` (optional) | ||
@@ -141,2 +154,3 @@ | ||
* **dispatcher** `Dispatcher` - Default: [getGlobalDispatcher](#undicigetglobaldispatcherdispatcher) | ||
* **maxRedirections** `Integer` - Default: `0` | ||
* **callback** `(error: Error | null, data: UpgradeData) => void` (optional) | ||
@@ -154,7 +168,7 @@ | ||
Sets the global dispatcher used by global API methods. | ||
Sets the global dispatcher used by Common API Methods. | ||
### `undici.getGlobalDispatcher()` | ||
Gets the global dispatcher used by global API methods. | ||
Gets the global dispatcher used by Common API Methods. | ||
@@ -161,0 +175,0 @@ Returns: `Dispatcher` |
@@ -95,2 +95,3 @@ import { URL } from 'url' | ||
opaque: unknown; | ||
context: object; | ||
} | ||
@@ -102,2 +103,3 @@ export interface PipelineHandlerData { | ||
body: Readable; | ||
context: object; | ||
} | ||
@@ -117,2 +119,3 @@ export interface StreamData { | ||
opaque: unknown; | ||
context: object; | ||
} | ||
@@ -119,0 +122,0 @@ export type StreamFactory = (data: StreamFactoryData) => Writable; |
@@ -49,4 +49,4 @@ export = Errors | ||
/** Body does not match content-length header. */ | ||
export class ContentLengthMismatchError extends UndiciError { | ||
name: 'ContentLengthMismatchError'; | ||
export class RequestContentLengthMismatchError extends UndiciError { | ||
name: 'RequestContentLengthMismatchError'; | ||
code: 'UND_ERR_CONTENT_LENGTH_MISMATCH'; | ||
@@ -53,0 +53,0 @@ } |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
411556
59
4751
219
19