Skip to content

Commit b3f940c

Browse files
committed
removed .fallback and added error class
1 parent 93c0ed1 commit b3f940c

File tree

3 files changed

+110
-20
lines changed

3 files changed

+110
-20
lines changed

README.md

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,28 @@ const client = new HttpClient(options);
3939

4040
#### options
4141

42-
| option | default | type | required | details |
43-
|-----------------------|--------------|------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------|
44-
| connections | `50` | `number` | no | See [connections](#connections) |
45-
| fallback | `undefined` | `function` | no | Function to call when requests fail |
46-
| keepAliveMaxTimeout | `undefined` | `number` | no | See [keepAliveMaxTimeout](#keepAliveMaxTimeout) |
47-
| keepAliveTimeout | `undefined` | `number` | no | See [keepAliveTimeout](#keepAliveTimeout) |
48-
| logger | `undefined ` | `object` | no | A logger which conform to a log4j interface |
49-
| pipelining | `10` | `number` | no | See [pipelining](#pipelining) |
50-
| reset | `2000` | `number` | no | Circuit breaker: How long, in milliseconds, to wait before a tripped circuit should be reset. |
51-
| threshold | `25` | `number` | no | Circuit breaker: How many, in %, requests should error before the circuit should trip. Ex; when 25% of requests fail, trip the circuit. |
52-
| throwOn400 | `false` | `boolean` | no | If the client should throw on HTTP 400 errors.If true, HTTP 400 errors will counts against tripping the circuit. |
53-
| throwOn500 | `true` | `boolean` | no | If the client should throw on HTTP 500 errors.If true, HTTP 500 errors will counts against tripping the circuit. |
54-
| timeout | `500` | `number` | no | Circuit breaker: How long, in milliseconds, a request can maximum take. Requests exceeding this limit counts against tripping the circuit. |
55-
42+
| option | default | type | required | details |
43+
|---------------------|--------------|------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------|
44+
| abortController | `undefined` | `object` | no | See [abortController](#abortController) |
45+
| connections | `50` | `number` | no | See [connections](#connections) |
46+
| fallback | `undefined` | `function` | no | Function to call when requests fail |
47+
| keepAliveMaxTimeout | `undefined` | `number` | no | See [keepAliveMaxTimeout](#keepAliveMaxTimeout) |
48+
| keepAliveTimeout | `undefined` | `number` | no | See [keepAliveTimeout](#keepAliveTimeout) |
49+
| logger | `undefined ` | `object` | no | A logger which conform to a log4j interface |
50+
| pipelining | `10` | `number` | no | See [pipelining](#pipelining) |
51+
| reset | `2000` | `number` | no | Circuit breaker: How long, in milliseconds, to wait before a tripped circuit should be reset. |
52+
| threshold | `25` | `number` | no | Circuit breaker: How many, in %, requests should error before the circuit should trip. Ex; when 25% of requests fail, trip the circuit. |
53+
| throwOn400 | `false` | `boolean` | no | If the client should throw on HTTP 400 errors.If true, HTTP 400 errors will counts against tripping the circuit. |
54+
| throwOn500 | `true` | `boolean` | no | If the client should throw on HTTP 500 errors.If true, HTTP 500 errors will counts against tripping the circuit. |
55+
| timeout | `500` | `number` | no | Circuit breaker: How long, in milliseconds, a request can maximum take. Requests exceeding this limit counts against tripping the circuit. |
56+
57+
58+
##### abortController
59+
60+
Passing in an [AbortController](https://nodejs.org/docs/latest/api/globals.html#globals_class_abortcontroller) enables aborting requests.
5661

5762
##### connections
63+
5864
Property is sent to the underlying http library.
5965
See library docs on [connections](https://undici.nodejs.org/#/docs/api/Pool?id=parameter-pooloptions)
6066

@@ -120,7 +126,29 @@ If the client should throw on http 500 errors. If true, http 500 errors will cou
120126

121127
## Methods
122128

129+
### async request(options = {})
130+
131+
Sends a request using the passed in options object.
132+
133+
| name | type | description |
134+
|---------|-----------------|-------------------------------------------------|
135+
| origin | `string \| URL` | Request origin, ex `https://server.domain:9090` |
136+
| path | `string` | URL path, ex `/foo` |
137+
| method | `string` | HTTP method name |
138+
| headers | `object` | Object with key / value which are strings |
139+
| query | `object` | Object with key / value which are strings |
140+
| signal | `AbortSignal` | Abort signal for canceling requests. |
141+
142+
For a complete list of options, consult the [undici documentation](https://undici.nodejs.org/#/?id=undicirequesturl-options-promise).
143+
144+
145+
### async close()
146+
147+
Closes the client and it's connections.
148+
149+
## HttpClientError
123150

151+
Errors thrown from the library will be of the class `HttpClientError`
124152

125153
[@metrics/metric]: https://github.com/metrics-js/metric '@metrics/metric'
126154
[abslog]: https://github.com/trygve-lie/abslog 'abslog'

lib/http-client.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export default class HttpClient {
6969
});
7070

7171
if (fallback) {
72-
this.fallback(fallback);
72+
this.#fallback(fallback);
7373
}
7474
}
7575

@@ -107,7 +107,7 @@ export default class HttpClient {
107107
* Function called if the request fails.
108108
* @param {import('opossum')} func
109109
*/
110-
fallback(func) {
110+
#fallback(func) {
111111
this.#breaker.fallback(func);
112112
}
113113

@@ -117,7 +117,20 @@ export default class HttpClient {
117117
* @returns {Promise<any>}
118118
*/
119119
async request(options = {}) {
120-
return await this.#breaker.fire(options);
120+
try {
121+
return await this.#breaker.fire(options);
122+
} catch (error) {
123+
if (!this.fallback) {
124+
throw new HttpClientError(
125+
`Error on ${options.method} ${options.origin}${options.path}`,
126+
{
127+
code: error.code,
128+
cause: error,
129+
options,
130+
},
131+
);
132+
}
133+
}
121134
}
122135

123136
/**
@@ -131,3 +144,16 @@ export default class HttpClient {
131144
}
132145
}
133146
}
147+
148+
/**
149+
* Error class for the client
150+
*/
151+
export class HttpClientError extends Error {
152+
constructor(message, { code, cause, options }) {
153+
super(message);
154+
this.name = 'HttpClientError';
155+
this.code = code;
156+
this.cause = cause;
157+
this.options = options;
158+
}
159+
}

tests/http-client.test.js

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ async function queryUrl({
3939
try {
4040
await client.request({ path, origin: url, method: 'GET' });
4141
} catch (err) {
42-
if (!suppressErrors) errors.push(err);
42+
// Push the actual cause here for the tests
43+
if (!suppressErrors) errors.push(err.cause);
4344
}
4445
}
4546
if (errors.length > 0) {
@@ -77,6 +78,41 @@ await test('http-client - basics', async (t) => {
7778
await fetch(url);
7879
await client.close();
7980
});
81+
82+
await t.test('throws error when no fallback provided', async () => {
83+
const client = new HttpClient();
84+
assert.rejects(
85+
async () => {
86+
await client.request({
87+
path: '/',
88+
origin: 'https://does-not-exist.domain',
89+
method: 'GET',
90+
});
91+
},
92+
{
93+
name: 'HttpClientError',
94+
message: 'Error on GET https://does-not-exist.domain/',
95+
},
96+
);
97+
await client.close();
98+
});
99+
await t.test('does not throw when fallback provided', async () => {
100+
let isCaught = false;
101+
const client = new HttpClient({
102+
fallback: () => {
103+
isCaught = true;
104+
},
105+
});
106+
107+
await client.request({
108+
path: '/',
109+
origin: 'https://does-not-exist.domain',
110+
method: 'GET',
111+
});
112+
assert.strictEqual(isCaught, true);
113+
await client.close();
114+
});
115+
80116
await closeServer(server);
81117
});
82118

@@ -123,7 +159,7 @@ await test('http-client - circuit breaker behaviour', async (t) => {
123159
const url = `http://${host}:${port}`;
124160
await t.test('opens on failure threshold', async () => {
125161
beforeEach();
126-
const invalidUrl = `http://${host}asas:3013`;
162+
const invalidUrl = `http://${host}:3013`;
127163
const client = new HttpClient({ threshold: 50 });
128164

129165
let broken = 0;
@@ -135,7 +171,7 @@ await test('http-client - circuit breaker behaviour', async (t) => {
135171
method: 'GET',
136172
});
137173
} catch (err) {
138-
if (err.code === 'EOPENBREAKER') {
174+
if (err.cause.code === 'EOPENBREAKER') {
139175
broken++;
140176
}
141177
}

0 commit comments

Comments
 (0)