@algolia/requester-node-http 5.8.1 → 5.9.1

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.
@@ -1,16 +1,81 @@
1
1
  import http from 'http';
2
2
  import https from 'https';
3
- import { Requester } from '@algolia/client-common';
3
+ import { URL } from 'url';
4
4
 
5
- type CreateHttpRequesterOptions = Partial<{
6
- agent: http.Agent | https.Agent;
7
- httpAgent: http.Agent;
8
- httpsAgent: https.Agent;
9
- /**
10
- * RequestOptions to be merged with the end request, it will override default options if provided.
11
- */
12
- requesterOptions: https.RequestOptions;
13
- }>;
14
- declare function createHttpRequester({ agent: userGlobalAgent, httpAgent: userHttpAgent, httpsAgent: userHttpsAgent, requesterOptions, }?: CreateHttpRequesterOptions): Requester;
5
+ // src/createHttpRequester.ts
6
+ var agentOptions = { keepAlive: true };
7
+ var defaultHttpAgent = new http.Agent(agentOptions);
8
+ var defaultHttpsAgent = new https.Agent(agentOptions);
9
+ function createHttpRequester({
10
+ agent: userGlobalAgent,
11
+ httpAgent: userHttpAgent,
12
+ httpsAgent: userHttpsAgent,
13
+ requesterOptions = {}
14
+ } = {}) {
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
+ let connectTimeout;
21
+ const url = new URL(request.url);
22
+ const path = url.search === null ? url.pathname : `${url.pathname}${url.search}`;
23
+ const options = {
24
+ agent: url.protocol === "https:" ? httpsAgent : httpAgent,
25
+ hostname: url.hostname,
26
+ path,
27
+ method: request.method,
28
+ ...requesterOptions,
29
+ headers: {
30
+ ...request.headers,
31
+ ...requesterOptions.headers
32
+ }
33
+ };
34
+ if (url.port && !requesterOptions.port) {
35
+ options.port = url.port;
36
+ }
37
+ const req = (url.protocol === "https:" ? https : http).request(options, (response) => {
38
+ let contentBuffers = [];
39
+ response.on("data", (chunk) => {
40
+ contentBuffers = contentBuffers.concat(chunk);
41
+ });
42
+ response.on("end", () => {
43
+ clearTimeout(connectTimeout);
44
+ clearTimeout(responseTimeout);
45
+ resolve({
46
+ status: response.statusCode || 0,
47
+ content: Buffer.concat(contentBuffers).toString(),
48
+ isTimedOut: false
49
+ });
50
+ });
51
+ });
52
+ const createTimeout = (timeout, content) => {
53
+ return setTimeout(() => {
54
+ req.destroy();
55
+ resolve({
56
+ status: 0,
57
+ content,
58
+ isTimedOut: true
59
+ });
60
+ }, timeout);
61
+ };
62
+ connectTimeout = createTimeout(request.connectTimeout, "Connection timeout");
63
+ req.on("error", (error) => {
64
+ clearTimeout(connectTimeout);
65
+ clearTimeout(responseTimeout);
66
+ resolve({ status: 0, content: error.message, isTimedOut: false });
67
+ });
68
+ req.once("response", () => {
69
+ clearTimeout(connectTimeout);
70
+ responseTimeout = createTimeout(request.responseTimeout, "Socket timeout");
71
+ });
72
+ if (request.data !== void 0) {
73
+ req.write(request.data);
74
+ }
75
+ req.end();
76
+ });
77
+ }
78
+ return { send };
79
+ }
15
80
 
16
- export { type CreateHttpRequesterOptions, createHttpRequester };
81
+ export { createHttpRequester };
@@ -1,16 +1,81 @@
1
1
  import http from 'http';
2
2
  import https from 'https';
3
- import { Requester } from '@algolia/client-common';
3
+ import { URL } from 'url';
4
4
 
5
- type CreateHttpRequesterOptions = Partial<{
6
- agent: http.Agent | https.Agent;
7
- httpAgent: http.Agent;
8
- httpsAgent: https.Agent;
9
- /**
10
- * RequestOptions to be merged with the end request, it will override default options if provided.
11
- */
12
- requesterOptions: https.RequestOptions;
13
- }>;
14
- declare function createHttpRequester({ agent: userGlobalAgent, httpAgent: userHttpAgent, httpsAgent: userHttpsAgent, requesterOptions, }?: CreateHttpRequesterOptions): Requester;
5
+ // src/createHttpRequester.ts
6
+ var agentOptions = { keepAlive: true };
7
+ var defaultHttpAgent = new http.Agent(agentOptions);
8
+ var defaultHttpsAgent = new https.Agent(agentOptions);
9
+ function createHttpRequester({
10
+ agent: userGlobalAgent,
11
+ httpAgent: userHttpAgent,
12
+ httpsAgent: userHttpsAgent,
13
+ requesterOptions = {}
14
+ } = {}) {
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
+ let connectTimeout;
21
+ const url = new URL(request.url);
22
+ const path = url.search === null ? url.pathname : `${url.pathname}${url.search}`;
23
+ const options = {
24
+ agent: url.protocol === "https:" ? httpsAgent : httpAgent,
25
+ hostname: url.hostname,
26
+ path,
27
+ method: request.method,
28
+ ...requesterOptions,
29
+ headers: {
30
+ ...request.headers,
31
+ ...requesterOptions.headers
32
+ }
33
+ };
34
+ if (url.port && !requesterOptions.port) {
35
+ options.port = url.port;
36
+ }
37
+ const req = (url.protocol === "https:" ? https : http).request(options, (response) => {
38
+ let contentBuffers = [];
39
+ response.on("data", (chunk) => {
40
+ contentBuffers = contentBuffers.concat(chunk);
41
+ });
42
+ response.on("end", () => {
43
+ clearTimeout(connectTimeout);
44
+ clearTimeout(responseTimeout);
45
+ resolve({
46
+ status: response.statusCode || 0,
47
+ content: Buffer.concat(contentBuffers).toString(),
48
+ isTimedOut: false
49
+ });
50
+ });
51
+ });
52
+ const createTimeout = (timeout, content) => {
53
+ return setTimeout(() => {
54
+ req.destroy();
55
+ resolve({
56
+ status: 0,
57
+ content,
58
+ isTimedOut: true
59
+ });
60
+ }, timeout);
61
+ };
62
+ connectTimeout = createTimeout(request.connectTimeout, "Connection timeout");
63
+ req.on("error", (error) => {
64
+ clearTimeout(connectTimeout);
65
+ clearTimeout(responseTimeout);
66
+ resolve({ status: 0, content: error.message, isTimedOut: false });
67
+ });
68
+ req.once("response", () => {
69
+ clearTimeout(connectTimeout);
70
+ responseTimeout = createTimeout(request.responseTimeout, "Socket timeout");
71
+ });
72
+ if (request.data !== void 0) {
73
+ req.write(request.data);
74
+ }
75
+ req.end();
76
+ });
77
+ }
78
+ return { send };
79
+ }
15
80
 
16
- export { type CreateHttpRequesterOptions, createHttpRequester };
81
+ export { createHttpRequester };
package/index.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './dist/requester.http';
package/index.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./dist/requester.http.cjs');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algolia/requester-node-http",
3
- "version": "5.8.1",
3
+ "version": "5.9.1",
4
4
  "description": "Promise-based request library for node using the native http module.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,8 +11,8 @@
11
11
  "type": "module",
12
12
  "files": [
13
13
  "dist",
14
- "src",
15
- "index.ts"
14
+ "index.d.ts",
15
+ "index.js"
16
16
  ],
17
17
  "exports": {
18
18
  ".": {
@@ -34,15 +34,15 @@
34
34
  "test:bundle": "publint . && attw --pack ."
35
35
  },
36
36
  "dependencies": {
37
- "@algolia/client-common": "5.8.1"
37
+ "@algolia/client-common": "5.9.1"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@arethetypeswrong/cli": "0.16.4",
41
- "@types/node": "22.7.4",
41
+ "@types/node": "22.7.5",
42
42
  "nock": "13.5.5",
43
43
  "publint": "0.2.11",
44
44
  "tsup": "8.3.0",
45
- "typescript": "5.6.2",
45
+ "typescript": "5.6.3",
46
46
  "vitest": "2.1.2"
47
47
  },
48
48
  "engines": {
package/index.ts DELETED
@@ -1 +0,0 @@
1
- export * from './src/createHttpRequester';
@@ -1,281 +0,0 @@
1
- import http from 'http';
2
- import https from 'https';
3
- import nock from 'nock';
4
- import { Readable } from 'stream';
5
- import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest';
6
-
7
- import type { EndRequest } from '@algolia/client-common';
8
-
9
- import { createHttpRequester } from '../..';
10
- import {
11
- createTestServer,
12
- getStringifiedBody,
13
- headers,
14
- requestStub,
15
- testQueryBaseUrl,
16
- testQueryHeader,
17
- timeoutRequest,
18
- } from '../../../../tests/utils';
19
-
20
- const requester = createHttpRequester();
21
-
22
- const httpsBaseRequest = https.request;
23
- const httpBaseRequest = http.request;
24
-
25
- describe('api', () => {
26
- const mockedRequestResponse = {
27
- destroy: vi.fn(),
28
- on: vi.fn(),
29
- once: vi.fn(),
30
- write: vi.fn(),
31
- end: vi.fn(),
32
- };
33
-
34
- beforeAll(() => {
35
- // @ts-expect-error we don't care about the response for those tests
36
- https.request = vi.fn(() => mockedRequestResponse);
37
- });
38
-
39
- afterAll(() => {
40
- https.request = httpsBaseRequest;
41
- http.request = httpBaseRequest;
42
- vi.resetAllMocks();
43
- vi.clearAllMocks();
44
- });
45
-
46
- test('allow init without parameters', () => {
47
- expect(() => createHttpRequester()).not.toThrow();
48
- });
49
-
50
- test('allow providing custom agent', async () => {
51
- const agent = new http.Agent();
52
- // @ts-expect-error we don't care about the response for those tests
53
- http.request = vi.fn(() => mockedRequestResponse);
54
- const tmpRequester = createHttpRequester({
55
- agent,
56
- });
57
-
58
- await tmpRequester.send({
59
- ...requestStub,
60
- url: 'http://algolia-dns.net/foo?x-algolia-header=bar',
61
- });
62
-
63
- expect(http.request).toHaveBeenCalled();
64
- });
65
-
66
- test('allow overriding default options', async () => {
67
- const tmpRequester = createHttpRequester({
68
- requesterOptions: {
69
- headers: {
70
- 'my-extra-header': 'algolia',
71
- },
72
- },
73
- });
74
-
75
- await tmpRequester.send(requestStub);
76
-
77
- expect(https.request).toHaveBeenCalledWith(
78
- expect.objectContaining({
79
- headers: expect.objectContaining({
80
- 'my-extra-header': 'algolia',
81
- }),
82
- }),
83
- expect.any(Function),
84
- );
85
- });
86
- });
87
-
88
- describe('status code handling', () => {
89
- test('sends requests', async () => {
90
- const body = getStringifiedBody();
91
-
92
- nock(testQueryBaseUrl, { reqheaders: headers }).post('/foo').query(testQueryHeader).reply(200, body);
93
-
94
- const response = await requester.send(requestStub);
95
-
96
- expect(response.content).toEqual(body);
97
- });
98
-
99
- test('resolves status 200', async () => {
100
- const body = getStringifiedBody();
101
-
102
- nock(testQueryBaseUrl, { reqheaders: headers }).post('/foo').query(testQueryHeader).reply(200, body);
103
-
104
- const response = await requester.send(requestStub);
105
-
106
- expect(response.status).toBe(200);
107
- expect(response.content).toBe(body);
108
- expect(response.isTimedOut).toBe(false);
109
- });
110
-
111
- test('resolves status 300', async () => {
112
- const reason = 'Multiple Choices';
113
-
114
- nock(testQueryBaseUrl, { reqheaders: headers }).post('/foo').query(testQueryHeader).reply(300, reason);
115
-
116
- const response = await requester.send(requestStub);
117
-
118
- expect(response.status).toBe(300);
119
- expect(response.content).toBe(reason);
120
- expect(response.isTimedOut).toBe(false);
121
- });
122
-
123
- test('resolves status 400', async () => {
124
- const body = getStringifiedBody({
125
- message: 'Invalid Application-Id or API-Key',
126
- });
127
-
128
- nock(testQueryBaseUrl, { reqheaders: headers }).post('/foo').query(testQueryHeader).reply(400, body);
129
-
130
- const response = await requester.send(requestStub);
131
-
132
- expect(response.status).toBe(400);
133
- expect(response.content).toBe(body);
134
- expect(response.isTimedOut).toBe(false);
135
- });
136
-
137
- test('handles chunked responses inside unicode character boundaries', async () => {
138
- const data = Buffer.from('äöü');
139
-
140
- // create a test response stream that is chunked inside a unicode character
141
- function* generate() {
142
- yield data.subarray(0, 3);
143
- yield data.subarray(3);
144
- }
145
-
146
- const testStream = Readable.from(generate());
147
-
148
- nock(testQueryBaseUrl, { reqheaders: headers }).post('/foo').query(testQueryHeader).reply(200, testStream);
149
-
150
- const response = await requester.send(requestStub);
151
-
152
- expect(response.content).toEqual(data.toString());
153
- });
154
- });
155
-
156
- describe('timeout handling', () => {
157
- let server: http.Server;
158
- // setup http server to test timeout
159
- beforeAll(() => {
160
- server = createTestServer();
161
-
162
- server.listen('1112');
163
- });
164
-
165
- afterAll(
166
- () =>
167
- new Promise((done) => {
168
- done();
169
- }),
170
- );
171
-
172
- test('timeouts with the given 1 seconds connection timeout', async () => {
173
- const before = Date.now();
174
- const response = await requester.send({
175
- ...timeoutRequest,
176
- connectTimeout: 1000,
177
- url: 'http://localhost:1112/connection_timeout',
178
- });
179
-
180
- const now = Date.now();
181
-
182
- expect(response.content).toBe('Connection timeout');
183
- expect(now - before).toBeGreaterThanOrEqual(999);
184
- expect(now - before).toBeLessThanOrEqual(1200);
185
- });
186
-
187
- test('connection timeouts with the given 2 seconds connection timeout', async () => {
188
- const before = Date.now();
189
- const response = await requester.send({
190
- ...timeoutRequest,
191
- connectTimeout: 2000,
192
- url: 'http://localhost:1112/connection_timeout',
193
- });
194
-
195
- const now = Date.now();
196
-
197
- expect(response.content).toBe('Connection timeout');
198
- expect(now - before).toBeGreaterThanOrEqual(1999);
199
- expect(now - before).toBeLessThanOrEqual(2200);
200
- });
201
-
202
- test("socket timeouts if response don't appears before the timeout with 2 seconds timeout", async () => {
203
- const before = Date.now();
204
-
205
- const response = await requester.send({
206
- ...timeoutRequest,
207
- responseTimeout: 2000,
208
- url: 'http://localhost:1112',
209
- });
210
-
211
- const now = Date.now();
212
-
213
- expect(response.content).toBe('Socket timeout');
214
- expect(now - before).toBeGreaterThanOrEqual(1999);
215
- expect(now - before).toBeLessThanOrEqual(2200);
216
- });
217
-
218
- test("socket timeouts if response don't appears before the timeout with 3 seconds timeout", async () => {
219
- const before = Date.now();
220
- const response = await requester.send({
221
- ...timeoutRequest,
222
- responseTimeout: 3000,
223
- url: 'http://localhost:1112',
224
- });
225
-
226
- const now = Date.now();
227
-
228
- expect(response.content).toBe('Socket timeout');
229
- expect(now - before).toBeGreaterThanOrEqual(2999);
230
- expect(now - before).toBeLessThanOrEqual(3200);
231
- });
232
-
233
- test('do not timeouts if response appears before the timeout', async () => {
234
- const before = Date.now();
235
- const response = await requester.send({
236
- ...requestStub,
237
- url: 'http://localhost:1112',
238
- responseTimeout: 6000,
239
- });
240
-
241
- const now = Date.now();
242
-
243
- expect(response.isTimedOut).toBe(false);
244
- expect(response.status).toBe(200);
245
- expect(response.content).toBe('{"foo": "bar"}');
246
- expect(now - before).toBeGreaterThanOrEqual(4999);
247
- expect(now - before).toBeLessThanOrEqual(5200);
248
- }, 10000); // This is a long-running test, default server timeout is set to 5000ms
249
- });
250
-
251
- describe('error handling', (): void => {
252
- test('resolves dns not found', async () => {
253
- const request: EndRequest = {
254
- url: 'https://this-dont-exist.algolia.com',
255
- method: 'POST',
256
- headers,
257
- data: getStringifiedBody(),
258
- responseTimeout: 2000,
259
- connectTimeout: 1000,
260
- };
261
-
262
- const response = await requester.send(request);
263
-
264
- expect(response.status).toBe(0);
265
- expect(response.content).toContain('');
266
- expect(response.isTimedOut).toBe(false);
267
- });
268
-
269
- test('resolves general network errors', async () => {
270
- nock(testQueryBaseUrl, { reqheaders: headers })
271
- .post('/foo')
272
- .query(testQueryHeader)
273
- .replyWithError('This is a general error');
274
-
275
- const response = await requester.send(requestStub);
276
-
277
- expect(response.status).toBe(0);
278
- expect(response.content).toBe('This is a general error');
279
- expect(response.isTimedOut).toBe(false);
280
- });
281
- });
@@ -1,106 +0,0 @@
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
- let connectTimeout: NodeJS.Timeout | undefined;
35
- const url = new URL(request.url);
36
- const path = url.search === null ? url.pathname : `${url.pathname}${url.search}`;
37
- const options: https.RequestOptions = {
38
- agent: url.protocol === 'https:' ? httpsAgent : httpAgent,
39
- hostname: url.hostname,
40
- path,
41
- method: request.method,
42
- ...requesterOptions,
43
- headers: {
44
- ...request.headers,
45
- ...requesterOptions.headers,
46
- },
47
- };
48
-
49
- if (url.port && !requesterOptions.port) {
50
- options.port = url.port;
51
- }
52
-
53
- const req = (url.protocol === 'https:' ? https : http).request(options, (response) => {
54
- let contentBuffers: Buffer[] = [];
55
-
56
- response.on('data', (chunk) => {
57
- contentBuffers = contentBuffers.concat(chunk);
58
- });
59
-
60
- response.on('end', () => {
61
- clearTimeout(connectTimeout as NodeJS.Timeout);
62
- clearTimeout(responseTimeout as NodeJS.Timeout);
63
-
64
- resolve({
65
- status: response.statusCode || 0,
66
- content: Buffer.concat(contentBuffers).toString(),
67
- isTimedOut: false,
68
- });
69
- });
70
- });
71
-
72
- const createTimeout = (timeout: number, content: string): NodeJS.Timeout => {
73
- return setTimeout(() => {
74
- req.destroy();
75
-
76
- resolve({
77
- status: 0,
78
- content,
79
- isTimedOut: true,
80
- });
81
- }, timeout);
82
- };
83
-
84
- connectTimeout = createTimeout(request.connectTimeout, 'Connection timeout');
85
-
86
- req.on('error', (error) => {
87
- clearTimeout(connectTimeout as NodeJS.Timeout);
88
- clearTimeout(responseTimeout!);
89
- resolve({ status: 0, content: error.message, isTimedOut: false });
90
- });
91
-
92
- req.once('response', () => {
93
- clearTimeout(connectTimeout as NodeJS.Timeout);
94
- responseTimeout = createTimeout(request.responseTimeout, 'Socket timeout');
95
- });
96
-
97
- if (request.data !== undefined) {
98
- req.write(request.data);
99
- }
100
-
101
- req.end();
102
- });
103
- }
104
-
105
- return { send };
106
- }