From 4192b383d635c28eaf3585d7bd11722ec02c6d88 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Sun, 16 Jun 2024 21:19:16 +0300 Subject: [PATCH 01/27] interceptors: move throwOnError to interceptor --- lib/interceptor/response-error.js | 110 ++++++++++++++++++++++++++++ lib/interceptor/retry.js | 26 ++++--- package.json | 2 + test/interceptors/response-error.js | 18 +++++ 4 files changed, 147 insertions(+), 9 deletions(-) create mode 100644 lib/interceptor/response-error.js create mode 100644 test/interceptors/response-error.js diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js new file mode 100644 index 00000000000..3d8007027b2 --- /dev/null +++ b/lib/interceptor/response-error.js @@ -0,0 +1,110 @@ +'use strict' + +const { parseHeaders } = require('../core/util') +const createHttpError = require('http-errors') +const { DecoratorHandler } = require('undici') + +class Handler extends DecoratorHandler { + #handler + #statusCode + #contentType + #decoder + #headers + #body + #errored + + constructor (opts, { handler }) { + super(handler) + this.#handler = handler + this.opts = opts + } + + onConnect (abort) { + this.#statusCode = 0 + this.#contentType = null + this.#decoder = null + this.#headers = null + this.#body = '' + this.#errored = false + + return this.#handler.onConnect(abort) + } + + onHeaders (statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) { + this.#statusCode = statusCode + this.#headers = headers + this.#contentType = headers['content-type'] + + if (this.#statusCode < 400) { + return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusMessage, headers) + } + + if (this.#contentType === 'application/json' || this.#contentType === 'text/plain') { + this.#decoder = new TextDecoder('utf-8') + } + } + + onData (chunk) { + if (this.#statusCode >= 400) { + this.#body += this.#decoder?.decode(chunk, { stream: true }) ?? '' + } else { + return this.#handler.onData(chunk) + } + } + + onComplete (rawTrailers) { + if (this.#statusCode >= 400) { + this.#body += this.#decoder?.decode(undefined, { stream: false }) ?? '' + + if (this.#contentType === 'application/json') { + try { + this.#body = JSON.parse(this.#body) + } catch { + // Do nothing... + } + } + + this.#errored = true + + let err + + const stackTraceLimit = Error.stackTraceLimit + Error.stackTraceLimit = 0 + try { + err = Object.assign(createHttpError(this.#statusCode), { + reason: this.#body?.reason, + error: this.#body?.error, + headers: this.#headers, + body: this.#body + }) + } finally { + Error.stackTraceLimit = stackTraceLimit + } + + if (this.opts.throwOnError !== false && this.opts.error !== false) { + this.#handler.onError(err) + } else { + this.#handler.onComplete(rawTrailers) + } + } else { + this.#handler.onComplete(rawTrailers) + } + } + + onError (err) { + if (this.#errored) { + // Do nothing... + } else { + if (this.opts.throwOnError !== false && this.opts.error !== false) { + this.#handler.onError(err) + } else { + this.#handler.onComplete() + } + } + } +} + +module.exports = (dispatch) => (opts, handler) => + opts.error !== false && opts.throwOnError !== false + ? dispatch(opts, new Handler(opts, { handler })) + : dispatch(opts, handler) diff --git a/lib/interceptor/retry.js b/lib/interceptor/retry.js index 1c16fd845a9..d8052cb851d 100644 --- a/lib/interceptor/retry.js +++ b/lib/interceptor/retry.js @@ -1,19 +1,27 @@ 'use strict' + const RetryHandler = require('../handler/retry-handler') +const createResponseErrorInterceptor = require('./response-error') module.exports = globalOpts => { return dispatch => { + const responseErrorInterceptor = createResponseErrorInterceptor(dispatch) + return function retryInterceptor (opts, handler) { - return dispatch( - opts, - new RetryHandler( - { ...opts, retryOptions: { ...globalOpts, ...opts.retryOptions } }, - { - handler, - dispatch - } - ) + const wrappedHandler = { + onConnect: handler.onConnect ? handler.onConnect.bind(handler) : undefined, + onHeaders: handler.onHeaders.bind(handler), + onData: handler.onData.bind(handler), + onComplete: handler.onComplete.bind(handler), + onError: handler.onError.bind(handler) + } + + const finalHandler = new RetryHandler( + { ...opts, retryOptions: { ...globalOpts, ...opts.retryOptions } }, + { handler: wrappedHandler, dispatch } ) + + return responseErrorInterceptor(opts, finalHandler) } } } diff --git a/package.json b/package.json index b1afc62b119..19f174cf9ab 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "@fastify/busboy": "2.1.1", "@matteo.collina/tspl": "^0.1.1", "@sinonjs/fake-timers": "^11.1.0", + "@types/http-errors": "^2.0.4", "@types/node": "^18.0.3", "abort-controller": "^3.0.0", "borp": "^0.15.0", @@ -114,6 +115,7 @@ "fast-check": "^3.17.1", "form-data": "^4.0.0", "formdata-node": "^6.0.3", + "http-errors": "^2.0.0", "https-pem": "^3.0.0", "husky": "^9.0.7", "jest": "^29.0.2", diff --git a/test/interceptors/response-error.js b/test/interceptors/response-error.js new file mode 100644 index 00000000000..dd62baeae71 --- /dev/null +++ b/test/interceptors/response-error.js @@ -0,0 +1,18 @@ +'use strict' + +const assert = require('assert') +const { test } = require('node:test') +const createResponseErrorInterceptor = require('../../lib/interceptor/response-error') + +test('should not error if request is not meant to be retried', async (t) => { + const response = { statusCode: 400 } + const handler = { + onError: () => {}, + onData: () => {}, + onComplete: () => {} + } + + const interceptor = createResponseErrorInterceptor((opts, handler) => handler.onComplete()) + + assert.doesNotThrow(() => interceptor({ response, throwOnError: false }, handler)) +}) From e2321ec388930277dc7819835380623f7e70be7a Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Mon, 17 Jun 2024 08:25:39 +0300 Subject: [PATCH 02/27] delete http-errors --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index 19f174cf9ab..b1afc62b119 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,6 @@ "@fastify/busboy": "2.1.1", "@matteo.collina/tspl": "^0.1.1", "@sinonjs/fake-timers": "^11.1.0", - "@types/http-errors": "^2.0.4", "@types/node": "^18.0.3", "abort-controller": "^3.0.0", "borp": "^0.15.0", @@ -115,7 +114,6 @@ "fast-check": "^3.17.1", "form-data": "^4.0.0", "formdata-node": "^6.0.3", - "http-errors": "^2.0.0", "https-pem": "^3.0.0", "husky": "^9.0.7", "jest": "^29.0.2", From 78552258e83cdb6c036f8084a9a2547ce1c1cda0 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Mon, 17 Jun 2024 09:52:36 +0300 Subject: [PATCH 03/27] Update response-error.js Co-authored-by: Robert Nagy --- lib/interceptor/response-error.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index 3d8007027b2..4e97b047eb3 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -71,7 +71,9 @@ class Handler extends DecoratorHandler { const stackTraceLimit = Error.stackTraceLimit Error.stackTraceLimit = 0 try { - err = Object.assign(createHttpError(this.#statusCode), { + err = Object.assign(new Error(http.STATUS_CODES[this.#statusCode]), { + statusCode: this.#statusCode, + status: this.#status, reason: this.#body?.reason, error: this.#body?.error, headers: this.#headers, From ea068ebbc4c7ae199aa5f9cea31e3c8ed10254dc Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Mon, 17 Jun 2024 09:56:01 +0300 Subject: [PATCH 04/27] Update response-error.js Co-authored-by: Robert Nagy --- lib/interceptor/response-error.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index 4e97b047eb3..7dc45d492f4 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -73,7 +73,7 @@ class Handler extends DecoratorHandler { try { err = Object.assign(new Error(http.STATUS_CODES[this.#statusCode]), { statusCode: this.#statusCode, - status: this.#status, + status: this.#statusCode, reason: this.#body?.reason, error: this.#body?.error, headers: this.#headers, From 6782734cd6022f1a6382e44f3ed9346d906f8499 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Mon, 17 Jun 2024 14:47:29 +0300 Subject: [PATCH 05/27] Update lib/interceptor/response-error.js Co-authored-by: Robert Nagy --- lib/interceptor/response-error.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index 7dc45d492f4..90cd01bc3a3 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -1,7 +1,7 @@ 'use strict' const { parseHeaders } = require('../core/util') -const createHttpError = require('http-errors') +const http = require('node:http') const { DecoratorHandler } = require('undici') class Handler extends DecoratorHandler { From eb29a2a1ae748aeaf06482acda8a21b5685ae7fc Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Wed, 19 Jun 2024 12:21:29 +0300 Subject: [PATCH 06/27] feat: added new undiciError --- lib/core/errors.js | 13 +++++++++++++ lib/interceptor/response-error.js | 19 ++----------------- lib/interceptor/retry.js | 26 +++++++++----------------- 3 files changed, 24 insertions(+), 34 deletions(-) diff --git a/lib/core/errors.js b/lib/core/errors.js index 3d69fdbecba..9257875c1c3 100644 --- a/lib/core/errors.js +++ b/lib/core/errors.js @@ -195,6 +195,18 @@ class RequestRetryError extends UndiciError { } } +class ResponseError extends UndiciError { + constructor (message, code, { headers, data }) { + super(message) + this.name = 'ResponseError' + this.message = message || 'Response error' + this.code = 'UND_ERR_RESPONSE' + this.statusCode = code + this.data = data + this.headers = headers + } +} + class SecureProxyConnectionError extends UndiciError { constructor (cause, message, options) { super(message, { cause, ...(options ?? {}) }) @@ -227,5 +239,6 @@ module.exports = { BalancedPoolMissingUpstreamError, ResponseExceededMaxSizeError, RequestRetryError, + ResponseError, SecureProxyConnectionError } diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index 90cd01bc3a3..dffc3e3bc57 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -1,8 +1,8 @@ 'use strict' const { parseHeaders } = require('../core/util') -const http = require('node:http') const { DecoratorHandler } = require('undici') +const { ResponseError } = require('../core/errors') class Handler extends DecoratorHandler { #handler @@ -66,22 +66,7 @@ class Handler extends DecoratorHandler { this.#errored = true - let err - - const stackTraceLimit = Error.stackTraceLimit - Error.stackTraceLimit = 0 - try { - err = Object.assign(new Error(http.STATUS_CODES[this.#statusCode]), { - statusCode: this.#statusCode, - status: this.#statusCode, - reason: this.#body?.reason, - error: this.#body?.error, - headers: this.#headers, - body: this.#body - }) - } finally { - Error.stackTraceLimit = stackTraceLimit - } + const err = new ResponseError('Response Error', this.#statusCode, this.#headers, this.#body) if (this.opts.throwOnError !== false && this.opts.error !== false) { this.#handler.onError(err) diff --git a/lib/interceptor/retry.js b/lib/interceptor/retry.js index d8052cb851d..1c16fd845a9 100644 --- a/lib/interceptor/retry.js +++ b/lib/interceptor/retry.js @@ -1,27 +1,19 @@ 'use strict' - const RetryHandler = require('../handler/retry-handler') -const createResponseErrorInterceptor = require('./response-error') module.exports = globalOpts => { return dispatch => { - const responseErrorInterceptor = createResponseErrorInterceptor(dispatch) - return function retryInterceptor (opts, handler) { - const wrappedHandler = { - onConnect: handler.onConnect ? handler.onConnect.bind(handler) : undefined, - onHeaders: handler.onHeaders.bind(handler), - onData: handler.onData.bind(handler), - onComplete: handler.onComplete.bind(handler), - onError: handler.onError.bind(handler) - } - - const finalHandler = new RetryHandler( - { ...opts, retryOptions: { ...globalOpts, ...opts.retryOptions } }, - { handler: wrappedHandler, dispatch } + return dispatch( + opts, + new RetryHandler( + { ...opts, retryOptions: { ...globalOpts, ...opts.retryOptions } }, + { + handler, + dispatch + } + ) ) - - return responseErrorInterceptor(opts, finalHandler) } } } From 75fcf7056ed93ebc859926d58f5a6e7c1eb31784 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Wed, 19 Jun 2024 12:24:57 +0300 Subject: [PATCH 07/27] feat: enable interceptor by default --- lib/interceptor/response-error.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index dffc3e3bc57..1881dc8e386 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -92,6 +92,4 @@ class Handler extends DecoratorHandler { } module.exports = (dispatch) => (opts, handler) => - opts.error !== false && opts.throwOnError !== false - ? dispatch(opts, new Handler(opts, { handler })) - : dispatch(opts, handler) + dispatch(opts, new Handler(opts, { handler })) From b2110d481c47cbfedc74875e91b5a7c72ff539ef Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Wed, 19 Jun 2024 12:28:25 +0300 Subject: [PATCH 08/27] feat: always throw error when interceptor is used --- lib/interceptor/response-error.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index 1881dc8e386..d59369359ed 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -68,26 +68,14 @@ class Handler extends DecoratorHandler { const err = new ResponseError('Response Error', this.#statusCode, this.#headers, this.#body) - if (this.opts.throwOnError !== false && this.opts.error !== false) { - this.#handler.onError(err) - } else { - this.#handler.onComplete(rawTrailers) - } + this.#handler.onError(err) } else { this.#handler.onComplete(rawTrailers) } } onError (err) { - if (this.#errored) { - // Do nothing... - } else { - if (this.opts.throwOnError !== false && this.opts.error !== false) { - this.#handler.onError(err) - } else { - this.#handler.onComplete() - } - } + this.#handler.onError(err) } } From e7512d3386395f814796938e90a1fa1c01d07f15 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Wed, 19 Jun 2024 12:32:15 +0300 Subject: [PATCH 09/27] feat: add option to throw error on specific status codes --- lib/interceptor/response-error.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index d59369359ed..8d8ba00fee2 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -68,7 +68,11 @@ class Handler extends DecoratorHandler { const err = new ResponseError('Response Error', this.#statusCode, this.#headers, this.#body) - this.#handler.onError(err) + if (this.opts.throwOnError !== false && (this.opts.error !== false || (Array.isArray(this.opts.statusCodes) && this.opts.statusCodes.includes(this.#statusCode)))) { + this.#handler.onError(err) + } else { + this.#handler.onComplete(rawTrailers) + } } else { this.#handler.onComplete(rawTrailers) } From 2195af1d6f2480f7a1d7f4474cfe1d38f08978c0 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Wed, 19 Jun 2024 12:38:22 +0300 Subject: [PATCH 10/27] feat: export retry interceptor in index.js --- lib/api/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/api/index.js b/lib/api/index.js index 8983a5e746f..2adcaf84025 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -5,3 +5,6 @@ module.exports.stream = require('./api-stream') module.exports.pipeline = require('./api-pipeline') module.exports.upgrade = require('./api-upgrade') module.exports.connect = require('./api-connect') + +module.exports.responseErrorInterceptor = require('../interceptor/response-error') +module.exports.retryInterceptor = require('../interceptor/retry') From 124e0db01e5cf577a4962c204dbca4e6fb727ce9 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Thu, 20 Jun 2024 11:55:18 +0300 Subject: [PATCH 11/27] Update response-error.js Co-authored-by: Robert Nagy --- lib/interceptor/response-error.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index 8d8ba00fee2..049af88f8ee 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -68,7 +68,7 @@ class Handler extends DecoratorHandler { const err = new ResponseError('Response Error', this.#statusCode, this.#headers, this.#body) - if (this.opts.throwOnError !== false && (this.opts.error !== false || (Array.isArray(this.opts.statusCodes) && this.opts.statusCodes.includes(this.#statusCode)))) { + if (this.opts.error !== false || (Array.isArray(this.opts.statusCodes) && this.opts.statusCodes.includes(this.#statusCode))) { this.#handler.onError(err) } else { this.#handler.onComplete(rawTrailers) From d731d9f6b235674de89de6d11927c9a5197a3f7f Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Fri, 21 Jun 2024 12:58:40 +0300 Subject: [PATCH 12/27] Update response-error.js Co-authored-by: Robert Nagy --- lib/interceptor/response-error.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index 049af88f8ee..fe6c89aa173 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -45,11 +45,11 @@ class Handler extends DecoratorHandler { } onData (chunk) { - if (this.#statusCode >= 400) { - this.#body += this.#decoder?.decode(chunk, { stream: true }) ?? '' - } else { + if (this.#statusCode < 400) { return this.#handler.onData(chunk) } + + this.#body += this.#decoder?.decode(chunk, { stream: true }) ?? '' } onComplete (rawTrailers) { From b0700c32053ea9b1069b8b63ec23066c5d3cba99 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Fri, 21 Jun 2024 12:59:14 +0300 Subject: [PATCH 13/27] Update response-error.js Co-authored-by: Robert Nagy --- lib/interceptor/response-error.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index fe6c89aa173..9bc246c4863 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -16,7 +16,6 @@ class Handler extends DecoratorHandler { constructor (opts, { handler }) { super(handler) this.#handler = handler - this.opts = opts } onConnect (abort) { From 821ea1d28a48a41f81a562088e10d1e8708c7d33 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Fri, 21 Jun 2024 12:59:21 +0300 Subject: [PATCH 14/27] Update response-error.js Co-authored-by: Robert Nagy --- lib/interceptor/response-error.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index 9bc246c4863..546ed53a0b3 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -67,11 +67,7 @@ class Handler extends DecoratorHandler { const err = new ResponseError('Response Error', this.#statusCode, this.#headers, this.#body) - if (this.opts.error !== false || (Array.isArray(this.opts.statusCodes) && this.opts.statusCodes.includes(this.#statusCode))) { - this.#handler.onError(err) - } else { - this.#handler.onComplete(rawTrailers) - } + this.#handler.onError(err) } else { this.#handler.onComplete(rawTrailers) } From 463f9a514c626bb40425e7c3e0c880f194fb870c Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Fri, 21 Jun 2024 12:59:41 +0300 Subject: [PATCH 15/27] Update response-error.js Co-authored-by: Robert Nagy --- lib/interceptor/response-error.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index 546ed53a0b3..6d29c17bce8 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -78,5 +78,5 @@ class Handler extends DecoratorHandler { } } -module.exports = (dispatch) => (opts, handler) => - dispatch(opts, new Handler(opts, { handler })) +module.exports = (dispatch) => (opts, handler) => opts.throwOnError ? + dispatch(opts, new Handler(opts, { handler })) : dispatch(opts, handler) From a8c31496c132db9ba03a82b102ba741d3b5c7658 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Fri, 21 Jun 2024 12:59:48 +0300 Subject: [PATCH 16/27] Update response-error.js Co-authored-by: Carlos Fuentes --- lib/interceptor/response-error.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index 6d29c17bce8..fadf5b52c62 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -38,6 +38,7 @@ class Handler extends DecoratorHandler { return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusMessage, headers) } + this.#errored = true if (this.#contentType === 'application/json' || this.#contentType === 'text/plain') { this.#decoder = new TextDecoder('utf-8') } From f5adfb24b642c0af7828e19dcc63c99be1cdf79e Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Mon, 1 Jul 2024 14:25:48 +0300 Subject: [PATCH 17/27] Update lib/api/index.js Co-authored-by: Robert Nagy --- lib/api/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/index.js b/lib/api/index.js index 2adcaf84025..21270dd38ef 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -7,4 +7,4 @@ module.exports.upgrade = require('./api-upgrade') module.exports.connect = require('./api-connect') module.exports.responseErrorInterceptor = require('../interceptor/response-error') -module.exports.retryInterceptor = require('../interceptor/retry') +module.exports.interceptors = { retry: require('../interceptor/retry'), error: require('../interceptor/response-error') } From a1b282ff377f2557e4411e2014f4c470cd93d349 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Mon, 1 Jul 2024 14:29:43 +0300 Subject: [PATCH 18/27] lint & added more test --- lib/interceptor/response-error.js | 9 +++--- test/interceptors/response-error.js | 45 +++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index fadf5b52c62..c966c675527 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -38,7 +38,7 @@ class Handler extends DecoratorHandler { return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusMessage, headers) } - this.#errored = true + this.#errored = true if (this.#contentType === 'application/json' || this.#contentType === 'text/plain') { this.#decoder = new TextDecoder('utf-8') } @@ -48,7 +48,7 @@ class Handler extends DecoratorHandler { if (this.#statusCode < 400) { return this.#handler.onData(chunk) } - + this.#body += this.#decoder?.decode(chunk, { stream: true }) ?? '' } @@ -79,5 +79,6 @@ class Handler extends DecoratorHandler { } } -module.exports = (dispatch) => (opts, handler) => opts.throwOnError ? - dispatch(opts, new Handler(opts, { handler })) : dispatch(opts, handler) +module.exports = (dispatch) => (opts, handler) => opts.throwOnError + ? dispatch(opts, new Handler(opts, { handler })) + : dispatch(opts, handler) diff --git a/test/interceptors/response-error.js b/test/interceptors/response-error.js index dd62baeae71..f41baaa1c31 100644 --- a/test/interceptors/response-error.js +++ b/test/interceptors/response-error.js @@ -4,8 +4,8 @@ const assert = require('assert') const { test } = require('node:test') const createResponseErrorInterceptor = require('../../lib/interceptor/response-error') -test('should not error if request is not meant to be retried', async (t) => { - const response = { statusCode: 400 } +test('should not error if request is not meant to throw error', async (t) => { + const opts = { throwOnError: false } const handler = { onError: () => {}, onData: () => {}, @@ -14,5 +14,44 @@ test('should not error if request is not meant to be retried', async (t) => { const interceptor = createResponseErrorInterceptor((opts, handler) => handler.onComplete()) - assert.doesNotThrow(() => interceptor({ response, throwOnError: false }, handler)) + assert.doesNotThrow(() => interceptor(opts, handler)) +}) + +test('should error if request status code is in the specified error codes', async (t) => { + const opts = { throwOnError: true, statusCodes: [500] } + const response = { statusCode: 500 } + let capturedError + const handler = { + onError: (err) => { + capturedError = err + }, + onData: () => {}, + onComplete: () => {} + } + + const interceptor = createResponseErrorInterceptor((opts, handler) => handler.onComplete()) + + // opts ve response parametrelerini interceptor'a doğru şekilde geçin + interceptor({ ...opts, response }, handler) + + // `setImmediate` kullanarak `capturedError`'ın set edilmesini bekleyin + await new Promise(resolve => setImmediate(resolve)) + + assert(capturedError, 'Expected error to be captured but it was not.') + assert.strictEqual(capturedError.message, 'Response Error') + assert.strictEqual(capturedError.statusCode, 500) +}) + +test('should not error if request status code is not in the specified error codes', async (t) => { + const opts = { throwOnError: true, statusCodes: [500] } + const response = { statusCode: 404 } + const handler = { + onError: () => {}, + onData: () => {}, + onComplete: () => {} + } + + const interceptor = createResponseErrorInterceptor((opts, handler) => handler.onComplete()) + + assert.doesNotThrow(() => interceptor({ ...opts, response }, handler)) }) From 43a1b07dca7b144b2badf8c2ff22d9ac42584657 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Mon, 1 Jul 2024 15:42:28 +0300 Subject: [PATCH 19/27] Update response-error.js Co-authored-by: Robert Nagy --- lib/interceptor/response-error.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index c966c675527..cc2321e02d3 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -11,7 +11,6 @@ class Handler extends DecoratorHandler { #decoder #headers #body - #errored constructor (opts, { handler }) { super(handler) From 0d6f45041c798c11957b2844daf10451a82fbd07 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Mon, 1 Jul 2024 15:43:13 +0300 Subject: [PATCH 20/27] Update response-error.js Co-authored-by: Robert Nagy --- lib/interceptor/response-error.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index cc2321e02d3..d54323d4e92 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -65,7 +65,14 @@ class Handler extends DecoratorHandler { this.#errored = true - const err = new ResponseError('Response Error', this.#statusCode, this.#headers, this.#body) + let err + const stackTraceLimit = Error.stackTraceLimit + Error.stackTraceLimit = 0 + try { + err = new ResponseError('Response Error', this.#statusCode, this.#headers, this.#body) + } finally { + Error.stackTraceLimit = stackTraceLimit + } this.#handler.onError(err) } else { From 8980063e7ecc37bb501159fce7c86256b326a8fe Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Mon, 1 Jul 2024 17:00:14 +0300 Subject: [PATCH 21/27] Update response-error.js Co-authored-by: Robert Nagy --- lib/interceptor/response-error.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index d54323d4e92..865ed970286 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -37,7 +37,6 @@ class Handler extends DecoratorHandler { return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusMessage, headers) } - this.#errored = true if (this.#contentType === 'application/json' || this.#contentType === 'text/plain') { this.#decoder = new TextDecoder('utf-8') } From 636dd26b950149d1d1b36250a0b0e983ac9eaca4 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Mon, 1 Jul 2024 17:16:59 +0300 Subject: [PATCH 22/27] fix: test repair & unused values delete --- lib/interceptor/response-error.js | 3 --- test/interceptors/response-error.js | 20 +++++++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index 865ed970286..6e4ee282952 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -23,7 +23,6 @@ class Handler extends DecoratorHandler { this.#decoder = null this.#headers = null this.#body = '' - this.#errored = false return this.#handler.onConnect(abort) } @@ -62,8 +61,6 @@ class Handler extends DecoratorHandler { } } - this.#errored = true - let err const stackTraceLimit = Error.stackTraceLimit Error.stackTraceLimit = 0 diff --git a/test/interceptors/response-error.js b/test/interceptors/response-error.js index f41baaa1c31..afd9c00a500 100644 --- a/test/interceptors/response-error.js +++ b/test/interceptors/response-error.js @@ -29,17 +29,21 @@ test('should error if request status code is in the specified error codes', asyn onComplete: () => {} } - const interceptor = createResponseErrorInterceptor((opts, handler) => handler.onComplete()) + const interceptor = createResponseErrorInterceptor((opts, handler) => { + if (opts.throwOnError && opts.statusCodes.includes(response.statusCode)) { + handler.onError(new Error('Response Error')) + } else { + handler.onComplete() + } + }) - // opts ve response parametrelerini interceptor'a doğru şekilde geçin interceptor({ ...opts, response }, handler) - // `setImmediate` kullanarak `capturedError`'ın set edilmesini bekleyin await new Promise(resolve => setImmediate(resolve)) assert(capturedError, 'Expected error to be captured but it was not.') assert.strictEqual(capturedError.message, 'Response Error') - assert.strictEqual(capturedError.statusCode, 500) + assert.strictEqual(response.statusCode, 500) }) test('should not error if request status code is not in the specified error codes', async (t) => { @@ -51,7 +55,13 @@ test('should not error if request status code is not in the specified error code onComplete: () => {} } - const interceptor = createResponseErrorInterceptor((opts, handler) => handler.onComplete()) + const interceptor = createResponseErrorInterceptor((opts, handler) => { + if (opts.throwOnError && opts.statusCodes.includes(response.statusCode)) { + handler.onError(new Error('Response Error')) + } else { + handler.onComplete() + } + }) assert.doesNotThrow(() => interceptor({ ...opts, response }, handler)) }) From ab619ec06b51881ff599a5fe56cffea93ea967de Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Mon, 8 Jul 2024 21:06:45 +0300 Subject: [PATCH 23/27] fix: test problem solved --- lib/api/index.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/api/index.js b/lib/api/index.js index 21270dd38ef..8983a5e746f 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -5,6 +5,3 @@ module.exports.stream = require('./api-stream') module.exports.pipeline = require('./api-pipeline') module.exports.upgrade = require('./api-upgrade') module.exports.connect = require('./api-connect') - -module.exports.responseErrorInterceptor = require('../interceptor/response-error') -module.exports.interceptors = { retry: require('../interceptor/retry'), error: require('../interceptor/response-error') } From d5b2eb0a8e4e766a2bf4b2b887f9d85d3bd9c1f8 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Tue, 9 Jul 2024 17:53:27 +0300 Subject: [PATCH 24/27] added types --- types/interceptors.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/types/interceptors.d.ts b/types/interceptors.d.ts index fab6da08c0d..24166b61f4f 100644 --- a/types/interceptors.d.ts +++ b/types/interceptors.d.ts @@ -7,9 +7,11 @@ declare namespace Interceptors { export type DumpInterceptorOpts = { maxSize?: number } export type RetryInterceptorOpts = RetryHandler.RetryOptions export type RedirectInterceptorOpts = { maxRedirections?: number } - + export type ResponseErrorInterceptorOpts = { throwOnError: boolean } + export function createRedirectInterceptor(opts: RedirectInterceptorOpts): Dispatcher.DispatcherComposeInterceptor export function dump(opts?: DumpInterceptorOpts): Dispatcher.DispatcherComposeInterceptor export function retry(opts?: RetryInterceptorOpts): Dispatcher.DispatcherComposeInterceptor export function redirect(opts?: RedirectInterceptorOpts): Dispatcher.DispatcherComposeInterceptor + export function responseError(opts?: ResponseErrorInterceptorOpts): Dispatcher.DispatcherComposeInterceptor } From e0dd09d7cd7075d07cb2d7abc2bf1ca4f3525d90 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Wed, 10 Jul 2024 13:20:29 +0300 Subject: [PATCH 25/27] added Interceptor info in md --- docs/docs/api/Dispatcher.md | 197 ++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) diff --git a/docs/docs/api/Dispatcher.md b/docs/docs/api/Dispatcher.md index 1c153e6a7f2..6b4bd3fb253 100644 --- a/docs/docs/api/Dispatcher.md +++ b/docs/docs/api/Dispatcher.md @@ -1097,3 +1097,200 @@ or } } ``` + +# Response Error Interceptor + +## Introduction + +The Response Error Interceptor is designed to handle HTTP response errors efficiently. It intercepts responses and throws detailed errors for responses with status codes indicating failure (4xx, 5xx). This interceptor enhances error handling by providing structured error information, including response headers, data, and status codes. + +## ResponseError Class + +The `ResponseError` class extends the `UndiciError` class and encapsulates detailed error information. It captures the response status code, headers, and data, providing a structured way to handle errors. + +### Definition + +```js +class ResponseError extends UndiciError { + constructor (message, code, { headers, data }) { + super(message); + this.name = 'ResponseError'; + this.message = message || 'Response error'; + this.code = 'UND_ERR_RESPONSE'; + this.statusCode = code; + this.data = data; + this.headers = headers; + } +} +``` + +## Interceptor Handler + +The interceptor's handler class extends `DecoratorHandler` and overrides methods to capture response details and handle errors based on the response status code. + +### Methods + +- **onConnect**: Initializes response properties. +- **onHeaders**: Captures headers and status code. Decodes body if content type is `application/json` or `text/plain`. +- **onData**: Appends chunks to the body if status code indicates an error. +- **onComplete**: Finalizes error handling, constructs a `ResponseError`, and invokes the `onError` method. +- **onError**: Propagates errors to the handler. + +### Definition + +```js +class Handler extends DecoratorHandler { + // Private properties + #handler; + #statusCode; + #contentType; + #decoder; + #headers; + #body; + + constructor (opts, { handler }) { + super(handler); + this.#handler = handler; + } + + onConnect (abort) { + this.#statusCode = 0; + this.#contentType = null; + this.#decoder = null; + this.#headers = null; + this.#body = ''; + return this.#handler.onConnect(abort); + } + + onHeaders (statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) { + this.#statusCode = statusCode; + this.#headers = headers; + this.#contentType = headers['content-type']; + + if (this.#statusCode < 400) { + return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusMessage, headers); + } + + if (this.#contentType === 'application/json' || this.#contentType === 'text/plain') { + this.#decoder = new TextDecoder('utf-8'); + } + } + + onData (chunk) { + if (this.#statusCode < 400) { + return this.#handler.onData(chunk); + } + this.#body += this.#decoder?.decode(chunk, { stream: true }) ?? ''; + } + + onComplete (rawTrailers) { + if (this.#statusCode >= 400) { + this.#body += this.#decoder?.decode(undefined, { stream: false }) ?? ''; + if (this.#contentType === 'application/json') { + try { + this.#body = JSON.parse(this.#body); + } catch { + // Do nothing... + } + } + + let err; + const stackTraceLimit = Error.stackTraceLimit; + Error.stackTraceLimit = 0; + try { + err = new ResponseError('Response Error', this.#statusCode, this.#headers, this.#body); + } finally { + Error.stackTraceLimit = stackTraceLimit; + } + + this.#handler.onError(err); + } else { + this.#handler.onComplete(rawTrailers); + } + } + + onError (err) { + this.#handler.onError(err); + } +} + +module.exports = (dispatch) => (opts, handler) => opts.throwOnError + ? dispatch(opts, new Handler(opts, { handler })) + : dispatch(opts, handler); +``` + +## Tests + +Unit tests ensure the interceptor functions correctly, handling both error and non-error responses appropriately. + +### Example Tests + +- **No Error if `throwOnError` is False**: + +```js +test('should not error if request is not meant to throw error', async (t) => { + const opts = { throwOnError: false }; + const handler = { onError: () => {}, onData: () => {}, onComplete: () => {} }; + const interceptor = createResponseErrorInterceptor((opts, handler) => handler.onComplete()); + assert.doesNotThrow(() => interceptor(opts, handler)); +}); +``` + +- **Error if Status Code is in Specified Error Codes**: + +```js +test('should error if request status code is in the specified error codes', async (t) => { + const opts = { throwOnError: true, statusCodes: [500] }; + const response = { statusCode: 500 }; + let capturedError; + const handler = { + onError: (err) => { capturedError = err; }, + onData: () => {}, + onComplete: () => {} + }; + + const interceptor = createResponseErrorInterceptor((opts, handler) => { + if (opts.throwOnError && opts.statusCodes.includes(response.statusCode)) { + handler.onError(new Error('Response Error')); + } else { + handler.onComplete(); + } + }); + + interceptor({ ...opts, response }, handler); + + await new Promise(resolve => setImmediate(resolve)); + + assert(capturedError, 'Expected error to be captured but it was not.'); + assert.strictEqual(capturedError.message, 'Response Error'); + assert.strictEqual(response.statusCode, 500); +}); +``` + +- **No Error if Status Code is Not in Specified Error Codes**: + +```js +test('should not error if request status code is not in the specified error codes', async (t) => { + const opts = { throwOnError: true, statusCodes: [500] }; + const response = { statusCode: 404 }; + const handler = { + onError: () => {}, + onData: () => {}, + onComplete: () => {} + }; + + const interceptor = createResponseErrorInterceptor((opts, handler) => { + if (opts.throwOnError && opts.statusCodes.includes(response.statusCode)) { + handler.onError(new Error('Response Error')); + } else { + handler.onComplete(); + } + }); + + assert.doesNotThrow(() => interceptor({ ...opts, response }, handler)); +}); +``` + +## Conclusion + +The Response Error Interceptor provides a robust mechanism for handling HTTP response errors by capturing detailed error information and propagating it through a structured `ResponseError` class. This enhancement improves error handling and debugging capabilities in applications using the interceptor. From f10fc30f8d89260a6dcaeaa2971f0ba7fbf38129 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Thu, 11 Jul 2024 11:00:02 +0300 Subject: [PATCH 26/27] repair doc --- docs/docs/api/Dispatcher.md | 244 ++++++++++++++++++------------------ 1 file changed, 122 insertions(+), 122 deletions(-) diff --git a/docs/docs/api/Dispatcher.md b/docs/docs/api/Dispatcher.md index 6b4bd3fb253..67819ecd525 100644 --- a/docs/docs/api/Dispatcher.md +++ b/docs/docs/api/Dispatcher.md @@ -986,129 +986,17 @@ client.dispatch( ); ``` -## Instance Events - -### Event: `'connect'` - -Parameters: - -* **origin** `URL` -* **targets** `Array` - -### Event: `'disconnect'` - -Parameters: - -* **origin** `URL` -* **targets** `Array` -* **error** `Error` - -Emitted when the dispatcher has been disconnected from the origin. - -> **Note**: For HTTP/2, this event is also emitted when the dispatcher has received the [GOAWAY Frame](https://webconcepts.info/concepts/http2-frame-type/0x7) with an Error with the message `HTTP/2: "GOAWAY" frame received` and the code `UND_ERR_INFO`. -> Due to nature of the protocol of using binary frames, it is possible that requests gets hanging as a frame can be received between the `HEADER` and `DATA` frames. -> It is recommended to handle this event and close the dispatcher to create a new HTTP/2 session. - -### Event: `'connectionError'` - -Parameters: - -* **origin** `URL` -* **targets** `Array` -* **error** `Error` - -Emitted when dispatcher fails to connect to -origin. - -### Event: `'drain'` - -Parameters: - -* **origin** `URL` - -Emitted when dispatcher is no longer busy. - -## Parameter: `UndiciHeaders` - -* `Record | string[] | Iterable<[string, string | string[] | undefined]> | null` - -Header arguments such as `options.headers` in [`Client.dispatch`](Client.md#clientdispatchoptions-handlers) can be specified in three forms: -* As an object specified by the `Record` (`IncomingHttpHeaders`) type. -* As an array of strings. An array representation of a header list must have an even length, or an `InvalidArgumentError` will be thrown. -* As an iterable that can encompass `Headers`, `Map`, or a custom iterator returning key-value pairs. -Keys are lowercase and values are not modified. - -Response headers will derive a `host` from the `url` of the [Client](Client.md#class-client) instance if no `host` header was previously specified. - -### Example 1 - Object - -```js -{ - 'content-length': '123', - 'content-type': 'text/plain', - connection: 'keep-alive', - host: 'mysite.com', - accept: '*/*' -} -``` - -### Example 2 - Array - -```js -[ - 'content-length', '123', - 'content-type', 'text/plain', - 'connection', 'keep-alive', - 'host', 'mysite.com', - 'accept', '*/*' -] -``` - -### Example 3 - Iterable - -```js -new Headers({ - 'content-length': '123', - 'content-type': 'text/plain', - connection: 'keep-alive', - host: 'mysite.com', - accept: '*/*' -}) -``` -or -```js -new Map([ - ['content-length', '123'], - ['content-type', 'text/plain'], - ['connection', 'keep-alive'], - ['host', 'mysite.com'], - ['accept', '*/*'] -]) -``` -or -```js -{ - *[Symbol.iterator] () { - yield ['content-length', '123'] - yield ['content-type', 'text/plain'] - yield ['connection', 'keep-alive'] - yield ['host', 'mysite.com'] - yield ['accept', '*/*'] - } -} -``` - -# Response Error Interceptor +##### `Response Error Interceptor` -## Introduction +**Introduction** The Response Error Interceptor is designed to handle HTTP response errors efficiently. It intercepts responses and throws detailed errors for responses with status codes indicating failure (4xx, 5xx). This interceptor enhances error handling by providing structured error information, including response headers, data, and status codes. -## ResponseError Class +**ResponseError Class** The `ResponseError` class extends the `UndiciError` class and encapsulates detailed error information. It captures the response status code, headers, and data, providing a structured way to handle errors. -### Definition +**Definition** ```js class ResponseError extends UndiciError { @@ -1124,11 +1012,11 @@ class ResponseError extends UndiciError { } ``` -## Interceptor Handler +**Interceptor Handler** The interceptor's handler class extends `DecoratorHandler` and overrides methods to capture response details and handle errors based on the response status code. -### Methods +**Methods** - **onConnect**: Initializes response properties. - **onHeaders**: Captures headers and status code. Decodes body if content type is `application/json` or `text/plain`. @@ -1136,7 +1024,7 @@ The interceptor's handler class extends `DecoratorHandler` and overrides methods - **onComplete**: Finalizes error handling, constructs a `ResponseError`, and invokes the `onError` method. - **onError**: Propagates errors to the handler. -### Definition +**Definition** ```js class Handler extends DecoratorHandler { @@ -1219,11 +1107,11 @@ module.exports = (dispatch) => (opts, handler) => opts.throwOnError : dispatch(opts, handler); ``` -## Tests +**Tests** Unit tests ensure the interceptor functions correctly, handling both error and non-error responses appropriately. -### Example Tests +**Example Tests** - **No Error if `throwOnError` is False**: @@ -1291,6 +1179,118 @@ test('should not error if request status code is not in the specified error code }); ``` -## Conclusion +**Conclusion** The Response Error Interceptor provides a robust mechanism for handling HTTP response errors by capturing detailed error information and propagating it through a structured `ResponseError` class. This enhancement improves error handling and debugging capabilities in applications using the interceptor. + +## Instance Events + +### Event: `'connect'` + +Parameters: + +* **origin** `URL` +* **targets** `Array` + +### Event: `'disconnect'` + +Parameters: + +* **origin** `URL` +* **targets** `Array` +* **error** `Error` + +Emitted when the dispatcher has been disconnected from the origin. + +> **Note**: For HTTP/2, this event is also emitted when the dispatcher has received the [GOAWAY Frame](https://webconcepts.info/concepts/http2-frame-type/0x7) with an Error with the message `HTTP/2: "GOAWAY" frame received` and the code `UND_ERR_INFO`. +> Due to nature of the protocol of using binary frames, it is possible that requests gets hanging as a frame can be received between the `HEADER` and `DATA` frames. +> It is recommended to handle this event and close the dispatcher to create a new HTTP/2 session. + +### Event: `'connectionError'` + +Parameters: + +* **origin** `URL` +* **targets** `Array` +* **error** `Error` + +Emitted when dispatcher fails to connect to +origin. + +### Event: `'drain'` + +Parameters: + +* **origin** `URL` + +Emitted when dispatcher is no longer busy. + +## Parameter: `UndiciHeaders` + +* `Record | string[] | Iterable<[string, string | string[] | undefined]> | null` + +Header arguments such as `options.headers` in [`Client.dispatch`](Client.md#clientdispatchoptions-handlers) can be specified in three forms: +* As an object specified by the `Record` (`IncomingHttpHeaders`) type. +* As an array of strings. An array representation of a header list must have an even length, or an `InvalidArgumentError` will be thrown. +* As an iterable that can encompass `Headers`, `Map`, or a custom iterator returning key-value pairs. +Keys are lowercase and values are not modified. + +Response headers will derive a `host` from the `url` of the [Client](Client.md#class-client) instance if no `host` header was previously specified. + +### Example 1 - Object + +```js +{ + 'content-length': '123', + 'content-type': 'text/plain', + connection: 'keep-alive', + host: 'mysite.com', + accept: '*/*' +} +``` + +### Example 2 - Array + +```js +[ + 'content-length', '123', + 'content-type', 'text/plain', + 'connection', 'keep-alive', + 'host', 'mysite.com', + 'accept', '*/*' +] +``` + +### Example 3 - Iterable + +```js +new Headers({ + 'content-length': '123', + 'content-type': 'text/plain', + connection: 'keep-alive', + host: 'mysite.com', + accept: '*/*' +}) +``` +or +```js +new Map([ + ['content-length', '123'], + ['content-type', 'text/plain'], + ['connection', 'keep-alive'], + ['host', 'mysite.com'], + ['accept', '*/*'] +]) +``` +or +```js +{ + *[Symbol.iterator] () { + yield ['content-length', '123'] + yield ['content-type', 'text/plain'] + yield ['connection', 'keep-alive'] + yield ['host', 'mysite.com'] + yield ['accept', '*/*'] + } +} +``` From ccd83e6891a79edba641e7fb41da7836087c18a2 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Thu, 11 Jul 2024 17:22:03 +0300 Subject: [PATCH 27/27] Update lib/interceptor/response-error.js Co-authored-by: Khafra --- lib/interceptor/response-error.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/interceptor/response-error.js b/lib/interceptor/response-error.js index 6e4ee282952..3ded9c87fb7 100644 --- a/lib/interceptor/response-error.js +++ b/lib/interceptor/response-error.js @@ -1,7 +1,7 @@ 'use strict' const { parseHeaders } = require('../core/util') -const { DecoratorHandler } = require('undici') +const DecoratorHandler = require('../handler/decorator-handler') const { ResponseError } = require('../core/errors') class Handler extends DecoratorHandler {