diff --git a/README.md b/README.md index 4f60c9b..c339382 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,10 @@ const client = new HttpClient(options); | option | default | type | required | details | |---------------------|--------------|------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------| -| abortController | `undefined` | `object` | no | See [abortController](#abortController) | +| abortController | `undefined` | `object` | no | See [abortController](#abortController) | | connections | `50` | `number` | no | See [connections](#connections) | -| fallback | `undefined` | `function` | no | Function to call when requests fail | +| fallback | `undefined` | `function` | no | Function to call when requests fail, see [fallback](#fallback) | +| followRedirects | `false` | `boolean` | no | Flag for whether to follow redirects or not, see [followRedirects](#followRedirects). | | keepAliveMaxTimeout | `undefined` | `number` | no | See [keepAliveMaxTimeout](#keepAliveMaxTimeout) | | keepAliveTimeout | `undefined` | `number` | no | See [keepAliveTimeout](#keepAliveTimeout) | | logger | `undefined ` | `object` | no | A logger which conform to a log4j interface | @@ -72,6 +73,15 @@ Optional function to run when a request fails. // TBA ``` +##### followRedirects + +TODO!!! decide what to do with the redirects stuff... + +By default, the library does not follow redirect. +If set to true it will follow redirects according to `maxRedirections`. +It will by default throw on reaching `throwOnMaxRedirects` + + ##### keepAliveMaxTimeout Property is sent to the underlying http library. diff --git a/lib/http-client.js b/lib/http-client.js index e918fa8..f0b302a 100644 --- a/lib/http-client.js +++ b/lib/http-client.js @@ -1,4 +1,4 @@ -import { Agent, request } from 'undici'; +import { Agent, request, interceptors } from 'undici'; import createError from 'http-errors'; import Opossum from 'opossum'; import abslog from 'abslog'; @@ -35,6 +35,7 @@ export default class HttpClient { autoRenewAbortController = false, connections = 50, fallback = undefined, + followRedirects = false, keepAliveMaxTimeout = undefined, keepAliveTimeout = undefined, logger = undefined, @@ -62,12 +63,19 @@ export default class HttpClient { timeout, }); - this.#agent = new Agent({ - keepAliveMaxTimeout, // TODO unknown option, consider removing - keepAliveTimeout, // TODO unknown option, consider removing + const { redirect } = interceptors; + let agent = new Agent({ + keepAliveMaxTimeout, + keepAliveTimeout, connections, - pipelining, // TODO unknown option, consider removing + pipelining, }); + if (followRedirects) { + agent = agent.compose( + redirect({ maxRedirections: 1, throwOnMaxRedirects: true }), + ); + } + this.#agent = agent; if (fallback) { this.#hasFallback = true; @@ -77,11 +85,8 @@ export default class HttpClient { async #request(options = {}) { const { statusCode, headers, trailers, body } = await request({ + dispatcher: this.#agent, ...options, - dispatcher: new Agent({ - keepAliveTimeout: 10, - keepAliveMaxTimeout: 10, - }), }); if (this.#throwOn400 && statusCode >= 400 && statusCode <= 499) { @@ -157,6 +162,7 @@ export default class HttpClient { * Error class for the client */ export class HttpClientError extends Error { + // Not sure if there is a need for this tbh, but I threw it in there so we can see how it feels. static ServerDown = 'EOPENBREAKER'; constructor(message, { code, cause, options }) { super(message); diff --git a/tests/http-client.test.js b/tests/http-client.test.js index 4f399a6..fc58792 100644 --- a/tests/http-client.test.js +++ b/tests/http-client.test.js @@ -155,6 +155,45 @@ await test('http-client - abort controller', async (t) => { slowServer.close(); }); +await test('http-client - redirects', async (t) => { + const to = http.createServer(async (request, response) => { + response.writeHead(200); + response.end(); + }); + to.listen(3033, host); + + const from = http.createServer(async (request, response) => { + if (request.url === '/redirect') { + response.setHeader('location', 'http://localhost:3033'); + } + response.writeHead(301); + response.end(); + }); + from.listen(port, host); + + await t.test('can follow redirects', async () => { + const client = new HttpClient({ threshold: 50, followRedirects: true }); + const response = await client.request({ + method: 'GET', + origin: `http://${host}:${port}`, + path: '/redirect', + }); + assert.strictEqual(response.statusCode, 200); + }); + // await t.test.skip('throw on max redirects', async () => {}); + await t.test('does not follow redirects by default', async () => { + const client = new HttpClient({ threshold: 50 }); + const response = await client.request({ + method: 'GET', + origin: `http://${host}:${port}`, + path: '/redirect', + }); + assert.strictEqual(response.statusCode, 301); + }); + from.close(); + to.close(); +}); + await test('http-client - circuit breaker behaviour', async (t) => { const url = `http://${host}:${port}`; await t.test('opens on failure threshold', async () => {