Skip to content

Commit f8f7d55

Browse files
motiz88facebook-github-bot
authored andcommitted
Allow configuring additional success statuses in HttpStore
Summary: Changelog: * **[Feature]** Add `additionalSuccessStatuses` option to `HttpStore`. Adds a mechanism for specifying HTTP response codes to treat as successful (on top of actual success codes like 200) when reading from/writing to a remote cache server. It can be useful to tell Metro to ignore certain errors coming from an HTTP cache backend, particularly on write: if the error is, for example, that a concurrent write has happened to the same key, it's conceptually safe to ignore - because we can reasonably assume that the write is coming from another instance of Metro that has the same cache configuration. Reviewed By: GijsWeterings Differential Revision: D55752523 fbshipit-source-id: f4953d3151e6d74714690bda9cc43d004db91b79
1 parent 3e647a6 commit f8f7d55

File tree

2 files changed

+67
-7
lines changed

2 files changed

+67
-7
lines changed

packages/metro-cache/src/stores/HttpStore.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type EndpointOptions = {
3333
ca?: string | $ReadOnlyArray<string> | Buffer | $ReadOnlyArray<Buffer>,
3434
params?: URLSearchParams,
3535
headers?: {[string]: string},
36+
additionalSuccessStatuses?: $ReadOnlyArray<number>,
3637
};
3738

3839
type Endpoint = {
@@ -44,6 +45,7 @@ type Endpoint = {
4445
params: URLSearchParams,
4546
headers?: {[string]: string},
4647
timeout: number,
48+
additionalSuccessStatuses: $ReadOnlySet<number>,
4749
};
4850

4951
const ZLIB_OPTIONS = {
@@ -109,6 +111,9 @@ class HttpStore<T> {
109111
params: new URLSearchParams(options.params),
110112
timeout: options.timeout || 5000,
111113
module: uri.protocol === 'http:' ? http : https,
114+
additionalSuccessStatuses: new Set(
115+
options.additionalSuccessStatuses ?? [],
116+
),
112117
};
113118
}
114119

@@ -142,7 +147,10 @@ class HttpStore<T> {
142147
resolve(null);
143148

144149
return;
145-
} else if (code !== 200) {
150+
} else if (
151+
code !== 200 &&
152+
!this._getEndpoint.additionalSuccessStatuses.has(code)
153+
) {
146154
res.resume();
147155
reject(new HttpError('HTTP error: ' + code, code));
148156

@@ -215,7 +223,10 @@ class HttpStore<T> {
215223
const req = this._setEndpoint.module.request(options, res => {
216224
const code = res.statusCode;
217225

218-
if (code < 200 || code > 299) {
226+
if (
227+
(code < 200 || code > 299) &&
228+
!this._setEndpoint.additionalSuccessStatuses.has(code)
229+
) {
219230
res.resume();
220231
reject(new HttpError('HTTP error: ' + code, code));
221232

packages/metro-cache/src/stores/__tests__/HttpStore-test.js

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ describe('HttpStore', () => {
1717
let HttpStore;
1818
let httpPassThrough;
1919

20-
function responseHttpOk(data) {
20+
function responseHttpOk(data, statusCode = 200) {
2121
const res = Object.assign(new PassThrough(), {
22-
statusCode: 200,
22+
statusCode,
2323
});
2424

2525
process.nextTick(() => {
@@ -30,10 +30,16 @@ describe('HttpStore', () => {
3030
return res;
3131
}
3232

33-
function responseHttpError(code) {
34-
return Object.assign(new PassThrough(), {
35-
statusCode: code,
33+
function responseHttpError(statusCode) {
34+
const res = Object.assign(new PassThrough(), {
35+
statusCode,
36+
});
37+
38+
process.nextTick(() => {
39+
res.end();
3640
});
41+
42+
return res;
3743
}
3844

3945
function responseError(err) {
@@ -112,6 +118,25 @@ describe('HttpStore', () => {
112118
});
113119
});
114120

121+
it('get() resolves when the HTTP code is in additionalSuccessStatuses', async () => {
122+
const store = new HttpStore({
123+
endpoint: 'http://www.example.com/endpoint',
124+
additionalSuccessStatuses: [419],
125+
});
126+
const promise = store.get(Buffer.from('key'));
127+
const [opts, callback] = require('http').request.mock.calls[0];
128+
129+
expect(opts.method).toEqual('GET');
130+
expect(opts.host).toEqual('www.example.com');
131+
expect(opts.path).toEqual('/endpoint/6b6579');
132+
expect(opts.timeout).toEqual(5000);
133+
134+
callback(responseHttpOk(JSON.stringify({foo: 42}), 419));
135+
jest.runAllTimers();
136+
137+
expect(await promise).toEqual({foo: 42});
138+
});
139+
115140
it('rejects when it gets an invalid JSON response', done => {
116141
const store = new HttpStore({endpoint: 'http://example.com'});
117142
const promise = store.get(Buffer.from('key'));
@@ -187,6 +212,30 @@ describe('HttpStore', () => {
187212
});
188213
});
189214

215+
test('set() resolves when the HTTP code is in additionalSuccessStatuses', done => {
216+
const store = new HttpStore({
217+
endpoint: 'http://www.example.com/endpoint',
218+
additionalSuccessStatuses: [403],
219+
});
220+
const promise = store.set(Buffer.from('key-set'), {foo: 42});
221+
const [opts, callback] = require('http').request.mock.calls[0];
222+
223+
expect(opts.method).toEqual('PUT');
224+
expect(opts.host).toEqual('www.example.com');
225+
expect(opts.path).toEqual('/endpoint/6b65792d736574');
226+
expect(opts.timeout).toEqual(5000);
227+
228+
callback(responseHttpError(403));
229+
230+
httpPassThrough.on('data', () => {});
231+
232+
httpPassThrough.on('end', async () => {
233+
await promise; // Ensure that the setting promise successfully finishes.
234+
235+
done();
236+
});
237+
});
238+
190239
it('rejects when setting and HTTP returns an error response', done => {
191240
const store = new HttpStore({endpoint: 'http://example.com'});
192241
const promise = store.set(Buffer.from('key-set'), {foo: 42});

0 commit comments

Comments
 (0)