Skip to content

Commit

Permalink
fix: reuse connections (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
bgiori authored Apr 30, 2022
1 parent bbc2eae commit 13d897c
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 30 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ dist/
# caches
.cache
.DS_Store

.idea
38 changes: 24 additions & 14 deletions packages/node/src/transport/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ import url from 'url';

import { SimpleResponse, HttpClient } from '../types/transport';

const httpAgent = new https.Agent({
keepAlive: true,
// Timeout on the socket, not the request. When the socket times out, close
// the connection.
timeout: 10000,
});

/**
* Wraps the http and https libraries in a fetch()-like interface
* @param requestUrl
Expand All @@ -18,22 +25,21 @@ const request: HttpClient['request'] = (
data: string,
timeoutMillis?: number,
): Promise<SimpleResponse> => {
const urlParams = url.parse(requestUrl);
const options = {
...urlParams,
method: method,
headers: headers,
body: data,
// Adds timeout to the socket connection, not the response.
timeout: timeoutMillis,
};

return new Promise((resolve, reject) => {
const urlParams = url.parse(requestUrl);
const options = {
...urlParams,
method: method,
headers: headers,
body: data,
agent: httpAgent,
};
const protocol = urlParams.protocol === 'http:' ? http : https;
const req = protocol.request(options);

const responseTimeout = setTimeout(() => {
req.destroy(Error('Response timed out'));
clearTimeout(responseTimeout);
reject(Error('Response timed out'));
}, timeoutMillis);

req.on('response', (res) => {
Expand All @@ -53,15 +59,19 @@ const request: HttpClient['request'] = (
});
});

req.on('timeout', () => req.destroy(Error('Socket connection timed out')));
req.on('timeout', () => {
req.destroy(Error('Socket connection timed out'));
});

req.on('error', reject);
req.on('error', (e) => {
clearTimeout(responseTimeout);
reject(e);
});

if (method !== 'GET' && data) {
req.write(data);
}

req.on('close', () => clearTimeout(responseTimeout));
req.end();
});
};
Expand Down
9 changes: 8 additions & 1 deletion packages/node/src/util/performance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,11 @@ if (!performance) {
};
}

export { performance };
const measure = async (block: () => Promise<void>): Promise<number> => {
const start = performance.now();
await block();
const end = performance.now();
return end - start;
};

export { performance, measure };
40 changes: 25 additions & 15 deletions packages/node/test/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,44 @@
import { ExperimentClient } from 'src/client';
import { ExperimentUser } from 'src/types/user';

const API_KEY = 'client-DvWljIjiiuqLbyjqdvBaLFfEBrAvGuA3';
const API_KEY = 'server-qz35UwzJ5akieoAdIgzM4m9MIiOLXLoz';

const testUser: ExperimentUser = { user_id: 'test_user' };

const testClient: ExperimentClient = new ExperimentClient(API_KEY, {});

const testTimeoutNoRetriesClient = new ExperimentClient(API_KEY, {
fetchRetries: 0,
fetchTimeoutMillis: 1,
});

const testTimeoutRetrySuccessClient = new ExperimentClient(API_KEY, {
fetchTimeoutMillis: 1,
});

test('ExperimentClient.fetch, success', async () => {
const variants = await testClient.fetch(testUser);
const client = new ExperimentClient(API_KEY, {});
const variants = await client.fetch(testUser);
const variant = variants['sdk-ci-test'];
expect(variant).toEqual({ value: 'on', payload: 'payload' });
});

test('ExperimentClient.fetch, no retries, timeout failure', async () => {
const variants = await testTimeoutNoRetriesClient.fetch(testUser);
const client = new ExperimentClient(API_KEY, {
fetchRetries: 0,
fetchTimeoutMillis: 1,
});
const variants = await client.fetch(testUser);
expect(variants).toEqual({});
});

test('ExperimentClient.fetch, no retries, timeout failure', async () => {
const variants = await testTimeoutRetrySuccessClient.fetch(testUser);
const client = new ExperimentClient(API_KEY, {
fetchTimeoutMillis: 1,
});
const variants = await client.fetch(testUser);
const variant = variants['sdk-ci-test'];
expect(variant).toEqual({ value: 'on', payload: 'payload' });
});

test('ExperimentClient.fetch, retry once, timeout first then succeed with 0 backoff', async () => {
const client = new ExperimentClient(API_KEY, {
debug: true,
fetchTimeoutMillis: 1,
fetchRetries: 1,
fetchRetryBackoffMinMillis: 0,
fetchRetryTimeoutMillis: 10_000,
});
const variants = await client.fetch(testUser);
const variant = variants['sdk-ci-test'];
expect(variant).toEqual({ value: 'on', payload: 'payload' });
});

0 comments on commit 13d897c

Please sign in to comment.