Skip to content

Commit 1167644

Browse files
authored
Merge pull request #1714 from commercetools/feat/add-retry-on-abort
2 parents 9034675 + 79a1343 commit 1167644

File tree

4 files changed

+161
-12
lines changed

4 files changed

+161
-12
lines changed

docs/sdk/api/sdkMiddlewareHttp.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ The HTTP middleware can run in either a browser or Node.js environment. For Node
3838
9. `retryDelay` _(Number)_: amount of milliseconds to wait before retrying the next request. (Default: 200)
3939
10. `backoff` _(Boolean)_: activates exponential backoff. Recommended to prevent spamming of the server. (Default: true)
4040
11. `maxDelay` _(Number)_: The maximum duration (milliseconds) to wait before retrying, useful if the delay time grew exponentially more than reasonable
41-
12. `fetch` _(Function)_: A `fetch` implementation which can be e.g. `node-fetch` or `unfetch` but also the native browser `fetch` function
42-
13. `timeout` _(Number)_: Request/response timeout in ms. Must have globally available or passed in `AbortController`
43-
14. `abortController` or `getAbortController` depending on you chose to handle the timeout (_abortController_): This property accepts the `AbortController` instance. Could be [abort-controller](https://www.npmjs.com/package/abort-controller) or globally available one.
41+
12. `retryOnAbort` _(Boolean)_: Configure the client to retry an aborted request or not. Defaults to false.
42+
13. `fetch` _(Function)_: A `fetch` implementation which can be e.g. `node-fetch` or `unfetch` but also the native browser `fetch` function
43+
14. `timeout` _(Number)_: Request/response timeout in ms. Must be globally available or passed in `AbortController`
44+
15. `abortController` or `getAbortController` depending on what you chose to handle the timeout (_abortController_): This property accepts the `AbortController` instance. Could be [abort-controller](https://www.npmjs.com/package/abort-controller) or a globally available one.
4445

4546
#### Retrying requests
4647

@@ -70,9 +71,11 @@ const client = createClient({
7071
maxRetries: 2,
7172
retryDelay: 300, //milliseconds
7273
maxDelay: 5000, //milliseconds
74+
retryOnAbort: false,
7375
},
7476

7577
// Optional if not globally available
78+
timeout: 1000,
7679
fetch,
7780
}),
7881
],

packages/sdk-middleware-http/src/http.js

+26-9
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export default function createHttpMiddleware({
6565
backoff = true,
6666
retryDelay = 200,
6767
maxDelay = Infinity,
68+
retryOnAbort = false,
6869
} = {},
6970
fetch: fetcher,
7071
abortController: _abortController,
@@ -98,13 +99,6 @@ export default function createHttpMiddleware({
9899
response: MiddlewareResponse
99100
) => {
100101
let abortController: any
101-
if (timeout || getAbortController || _abortController)
102-
// eslint-disable-next-line
103-
abortController =
104-
(getAbortController ? getAbortController() : null) ||
105-
_abortController ||
106-
new AbortController()
107-
108102
const url = host.replace(/\/$/, '') + request.uri
109103
const body =
110104
typeof request.body === 'string' || Buffer.isBuffer(request.body)
@@ -126,15 +120,38 @@ export default function createHttpMiddleware({
126120
if (credentialsMode) {
127121
fetchOptions.credentials = credentialsMode
128122
}
129-
if (abortController) {
130-
fetchOptions.signal = abortController.signal
123+
124+
if (!retryOnAbort) {
125+
if (timeout || getAbortController || _abortController)
126+
// eslint-disable-next-line
127+
abortController =
128+
(getAbortController ? getAbortController() : null) ||
129+
_abortController ||
130+
new AbortController()
131+
132+
if (abortController) {
133+
fetchOptions.signal = abortController.signal
134+
}
131135
}
136+
132137
if (body) {
133138
fetchOptions.body = body
134139
}
135140
let retryCount = 0
136141
// wrap in a fn so we can retry if error occur
137142
function executeFetch() {
143+
if (retryOnAbort) {
144+
if (timeout || getAbortController || _abortController)
145+
// eslint-disable-next-line
146+
abortController =
147+
(getAbortController ? getAbortController() : null) ||
148+
_abortController ||
149+
new AbortController()
150+
151+
if (abortController) {
152+
fetchOptions.signal = abortController.signal
153+
}
154+
}
138155
// Kick off timer for abortController directly before fetch.
139156
let timer
140157
if (timeout)

packages/sdk-middleware-http/test/http.spec.js

+128
Original file line numberDiff line numberDiff line change
@@ -1116,4 +1116,132 @@ describe('Http', () => {
11161116

11171117
httpMiddleware(next)(request, response)
11181118
}))
1119+
1120+
test('should retry when request is aborted (success)', () => {
1121+
expect.assertions(1)
1122+
return new Promise((resolve, reject) => {
1123+
const request = createTestRequest({
1124+
uri: '/foo/bar',
1125+
})
1126+
const response = { resolve, reject }
1127+
const next = (req, res) => {
1128+
expect(res).toEqual({
1129+
...response,
1130+
body: { foo: 'bar' },
1131+
statusCode: 200,
1132+
})
1133+
resolve()
1134+
}
1135+
// Use default options
1136+
const httpMiddleware = createHttpMiddleware({
1137+
host: testHost,
1138+
timeout: 100,
1139+
fetch,
1140+
enableRetry: true,
1141+
retryConfig: {
1142+
retryOnAbort: true,
1143+
},
1144+
getAbortController: () => new AbortController(),
1145+
})
1146+
nock(testHost)
1147+
.defaultReplyHeaders({
1148+
'Content-Type': 'application/json',
1149+
})
1150+
.get('/foo/bar')
1151+
.once()
1152+
.delay(200) // delay response to fail
1153+
.reply(200, { foo: 'bar' })
1154+
.get('/foo/bar')
1155+
.delay(50) // delay lower then timeout
1156+
.reply(200, { foo: 'bar' })
1157+
1158+
httpMiddleware(next)(request, response)
1159+
})
1160+
})
1161+
1162+
test('should retry when request is aborted (fail)', () => {
1163+
expect.assertions(1)
1164+
return new Promise((resolve, reject) => {
1165+
const request = createTestRequest({
1166+
uri: '/foo/bar',
1167+
})
1168+
const response = { resolve, reject }
1169+
const next = (req, res) => {
1170+
expect(res).toEqual({
1171+
...response,
1172+
error: expect.any(Error),
1173+
statusCode: 0,
1174+
})
1175+
resolve()
1176+
}
1177+
// Use default options
1178+
const httpMiddleware = createHttpMiddleware({
1179+
host: testHost,
1180+
timeout: 100,
1181+
fetch,
1182+
enableRetry: true,
1183+
retryConfig: {
1184+
maxRetries: 2,
1185+
retryOnAbort: false,
1186+
},
1187+
getAbortController: () => new AbortController(),
1188+
})
1189+
nock(testHost)
1190+
.defaultReplyHeaders({
1191+
'Content-Type': 'application/json',
1192+
})
1193+
.get('/foo/bar')
1194+
.once()
1195+
.delay(200) // delay response to fail
1196+
.reply(200, { foo: 'bar' })
1197+
.get('/foo/bar')
1198+
.delay(150) // delay higher then timeout
1199+
.reply(200, { foo: 'bar' })
1200+
1201+
httpMiddleware(next)(request, response)
1202+
})
1203+
})
1204+
1205+
test('should retry when requests are aborted (fail)', () => {
1206+
expect.assertions(1)
1207+
return new Promise((resolve, reject) => {
1208+
const request = createTestRequest({
1209+
uri: '/foo/bar',
1210+
})
1211+
const response = { resolve, reject }
1212+
const next = (req, res) => {
1213+
expect(res).toEqual({
1214+
...response,
1215+
error: expect.any(Error),
1216+
statusCode: 0,
1217+
})
1218+
resolve()
1219+
}
1220+
// Use default options
1221+
const httpMiddleware = createHttpMiddleware({
1222+
host: testHost,
1223+
timeout: 100, // time out after 10ms
1224+
fetch,
1225+
enableRetry: true,
1226+
retryConfig: {
1227+
maxRetries: 1,
1228+
retryOnAbort: true,
1229+
},
1230+
getAbortController: () => new AbortController(),
1231+
})
1232+
nock(testHost)
1233+
.defaultReplyHeaders({
1234+
'Content-Type': 'application/json',
1235+
})
1236+
.get('/foo/bar')
1237+
.once()
1238+
.delay(150) // delay response to fail (higher than timeout)
1239+
.reply(200, { foo: 'bar' })
1240+
.get('/foo/bar')
1241+
.delay(150) // delay response to fail (higher than timeout)
1242+
.reply(200, { foo: 'bar' })
1243+
1244+
httpMiddleware(next)(request, response)
1245+
})
1246+
})
11191247
})

types/sdk.js

+1
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ export type HttpMiddlewareOptions = {
241241
retryDelay?: number,
242242
backoff?: boolean,
243243
maxDelay?: number,
244+
retryOnAbort: boolean
244245
},
245246
fetch?: typeof fetch,
246247
/**

0 commit comments

Comments
 (0)