Skip to content

Commit e9cc81d

Browse files
authored
Merge pull request #32 from puzzle-js/feature/retry
Feature/retry
2 parents 5c8970d + 227acfe commit e9cc81d

21 files changed

+639
-213
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## [1.4.0] - 2019-05-08
4+
### Added
5+
- Retry plugin for retrying failed responses
6+
- Added generic identifiers
7+
### Changed
8+
- Identifiers are not required anymore
9+
310
## [1.3.0] - 2019-05-02
411
### Added
512
- Security for set-cookie header to prevent dangerous response caching with credentials.

README.md

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ Warden is an outgoing request optimizer for creating fast and scalable applicati
1010
[![Codacy](https://api.codacy.com/project/badge/Grade/e806d72373414fd9818ab2a403f1b36d)](https://www.codacy.com/app/Acanguven/puzzle-warden?utm_source=github.com&utm_medium=referral&utm_content=puzzle-js/puzzle-warden&utm_campaign=Badge_Grade)
1111

1212
## Features
13-
- 📥 **Smart Caching** Caches requests by converting HTTP requests to smart key strings. ✅
14-
- 🚧 **Request Holder** Stopping same request to be sent multiple times. ✅
15-
- 🔌 **Support** Warden can be used with anything but it supports [request](https://github.com/request/request) out of the box. ✅
16-
- 😎 **Easy Implementation** Warden can be easily implemented with a few lines of codes. ✅
17-
- 🔁 **Request Retry** Requests will automatically be re-attempted on recoverable errors. 📝
18-
- 📇 **Schema Parser** Warden uses a schema which can be provided by you for parsing JSON faster. 📝
19-
- 🚥 **API Queue** Throttles API calls to protect target service. 📝
20-
- 👻 **Request Shadowing** Copies a fraction of traffic to a new deployment for observation. 📝
21-
- 🚉 **Reverse Proxy** It can be deployable as an external application which can serve as a reverse proxy. 📝
22-
- 📛 **Circuit Breaker** Immediately refuses new requests to provide time for the API to become healthy. 📝
13+
- 📥 **Smart Caching** Caches requests by converting HTTP requests to smart key strings. ✅
14+
- 🚧 **Request Holder** Stopping same request to be sent multiple times. ✅
15+
- 🔌 **Support** Warden can be used with anything but it supports [request](https://github.com/request/request) out of the box. ✅
16+
- 😎 **Easy Implementation** Warden can be easily implemented with a few lines of codes. ✅
17+
- 🔁 **Request Retry** Requests will automatically be re-attempted on recoverable errors.
18+
- 📇 **Schema Parser** Warden uses a schema which can be provided by you for parsing JSON faster. 📝
19+
- 🚥 **API Queue** Throttles API calls to protect target service. 📝
20+
- 👻 **Request Shadowing** Copies a fraction of traffic to a new deployment for observation. 📝
21+
- 🚉 **Reverse Proxy** It can be deployable as an external application which can serve as a reverse proxy. 📝
22+
- 📛 **Circuit Breaker** Immediately refuses new requests to provide time for the API to become healthy. 📝
2323

2424
![Warden Achitecture](./warden_architecture.svg)
2525

@@ -29,17 +29,18 @@ Warden is an outgoing request optimizer for creating fast and scalable applicati
2929
- [Identifier](#identifier)
3030
- [Registering Route](#registering-route)
3131
- [Cache](#cache)
32+
- [Retry](#retry)
3233
- [Holder](#holder)
3334
- [Api](#api)
3435

3536
### Installing
3637

3738
Yarn
38-
```
39+
```bash
3940
yarn add puzzle-warden
4041
```
4142
Npm
42-
```
43+
```bash
4344
npm i puzzle-warden --save
4445
```
4546

@@ -122,6 +123,8 @@ warden.register('test', {
122123
});
123124
```
124125

126+
`identifier` is an optional field. If an identifier is not provided warden will be use generic identifier which is `${name}_${url}_${JSON.stringify({cookie, headers, query})}_${method}`.
127+
125128
### Cache
126129

127130
You can simply enable cache with default values using.
@@ -176,7 +179,36 @@ Simple old school caching. Asks cache plugin if it has a valid cached response.
176179
### Holder
177180

178181
Holder prevents same HTTP requests to be sent at the same time.
179-
Let's assume we have an identifier for a request: `{query.foo}`. We send a HTTP request `/product?foo=bar`. While waiting for the response, warden received another HTTP request to the same address which means both HTTP requests are converted to the same key. Then Warden stops the second request. After receiving the response from the first request, Warden returns both requests with the same response by sending only one HTTP request.
182+
Let's assume we have an identifier for a request: `{query.foo}`. We send a HTTP request `/product?foo=bar`. While waiting for the response, warden received another HTTP request to the same address which means both HTTP requests are converted to the same key. Then Warden stops the second request. After receiving the response from the first request, Warden returns both requests with the same response by sending only one HTTP request.
183+
184+
### Retry
185+
186+
When the connection fails with one of ECONNRESET, ENOTFOUND, ESOCKETTIMEDOUT, ETIMEDOUT, ECONNREFUSED, EHOSTUNREACH, EPIPE, EAI_AGAIN or when an HTTP 5xx or 429 error occurrs, the request will automatically be re-attempted as these are often recoverable errors and will go away on retry.
187+
188+
```js
189+
warden.register('routeName', {
190+
retry: {
191+
delay: 100,
192+
count: 1,
193+
logger: (retryCount) => {
194+
console.log(retryCount);
195+
}
196+
}
197+
});
198+
199+
warden.register('routeName', {
200+
retry: true // default settings
201+
});
202+
```
203+
204+
Default values and properties
205+
206+
| Property | Required | Default Value | Definition |
207+
| :--- | :---: | ---: | :--- |
208+
| delay || 100 | Warden will wait for 100ms before retry |
209+
| count || 1 | It will try for 1 time by default |
210+
| logger || 1m | Logger will be called on each retry with retry count|
211+
180212

181213
### Api
182214

demo/demo-starter.ts

Lines changed: 0 additions & 99 deletions
This file was deleted.

jest.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module.exports = {
2-
preset: 'ts-jest',
3-
testEnvironment: 'node',
2+
preset: "ts-jest",
3+
testEnvironment: "node",
44
collectCoverageFrom: [
55
"src/**/*.ts",
66
"!src/**/*.d.ts",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "puzzle-warden",
3-
"version": "1.3.0",
3+
"version": "1.4.0",
44
"main": "dist/index.js",
55
"types": "dist/index.d.ts",
66
"license": "MIT",

src/cache-factory.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,10 @@ class CacheFactory {
5050
}
5151

5252
getPlugin(plugin?: CACHE_PLUGIN): CachePlugin {
53-
switch (plugin) {
54-
case CACHE_PLUGIN.Memory:
55-
return new MemoryCache();
56-
default:
57-
return new MemoryCache();
53+
if (plugin === CACHE_PLUGIN.Memory) {
54+
return new MemoryCache();
55+
} else {
56+
return new MemoryCache();
5857
}
5958
}
6059

src/cache-then-network.ts

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,47 +4,53 @@ import {StreamType} from "./stream-factory";
44
import {CachePlugin} from "./cache-factory";
55
import request from "request";
66

7+
interface CacheDecoratedResponse extends ResponseChunk {
8+
cacheHit: boolean;
9+
}
10+
11+
interface CacheDecoratedRequest extends RequestChunk {
12+
cacheHit: boolean;
13+
}
714

815
class CacheThenNetwork extends WardenStream {
9-
private readonly storage: CachePlugin;
10-
private readonly ms?: number;
11-
12-
constructor(plugin: CachePlugin, ms?: number) {
13-
super(StreamType.CACHE);
14-
15-
this.ms = ms;
16-
this.storage = plugin;
17-
}
18-
19-
async onResponse(chunk: ResponseChunk, callback: TransformCallback): Promise<void> {
20-
if (!chunk.cacheHit && !chunk.error && chunk.response) {
21-
if(chunk.response.headers["set-cookie"]){
22-
console.warn('Detected dangerous response with set-cookie header, not caching', chunk.key);
23-
}else{
24-
await this.storage.set(chunk.key, chunk.response, this.ms);
25-
}
16+
private readonly storage: CachePlugin;
17+
private readonly ms?: number;
18+
19+
constructor(plugin: CachePlugin, ms?: number) {
20+
super(StreamType.CACHE);
21+
22+
this.ms = ms;
23+
this.storage = plugin;
24+
}
25+
26+
async onResponse(chunk: CacheDecoratedResponse, callback: TransformCallback): Promise<void> {
27+
if (!chunk.cacheHit && !chunk.error && chunk.response) {
28+
if (chunk.response.headers["set-cookie"]) {
29+
console.warn('Detected dangerous response with set-cookie header, not caching', chunk.key);
30+
} else {
31+
await this.storage.set(chunk.key, chunk.response, this.ms);
32+
}
33+
}
34+
35+
callback(undefined, chunk);
2636
}
2737

28-
callback(undefined, chunk);
29-
}
30-
31-
async onRequest(chunk: RequestChunk, callback: TransformCallback): Promise<void> {
32-
const cachedData = await this.storage.get(chunk.key) as request.Response;
33-
34-
if (cachedData) {
35-
this.respond({
36-
key: chunk.key,
37-
cb: chunk.cb,
38-
response: cachedData,
39-
cacheHit: true
40-
});
41-
callback(undefined, null);
42-
} else {
43-
callback(undefined, chunk);
38+
async onRequest(chunk: CacheDecoratedRequest, callback: TransformCallback): Promise<void> {
39+
const cachedData = await this.storage.get(chunk.key) as request.Response;
40+
41+
if (cachedData) {
42+
this.respond<CacheDecoratedResponse>({
43+
...chunk,
44+
response: cachedData,
45+
cacheHit: true
46+
});
47+
callback(undefined, null);
48+
} else {
49+
callback(undefined, chunk);
50+
}
4451
}
45-
}
4652
}
4753

4854
export {
49-
CacheThenNetwork
55+
CacheThenNetwork
5056
};

src/holder.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ import {RequestChunk, ResponseChunk, WardenStream} from "./warden-stream";
22
import {TransformCallback} from "stream";
33
import {StreamType} from "./stream-factory";
44

5-
interface HolderConfiguration {
6-
7-
}
85

96
class Holder extends WardenStream {
107
private holdQueue: { [key: string]: RequestChunk[] | null } = {};
@@ -20,10 +17,8 @@ class Holder extends WardenStream {
2017
if (holdQueue) {
2118
holdQueue.forEach(holdChunk => {
2219
this.respond({
20+
...chunk,
2321
cb: holdChunk.cb,
24-
key: chunk.key,
25-
response: chunk.response,
26-
error: chunk.error
2722
});
2823
});
2924

@@ -48,6 +43,5 @@ class Holder extends WardenStream {
4843
}
4944

5045
export {
51-
HolderConfiguration,
5246
Holder
5347
};

src/network.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ class Network extends WardenStream {
2121
onRequest(chunk: RequestChunk, callback: TransformCallback): void {
2222
this.requestWrapper.request[chunk.requestOptions.method](chunk.requestOptions, (error, response) => {
2323
this.respond({
24-
key: chunk.key,
25-
cb: chunk.cb,
24+
...chunk,
2625
response,
2726
error
2827
});

0 commit comments

Comments
 (0)