format: prettify entire project

This commit is contained in:
Rim
2025-04-02 06:50:39 -04:00
parent 86f0782a98
commit 7ccc0be712
1711 changed files with 755867 additions and 235931 deletions

View File

@ -1,13 +1,19 @@
'use strict'
'use strict';
const assert = require('node:assert')
const { Readable } = require('node:stream')
const util = require('../core/util')
const CacheHandler = require('../handler/cache-handler')
const MemoryCacheStore = require('../cache/memory-cache-store')
const CacheRevalidationHandler = require('../handler/cache-revalidation-handler')
const { assertCacheStore, assertCacheMethods, makeCacheKey, normaliseHeaders, parseCacheControlHeader } = require('../util/cache.js')
const { AbortError } = require('../core/errors.js')
const assert = require('node:assert');
const { Readable } = require('node:stream');
const util = require('../core/util');
const CacheHandler = require('../handler/cache-handler');
const MemoryCacheStore = require('../cache/memory-cache-store');
const CacheRevalidationHandler = require('../handler/cache-revalidation-handler');
const {
assertCacheStore,
assertCacheMethods,
makeCacheKey,
normaliseHeaders,
parseCacheControlHeader,
} = require('../util/cache.js');
const { AbortError } = require('../core/errors.js');
/**
* @typedef {(options: import('../../types/dispatcher.d.ts').default.DispatchOptions, handler: import('../../types/dispatcher.d.ts').default.DispatchHandler) => void} DispatchFn
@ -18,37 +24,38 @@ const { AbortError } = require('../core/errors.js')
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives | undefined} cacheControlDirectives
* @returns {boolean}
*/
function needsRevalidation (result, cacheControlDirectives) {
function needsRevalidation(result, cacheControlDirectives) {
if (cacheControlDirectives?.['no-cache']) {
// Always revalidate requests with the no-cache directive
return true
return true;
}
const now = Date.now()
const now = Date.now();
if (now > result.staleAt) {
// Response is stale
if (cacheControlDirectives?.['max-stale']) {
// There's a threshold where we can serve stale responses, let's see if
// we're in it
// https://www.rfc-editor.org/rfc/rfc9111.html#name-max-stale
const gracePeriod = result.staleAt + (cacheControlDirectives['max-stale'] * 1000)
return now > gracePeriod
const gracePeriod =
result.staleAt + cacheControlDirectives['max-stale'] * 1000;
return now > gracePeriod;
}
return true
return true;
}
if (cacheControlDirectives?.['min-fresh']) {
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.3
// At this point, staleAt is always > now
const timeLeftTillStale = result.staleAt - now
const threshold = cacheControlDirectives['min-fresh'] * 1000
const timeLeftTillStale = result.staleAt - now;
const threshold = cacheControlDirectives['min-fresh'] * 1000;
return timeLeftTillStale <= threshold
return timeLeftTillStale <= threshold;
}
return false
return false;
}
/**
@ -59,7 +66,7 @@ function needsRevalidation (result, cacheControlDirectives) {
* @param {import('../../types/dispatcher.d.ts').default.RequestOptions} opts
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives | undefined} reqCacheControl
*/
function handleUncachedResponse (
function handleUncachedResponse(
dispatch,
globalOpts,
cacheKey,
@ -68,38 +75,38 @@ function handleUncachedResponse (
reqCacheControl
) {
if (reqCacheControl?.['only-if-cached']) {
let aborted = false
let aborted = false;
try {
if (typeof handler.onConnect === 'function') {
handler.onConnect(() => {
aborted = true
})
aborted = true;
});
if (aborted) {
return
return;
}
}
if (typeof handler.onHeaders === 'function') {
handler.onHeaders(504, [], () => {}, 'Gateway Timeout')
handler.onHeaders(504, [], () => {}, 'Gateway Timeout');
if (aborted) {
return
return;
}
}
if (typeof handler.onComplete === 'function') {
handler.onComplete([])
handler.onComplete([]);
}
} catch (err) {
if (typeof handler.onError === 'function') {
handler.onError(err)
handler.onError(err);
}
}
return true
return true;
}
return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler))
return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler));
}
/**
@ -110,76 +117,80 @@ function handleUncachedResponse (
* @param {any} context
* @param {boolean} isStale
*/
function sendCachedValue (handler, opts, result, age, context, isStale) {
function sendCachedValue(handler, opts, result, age, context, isStale) {
// TODO (perf): Readable.from path can be optimized...
const stream = util.isStream(result.body)
? result.body
: Readable.from(result.body ?? [])
const stream =
util.isStream(result.body) ? result.body : Readable.from(result.body ?? []);
assert(!stream.destroyed, 'stream should not be destroyed')
assert(!stream.readableDidRead, 'stream should not be readableDidRead')
assert(!stream.destroyed, 'stream should not be destroyed');
assert(!stream.readableDidRead, 'stream should not be readableDidRead');
const controller = {
resume () {
stream.resume()
resume() {
stream.resume();
},
pause () {
stream.pause()
pause() {
stream.pause();
},
get paused () {
return stream.isPaused()
get paused() {
return stream.isPaused();
},
get aborted () {
return stream.destroyed
get aborted() {
return stream.destroyed;
},
get reason () {
return stream.errored
get reason() {
return stream.errored;
},
abort (reason) {
stream.destroy(reason ?? new AbortError())
}
}
abort(reason) {
stream.destroy(reason ?? new AbortError());
},
};
stream
.on('error', function (err) {
if (!this.readableEnded) {
if (typeof handler.onResponseError === 'function') {
handler.onResponseError(controller, err)
handler.onResponseError(controller, err);
} else {
throw err
throw err;
}
}
})
.on('close', function () {
if (!this.errored) {
handler.onResponseEnd?.(controller, {})
handler.onResponseEnd?.(controller, {});
}
})
});
handler.onRequestStart?.(controller, context)
handler.onRequestStart?.(controller, context);
if (stream.destroyed) {
return
return;
}
// Add the age header
// https://www.rfc-editor.org/rfc/rfc9111.html#name-age
const headers = { ...result.headers, age: String(age) }
const headers = { ...result.headers, age: String(age) };
if (isStale) {
// Add warning header
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Warning
headers.warning = '110 - "response is stale"'
headers.warning = '110 - "response is stale"';
}
handler.onResponseStart?.(controller, result.statusCode, headers, result.statusMessage)
handler.onResponseStart?.(
controller,
result.statusCode,
headers,
result.statusMessage
);
if (opts.method === 'HEAD') {
stream.destroy()
stream.destroy();
} else {
stream.on('data', function (chunk) {
handler.onResponseData?.(controller, chunk)
})
handler.onResponseData?.(controller, chunk);
});
}
}
@ -192,7 +203,7 @@ function sendCachedValue (handler, opts, result, age, context, isStale) {
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives | undefined} reqCacheControl
* @param {import('../../types/cache-interceptor.d.ts').default.GetResult | undefined} result
*/
function handleResult (
function handleResult(
dispatch,
globalOpts,
cacheKey,
@ -202,20 +213,27 @@ function handleResult (
result
) {
if (!result) {
return handleUncachedResponse(dispatch, globalOpts, cacheKey, handler, opts, reqCacheControl)
return handleUncachedResponse(
dispatch,
globalOpts,
cacheKey,
handler,
opts,
reqCacheControl
);
}
const now = Date.now()
const now = Date.now();
if (now > result.deleteAt) {
// Response is expired, cache store shouldn't have given this to us
return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler))
return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler));
}
const age = Math.round((now - result.cachedAt) / 1000)
const age = Math.round((now - result.cachedAt) / 1000);
if (reqCacheControl?.['max-age'] && age >= reqCacheControl['max-age']) {
// Response is considered expired for this specific request
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.1
return dispatch(opts, handler)
return dispatch(opts, handler);
}
// Check if the response is stale
@ -223,57 +241,60 @@ function handleResult (
if (util.isStream(opts.body) && util.bodyLength(opts.body) !== 0) {
// If body is a stream we can't revalidate...
// TODO (fix): This could be less strict...
return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler))
return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler));
}
let withinStaleIfErrorThreshold = false
const staleIfErrorExpiry = result.cacheControlDirectives['stale-if-error'] ?? reqCacheControl?.['stale-if-error']
let withinStaleIfErrorThreshold = false;
const staleIfErrorExpiry =
result.cacheControlDirectives['stale-if-error'] ??
reqCacheControl?.['stale-if-error'];
if (staleIfErrorExpiry) {
withinStaleIfErrorThreshold = now < (result.staleAt + (staleIfErrorExpiry * 1000))
withinStaleIfErrorThreshold =
now < result.staleAt + staleIfErrorExpiry * 1000;
}
let headers = {
...normaliseHeaders(opts),
'if-modified-since': new Date(result.cachedAt).toUTCString()
}
'if-modified-since': new Date(result.cachedAt).toUTCString(),
};
if (result.etag) {
headers['if-none-match'] = result.etag
headers['if-none-match'] = result.etag;
}
if (result.vary) {
headers = {
...headers,
...result.vary
}
...result.vary,
};
}
// We need to revalidate the response
return dispatch(
{
...opts,
headers
headers,
},
new CacheRevalidationHandler(
(success, context) => {
if (success) {
sendCachedValue(handler, opts, result, age, context, true)
sendCachedValue(handler, opts, result, age, context, true);
} else if (util.isStream(result.body)) {
result.body.on('error', () => {}).destroy()
result.body.on('error', () => {}).destroy();
}
},
new CacheHandler(globalOpts, cacheKey, handler),
withinStaleIfErrorThreshold
)
)
);
}
// Dump request body.
if (util.isStream(opts.body)) {
opts.body.on('error', () => {}).destroy()
opts.body.on('error', () => {}).destroy();
}
sendCachedValue(handler, opts, result, age, null, false)
sendCachedValue(handler, opts, result, age, null, false);
}
/**
@ -285,65 +306,78 @@ module.exports = (opts = {}) => {
store = new MemoryCacheStore(),
methods = ['GET'],
cacheByDefault = undefined,
type = 'shared'
} = opts
type = 'shared',
} = opts;
if (typeof opts !== 'object' || opts === null) {
throw new TypeError(`expected type of opts to be an Object, got ${opts === null ? 'null' : typeof opts}`)
throw new TypeError(
`expected type of opts to be an Object, got ${opts === null ? 'null' : typeof opts}`
);
}
assertCacheStore(store, 'opts.store')
assertCacheMethods(methods, 'opts.methods')
assertCacheStore(store, 'opts.store');
assertCacheMethods(methods, 'opts.methods');
if (typeof cacheByDefault !== 'undefined' && typeof cacheByDefault !== 'number') {
throw new TypeError(`exepcted opts.cacheByDefault to be number or undefined, got ${typeof cacheByDefault}`)
if (
typeof cacheByDefault !== 'undefined' &&
typeof cacheByDefault !== 'number'
) {
throw new TypeError(
`exepcted opts.cacheByDefault to be number or undefined, got ${typeof cacheByDefault}`
);
}
if (typeof type !== 'undefined' && type !== 'shared' && type !== 'private') {
throw new TypeError(`exepcted opts.type to be shared, private, or undefined, got ${typeof type}`)
throw new TypeError(
`exepcted opts.type to be shared, private, or undefined, got ${typeof type}`
);
}
const globalOpts = {
store,
methods,
cacheByDefault,
type
}
type,
};
const safeMethodsToNotCache = util.safeHTTPMethods.filter(method => methods.includes(method) === false)
const safeMethodsToNotCache = util.safeHTTPMethods.filter(
(method) => methods.includes(method) === false
);
return dispatch => {
return (dispatch) => {
return (opts, handler) => {
if (!opts.origin || safeMethodsToNotCache.includes(opts.method)) {
// Not a method we want to cache or we don't have the origin, skip
return dispatch(opts, handler)
return dispatch(opts, handler);
}
const reqCacheControl = opts.headers?.['cache-control']
? parseCacheControlHeader(opts.headers['cache-control'])
: undefined
const reqCacheControl =
opts.headers?.['cache-control'] ?
parseCacheControlHeader(opts.headers['cache-control'])
: undefined;
if (reqCacheControl?.['no-store']) {
return dispatch(opts, handler)
return dispatch(opts, handler);
}
/**
* @type {import('../../types/cache-interceptor.d.ts').default.CacheKey}
*/
const cacheKey = makeCacheKey(opts)
const result = store.get(cacheKey)
const cacheKey = makeCacheKey(opts);
const result = store.get(cacheKey);
if (result && typeof result.then === 'function') {
result.then(result => {
handleResult(dispatch,
result.then((result) => {
handleResult(
dispatch,
globalOpts,
cacheKey,
handler,
opts,
reqCacheControl,
result
)
})
);
});
} else {
handleResult(
dispatch,
@ -353,10 +387,10 @@ module.exports = (opts = {}) => {
opts,
reqCacheControl,
result
)
);
}
return true
}
}
}
return true;
};
};
};

View File

@ -1,39 +1,39 @@
'use strict'
const { isIP } = require('node:net')
const { lookup } = require('node:dns')
const DecoratorHandler = require('../handler/decorator-handler')
const { InvalidArgumentError, InformationalError } = require('../core/errors')
const maxInt = Math.pow(2, 31) - 1
'use strict';
const { isIP } = require('node:net');
const { lookup } = require('node:dns');
const DecoratorHandler = require('../handler/decorator-handler');
const { InvalidArgumentError, InformationalError } = require('../core/errors');
const maxInt = Math.pow(2, 31) - 1;
class DNSInstance {
#maxTTL = 0
#maxItems = 0
#records = new Map()
dualStack = true
affinity = null
lookup = null
pick = null
#maxTTL = 0;
#maxItems = 0;
#records = new Map();
dualStack = true;
affinity = null;
lookup = null;
pick = null;
constructor (opts) {
this.#maxTTL = opts.maxTTL
this.#maxItems = opts.maxItems
this.dualStack = opts.dualStack
this.affinity = opts.affinity
this.lookup = opts.lookup ?? this.#defaultLookup
this.pick = opts.pick ?? this.#defaultPick
constructor(opts) {
this.#maxTTL = opts.maxTTL;
this.#maxItems = opts.maxItems;
this.dualStack = opts.dualStack;
this.affinity = opts.affinity;
this.lookup = opts.lookup ?? this.#defaultLookup;
this.pick = opts.pick ?? this.#defaultPick;
}
get full () {
return this.#records.size === this.#maxItems
get full() {
return this.#records.size === this.#maxItems;
}
runLookup (origin, opts, cb) {
const ips = this.#records.get(origin.hostname)
runLookup(origin, opts, cb) {
const ips = this.#records.get(origin.hostname);
// If full, we just return the origin
if (ips == null && this.full) {
cb(null, origin)
return
cb(null, origin);
return;
}
const newOpts = {
@ -43,294 +43,292 @@ class DNSInstance {
pick: this.pick,
...opts.dns,
maxTTL: this.#maxTTL,
maxItems: this.#maxItems
}
maxItems: this.#maxItems,
};
// If no IPs we lookup
if (ips == null) {
this.lookup(origin, newOpts, (err, addresses) => {
if (err || addresses == null || addresses.length === 0) {
cb(err ?? new InformationalError('No DNS entries found'))
return
cb(err ?? new InformationalError('No DNS entries found'));
return;
}
this.setRecords(origin, addresses)
const records = this.#records.get(origin.hostname)
this.setRecords(origin, addresses);
const records = this.#records.get(origin.hostname);
const ip = this.pick(
origin,
records,
newOpts.affinity
)
const ip = this.pick(origin, records, newOpts.affinity);
let port
let port;
if (typeof ip.port === 'number') {
port = `:${ip.port}`
port = `:${ip.port}`;
} else if (origin.port !== '') {
port = `:${origin.port}`
port = `:${origin.port}`;
} else {
port = ''
port = '';
}
cb(
null,
new URL(`${origin.protocol}//${
ip.family === 6 ? `[${ip.address}]` : ip.address
}${port}`)
)
})
new URL(
`${origin.protocol}//${
ip.family === 6 ? `[${ip.address}]` : ip.address
}${port}`
)
);
});
} else {
// If there's IPs we pick
const ip = this.pick(
origin,
ips,
newOpts.affinity
)
const ip = this.pick(origin, ips, newOpts.affinity);
// If no IPs we lookup - deleting old records
if (ip == null) {
this.#records.delete(origin.hostname)
this.runLookup(origin, opts, cb)
return
this.#records.delete(origin.hostname);
this.runLookup(origin, opts, cb);
return;
}
let port
let port;
if (typeof ip.port === 'number') {
port = `:${ip.port}`
port = `:${ip.port}`;
} else if (origin.port !== '') {
port = `:${origin.port}`
port = `:${origin.port}`;
} else {
port = ''
port = '';
}
cb(
null,
new URL(`${origin.protocol}//${
ip.family === 6 ? `[${ip.address}]` : ip.address
}${port}`)
)
new URL(
`${origin.protocol}//${
ip.family === 6 ? `[${ip.address}]` : ip.address
}${port}`
)
);
}
}
#defaultLookup (origin, opts, cb) {
#defaultLookup(origin, opts, cb) {
lookup(
origin.hostname,
{
all: true,
family: this.dualStack === false ? this.affinity : 0,
order: 'ipv4first'
order: 'ipv4first',
},
(err, addresses) => {
if (err) {
return cb(err)
return cb(err);
}
const results = new Map()
const results = new Map();
for (const addr of addresses) {
// On linux we found duplicates, we attempt to remove them with
// the latest record
results.set(`${addr.address}:${addr.family}`, addr)
results.set(`${addr.address}:${addr.family}`, addr);
}
cb(null, results.values())
cb(null, results.values());
}
)
);
}
#defaultPick (origin, hostnameRecords, affinity) {
let ip = null
const { records, offset } = hostnameRecords
#defaultPick(origin, hostnameRecords, affinity) {
let ip = null;
const { records, offset } = hostnameRecords;
let family
let family;
if (this.dualStack) {
if (affinity == null) {
// Balance between ip families
if (offset == null || offset === maxInt) {
hostnameRecords.offset = 0
affinity = 4
hostnameRecords.offset = 0;
affinity = 4;
} else {
hostnameRecords.offset++
affinity = (hostnameRecords.offset & 1) === 1 ? 6 : 4
hostnameRecords.offset++;
affinity = (hostnameRecords.offset & 1) === 1 ? 6 : 4;
}
}
if (records[affinity] != null && records[affinity].ips.length > 0) {
family = records[affinity]
family = records[affinity];
} else {
family = records[affinity === 4 ? 6 : 4]
family = records[affinity === 4 ? 6 : 4];
}
} else {
family = records[affinity]
family = records[affinity];
}
// If no IPs we return null
if (family == null || family.ips.length === 0) {
return ip
return ip;
}
if (family.offset == null || family.offset === maxInt) {
family.offset = 0
family.offset = 0;
} else {
family.offset++
family.offset++;
}
const position = family.offset % family.ips.length
ip = family.ips[position] ?? null
const position = family.offset % family.ips.length;
ip = family.ips[position] ?? null;
if (ip == null) {
return ip
return ip;
}
if (Date.now() - ip.timestamp > ip.ttl) { // record TTL is already in ms
if (Date.now() - ip.timestamp > ip.ttl) {
// record TTL is already in ms
// We delete expired records
// It is possible that they have different TTL, so we manage them individually
family.ips.splice(position, 1)
return this.pick(origin, hostnameRecords, affinity)
family.ips.splice(position, 1);
return this.pick(origin, hostnameRecords, affinity);
}
return ip
return ip;
}
pickFamily (origin, ipFamily) {
const records = this.#records.get(origin.hostname)?.records
pickFamily(origin, ipFamily) {
const records = this.#records.get(origin.hostname)?.records;
if (!records) {
return null
return null;
}
const family = records[ipFamily]
const family = records[ipFamily];
if (!family) {
return null
return null;
}
if (family.offset == null || family.offset === maxInt) {
family.offset = 0
family.offset = 0;
} else {
family.offset++
family.offset++;
}
const position = family.offset % family.ips.length
const ip = family.ips[position] ?? null
const position = family.offset % family.ips.length;
const ip = family.ips[position] ?? null;
if (ip == null) {
return ip
return ip;
}
if (Date.now() - ip.timestamp > ip.ttl) { // record TTL is already in ms
if (Date.now() - ip.timestamp > ip.ttl) {
// record TTL is already in ms
// We delete expired records
// It is possible that they have different TTL, so we manage them individually
family.ips.splice(position, 1)
family.ips.splice(position, 1);
}
return ip
return ip;
}
setRecords (origin, addresses) {
const timestamp = Date.now()
const records = { records: { 4: null, 6: null } }
setRecords(origin, addresses) {
const timestamp = Date.now();
const records = { records: { 4: null, 6: null } };
for (const record of addresses) {
record.timestamp = timestamp
record.timestamp = timestamp;
if (typeof record.ttl === 'number') {
// The record TTL is expected to be in ms
record.ttl = Math.min(record.ttl, this.#maxTTL)
record.ttl = Math.min(record.ttl, this.#maxTTL);
} else {
record.ttl = this.#maxTTL
record.ttl = this.#maxTTL;
}
const familyRecords = records.records[record.family] ?? { ips: [] }
const familyRecords = records.records[record.family] ?? { ips: [] };
familyRecords.ips.push(record)
records.records[record.family] = familyRecords
familyRecords.ips.push(record);
records.records[record.family] = familyRecords;
}
this.#records.set(origin.hostname, records)
this.#records.set(origin.hostname, records);
}
deleteRecords (origin) {
this.#records.delete(origin.hostname)
deleteRecords(origin) {
this.#records.delete(origin.hostname);
}
getHandler (meta, opts) {
return new DNSDispatchHandler(this, meta, opts)
getHandler(meta, opts) {
return new DNSDispatchHandler(this, meta, opts);
}
}
class DNSDispatchHandler extends DecoratorHandler {
#state = null
#opts = null
#dispatch = null
#origin = null
#controller = null
#newOrigin = null
#firstTry = true
#state = null;
#opts = null;
#dispatch = null;
#origin = null;
#controller = null;
#newOrigin = null;
#firstTry = true;
constructor (state, { origin, handler, dispatch, newOrigin }, opts) {
super(handler)
this.#origin = origin
this.#newOrigin = newOrigin
this.#opts = { ...opts }
this.#state = state
this.#dispatch = dispatch
constructor(state, { origin, handler, dispatch, newOrigin }, opts) {
super(handler);
this.#origin = origin;
this.#newOrigin = newOrigin;
this.#opts = { ...opts };
this.#state = state;
this.#dispatch = dispatch;
}
onResponseError (controller, err) {
onResponseError(controller, err) {
switch (err.code) {
case 'ETIMEDOUT':
case 'ECONNREFUSED': {
if (this.#state.dualStack) {
if (!this.#firstTry) {
super.onResponseError(controller, err)
return
super.onResponseError(controller, err);
return;
}
this.#firstTry = false
this.#firstTry = false;
// Pick an ip address from the other family
const otherFamily = this.#newOrigin.hostname[0] === '[' ? 4 : 6
const ip = this.#state.pickFamily(this.#origin, otherFamily)
const otherFamily = this.#newOrigin.hostname[0] === '[' ? 4 : 6;
const ip = this.#state.pickFamily(this.#origin, otherFamily);
if (ip == null) {
super.onResponseError(controller, err)
return
super.onResponseError(controller, err);
return;
}
let port
let port;
if (typeof ip.port === 'number') {
port = `:${ip.port}`
port = `:${ip.port}`;
} else if (this.#origin.port !== '') {
port = `:${this.#origin.port}`
port = `:${this.#origin.port}`;
} else {
port = ''
port = '';
}
const dispatchOpts = {
...this.#opts,
origin: `${this.#origin.protocol}//${
ip.family === 6 ? `[${ip.address}]` : ip.address
}${port}`
}
this.#dispatch(dispatchOpts, this)
return
ip.family === 6 ? `[${ip.address}]` : ip.address
}${port}`,
};
this.#dispatch(dispatchOpts, this);
return;
}
// if dual-stack disabled, we error out
super.onResponseError(controller, err)
break
super.onResponseError(controller, err);
break;
}
case 'ENOTFOUND':
this.#state.deleteRecords(this.#origin)
super.onResponseError(controller, err)
break
this.#state.deleteRecords(this.#origin);
super.onResponseError(controller, err);
break;
default:
super.onResponseError(controller, err)
break
super.onResponseError(controller, err);
break;
}
}
}
module.exports = interceptorOpts => {
module.exports = (interceptorOpts) => {
if (
interceptorOpts?.maxTTL != null &&
(typeof interceptorOpts?.maxTTL !== 'number' || interceptorOpts?.maxTTL < 0)
) {
throw new InvalidArgumentError('Invalid maxTTL. Must be a positive number')
throw new InvalidArgumentError('Invalid maxTTL. Must be a positive number');
}
if (
@ -340,7 +338,7 @@ module.exports = interceptorOpts => {
) {
throw new InvalidArgumentError(
'Invalid maxItems. Must be a positive number and greater than zero'
)
);
}
if (
@ -348,36 +346,36 @@ module.exports = interceptorOpts => {
interceptorOpts?.affinity !== 4 &&
interceptorOpts?.affinity !== 6
) {
throw new InvalidArgumentError('Invalid affinity. Must be either 4 or 6')
throw new InvalidArgumentError('Invalid affinity. Must be either 4 or 6');
}
if (
interceptorOpts?.dualStack != null &&
typeof interceptorOpts?.dualStack !== 'boolean'
) {
throw new InvalidArgumentError('Invalid dualStack. Must be a boolean')
throw new InvalidArgumentError('Invalid dualStack. Must be a boolean');
}
if (
interceptorOpts?.lookup != null &&
typeof interceptorOpts?.lookup !== 'function'
) {
throw new InvalidArgumentError('Invalid lookup. Must be a function')
throw new InvalidArgumentError('Invalid lookup. Must be a function');
}
if (
interceptorOpts?.pick != null &&
typeof interceptorOpts?.pick !== 'function'
) {
throw new InvalidArgumentError('Invalid pick. Must be a function')
throw new InvalidArgumentError('Invalid pick. Must be a function');
}
const dualStack = interceptorOpts?.dualStack ?? true
let affinity
const dualStack = interceptorOpts?.dualStack ?? true;
let affinity;
if (dualStack) {
affinity = interceptorOpts?.affinity ?? null
affinity = interceptorOpts?.affinity ?? null;
} else {
affinity = interceptorOpts?.affinity ?? 4
affinity = interceptorOpts?.affinity ?? 4;
}
const opts = {
@ -386,25 +384,25 @@ module.exports = interceptorOpts => {
pick: interceptorOpts?.pick ?? null,
dualStack,
affinity,
maxItems: interceptorOpts?.maxItems ?? Infinity
}
maxItems: interceptorOpts?.maxItems ?? Infinity,
};
const instance = new DNSInstance(opts)
const instance = new DNSInstance(opts);
return dispatch => {
return function dnsInterceptor (origDispatchOpts, handler) {
return (dispatch) => {
return function dnsInterceptor(origDispatchOpts, handler) {
const origin =
origDispatchOpts.origin.constructor === URL
? origDispatchOpts.origin
: new URL(origDispatchOpts.origin)
origDispatchOpts.origin.constructor === URL ?
origDispatchOpts.origin
: new URL(origDispatchOpts.origin);
if (isIP(origin.hostname) !== 0) {
return dispatch(origDispatchOpts, handler)
return dispatch(origDispatchOpts, handler);
}
instance.runLookup(origin, origDispatchOpts, (err, newOrigin) => {
if (err) {
return handler.onResponseError(null, err)
return handler.onResponseError(null, err);
}
const dispatchOpts = {
@ -413,9 +411,9 @@ module.exports = interceptorOpts => {
origin: newOrigin.origin,
headers: {
host: origin.host,
...origDispatchOpts.headers
}
}
...origDispatchOpts.headers,
},
};
dispatch(
dispatchOpts,
@ -423,10 +421,10 @@ module.exports = interceptorOpts => {
{ origin, dispatch, handler, newOrigin },
origDispatchOpts
)
)
})
);
});
return true
}
}
}
return true;
};
};
};

View File

@ -1,111 +1,119 @@
'use strict'
'use strict';
const { InvalidArgumentError, RequestAbortedError } = require('../core/errors')
const DecoratorHandler = require('../handler/decorator-handler')
const { InvalidArgumentError, RequestAbortedError } = require('../core/errors');
const DecoratorHandler = require('../handler/decorator-handler');
class DumpHandler extends DecoratorHandler {
#maxSize = 1024 * 1024
#dumped = false
#size = 0
#controller = null
aborted = false
reason = false
#maxSize = 1024 * 1024;
#dumped = false;
#size = 0;
#controller = null;
aborted = false;
reason = false;
constructor ({ maxSize, signal }, handler) {
constructor({ maxSize, signal }, handler) {
if (maxSize != null && (!Number.isFinite(maxSize) || maxSize < 1)) {
throw new InvalidArgumentError('maxSize must be a number greater than 0')
throw new InvalidArgumentError('maxSize must be a number greater than 0');
}
super(handler)
super(handler);
this.#maxSize = maxSize ?? this.#maxSize
this.#maxSize = maxSize ?? this.#maxSize;
// this.#handler = handler
}
#abort (reason) {
this.aborted = true
this.reason = reason
#abort(reason) {
this.aborted = true;
this.reason = reason;
}
onRequestStart (controller, context) {
controller.abort = this.#abort.bind(this)
this.#controller = controller
onRequestStart(controller, context) {
controller.abort = this.#abort.bind(this);
this.#controller = controller;
return super.onRequestStart(controller, context)
return super.onRequestStart(controller, context);
}
onResponseStart (controller, statusCode, headers, statusMessage) {
const contentLength = headers['content-length']
onResponseStart(controller, statusCode, headers, statusMessage) {
const contentLength = headers['content-length'];
if (contentLength != null && contentLength > this.#maxSize) {
throw new RequestAbortedError(
`Response size (${contentLength}) larger than maxSize (${
this.#maxSize
})`
)
);
}
if (this.aborted === true) {
return true
return true;
}
return super.onResponseStart(controller, statusCode, headers, statusMessage)
return super.onResponseStart(
controller,
statusCode,
headers,
statusMessage
);
}
onResponseError (controller, err) {
onResponseError(controller, err) {
if (this.#dumped) {
return
return;
}
err = this.#controller.reason ?? err
err = this.#controller.reason ?? err;
super.onResponseError(controller, err)
super.onResponseError(controller, err);
}
onResponseData (controller, chunk) {
this.#size = this.#size + chunk.length
onResponseData(controller, chunk) {
this.#size = this.#size + chunk.length;
if (this.#size >= this.#maxSize) {
this.#dumped = true
this.#dumped = true;
if (this.aborted === true) {
super.onResponseError(controller, this.reason)
super.onResponseError(controller, this.reason);
} else {
super.onResponseEnd(controller, {})
super.onResponseEnd(controller, {});
}
}
return true
return true;
}
onResponseEnd (controller, trailers) {
onResponseEnd(controller, trailers) {
if (this.#dumped) {
return
return;
}
if (this.#controller.aborted === true) {
super.onResponseError(controller, this.reason)
return
super.onResponseError(controller, this.reason);
return;
}
super.onResponseEnd(controller, trailers)
super.onResponseEnd(controller, trailers);
}
}
function createDumpInterceptor (
function createDumpInterceptor(
{ maxSize: defaultMaxSize } = {
maxSize: 1024 * 1024
maxSize: 1024 * 1024,
}
) {
return dispatch => {
return function Intercept (opts, handler) {
const { dumpMaxSize = defaultMaxSize } = opts
return (dispatch) => {
return function Intercept(opts, handler) {
const { dumpMaxSize = defaultMaxSize } = opts;
const dumpHandler = new DumpHandler({ maxSize: dumpMaxSize, signal: opts.signal }, handler)
const dumpHandler = new DumpHandler(
{ maxSize: dumpMaxSize, signal: opts.signal },
handler
);
return dispatch(opts, dumpHandler)
}
}
return dispatch(opts, dumpHandler);
};
};
}
module.exports = createDumpInterceptor
module.exports = createDumpInterceptor;

View File

@ -1,21 +1,28 @@
'use strict'
'use strict';
const RedirectHandler = require('../handler/redirect-handler')
const RedirectHandler = require('../handler/redirect-handler');
function createRedirectInterceptor ({ maxRedirections: defaultMaxRedirections } = {}) {
function createRedirectInterceptor({
maxRedirections: defaultMaxRedirections,
} = {}) {
return (dispatch) => {
return function Intercept (opts, handler) {
const { maxRedirections = defaultMaxRedirections, ...rest } = opts
return function Intercept(opts, handler) {
const { maxRedirections = defaultMaxRedirections, ...rest } = opts;
if (maxRedirections == null || maxRedirections === 0) {
return dispatch(opts, handler)
return dispatch(opts, handler);
}
const dispatchOpts = { ...rest, maxRedirections: 0 } // Stop sub dispatcher from also redirecting.
const redirectHandler = new RedirectHandler(dispatch, maxRedirections, dispatchOpts, handler)
return dispatch(dispatchOpts, redirectHandler)
}
}
const dispatchOpts = { ...rest, maxRedirections: 0 }; // Stop sub dispatcher from also redirecting.
const redirectHandler = new RedirectHandler(
dispatch,
maxRedirections,
dispatchOpts,
handler
);
return dispatch(dispatchOpts, redirectHandler);
};
};
}
module.exports = createRedirectInterceptor
module.exports = createRedirectInterceptor;

View File

@ -1,95 +1,103 @@
'use strict'
'use strict';
// const { parseHeaders } = require('../core/util')
const DecoratorHandler = require('../handler/decorator-handler')
const { ResponseError } = require('../core/errors')
const DecoratorHandler = require('../handler/decorator-handler');
const { ResponseError } = require('../core/errors');
class ResponseErrorHandler extends DecoratorHandler {
#statusCode
#contentType
#decoder
#headers
#body
#statusCode;
#contentType;
#decoder;
#headers;
#body;
constructor (_opts, { handler }) {
super(handler)
constructor(_opts, { handler }) {
super(handler);
}
#checkContentType (contentType) {
return (this.#contentType ?? '').indexOf(contentType) === 0
#checkContentType(contentType) {
return (this.#contentType ?? '').indexOf(contentType) === 0;
}
onRequestStart (controller, context) {
this.#statusCode = 0
this.#contentType = null
this.#decoder = null
this.#headers = null
this.#body = ''
onRequestStart(controller, context) {
this.#statusCode = 0;
this.#contentType = null;
this.#decoder = null;
this.#headers = null;
this.#body = '';
return super.onRequestStart(controller, context)
return super.onRequestStart(controller, context);
}
onResponseStart (controller, statusCode, headers, statusMessage) {
this.#statusCode = statusCode
this.#headers = headers
this.#contentType = headers['content-type']
onResponseStart(controller, statusCode, headers, statusMessage) {
this.#statusCode = statusCode;
this.#headers = headers;
this.#contentType = headers['content-type'];
if (this.#statusCode < 400) {
return super.onResponseStart(controller, statusCode, headers, statusMessage)
return super.onResponseStart(
controller,
statusCode,
headers,
statusMessage
);
}
if (this.#checkContentType('application/json') || this.#checkContentType('text/plain')) {
this.#decoder = new TextDecoder('utf-8')
if (
this.#checkContentType('application/json') ||
this.#checkContentType('text/plain')
) {
this.#decoder = new TextDecoder('utf-8');
}
}
onResponseData (controller, chunk) {
onResponseData(controller, chunk) {
if (this.#statusCode < 400) {
return super.onResponseData(controller, chunk)
return super.onResponseData(controller, chunk);
}
this.#body += this.#decoder?.decode(chunk, { stream: true }) ?? ''
this.#body += this.#decoder?.decode(chunk, { stream: true }) ?? '';
}
onResponseEnd (controller, trailers) {
onResponseEnd(controller, trailers) {
if (this.#statusCode >= 400) {
this.#body += this.#decoder?.decode(undefined, { stream: false }) ?? ''
this.#body += this.#decoder?.decode(undefined, { stream: false }) ?? '';
if (this.#checkContentType('application/json')) {
try {
this.#body = JSON.parse(this.#body)
this.#body = JSON.parse(this.#body);
} catch {
// Do nothing...
}
}
let err
const stackTraceLimit = Error.stackTraceLimit
Error.stackTraceLimit = 0
let err;
const stackTraceLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 0;
try {
err = new ResponseError('Response Error', this.#statusCode, {
body: this.#body,
headers: this.#headers
})
headers: this.#headers,
});
} finally {
Error.stackTraceLimit = stackTraceLimit
Error.stackTraceLimit = stackTraceLimit;
}
super.onResponseError(controller, err)
super.onResponseError(controller, err);
} else {
super.onResponseEnd(controller, trailers)
super.onResponseEnd(controller, trailers);
}
}
onResponseError (controller, err) {
super.onResponseError(controller, err)
onResponseError(controller, err) {
super.onResponseError(controller, err);
}
}
module.exports = () => {
return (dispatch) => {
return function Intercept (opts, handler) {
return dispatch(opts, new ResponseErrorHandler(opts, { handler }))
}
}
}
return function Intercept(opts, handler) {
return dispatch(opts, new ResponseErrorHandler(opts, { handler }));
};
};
};

View File

@ -1,19 +1,19 @@
'use strict'
const RetryHandler = require('../handler/retry-handler')
'use strict';
const RetryHandler = require('../handler/retry-handler');
module.exports = globalOpts => {
return dispatch => {
return function retryInterceptor (opts, handler) {
module.exports = (globalOpts) => {
return (dispatch) => {
return function retryInterceptor(opts, handler) {
return dispatch(
opts,
new RetryHandler(
{ ...opts, retryOptions: { ...globalOpts, ...opts.retryOptions } },
{
handler,
dispatch
dispatch,
}
)
)
}
}
}
);
};
};
};