@algolia/requester-node-http 4.14.2 → 5.0.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ export * from './src/echoRequester';
2
+ export * from './src/createHttpRequester';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC"}
@@ -2,86 +2,91 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var url = require('url');
6
+ var clientCommon = require('@algolia/client-common');
5
7
  var http = require('http');
6
8
  var https = require('https');
7
- var URL = require('url');
8
9
 
9
- /* eslint functional/prefer-readonly-type: 0 */
10
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
11
+
12
+ var http__default = /*#__PURE__*/_interopDefaultLegacy(http);
13
+ var https__default = /*#__PURE__*/_interopDefaultLegacy(https);
14
+
15
+ function echoRequester(status = 200) {
16
+ return clientCommon.createEchoRequester({ getURL: (url$1) => new url.URL(url$1), status });
17
+ }
18
+
19
+ // Global agents allow us to reuse the TCP protocol with multiple clients
10
20
  const agentOptions = { keepAlive: true };
11
- const defaultHttpAgent = new http.Agent(agentOptions);
12
- const defaultHttpsAgent = new https.Agent(agentOptions);
13
- function createNodeHttpRequester({ agent: userGlobalAgent, httpAgent: userHttpAgent, httpsAgent: userHttpsAgent, requesterOptions = {}, } = {}) {
21
+ const defaultHttpAgent = new http__default["default"].Agent(agentOptions);
22
+ const defaultHttpsAgent = new https__default["default"].Agent(agentOptions);
23
+ function createHttpRequester({ agent: userGlobalAgent, httpAgent: userHttpAgent, httpsAgent: userHttpsAgent, requesterOptions = {}, } = {}) {
14
24
  const httpAgent = userHttpAgent || userGlobalAgent || defaultHttpAgent;
15
25
  const httpsAgent = userHttpsAgent || userGlobalAgent || defaultHttpsAgent;
16
- return {
17
- send(request) {
18
- return new Promise(resolve => {
19
- const url = URL.parse(request.url);
20
- const path = url.query === null ? url.pathname : `${url.pathname}?${url.query}`;
21
- const options = {
22
- ...requesterOptions,
23
- agent: url.protocol === 'https:' ? httpsAgent : httpAgent,
24
- hostname: url.hostname,
25
- path,
26
- method: request.method,
27
- headers: {
28
- ...(requesterOptions && requesterOptions.headers ? requesterOptions.headers : {}),
29
- ...request.headers,
30
- },
31
- ...(url.port !== undefined ? { port: url.port || '' } : {}),
32
- };
33
- const req = (url.protocol === 'https:' ? https : http).request(options, response => {
34
- // eslint-disable-next-line functional/no-let
35
- let contentBuffers = [];
36
- response.on('data', chunk => {
37
- contentBuffers = contentBuffers.concat(chunk);
38
- });
39
- response.on('end', () => {
40
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
41
- clearTimeout(connectTimeout);
42
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
43
- clearTimeout(responseTimeout);
44
- resolve({
45
- status: response.statusCode || 0,
46
- content: Buffer.concat(contentBuffers).toString(),
47
- isTimedOut: false,
48
- });
49
- });
26
+ function send(request) {
27
+ return new Promise((resolve) => {
28
+ let responseTimeout;
29
+ // eslint-disable-next-line prefer-const -- linter thinks this is not reassigned
30
+ let connectTimeout;
31
+ const url$1 = new url.URL(request.url);
32
+ const path = url$1.search === null ? url$1.pathname : `${url$1.pathname}${url$1.search}`;
33
+ const options = {
34
+ agent: url$1.protocol === 'https:' ? httpsAgent : httpAgent,
35
+ hostname: url$1.hostname,
36
+ path,
37
+ method: request.method,
38
+ ...requesterOptions,
39
+ headers: {
40
+ ...request.headers,
41
+ ...requesterOptions.headers,
42
+ },
43
+ };
44
+ if (url$1.port && !requesterOptions.port) {
45
+ options.port = url$1.port;
46
+ }
47
+ const req = (url$1.protocol === 'https:' ? https__default["default"] : http__default["default"]).request(options, (response) => {
48
+ let contentBuffers = [];
49
+ response.on('data', (chunk) => {
50
+ contentBuffers = contentBuffers.concat(chunk);
50
51
  });
51
- const createTimeout = (timeout, content) => {
52
- return setTimeout(() => {
53
- req.abort();
54
- resolve({
55
- status: 0,
56
- content,
57
- isTimedOut: true,
58
- });
59
- }, timeout * 1000);
60
- };
61
- const connectTimeout = createTimeout(request.connectTimeout, 'Connection timeout');
62
- // eslint-disable-next-line functional/no-let
63
- let responseTimeout;
64
- req.on('error', error => {
52
+ response.on('end', () => {
65
53
  clearTimeout(connectTimeout);
66
54
  clearTimeout(responseTimeout);
67
- resolve({ status: 0, content: error.message, isTimedOut: false });
68
- });
69
- req.once('response', () => {
70
- clearTimeout(connectTimeout);
71
- responseTimeout = createTimeout(request.responseTimeout, 'Socket timeout');
55
+ resolve({
56
+ status: response.statusCode || 0,
57
+ content: Buffer.concat(contentBuffers).toString(),
58
+ isTimedOut: false,
59
+ });
72
60
  });
73
- if (request.data !== undefined) {
74
- req.write(request.data);
75
- }
76
- req.end();
77
61
  });
78
- },
79
- destroy() {
80
- httpAgent.destroy();
81
- httpsAgent.destroy();
82
- return Promise.resolve();
83
- },
84
- };
62
+ const createTimeout = (timeout, content) => {
63
+ return setTimeout(() => {
64
+ req.destroy();
65
+ resolve({
66
+ status: 0,
67
+ content,
68
+ isTimedOut: true,
69
+ });
70
+ }, timeout);
71
+ };
72
+ connectTimeout = createTimeout(request.connectTimeout, 'Connection timeout');
73
+ req.on('error', (error) => {
74
+ clearTimeout(connectTimeout);
75
+ clearTimeout(responseTimeout);
76
+ resolve({ status: 0, content: error.message, isTimedOut: false });
77
+ });
78
+ req.once('response', () => {
79
+ clearTimeout(connectTimeout);
80
+ responseTimeout = createTimeout(request.responseTimeout, 'Socket timeout');
81
+ });
82
+ if (request.data !== undefined) {
83
+ req.write(request.data);
84
+ }
85
+ req.end();
86
+ });
87
+ }
88
+ return { send };
85
89
  }
86
90
 
87
- exports.createNodeHttpRequester = createNodeHttpRequester;
91
+ exports.createHttpRequester = createHttpRequester;
92
+ exports.echoRequester = echoRequester;
@@ -0,0 +1,82 @@
1
+ import { URL } from 'url';
2
+ import { createEchoRequester } from '@algolia/client-common';
3
+ import http from 'http';
4
+ import https from 'https';
5
+
6
+ function echoRequester(status = 200) {
7
+ return createEchoRequester({ getURL: (url) => new URL(url), status });
8
+ }
9
+
10
+ // Global agents allow us to reuse the TCP protocol with multiple clients
11
+ const agentOptions = { keepAlive: true };
12
+ const defaultHttpAgent = new http.Agent(agentOptions);
13
+ const defaultHttpsAgent = new https.Agent(agentOptions);
14
+ function createHttpRequester({ agent: userGlobalAgent, httpAgent: userHttpAgent, httpsAgent: userHttpsAgent, requesterOptions = {}, } = {}) {
15
+ const httpAgent = userHttpAgent || userGlobalAgent || defaultHttpAgent;
16
+ const httpsAgent = userHttpsAgent || userGlobalAgent || defaultHttpsAgent;
17
+ function send(request) {
18
+ return new Promise((resolve) => {
19
+ let responseTimeout;
20
+ // eslint-disable-next-line prefer-const -- linter thinks this is not reassigned
21
+ let connectTimeout;
22
+ const url = new URL(request.url);
23
+ const path = url.search === null ? url.pathname : `${url.pathname}${url.search}`;
24
+ const options = {
25
+ agent: url.protocol === 'https:' ? httpsAgent : httpAgent,
26
+ hostname: url.hostname,
27
+ path,
28
+ method: request.method,
29
+ ...requesterOptions,
30
+ headers: {
31
+ ...request.headers,
32
+ ...requesterOptions.headers,
33
+ },
34
+ };
35
+ if (url.port && !requesterOptions.port) {
36
+ options.port = url.port;
37
+ }
38
+ const req = (url.protocol === 'https:' ? https : http).request(options, (response) => {
39
+ let contentBuffers = [];
40
+ response.on('data', (chunk) => {
41
+ contentBuffers = contentBuffers.concat(chunk);
42
+ });
43
+ response.on('end', () => {
44
+ clearTimeout(connectTimeout);
45
+ clearTimeout(responseTimeout);
46
+ resolve({
47
+ status: response.statusCode || 0,
48
+ content: Buffer.concat(contentBuffers).toString(),
49
+ isTimedOut: false,
50
+ });
51
+ });
52
+ });
53
+ const createTimeout = (timeout, content) => {
54
+ return setTimeout(() => {
55
+ req.destroy();
56
+ resolve({
57
+ status: 0,
58
+ content,
59
+ isTimedOut: true,
60
+ });
61
+ }, timeout);
62
+ };
63
+ connectTimeout = createTimeout(request.connectTimeout, 'Connection timeout');
64
+ req.on('error', (error) => {
65
+ clearTimeout(connectTimeout);
66
+ clearTimeout(responseTimeout);
67
+ resolve({ status: 0, content: error.message, isTimedOut: false });
68
+ });
69
+ req.once('response', () => {
70
+ clearTimeout(connectTimeout);
71
+ responseTimeout = createTimeout(request.responseTimeout, 'Socket timeout');
72
+ });
73
+ if (request.data !== undefined) {
74
+ req.write(request.data);
75
+ }
76
+ req.end();
77
+ });
78
+ }
79
+ return { send };
80
+ }
81
+
82
+ export { createHttpRequester, echoRequester };
@@ -0,0 +1,16 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import http from 'http';
4
+ import https from 'https';
5
+ import type { Requester } from '@algolia/client-common';
6
+ export declare type CreateHttpRequesterOptions = Partial<{
7
+ agent: http.Agent | https.Agent;
8
+ httpAgent: http.Agent;
9
+ httpsAgent: https.Agent;
10
+ /**
11
+ * RequestOptions to be merged with the end request, it will override default options if provided.
12
+ */
13
+ requesterOptions: https.RequestOptions;
14
+ }>;
15
+ export declare function createHttpRequester({ agent: userGlobalAgent, httpAgent: userHttpAgent, httpsAgent: userHttpsAgent, requesterOptions, }?: CreateHttpRequesterOptions): Requester;
16
+ //# sourceMappingURL=createHttpRequester.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createHttpRequester.d.ts","sourceRoot":"","sources":["../../../../packages/requester-node-http/src/createHttpRequester.ts"],"names":[],"mappings":";;AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAc,SAAS,EAAY,MAAM,wBAAwB,CAAC;AAE9E,oBAAY,0BAA0B,GAAG,OAAO,CAAC;IAC/C,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IAChC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC;IACtB,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC;IACxB;;OAEG;IACH,gBAAgB,EAAE,KAAK,CAAC,cAAc,CAAC;CACxC,CAAC,CAAC;AAOH,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EAAE,eAAe,EACtB,SAAS,EAAE,aAAa,EACxB,UAAU,EAAE,cAAc,EAC1B,gBAAqB,GACtB,GAAE,0BAA+B,GAAG,SAAS,CA6F7C"}
@@ -0,0 +1,3 @@
1
+ import type { Requester } from '@algolia/client-common';
2
+ export declare function echoRequester(status?: number): Requester;
3
+ //# sourceMappingURL=echoRequester.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"echoRequester.d.ts","sourceRoot":"","sources":["../../../../packages/requester-node-http/src/echoRequester.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD,wBAAgB,aAAa,CAAC,MAAM,GAAE,MAAY,GAAG,SAAS,CAE7D"}
package/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './src/echoRequester';
2
+ export * from './src/createHttpRequester';
package/package.json CHANGED
@@ -1,22 +1,34 @@
1
1
  {
2
2
  "name": "@algolia/requester-node-http",
3
- "version": "4.14.2",
4
- "private": false,
3
+ "version": "5.0.0-alpha.3",
5
4
  "description": "Promise-based request library for node using the native http module.",
6
- "repository": {
7
- "type": "git",
8
- "url": "git://github.com/algolia/algoliasearch-client-javascript.git"
9
- },
5
+ "repository": "algolia/algoliasearch-client-javascript",
10
6
  "license": "MIT",
11
- "sideEffects": false,
12
- "main": "index.js",
13
- "module": "dist/requester-node-http.esm.js",
14
- "types": "dist/requester-node-http.d.ts",
7
+ "author": "Algolia",
8
+ "main": "dist/requester-node-http.cjs.js",
9
+ "module": "dist/requester-node-http.esm.node.js",
10
+ "types": "dist/index.d.ts",
15
11
  "files": [
16
- "index.js",
17
- "dist"
12
+ "dist",
13
+ "src",
14
+ "index.ts"
18
15
  ],
16
+ "scripts": {
17
+ "clean": "rm -rf dist/",
18
+ "test": "jest"
19
+ },
19
20
  "dependencies": {
20
- "@algolia/requester-common": "4.14.2"
21
+ "@algolia/client-common": "5.0.0-alpha.3"
22
+ },
23
+ "devDependencies": {
24
+ "@types/jest": "28.1.6",
25
+ "@types/node": "16.11.45",
26
+ "jest": "28.1.3",
27
+ "nock": "13.2.9",
28
+ "ts-jest": "28.0.5",
29
+ "typescript": "4.7.4"
30
+ },
31
+ "engines": {
32
+ "node": ">= 14.0.0"
21
33
  }
22
34
  }
@@ -0,0 +1,293 @@
1
+ import http from 'http';
2
+ import https from 'https';
3
+ import { Readable } from 'stream';
4
+
5
+ import type { EndRequest } from '@algolia/client-common';
6
+ import nock from 'nock';
7
+
8
+ import { createHttpRequester } from '../..';
9
+ import {
10
+ headers,
11
+ timeoutRequest,
12
+ requestStub,
13
+ testQueryHeader,
14
+ testQueryBaseUrl,
15
+ getStringifiedBody,
16
+ createTestServer,
17
+ } from '../../../../tests/utils';
18
+
19
+ const requester = createHttpRequester();
20
+
21
+ const httpsBaseRequest = https.request;
22
+ const httpBaseRequest = http.request;
23
+
24
+ describe('api', () => {
25
+ const mockedRequestResponse = {
26
+ destroy: jest.fn(),
27
+ on: jest.fn(),
28
+ once: jest.fn(),
29
+ write: jest.fn(),
30
+ end: jest.fn(),
31
+ };
32
+
33
+ beforeAll(() => {
34
+ // @ts-expect-error we don't care about the response for those tests
35
+ https.request = jest.fn(() => mockedRequestResponse);
36
+ });
37
+
38
+ afterAll(() => {
39
+ https.request = httpsBaseRequest;
40
+ http.request = httpBaseRequest;
41
+ jest.resetAllMocks();
42
+ jest.clearAllMocks();
43
+ });
44
+
45
+ it('allow init without parameters', () => {
46
+ expect(() => createHttpRequester()).not.toThrow();
47
+ });
48
+
49
+ it('allow providing custom agent', async () => {
50
+ const agent = new http.Agent();
51
+ // @ts-expect-error we don't care about the response for those tests
52
+ http.request = jest.fn(() => mockedRequestResponse);
53
+ const tmpRequester = createHttpRequester({
54
+ agent,
55
+ });
56
+
57
+ await tmpRequester.send({
58
+ ...requestStub,
59
+ url: 'http://algolia-dns.net/foo?x-algolia-header=bar',
60
+ });
61
+
62
+ expect(http.request).toHaveBeenCalled();
63
+ });
64
+
65
+ it('allow overriding default options', async () => {
66
+ const tmpRequester = createHttpRequester({
67
+ requesterOptions: {
68
+ headers: {
69
+ 'my-extra-header': 'algolia',
70
+ },
71
+ },
72
+ });
73
+
74
+ await tmpRequester.send(requestStub);
75
+
76
+ expect(https.request).toHaveBeenCalledWith(
77
+ expect.objectContaining({
78
+ headers: expect.objectContaining({
79
+ 'my-extra-header': 'algolia',
80
+ }),
81
+ }),
82
+ expect.any(Function)
83
+ );
84
+ });
85
+ });
86
+
87
+ describe('status code handling', () => {
88
+ it('sends requests', async () => {
89
+ const body = getStringifiedBody();
90
+
91
+ nock(testQueryBaseUrl, { reqheaders: headers })
92
+ .post('/foo')
93
+ .query(testQueryHeader)
94
+ .reply(200, body);
95
+
96
+ const response = await requester.send(requestStub);
97
+
98
+ expect(response.content).toEqual(body);
99
+ });
100
+
101
+ it('resolves status 200', async () => {
102
+ const body = getStringifiedBody();
103
+
104
+ nock(testQueryBaseUrl, { reqheaders: headers })
105
+ .post('/foo')
106
+ .query(testQueryHeader)
107
+ .reply(200, body);
108
+
109
+ const response = await requester.send(requestStub);
110
+
111
+ expect(response.status).toBe(200);
112
+ expect(response.content).toBe(body);
113
+ expect(response.isTimedOut).toBe(false);
114
+ });
115
+
116
+ it('resolves status 300', async () => {
117
+ const reason = 'Multiple Choices';
118
+
119
+ nock(testQueryBaseUrl, { reqheaders: headers })
120
+ .post('/foo')
121
+ .query(testQueryHeader)
122
+ .reply(300, reason);
123
+
124
+ const response = await requester.send(requestStub);
125
+
126
+ expect(response.status).toBe(300);
127
+ expect(response.content).toBe(reason);
128
+ expect(response.isTimedOut).toBe(false);
129
+ });
130
+
131
+ it('resolves status 400', async () => {
132
+ const body = getStringifiedBody({
133
+ message: 'Invalid Application-Id or API-Key',
134
+ });
135
+
136
+ nock(testQueryBaseUrl, { reqheaders: headers })
137
+ .post('/foo')
138
+ .query(testQueryHeader)
139
+ .reply(400, body);
140
+
141
+ const response = await requester.send(requestStub);
142
+
143
+ expect(response.status).toBe(400);
144
+ expect(response.content).toBe(body);
145
+ expect(response.isTimedOut).toBe(false);
146
+ });
147
+
148
+ it('handles chunked responses inside unicode character boundaries', async () => {
149
+ const data = Buffer.from('äöü');
150
+
151
+ // create a test response stream that is chunked inside a unicode character
152
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
153
+ function* generate() {
154
+ yield data.slice(0, 3);
155
+ yield data.slice(3);
156
+ }
157
+
158
+ const testStream = Readable.from(generate());
159
+
160
+ nock(testQueryBaseUrl, { reqheaders: headers })
161
+ .post('/foo')
162
+ .query(testQueryHeader)
163
+ .reply(200, testStream);
164
+
165
+ const response = await requester.send(requestStub);
166
+
167
+ expect(response.content).toEqual(data.toString());
168
+ });
169
+ });
170
+
171
+ describe('timeout handling', () => {
172
+ let server: http.Server;
173
+ // setup http server to test timeout
174
+ beforeAll(() => {
175
+ server = createTestServer();
176
+
177
+ server.listen('1111');
178
+ });
179
+
180
+ afterAll((done) => {
181
+ server.close(() => done());
182
+ });
183
+
184
+ it('timeouts with the given 1 seconds connection timeout', async () => {
185
+ const before = Date.now();
186
+ const response = await requester.send({
187
+ ...timeoutRequest,
188
+ connectTimeout: 1000,
189
+ url: 'http://www.google.com:81',
190
+ });
191
+
192
+ const now = Date.now();
193
+
194
+ expect(response.content).toBe('Connection timeout');
195
+ expect(now - before).toBeGreaterThan(999);
196
+ expect(now - before).toBeLessThan(1200);
197
+ });
198
+
199
+ it('connection timeouts with the given 2 seconds connection timeout', async () => {
200
+ const before = Date.now();
201
+ const response = await requester.send({
202
+ ...timeoutRequest,
203
+ connectTimeout: 2000,
204
+ url: 'http://www.google.com:81',
205
+ });
206
+
207
+ const now = Date.now();
208
+
209
+ expect(response.content).toBe('Connection timeout');
210
+ expect(now - before).toBeGreaterThan(1999);
211
+ expect(now - before).toBeLessThan(2200);
212
+ });
213
+
214
+ it("socket timeouts if response don't appears before the timeout with 2 seconds timeout", async () => {
215
+ const before = Date.now();
216
+
217
+ const response = await requester.send({
218
+ ...timeoutRequest,
219
+ responseTimeout: 2000,
220
+ url: 'http://localhost:1111',
221
+ });
222
+
223
+ const now = Date.now();
224
+
225
+ expect(response.content).toBe('Socket timeout');
226
+ expect(now - before).toBeGreaterThan(1999);
227
+ expect(now - before).toBeLessThan(2200);
228
+ });
229
+
230
+ it("socket timeouts if response don't appears before the timeout with 3 seconds timeout", async () => {
231
+ const before = Date.now();
232
+ const response = await requester.send({
233
+ ...timeoutRequest,
234
+ responseTimeout: 3000,
235
+ url: 'http://localhost:1111',
236
+ });
237
+
238
+ const now = Date.now();
239
+
240
+ expect(response.content).toBe('Socket timeout');
241
+ expect(now - before).toBeGreaterThan(2999);
242
+ expect(now - before).toBeLessThan(3200);
243
+ });
244
+
245
+ it('do not timeouts if response appears before the timeout', async () => {
246
+ const before = Date.now();
247
+ const response = await requester.send({
248
+ ...requestStub,
249
+ url: 'http://localhost:1111',
250
+ responseTimeout: 6000,
251
+ });
252
+
253
+ const now = Date.now();
254
+
255
+ expect(response.isTimedOut).toBe(false);
256
+ expect(response.status).toBe(200);
257
+ expect(response.content).toBe('{"foo": "bar"}');
258
+ expect(now - before).toBeGreaterThan(4999);
259
+ expect(now - before).toBeLessThan(5200);
260
+ }, 10000); // This is a long-running test, default server timeout is set to 5000ms
261
+ });
262
+
263
+ describe('error handling', (): void => {
264
+ it('resolves dns not found', async () => {
265
+ const request: EndRequest = {
266
+ url: 'https://this-dont-exist.algolia.com',
267
+ method: 'POST',
268
+ headers,
269
+ data: getStringifiedBody(),
270
+ responseTimeout: 2000,
271
+ connectTimeout: 1000,
272
+ };
273
+
274
+ const response = await requester.send(request);
275
+
276
+ expect(response.status).toBe(0);
277
+ expect(response.content).toContain('');
278
+ expect(response.isTimedOut).toBe(false);
279
+ });
280
+
281
+ it('resolves general network errors', async () => {
282
+ nock(testQueryBaseUrl, { reqheaders: headers })
283
+ .post('/foo')
284
+ .query(testQueryHeader)
285
+ .replyWithError('This is a general error');
286
+
287
+ const response = await requester.send(requestStub);
288
+
289
+ expect(response.status).toBe(0);
290
+ expect(response.content).toBe('This is a general error');
291
+ expect(response.isTimedOut).toBe(false);
292
+ });
293
+ });
@@ -0,0 +1,120 @@
1
+ import http from 'http';
2
+ import https from 'https';
3
+ import { URL } from 'url';
4
+
5
+ import type { EndRequest, Requester, Response } from '@algolia/client-common';
6
+
7
+ export type CreateHttpRequesterOptions = Partial<{
8
+ agent: http.Agent | https.Agent;
9
+ httpAgent: http.Agent;
10
+ httpsAgent: https.Agent;
11
+ /**
12
+ * RequestOptions to be merged with the end request, it will override default options if provided.
13
+ */
14
+ requesterOptions: https.RequestOptions;
15
+ }>;
16
+
17
+ // Global agents allow us to reuse the TCP protocol with multiple clients
18
+ const agentOptions = { keepAlive: true };
19
+ const defaultHttpAgent = new http.Agent(agentOptions);
20
+ const defaultHttpsAgent = new https.Agent(agentOptions);
21
+
22
+ export function createHttpRequester({
23
+ agent: userGlobalAgent,
24
+ httpAgent: userHttpAgent,
25
+ httpsAgent: userHttpsAgent,
26
+ requesterOptions = {},
27
+ }: CreateHttpRequesterOptions = {}): Requester {
28
+ const httpAgent = userHttpAgent || userGlobalAgent || defaultHttpAgent;
29
+ const httpsAgent = userHttpsAgent || userGlobalAgent || defaultHttpsAgent;
30
+
31
+ function send(request: EndRequest): Promise<Response> {
32
+ return new Promise((resolve) => {
33
+ let responseTimeout: NodeJS.Timeout | undefined;
34
+ // eslint-disable-next-line prefer-const -- linter thinks this is not reassigned
35
+ let connectTimeout: NodeJS.Timeout | undefined;
36
+ const url = new URL(request.url);
37
+ const path =
38
+ url.search === null ? url.pathname : `${url.pathname}${url.search}`;
39
+ const options: https.RequestOptions = {
40
+ agent: url.protocol === 'https:' ? httpsAgent : httpAgent,
41
+ hostname: url.hostname,
42
+ path,
43
+ method: request.method,
44
+ ...requesterOptions,
45
+ headers: {
46
+ ...request.headers,
47
+ ...requesterOptions.headers,
48
+ },
49
+ };
50
+
51
+ if (url.port && !requesterOptions.port) {
52
+ options.port = url.port;
53
+ }
54
+
55
+ const req = (url.protocol === 'https:' ? https : http).request(
56
+ options,
57
+ (response) => {
58
+ let contentBuffers: Buffer[] = [];
59
+
60
+ response.on('data', (chunk) => {
61
+ contentBuffers = contentBuffers.concat(chunk);
62
+ });
63
+
64
+ response.on('end', () => {
65
+ clearTimeout(connectTimeout as NodeJS.Timeout);
66
+ clearTimeout(responseTimeout as NodeJS.Timeout);
67
+
68
+ resolve({
69
+ status: response.statusCode || 0,
70
+ content: Buffer.concat(contentBuffers).toString(),
71
+ isTimedOut: false,
72
+ });
73
+ });
74
+ }
75
+ );
76
+
77
+ const createTimeout = (
78
+ timeout: number,
79
+ content: string
80
+ ): NodeJS.Timeout => {
81
+ return setTimeout(() => {
82
+ req.destroy();
83
+
84
+ resolve({
85
+ status: 0,
86
+ content,
87
+ isTimedOut: true,
88
+ });
89
+ }, timeout);
90
+ };
91
+
92
+ connectTimeout = createTimeout(
93
+ request.connectTimeout,
94
+ 'Connection timeout'
95
+ );
96
+
97
+ req.on('error', (error) => {
98
+ clearTimeout(connectTimeout as NodeJS.Timeout);
99
+ clearTimeout(responseTimeout!);
100
+ resolve({ status: 0, content: error.message, isTimedOut: false });
101
+ });
102
+
103
+ req.once('response', () => {
104
+ clearTimeout(connectTimeout as NodeJS.Timeout);
105
+ responseTimeout = createTimeout(
106
+ request.responseTimeout,
107
+ 'Socket timeout'
108
+ );
109
+ });
110
+
111
+ if (request.data !== undefined) {
112
+ req.write(request.data);
113
+ }
114
+
115
+ req.end();
116
+ });
117
+ }
118
+
119
+ return { send };
120
+ }
@@ -0,0 +1,8 @@
1
+ import { URL } from 'url';
2
+
3
+ import { createEchoRequester } from '@algolia/client-common';
4
+ import type { Requester } from '@algolia/client-common';
5
+
6
+ export function echoRequester(status: number = 200): Requester {
7
+ return createEchoRequester({ getURL: (url: string) => new URL(url), status });
8
+ }
@@ -1,16 +0,0 @@
1
- /// <reference types="node" />
2
- import { Destroyable } from '@algolia/requester-common';
3
- import * as http from 'http';
4
- import * as https from 'https';
5
- import { Requester } from '@algolia/requester-common';
6
-
7
- export declare function createNodeHttpRequester({ agent: userGlobalAgent, httpAgent: userHttpAgent, httpsAgent: userHttpsAgent, requesterOptions, }?: NodeHttpRequesterOptions): Requester & Destroyable;
8
-
9
- export declare type NodeHttpRequesterOptions = {
10
- agent?: https.Agent | http.Agent;
11
- httpAgent?: http.Agent;
12
- httpsAgent?: https.Agent;
13
- requesterOptions?: https.RequestOptions;
14
- };
15
-
16
- export { }
@@ -1,85 +0,0 @@
1
- import * as http from 'http';
2
- import { Agent } from 'http';
3
- import * as https from 'https';
4
- import { Agent as Agent$1 } from 'https';
5
- import { parse } from 'url';
6
-
7
- /* eslint functional/prefer-readonly-type: 0 */
8
- const agentOptions = { keepAlive: true };
9
- const defaultHttpAgent = new Agent(agentOptions);
10
- const defaultHttpsAgent = new Agent$1(agentOptions);
11
- function createNodeHttpRequester({ agent: userGlobalAgent, httpAgent: userHttpAgent, httpsAgent: userHttpsAgent, requesterOptions = {}, } = {}) {
12
- const httpAgent = userHttpAgent || userGlobalAgent || defaultHttpAgent;
13
- const httpsAgent = userHttpsAgent || userGlobalAgent || defaultHttpsAgent;
14
- return {
15
- send(request) {
16
- return new Promise(resolve => {
17
- const url = parse(request.url);
18
- const path = url.query === null ? url.pathname : `${url.pathname}?${url.query}`;
19
- const options = {
20
- ...requesterOptions,
21
- agent: url.protocol === 'https:' ? httpsAgent : httpAgent,
22
- hostname: url.hostname,
23
- path,
24
- method: request.method,
25
- headers: {
26
- ...(requesterOptions && requesterOptions.headers ? requesterOptions.headers : {}),
27
- ...request.headers,
28
- },
29
- ...(url.port !== undefined ? { port: url.port || '' } : {}),
30
- };
31
- const req = (url.protocol === 'https:' ? https : http).request(options, response => {
32
- // eslint-disable-next-line functional/no-let
33
- let contentBuffers = [];
34
- response.on('data', chunk => {
35
- contentBuffers = contentBuffers.concat(chunk);
36
- });
37
- response.on('end', () => {
38
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
39
- clearTimeout(connectTimeout);
40
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
41
- clearTimeout(responseTimeout);
42
- resolve({
43
- status: response.statusCode || 0,
44
- content: Buffer.concat(contentBuffers).toString(),
45
- isTimedOut: false,
46
- });
47
- });
48
- });
49
- const createTimeout = (timeout, content) => {
50
- return setTimeout(() => {
51
- req.abort();
52
- resolve({
53
- status: 0,
54
- content,
55
- isTimedOut: true,
56
- });
57
- }, timeout * 1000);
58
- };
59
- const connectTimeout = createTimeout(request.connectTimeout, 'Connection timeout');
60
- // eslint-disable-next-line functional/no-let
61
- let responseTimeout;
62
- req.on('error', error => {
63
- clearTimeout(connectTimeout);
64
- clearTimeout(responseTimeout);
65
- resolve({ status: 0, content: error.message, isTimedOut: false });
66
- });
67
- req.once('response', () => {
68
- clearTimeout(connectTimeout);
69
- responseTimeout = createTimeout(request.responseTimeout, 'Socket timeout');
70
- });
71
- if (request.data !== undefined) {
72
- req.write(request.data);
73
- }
74
- req.end();
75
- });
76
- },
77
- destroy() {
78
- httpAgent.destroy();
79
- httpsAgent.destroy();
80
- return Promise.resolve();
81
- },
82
- };
83
- }
84
-
85
- export { createNodeHttpRequester };
package/index.js DELETED
@@ -1,2 +0,0 @@
1
- // eslint-disable-next-line functional/immutable-data, import/no-commonjs
2
- module.exports = require('./dist/requester-node-http.cjs.js');