react-native-nitro-net 0.2.0 → 0.3.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.
Files changed (83) hide show
  1. package/README.md +70 -12
  2. package/android/libs/arm64-v8a/librust_c_net.so +0 -0
  3. package/android/libs/armeabi-v7a/librust_c_net.so +0 -0
  4. package/android/libs/x86/librust_c_net.so +0 -0
  5. package/android/libs/x86_64/librust_c_net.so +0 -0
  6. package/cpp/HybridHttpParser.hpp +67 -0
  7. package/cpp/HybridNetDriver.hpp +6 -0
  8. package/cpp/HybridNetServerDriver.hpp +7 -0
  9. package/cpp/HybridNetSocketDriver.hpp +27 -0
  10. package/cpp/NetBindings.hpp +15 -0
  11. package/ios/Frameworks/RustCNet.xcframework/Info.plist +5 -5
  12. package/ios/Frameworks/RustCNet.xcframework/ios-arm64/RustCNet.framework/RustCNet +0 -0
  13. package/ios/Frameworks/RustCNet.xcframework/ios-arm64_x86_64-simulator/RustCNet.framework/RustCNet +0 -0
  14. package/lib/Driver.d.ts +1 -0
  15. package/lib/Driver.d.ts.map +1 -0
  16. package/lib/Driver.js +2 -5
  17. package/lib/Net.nitro.d.ts +20 -0
  18. package/lib/Net.nitro.d.ts.map +1 -0
  19. package/lib/Net.nitro.js +4 -7
  20. package/lib/http.d.ts +204 -0
  21. package/lib/http.d.ts.map +1 -0
  22. package/lib/http.js +1126 -0
  23. package/lib/https.d.ts +25 -0
  24. package/lib/https.d.ts.map +1 -0
  25. package/lib/https.js +102 -0
  26. package/lib/index.d.ts +41 -160
  27. package/lib/index.d.ts.map +1 -0
  28. package/lib/index.js +11 -821
  29. package/lib/net.d.ts +197 -0
  30. package/lib/net.d.ts.map +1 -0
  31. package/lib/net.js +875 -0
  32. package/lib/tls.d.ts +23 -1
  33. package/lib/tls.d.ts.map +1 -0
  34. package/lib/tls.js +108 -54
  35. package/nitrogen/generated/android/RustCNet+autolinking.cmake +2 -0
  36. package/nitrogen/generated/android/RustCNetOnLoad.cpp +2 -0
  37. package/nitrogen/generated/android/c++/JHybridHttpParserSpec.cpp +54 -0
  38. package/nitrogen/generated/android/c++/JHybridHttpParserSpec.hpp +65 -0
  39. package/nitrogen/generated/android/c++/JHybridNetDriverSpec.cpp +9 -0
  40. package/nitrogen/generated/android/c++/JHybridNetDriverSpec.hpp +1 -0
  41. package/nitrogen/generated/android/c++/JHybridNetServerDriverSpec.cpp +4 -0
  42. package/nitrogen/generated/android/c++/JHybridNetServerDriverSpec.hpp +1 -0
  43. package/nitrogen/generated/android/c++/JHybridNetSocketDriverSpec.cpp +9 -0
  44. package/nitrogen/generated/android/c++/JHybridNetSocketDriverSpec.hpp +2 -0
  45. package/nitrogen/generated/android/c++/JNetConfig.hpp +7 -3
  46. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridHttpParserSpec.kt +58 -0
  47. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetDriverSpec.kt +4 -0
  48. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetServerDriverSpec.kt +4 -0
  49. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/HybridNetSocketDriverSpec.kt +8 -0
  50. package/nitrogen/generated/android/kotlin/com/margelo/nitro/net/NetConfig.kt +6 -3
  51. package/nitrogen/generated/ios/RustCNet-Swift-Cxx-Bridge.cpp +17 -0
  52. package/nitrogen/generated/ios/RustCNet-Swift-Cxx-Bridge.hpp +26 -0
  53. package/nitrogen/generated/ios/RustCNet-Swift-Cxx-Umbrella.hpp +5 -0
  54. package/nitrogen/generated/ios/c++/HybridHttpParserSpecSwift.cpp +11 -0
  55. package/nitrogen/generated/ios/c++/HybridHttpParserSpecSwift.hpp +79 -0
  56. package/nitrogen/generated/ios/c++/HybridNetDriverSpecSwift.hpp +11 -0
  57. package/nitrogen/generated/ios/c++/HybridNetServerDriverSpecSwift.hpp +6 -0
  58. package/nitrogen/generated/ios/c++/HybridNetSocketDriverSpecSwift.hpp +14 -0
  59. package/nitrogen/generated/ios/swift/HybridHttpParserSpec.swift +56 -0
  60. package/nitrogen/generated/ios/swift/HybridHttpParserSpec_cxx.swift +131 -0
  61. package/nitrogen/generated/ios/swift/HybridNetDriverSpec.swift +1 -0
  62. package/nitrogen/generated/ios/swift/HybridNetDriverSpec_cxx.swift +15 -0
  63. package/nitrogen/generated/ios/swift/HybridNetServerDriverSpec.swift +1 -0
  64. package/nitrogen/generated/ios/swift/HybridNetServerDriverSpec_cxx.swift +11 -0
  65. package/nitrogen/generated/ios/swift/HybridNetSocketDriverSpec.swift +2 -0
  66. package/nitrogen/generated/ios/swift/HybridNetSocketDriverSpec_cxx.swift +36 -0
  67. package/nitrogen/generated/ios/swift/NetConfig.swift +19 -1
  68. package/nitrogen/generated/shared/c++/HybridHttpParserSpec.cpp +21 -0
  69. package/nitrogen/generated/shared/c++/HybridHttpParserSpec.hpp +63 -0
  70. package/nitrogen/generated/shared/c++/HybridNetDriverSpec.cpp +1 -0
  71. package/nitrogen/generated/shared/c++/HybridNetDriverSpec.hpp +4 -0
  72. package/nitrogen/generated/shared/c++/HybridNetServerDriverSpec.cpp +1 -0
  73. package/nitrogen/generated/shared/c++/HybridNetServerDriverSpec.hpp +1 -0
  74. package/nitrogen/generated/shared/c++/HybridNetSocketDriverSpec.cpp +2 -0
  75. package/nitrogen/generated/shared/c++/HybridNetSocketDriverSpec.hpp +2 -0
  76. package/nitrogen/generated/shared/c++/NetConfig.hpp +6 -2
  77. package/package.json +9 -7
  78. package/src/Net.nitro.ts +17 -0
  79. package/src/http.ts +1303 -0
  80. package/src/https.ts +125 -0
  81. package/src/index.ts +13 -874
  82. package/src/net.ts +1005 -0
  83. package/src/tls.ts +82 -6
package/lib/http.js ADDED
@@ -0,0 +1,1126 @@
1
+ import { Writable, Readable } from 'readable-stream';
2
+ import { EventEmitter } from 'eventemitter3';
3
+ import { Driver } from './Driver';
4
+ import { Socket, isVerbose } from './net';
5
+ import { TLSSocket } from './tls';
6
+ import { Buffer } from 'react-native-nitro-buffer';
7
+ function debugLog(message) {
8
+ if (isVerbose()) {
9
+ const timestamp = new Date().toISOString().split('T')[1].split('Z')[0];
10
+ console.log(`[HTTP DEBUG ${timestamp}] ${message}`);
11
+ }
12
+ }
13
+ // ========== STATUS_CODES ==========
14
+ export const STATUS_CODES = {
15
+ 100: 'Continue',
16
+ 101: 'Switching Protocols',
17
+ 102: 'Processing',
18
+ 200: 'OK',
19
+ 201: 'Created',
20
+ 202: 'Accepted',
21
+ 203: 'Non-Authoritative Information',
22
+ 204: 'No Content',
23
+ 205: 'Reset Content',
24
+ 206: 'Partial Content',
25
+ 300: 'Multiple Choices',
26
+ 301: 'Moved Permanently',
27
+ 302: 'Found',
28
+ 303: 'See Other',
29
+ 304: 'Not Modified',
30
+ 307: 'Temporary Redirect',
31
+ 308: 'Permanent Redirect',
32
+ 400: 'Bad Request',
33
+ 401: 'Unauthorized',
34
+ 402: 'Payment Required',
35
+ 403: 'Forbidden',
36
+ 404: 'Not Found',
37
+ 405: 'Method Not Allowed',
38
+ 406: 'Not Acceptable',
39
+ 407: 'Proxy Authentication Required',
40
+ 408: 'Request Timeout',
41
+ 409: 'Conflict',
42
+ 410: 'Gone',
43
+ 411: 'Length Required',
44
+ 412: 'Precondition Failed',
45
+ 413: 'Payload Too Large',
46
+ 414: 'URI Too Long',
47
+ 415: 'Unsupported Media Type',
48
+ 416: 'Range Not Satisfiable',
49
+ 417: 'Expectation Failed',
50
+ 418: "I'm a teapot",
51
+ 421: 'Misdirected Request',
52
+ 422: 'Unprocessable Entity',
53
+ 423: 'Locked',
54
+ 424: 'Failed Dependency',
55
+ 425: 'Too Early',
56
+ 426: 'Upgrade Required',
57
+ 428: 'Precondition Required',
58
+ 429: 'Too Many Requests',
59
+ 431: 'Request Header Fields Too Large',
60
+ 451: 'Unavailable For Legal Reasons',
61
+ 500: 'Internal Server Error',
62
+ 501: 'Not Implemented',
63
+ 502: 'Bad Gateway',
64
+ 503: 'Service Unavailable',
65
+ 504: 'Gateway Timeout',
66
+ 505: 'HTTP Version Not Supported',
67
+ 506: 'Variant Also Negotiates',
68
+ 507: 'Insufficient Storage',
69
+ 508: 'Loop Detected',
70
+ 510: 'Not Extended',
71
+ 511: 'Network Authentication Required',
72
+ };
73
+ export const METHODS = [
74
+ 'ACL', 'BIND', 'CHECKOUT', 'CONNECT', 'COPY', 'DELETE', 'GET', 'HEAD',
75
+ 'LINK', 'LOCK', 'M-SEARCH', 'MERGE', 'MKACTIVITY', 'MKCALENDAR', 'MKCOL',
76
+ 'MOVE', 'NOTIFY', 'OPTIONS', 'PATCH', 'POST', 'PROPFIND', 'PROPPATCH',
77
+ 'PURGE', 'PUT', 'REBIND', 'REPORT', 'SEARCH', 'SOURCE', 'SUBSCRIBE',
78
+ 'TRACE', 'UNBIND', 'UNLINK', 'UNLOCK', 'UNSUBSCRIBE'
79
+ ];
80
+ // ========== IncomingMessage ==========
81
+ export class IncomingMessage extends Readable {
82
+ constructor(socket) {
83
+ // @ts-ignore
84
+ super({ autoDestroy: false });
85
+ this.httpVersion = '1.1';
86
+ this.httpVersionMajor = 1;
87
+ this.httpVersionMinor = 1;
88
+ this.headers = {};
89
+ this.rawHeaders = [];
90
+ this.aborted = false;
91
+ this.complete = false;
92
+ this.trailers = {};
93
+ this.socket = socket;
94
+ }
95
+ _read() {
96
+ this.socket.resume();
97
+ }
98
+ setTimeout(msecs, callback) {
99
+ this.socket.setTimeout(msecs, callback);
100
+ return this;
101
+ }
102
+ destroy(error) {
103
+ super.destroy(error);
104
+ this.socket.destroy();
105
+ return this;
106
+ }
107
+ setNoDelay(noDelay = true) {
108
+ this.socket.setNoDelay(noDelay);
109
+ }
110
+ setKeepAlive(enable = false, initialDelay = 0) {
111
+ this.socket.setKeepAlive(enable, initialDelay);
112
+ }
113
+ }
114
+ // ========== OutgoingMessage ==========
115
+ export class OutgoingMessage extends Writable {
116
+ constructor() {
117
+ // @ts-ignore - disable autoDestroy to prevent socket from being destroyed when stream ends
118
+ super({ autoDestroy: false });
119
+ this.headersSent = false;
120
+ this._headers = {};
121
+ this._headerNames = {};
122
+ this.socket = null;
123
+ this.chunkedEncoding = false;
124
+ this._hasBody = true;
125
+ this._sendHeadersSent = false;
126
+ this.aborted = false;
127
+ this._trailers = null;
128
+ this.once('finish', () => {
129
+ this.emit('close');
130
+ });
131
+ }
132
+ destroy(error) {
133
+ super.destroy(error);
134
+ if (this.socket) {
135
+ this.socket.destroy();
136
+ }
137
+ return this;
138
+ }
139
+ setHeader(name, value) {
140
+ if (this.headersSent)
141
+ throw new Error('Cannot set headers after they are sent');
142
+ const key = name.toLowerCase();
143
+ this._headers[key] = value;
144
+ this._headerNames[key] = name;
145
+ return this;
146
+ }
147
+ getHeader(name) {
148
+ return this._headers[name.toLowerCase()];
149
+ }
150
+ removeHeader(name) {
151
+ if (this.headersSent)
152
+ throw new Error('Cannot remove headers after they are sent');
153
+ const key = name.toLowerCase();
154
+ delete this._headers[key];
155
+ delete this._headerNames[key];
156
+ }
157
+ hasHeader(name) {
158
+ return name.toLowerCase() in this._headers;
159
+ }
160
+ getHeaderNames() {
161
+ return Object.values(this._headerNames);
162
+ }
163
+ setTimeout(ms, callback) {
164
+ if (this.socket) {
165
+ this.socket.setTimeout(ms, () => {
166
+ this.emit('timeout');
167
+ if (callback)
168
+ callback();
169
+ });
170
+ }
171
+ else {
172
+ this.once('socket', (s) => {
173
+ s.setTimeout(ms, () => {
174
+ this.emit('timeout');
175
+ if (callback)
176
+ callback();
177
+ });
178
+ });
179
+ }
180
+ return this;
181
+ }
182
+ _renderHeaders(firstLine) {
183
+ let headerStr = firstLine + '\r\n';
184
+ for (const key in this._headers) {
185
+ const name = this._headerNames[key];
186
+ const value = this._headers[key];
187
+ if (Array.isArray(value)) {
188
+ for (const v of value) {
189
+ headerStr += `${name}: ${v}\r\n`;
190
+ }
191
+ }
192
+ else {
193
+ headerStr += `${name}: ${value}\r\n`;
194
+ }
195
+ }
196
+ headerStr += '\r\n';
197
+ return headerStr;
198
+ }
199
+ _sendHeaders(firstLine) {
200
+ if (this.headersSent)
201
+ return;
202
+ // Check for Chunked Encoding
203
+ if (!this.hasHeader('Content-Length') && this._hasBody) {
204
+ this.setHeader('Transfer-Encoding', 'chunked');
205
+ this.chunkedEncoding = true;
206
+ }
207
+ this.headersSent = true;
208
+ const headerStr = this._renderHeaders(firstLine);
209
+ debugLog(`OutgoingMessage._sendHeaders: writing ${headerStr.length} bytes to socket (socket=${!!this.socket})`);
210
+ this.socket.write(Buffer.from(headerStr));
211
+ }
212
+ _write(chunk, encoding, callback) {
213
+ if (!this.socket) {
214
+ callback(new Error('Socket not assigned'));
215
+ return;
216
+ }
217
+ if (this.chunkedEncoding) {
218
+ const len = typeof chunk === 'string' ? Buffer.byteLength(chunk, encoding) : chunk.length;
219
+ const header = len.toString(16) + '\r\n';
220
+ this.socket.write(Buffer.from(header));
221
+ // Note: We don't return the backpressure status here because we are doing multiple writes
222
+ // The final write determines the callback.
223
+ this.socket.write(chunk, encoding, (err) => {
224
+ if (err)
225
+ return callback(err);
226
+ this.socket.write(Buffer.from('\r\n'), undefined, callback);
227
+ });
228
+ }
229
+ else {
230
+ this.socket.write(chunk, encoding, callback);
231
+ }
232
+ }
233
+ write(chunk, encoding, callback) {
234
+ const ret = super.write(chunk, encoding, callback);
235
+ // If writableLength is too high, return false
236
+ // But since we are proxying to socket, we should also check socket backpressure
237
+ if (this.socket && this.socket._writableState) {
238
+ // This is a bit hacky but if we have a real Node-like socket, we respect its state
239
+ return ret && !this.socket._writableState.needDrain;
240
+ }
241
+ return ret;
242
+ }
243
+ // _final is called by the stream when all writes are complete before 'finish' event
244
+ _final(callback) {
245
+ if (this.chunkedEncoding && this.socket) {
246
+ let terminator = '0\r\n';
247
+ if (this._trailers) {
248
+ for (const [key, value] of Object.entries(this._trailers)) {
249
+ terminator += `${key}: ${value}\r\n`;
250
+ }
251
+ }
252
+ terminator += '\r\n';
253
+ this.socket.write(Buffer.from(terminator), undefined, (err) => {
254
+ callback(err);
255
+ });
256
+ }
257
+ else {
258
+ callback();
259
+ }
260
+ }
261
+ addTrailers(headers) {
262
+ if (this.headersSent && !this.chunkedEncoding) {
263
+ throw new Error('Trailers can only be used with chunked encoding');
264
+ }
265
+ this._trailers = headers;
266
+ }
267
+ end(chunk, encoding, callback) {
268
+ debugLog(`OutgoingMessage.end() called, already ending: ${this._writableState?.ending}, chunk: ${!!chunk}`);
269
+ if (chunk) {
270
+ this.write(chunk, encoding);
271
+ }
272
+ super.end(undefined, undefined, callback);
273
+ return this;
274
+ }
275
+ setNoDelay(noDelay = true) {
276
+ this.socket?.setNoDelay(noDelay);
277
+ }
278
+ setSocketKeepAlive(enable = false, initialDelay = 0) {
279
+ this.socket?.setKeepAlive(enable, initialDelay);
280
+ }
281
+ }
282
+ // ========== ServerResponse ==========
283
+ export class ServerResponse extends OutgoingMessage {
284
+ constructor(socket) {
285
+ super();
286
+ this.statusCode = 200;
287
+ this.socket = socket;
288
+ }
289
+ writeHead(statusCode, statusMessage, headers) {
290
+ if (this.headersSent)
291
+ throw new Error('Cannot write headers after they are sent');
292
+ this.statusCode = statusCode;
293
+ if (typeof statusMessage === 'object') {
294
+ headers = statusMessage;
295
+ statusMessage = undefined;
296
+ }
297
+ if (statusMessage)
298
+ this.statusMessage = statusMessage;
299
+ if (headers) {
300
+ for (const key in headers) {
301
+ this.setHeader(key, headers[key]);
302
+ }
303
+ }
304
+ // Note: Do NOT send headers here. They will be sent on first write/end
305
+ // when Content-Length can be determined.
306
+ return this;
307
+ }
308
+ _sendResponseHeaders() {
309
+ if (this.headersSent)
310
+ return;
311
+ const firstLine = `HTTP/1.1 ${this.statusCode} ${this.statusMessage || STATUS_CODES[this.statusCode] || 'OK'}`;
312
+ this._sendHeaders(firstLine);
313
+ }
314
+ _write(chunk, encoding, callback) {
315
+ if (!this.headersSent)
316
+ this._sendResponseHeaders();
317
+ super._write(chunk, encoding, callback);
318
+ }
319
+ write(chunk, encoding, callback) {
320
+ return super.write(chunk, encoding, callback);
321
+ }
322
+ end(chunk, encoding, callback) {
323
+ if (!this.headersSent) {
324
+ // If we have a single chunk and no headers sent yet, we can add Content-Length
325
+ // to avoid chunked encoding for simple responses.
326
+ if (chunk) {
327
+ const len = typeof chunk === 'string' ? Buffer.byteLength(chunk, encoding) : chunk.length;
328
+ this.setHeader('Content-Length', len);
329
+ }
330
+ else {
331
+ this.setHeader('Content-Length', 0);
332
+ }
333
+ this._sendResponseHeaders();
334
+ }
335
+ // super.end will trigger _write if chunk was provided.
336
+ super.end(chunk, encoding, () => {
337
+ if (callback)
338
+ callback();
339
+ });
340
+ return this;
341
+ }
342
+ }
343
+ export class Server extends EventEmitter {
344
+ constructor(options, requestListener) {
345
+ super();
346
+ this._httpConnections = new Set();
347
+ this.maxHeaderSize = 16384;
348
+ this.maxRequestsPerSocket = 0;
349
+ this.headersTimeout = 60000;
350
+ this.requestTimeout = 300000;
351
+ this.keepAliveTimeout = 5000;
352
+ // Use net.Server from index.ts
353
+ const { Server: NetServer } = require('./net');
354
+ this._netServer = new NetServer();
355
+ let listener;
356
+ if (typeof options === 'function') {
357
+ listener = options;
358
+ }
359
+ else if (options) {
360
+ if (options.keepAliveTimeout !== undefined)
361
+ this.keepAliveTimeout = options.keepAliveTimeout;
362
+ if (options.requestTimeout !== undefined)
363
+ this.requestTimeout = options.requestTimeout;
364
+ if (options.headersTimeout !== undefined)
365
+ this.headersTimeout = options.headersTimeout;
366
+ if (options.maxHeaderSize !== undefined)
367
+ this.maxHeaderSize = options.maxHeaderSize;
368
+ if (options.maxRequestsPerSocket !== undefined)
369
+ this.maxRequestsPerSocket = options.maxRequestsPerSocket;
370
+ listener = requestListener;
371
+ }
372
+ if (listener) {
373
+ this.on('request', listener);
374
+ }
375
+ // Forward net.Server events
376
+ this._netServer.on('listening', () => this.emit('listening'));
377
+ this._netServer.on('close', () => this.emit('close'));
378
+ this._netServer.on('error', (err) => this.emit('error', err));
379
+ this._netServer.on('connection', (socket) => {
380
+ this._setupHttpConnection(socket);
381
+ });
382
+ }
383
+ _setupHttpConnection(socket) {
384
+ this._httpConnections.add(socket);
385
+ let req = null;
386
+ let res = null;
387
+ const parser = Driver.createHttpParser(0); // 0 = Request mode
388
+ // @ts-ignore
389
+ let bodyBytesRead = 0;
390
+ // @ts-ignore
391
+ let contentLength = -1;
392
+ // headersTimeout logic
393
+ let headersTimer = null;
394
+ if (this.headersTimeout > 0) {
395
+ headersTimer = setTimeout(() => {
396
+ debugLog(`Server: headersTimeout reached for socket, destroying`);
397
+ socket.destroy();
398
+ }, this.headersTimeout);
399
+ }
400
+ const onData = (data) => {
401
+ const handleParsedResult = (result) => {
402
+ if (result.startsWith('ERROR:')) {
403
+ if (headersTimer)
404
+ clearTimeout(headersTimer);
405
+ this.emit('error', new Error(result));
406
+ socket.destroy();
407
+ return;
408
+ }
409
+ const parsed = JSON.parse(result);
410
+ if (parsed.is_headers) {
411
+ if (headersTimer) {
412
+ clearTimeout(headersTimer);
413
+ headersTimer = null;
414
+ }
415
+ // Handle CONNECT method (HTTP Tunneling)
416
+ if (parsed.is_connect) {
417
+ const req = new IncomingMessage(socket);
418
+ req.method = parsed.method;
419
+ req.url = parsed.path;
420
+ req.httpVersion = '1.' + parsed.version;
421
+ req.headers = parsed.headers;
422
+ // Remove our data listener to stop feeding the parser
423
+ // The user is responsible for handling the socket data stream from now on
424
+ socket.removeListener('data', onData);
425
+ debugLog(`Server: CONNECT request received, emitting 'connect' event`);
426
+ // TODO: retrieve any remaining body from parser as 'head'
427
+ const head = Buffer.alloc(0);
428
+ if (this.listenerCount('connect') > 0) {
429
+ this.emit('connect', req, socket, head);
430
+ }
431
+ else {
432
+ // Default behavior: close connection if no listener
433
+ socket.destroy();
434
+ }
435
+ return;
436
+ }
437
+ const currentReq = new IncomingMessage(socket);
438
+ currentReq.method = parsed.method;
439
+ currentReq.url = parsed.path;
440
+ currentReq.httpVersion = '1.' + parsed.version;
441
+ currentReq.headers = parsed.headers;
442
+ req = currentReq;
443
+ const currentRes = new ServerResponse(socket);
444
+ res = currentRes;
445
+ // Support Keep-Alive: reset state once response is done
446
+ currentRes.on('finish', () => {
447
+ req = null;
448
+ res = null;
449
+ // The parser should already be reset in Rust
450
+ });
451
+ const upgrade = req.headers['upgrade'];
452
+ if (upgrade && this.listenerCount('upgrade') > 0) {
453
+ debugLog(`Server: Upgrade request received, emitting 'upgrade' event`);
454
+ this.emit('upgrade', req, socket, Buffer.alloc(0));
455
+ return;
456
+ }
457
+ const expect = req.headers['expect'];
458
+ if (expect && (typeof expect === 'string' && expect.toLowerCase() === '100-continue')) {
459
+ if (this.listenerCount('checkContinue') > 0) {
460
+ this.emit('checkContinue', req, res);
461
+ }
462
+ else {
463
+ socket.write(Buffer.from('HTTP/1.1 100 Continue\r\n\r\n'));
464
+ this.emit('request', req, res);
465
+ }
466
+ }
467
+ else {
468
+ debugLog(`Server: Emitting 'request' for ${req.method} ${req.url}`);
469
+ this.emit('request', req, res);
470
+ }
471
+ }
472
+ if (req && parsed.body && parsed.body.length > 0) {
473
+ req.push(Buffer.from(parsed.body));
474
+ }
475
+ if (req && parsed.complete) {
476
+ req.complete = true;
477
+ if (parsed.trailers) {
478
+ req.trailers = parsed.trailers;
479
+ }
480
+ req.push(null);
481
+ }
482
+ // For Keep-Alive, try to parse remaining buffer in case of pipelining
483
+ if (parsed.complete && !req) {
484
+ // This case is handled by the feed loop if multiple messages in data
485
+ }
486
+ };
487
+ let input = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
488
+ let iterations = 0;
489
+ const maxIterations = 100; // Safety limit
490
+ while (iterations < maxIterations) {
491
+ iterations++;
492
+ const result = parser.feed(input);
493
+ if (!result || result === '' || result.startsWith('ERROR:')) {
494
+ // Empty result (partial) or error - exit loop
495
+ if (result && result.startsWith('ERROR:')) {
496
+ console.log(`[HTTP] Server: Parser error: ${result}`);
497
+ }
498
+ break;
499
+ }
500
+ handleParsedResult(result);
501
+ input = new ArrayBuffer(0); // Continue with empty input to drain Rust buffer
502
+ }
503
+ };
504
+ socket.on('data', onData);
505
+ // CRITICAL: Ensure server-side socket starts flowing!
506
+ socket.resume();
507
+ socket.on('close', () => {
508
+ if (headersTimer)
509
+ clearTimeout(headersTimer);
510
+ this._httpConnections.delete(socket);
511
+ if (req && !req.readableEnded) {
512
+ req.push(null);
513
+ }
514
+ });
515
+ socket.on('error', (err) => {
516
+ if (req)
517
+ req.emit('error', err);
518
+ else
519
+ this.emit('error', err);
520
+ });
521
+ }
522
+ listen(...args) {
523
+ this._netServer.listen(...args);
524
+ return this;
525
+ }
526
+ close(callback) {
527
+ this._netServer.close(callback);
528
+ return this;
529
+ }
530
+ // @ts-ignore
531
+ async [Symbol.asyncDispose]() {
532
+ return new Promise((resolve) => {
533
+ this.close(() => resolve());
534
+ });
535
+ }
536
+ address() {
537
+ return this._netServer.address();
538
+ }
539
+ get listening() {
540
+ return this._netServer.listening;
541
+ }
542
+ setTimeout(ms, callback) {
543
+ this._netServer.setTimeout(ms, callback);
544
+ return this;
545
+ }
546
+ }
547
+ export class Agent extends EventEmitter {
548
+ /**
549
+ * Gets the proxy URL for the given request options.
550
+ * Checks HTTP_PROXY, HTTPS_PROXY, and NO_PROXY environment variables.
551
+ *
552
+ * @param options Request options to determine if proxy should be used
553
+ * @returns Proxy URL or null if no proxy should be used
554
+ */
555
+ getProxy(options) {
556
+ // If explicitly set on agent, use that
557
+ if (this.proxy)
558
+ return this.proxy;
559
+ // Check environment variables (React Native may not have process.env)
560
+ const env = typeof process !== 'undefined' && process.env ? process.env : {};
561
+ const isHttps = options.protocol === 'https:';
562
+ const host = options.hostname || options.host || 'localhost';
563
+ // Check NO_PROXY first
564
+ const noProxy = env.NO_PROXY || env.no_proxy;
565
+ if (noProxy) {
566
+ const noProxyList = noProxy.split(',').map(s => s.trim().toLowerCase());
567
+ const hostLower = host.toLowerCase();
568
+ for (const pattern of noProxyList) {
569
+ if (pattern === '*')
570
+ return null;
571
+ if (pattern.startsWith('.') && hostLower.endsWith(pattern))
572
+ return null;
573
+ if (hostLower === pattern)
574
+ return null;
575
+ if (hostLower.endsWith('.' + pattern))
576
+ return null;
577
+ }
578
+ }
579
+ // Get proxy URL based on protocol
580
+ const proxyUrl = isHttps
581
+ ? (env.HTTPS_PROXY || env.https_proxy || env.HTTP_PROXY || env.http_proxy)
582
+ : (env.HTTP_PROXY || env.http_proxy);
583
+ return proxyUrl || null;
584
+ }
585
+ constructor(options) {
586
+ super();
587
+ this.maxSockets = Infinity;
588
+ this.maxTotalSockets = Infinity;
589
+ this.maxFreeSockets = 256;
590
+ this.keepAlive = false;
591
+ this.keepAliveMsecs = 1000;
592
+ this.maxCachedSessions = 100;
593
+ this.scheduling = 'lifo';
594
+ this.requests = {};
595
+ this.sockets = {};
596
+ this.freeSockets = {};
597
+ this._totalSockets = 0;
598
+ this.proxy = null;
599
+ if (options?.maxSockets)
600
+ this.maxSockets = options.maxSockets;
601
+ if (options?.maxTotalSockets)
602
+ this.maxTotalSockets = options.maxTotalSockets;
603
+ if (options?.maxFreeSockets)
604
+ this.maxFreeSockets = options.maxFreeSockets;
605
+ if (options?.keepAlive)
606
+ this.keepAlive = options.keepAlive;
607
+ if (options?.keepAliveMsecs)
608
+ this.keepAliveMsecs = options.keepAliveMsecs;
609
+ if (options?.scheduling)
610
+ this.scheduling = options.scheduling;
611
+ if (options?.maxCachedSessions !== undefined)
612
+ this.maxCachedSessions = options.maxCachedSessions;
613
+ }
614
+ getName(options) {
615
+ let name = `${options.host || options.hostname || 'localhost'}:${options.port || (options.protocol === 'https:' ? 443 : 80)}:`;
616
+ if (options.localAddress)
617
+ name += `${options.localAddress}:`;
618
+ if (options.family)
619
+ name += `${options.family}:`;
620
+ return name;
621
+ }
622
+ addRequest(req, options) {
623
+ const name = this.getName(options);
624
+ debugLog(`Agent.addRequest: name=${name}, totalSockets=${this._totalSockets}`);
625
+ // 1. Check if there's an idle socket in freeSockets
626
+ if (this.freeSockets[name] && this.freeSockets[name].length > 0) {
627
+ const socket = this.scheduling === 'lifo'
628
+ ? this.freeSockets[name].pop()
629
+ : this.freeSockets[name].shift();
630
+ if (this.freeSockets[name].length === 0)
631
+ delete this.freeSockets[name];
632
+ // Re-use socket
633
+ if (!this.sockets[name])
634
+ this.sockets[name] = [];
635
+ this.sockets[name].push(socket);
636
+ req.onSocket(socket);
637
+ return;
638
+ }
639
+ // 2. Check if we can create a new connection
640
+ const currentSockets = (this.sockets[name]?.length || 0);
641
+ if (currentSockets < this.maxSockets && this._totalSockets < this.maxTotalSockets) {
642
+ if (!this.sockets[name])
643
+ this.sockets[name] = [];
644
+ // Increment total sockets early
645
+ this._totalSockets++;
646
+ // ClientRequest handles connection but we signal it to proceed
647
+ req.onSocket(null);
648
+ return;
649
+ }
650
+ // 3. Queue the request
651
+ if (!this.requests[name])
652
+ this.requests[name] = [];
653
+ this.requests[name].push(req);
654
+ }
655
+ createConnection(options, callback) {
656
+ const name = this.getName(options);
657
+ const isHttps = options.protocol === 'https:';
658
+ const port = options.port || (isHttps ? 443 : 80);
659
+ const host = options.hostname || options.host || 'localhost';
660
+ debugLog(`Agent.createConnection: name=${name}, isHttps=${isHttps}, host=${host}, port=${port}`);
661
+ // Build clean connection options - DO NOT pass HTTP path as it will be confused with Unix socket path
662
+ const connectOptions = {
663
+ host: host,
664
+ port: port,
665
+ };
666
+ if (isHttps) {
667
+ connectOptions.servername = options.servername || host;
668
+ connectOptions.rejectUnauthorized = options.rejectUnauthorized !== false;
669
+ if (options.ca)
670
+ connectOptions.ca = options.ca;
671
+ if (options.cert)
672
+ connectOptions.cert = options.cert;
673
+ if (options.key)
674
+ connectOptions.key = options.key;
675
+ }
676
+ const socket = isHttps ? new TLSSocket(connectOptions) : new Socket();
677
+ // Re-emit keylog events from TLSSockets
678
+ if (isHttps) {
679
+ socket.on('keylog', (line) => {
680
+ // @ts-ignore - Agent is an EventEmitter via Node-like inheritance or internal use
681
+ this.emit('keylog', line, socket);
682
+ });
683
+ }
684
+ let called = false;
685
+ const onConnected = () => {
686
+ if (called)
687
+ return;
688
+ called = true;
689
+ debugLog(`Agent.createConnection: socket ${isHttps ? 'SECURE_CONNECTED' : 'CONNECTED'} for ${name}`);
690
+ callback(null, socket);
691
+ };
692
+ if (isHttps) {
693
+ socket.on('secureConnect', onConnected);
694
+ }
695
+ else {
696
+ socket.on('connect', onConnected);
697
+ }
698
+ socket.on('error', (err) => {
699
+ if (called)
700
+ return;
701
+ called = true;
702
+ debugLog(`Agent.createConnection: socket ERROR for ${name}: ${err.message}`);
703
+ this._totalSockets--;
704
+ if (this.sockets[name]) {
705
+ const idx = this.sockets[name].indexOf(socket);
706
+ if (idx !== -1)
707
+ this.sockets[name].splice(idx, 1);
708
+ }
709
+ callback(err, null);
710
+ });
711
+ socket.connect(connectOptions);
712
+ if (!this.sockets[name])
713
+ this.sockets[name] = [];
714
+ this.sockets[name].push(socket);
715
+ return socket;
716
+ }
717
+ releaseSocket(socket, options) {
718
+ const name = this.getName(options);
719
+ // Remove from active sockets
720
+ if (this.sockets[name]) {
721
+ const idx = this.sockets[name].indexOf(socket);
722
+ if (idx !== -1)
723
+ this.sockets[name].splice(idx, 1);
724
+ if (this.sockets[name].length === 0)
725
+ delete this.sockets[name];
726
+ }
727
+ const onClose = () => {
728
+ debugLog(`Agent: socket closed while in pool, removing from ${name}`);
729
+ this._removeSocket(socket, name);
730
+ };
731
+ socket.once('close', onClose);
732
+ socket.once('error', onClose);
733
+ socket._agentOnClose = onClose;
734
+ // Check if there are pending requests - ALWAYS reuse if something is waiting
735
+ if (this.requests[name] && this.requests[name].length > 0) {
736
+ const req = this.requests[name].shift();
737
+ if (this.requests[name].length === 0)
738
+ delete this.requests[name];
739
+ if (!this.sockets[name])
740
+ this.sockets[name] = [];
741
+ this.sockets[name].push(socket);
742
+ this.reuseSocket(socket, req);
743
+ return;
744
+ }
745
+ if (this.keepAlive && this.keepSocketAlive(socket)) {
746
+ // Return to free pool
747
+ if (!this.freeSockets[name])
748
+ this.freeSockets[name] = [];
749
+ if (this.freeSockets[name].length < this.maxFreeSockets) {
750
+ this.freeSockets[name].push(socket);
751
+ }
752
+ else {
753
+ this._totalSockets--;
754
+ socket.end();
755
+ }
756
+ }
757
+ else {
758
+ this._totalSockets--;
759
+ socket.end();
760
+ }
761
+ }
762
+ keepSocketAlive(_socket) {
763
+ return true;
764
+ }
765
+ reuseSocket(socket, req) {
766
+ debugLog(`Agent.reuseSocket: reusing socket for ${req.method} ${req.path}`);
767
+ // Remove agent listeners before reusing
768
+ if (socket._agentOnClose) {
769
+ socket.removeListener('close', socket._agentOnClose);
770
+ socket.removeListener('error', socket._agentOnClose);
771
+ delete socket._agentOnClose;
772
+ }
773
+ req.onSocket(socket);
774
+ }
775
+ _removeSocket(socket, name) {
776
+ if (this.sockets[name]) {
777
+ const idx = this.sockets[name].indexOf(socket);
778
+ if (idx !== -1) {
779
+ this.sockets[name].splice(idx, 1);
780
+ this._totalSockets--;
781
+ }
782
+ }
783
+ if (this.freeSockets[name]) {
784
+ const idx = this.freeSockets[name].indexOf(socket);
785
+ if (idx !== -1) {
786
+ this.freeSockets[name].splice(idx, 1);
787
+ this._totalSockets--;
788
+ }
789
+ }
790
+ }
791
+ destroy() {
792
+ for (const name in this.sockets) {
793
+ for (const socket of this.sockets[name]) {
794
+ socket.destroy();
795
+ }
796
+ }
797
+ for (const name in this.freeSockets) {
798
+ for (const socket of this.freeSockets[name]) {
799
+ socket.destroy();
800
+ }
801
+ }
802
+ }
803
+ }
804
+ export const globalAgent = new Agent();
805
+ export class ClientRequest extends OutgoingMessage {
806
+ constructor(options, callback) {
807
+ super();
808
+ this._connected = false;
809
+ this._pendingWrites = [];
810
+ this._ended = false;
811
+ this._expectContinue = false;
812
+ this._continueReceived = false;
813
+ this._options = options;
814
+ this.method = options.method || 'GET';
815
+ this.path = options.path || '/';
816
+ this.host = options.hostname || options.host || 'localhost';
817
+ if (['GET', 'HEAD'].includes(this.method.toUpperCase())) {
818
+ this._hasBody = false;
819
+ }
820
+ if (options.headers) {
821
+ for (const key in options.headers) {
822
+ this.setHeader(key, options.headers[key]);
823
+ }
824
+ }
825
+ if (callback) {
826
+ this.once('response', callback);
827
+ }
828
+ const expect = this.getHeader('expect');
829
+ if (expect && typeof expect === 'string' && expect.toLowerCase() === '100-continue') {
830
+ this._expectContinue = true;
831
+ }
832
+ if (options.timeout) {
833
+ this.setTimeout(options.timeout);
834
+ }
835
+ const agent = options.agent === false ? new Agent() : (options.agent instanceof Agent ? options.agent : globalAgent);
836
+ // Use setImmediate or setTimeout for React Native compatibility
837
+ const nextTick = typeof setImmediate !== 'undefined' ? setImmediate : (fn) => setTimeout(fn, 0);
838
+ nextTick(() => {
839
+ debugLog(`ClientRequest: nextTick fired for ${this.method} ${this.host}${this.path}`);
840
+ agent.addRequest(this, this._options);
841
+ });
842
+ }
843
+ /** @internal */
844
+ onSocket(socket) {
845
+ if (socket) {
846
+ this.socket = socket;
847
+ this._connected = true;
848
+ this.emit('socket', this.socket);
849
+ this._sendRequest();
850
+ this._flushPendingWrites();
851
+ this._attachSocketListeners();
852
+ }
853
+ else {
854
+ this._connect();
855
+ }
856
+ }
857
+ _connect() {
858
+ const agent = this._options.agent === false ? new Agent() : (this._options.agent instanceof Agent ? this._options.agent : globalAgent);
859
+ const connectCallback = (err, socket) => {
860
+ if (err) {
861
+ debugLog(`ClientRequest._connect: ERROR: ${err.message}`);
862
+ this.emit('error', err);
863
+ return;
864
+ }
865
+ debugLog(`ClientRequest._connect: Socket connected! socket=${!!socket}, socket._driver=${!!socket._driver}`);
866
+ console.log(`[HTTP] _connect: Socket connected!`);
867
+ this.socket = socket;
868
+ this._connected = true;
869
+ this.emit('socket', this.socket);
870
+ debugLog(`ClientRequest._connect: Calling _sendRequest`);
871
+ this._sendRequest();
872
+ this._flushPendingWrites();
873
+ this._attachSocketListeners();
874
+ };
875
+ this.socket = agent.createConnection(this._options, connectCallback);
876
+ }
877
+ _attachSocketListeners() {
878
+ if (!this.socket)
879
+ return;
880
+ const parser = Driver.createHttpParser(1); // 1 = Response mode
881
+ const onData = (data) => {
882
+ const handleParsedResult = (result) => {
883
+ if (result.startsWith('ERROR:')) {
884
+ this.emit('error', new Error(result));
885
+ this.socket.destroy();
886
+ return;
887
+ }
888
+ const parsed = JSON.parse(result);
889
+ console.log(`[HTTP] _connect: Parser result: ${parsed.is_headers ? 'HEADERS' : 'DATA'}${parsed.complete ? ' (COMPLETE)' : ''}`);
890
+ if (parsed.is_headers) {
891
+ const status = parsed.status || 0;
892
+ if (status >= 100 && status < 200 && status !== 101) {
893
+ const info = {
894
+ httpVersion: '1.' + parsed.version,
895
+ httpVersionMajor: 1,
896
+ httpVersionMinor: parsed.version,
897
+ statusCode: status,
898
+ statusMessage: STATUS_CODES[status] || '',
899
+ headers: parsed.headers,
900
+ rawHeaders: []
901
+ };
902
+ if (status === 100) {
903
+ this._continueReceived = true;
904
+ this.emit('continue');
905
+ this._flushPendingWrites();
906
+ }
907
+ else {
908
+ this.emit('information', info);
909
+ }
910
+ return;
911
+ }
912
+ this._res = new IncomingMessage(this.socket);
913
+ this._res.statusCode = status;
914
+ this._res.httpVersion = '1.' + parsed.version;
915
+ this._res.headers = parsed.headers;
916
+ if (status === 101) {
917
+ debugLog(`ClientRequest: 101 Switching Protocols received, detaching parser`);
918
+ this.socket.removeListener('data', onData);
919
+ this.socket.removeListener('error', onError);
920
+ this.emit('upgrade', this._res, this.socket, Buffer.alloc(0));
921
+ return;
922
+ }
923
+ // Handle CONNECT method response (HTTP Tunneling)
924
+ if (this.method.toUpperCase() === 'CONNECT' && status >= 200 && status < 300) {
925
+ debugLog(`ClientRequest: CONNECT tunnel established (status=${status}), emitting 'connect' event`);
926
+ this.socket.removeListener('data', onData);
927
+ this.socket.removeListener('error', onError);
928
+ this.emit('connect', this._res, this.socket, Buffer.alloc(0));
929
+ return;
930
+ }
931
+ this.emit('response', this._res);
932
+ }
933
+ if (this._res && parsed.body && parsed.body.length > 0) {
934
+ this._res.push(Buffer.from(parsed.body));
935
+ }
936
+ if (this._res && parsed.complete) {
937
+ this._res.complete = true;
938
+ if (parsed.trailers) {
939
+ this._res.trailers = parsed.trailers;
940
+ }
941
+ this._res.push(null);
942
+ this._finishResponse();
943
+ }
944
+ };
945
+ let input = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
946
+ let iterations = 0;
947
+ const maxIterations = 100; // Safety limit
948
+ while (iterations < maxIterations) {
949
+ iterations++;
950
+ const result = parser.feed(input);
951
+ if (!result || result === '' || result.startsWith('ERROR:')) {
952
+ // Empty result (partial) or error - exit loop
953
+ if (result && result.startsWith('ERROR:')) {
954
+ console.log(`[HTTP] ClientRequest: Parser error: ${result}`);
955
+ }
956
+ break;
957
+ }
958
+ handleParsedResult(result);
959
+ input = new ArrayBuffer(0); // Continue with empty input to drain Rust buffer
960
+ }
961
+ };
962
+ const onError = (err) => {
963
+ console.log(`[HTTP] _connect: Socket error: ${err.message}`);
964
+ this.emit('error', err);
965
+ this._cleanupSocket();
966
+ };
967
+ const onClose = () => {
968
+ console.log(`[HTTP] _connect: Socket closed`);
969
+ if (this._res && !this._res.readableEnded)
970
+ this._res.push(null);
971
+ this.emit('close');
972
+ this._cleanupSocket();
973
+ };
974
+ this.socket.on('data', onData);
975
+ this.socket.on('error', onError);
976
+ this.socket.on('close', onClose);
977
+ this._socketCleanup = () => {
978
+ this.socket?.removeListener('data', onData);
979
+ this.socket?.removeListener('error', onError);
980
+ this.socket?.removeListener('close', onClose);
981
+ };
982
+ }
983
+ _cleanupSocket() {
984
+ if (this._socketCleanup)
985
+ this._socketCleanup();
986
+ this._socketCleanup = undefined;
987
+ this.socket = null;
988
+ this._connected = false;
989
+ }
990
+ _finishResponse() {
991
+ // Release socket back to agent
992
+ const agent = this._options.agent === false ? new Agent() : (this._options.agent instanceof Agent ? this._options.agent : globalAgent);
993
+ const socket = this.socket;
994
+ this._cleanupSocket();
995
+ if (socket)
996
+ agent.releaseSocket(socket, this._options);
997
+ }
998
+ _flushPendingWrites() {
999
+ if (!this.socket)
1000
+ return;
1001
+ if (!this.headersSent)
1002
+ this._sendRequest();
1003
+ // If we are waiting for 100-continue, don't flush yet
1004
+ if (this._expectContinue && !this._continueReceived) {
1005
+ return;
1006
+ }
1007
+ const writes = this._pendingWrites;
1008
+ this._pendingWrites = [];
1009
+ for (const pending of writes) {
1010
+ this._write(pending.chunk, pending.encoding, pending.callback);
1011
+ }
1012
+ if (this._ended) {
1013
+ this._finishRequest();
1014
+ }
1015
+ }
1016
+ _finishRequest() {
1017
+ if (!this._ended)
1018
+ return;
1019
+ super.end();
1020
+ }
1021
+ _sendRequest() {
1022
+ debugLog(`ClientRequest._sendRequest: headersSent=${this.headersSent}, socket=${!!this.socket}`);
1023
+ if (this.headersSent)
1024
+ return;
1025
+ if (!this.hasHeader('host')) {
1026
+ this.setHeader('Host', this.host);
1027
+ }
1028
+ const firstLine = `${this.method} ${this.path} HTTP/1.1`;
1029
+ debugLog(`ClientRequest._sendRequest: sending firstLine=${firstLine}`);
1030
+ this._sendHeaders(firstLine);
1031
+ }
1032
+ _write(chunk, encoding, callback) {
1033
+ if (!this._connected) {
1034
+ this._pendingWrites.push({ chunk, encoding, callback });
1035
+ return;
1036
+ }
1037
+ if (!this.headersSent)
1038
+ this._sendRequest();
1039
+ super._write(chunk, encoding, callback);
1040
+ }
1041
+ write(chunk, encoding, callback) {
1042
+ if (!this._connected) {
1043
+ this._pendingWrites.push({ chunk, encoding, callback });
1044
+ return true;
1045
+ }
1046
+ if (!this.headersSent)
1047
+ this._sendRequest();
1048
+ return super.write(chunk, encoding, callback);
1049
+ }
1050
+ end(chunk, encoding, callback) {
1051
+ debugLog(`ClientRequest.end() called, connected=${this._connected}, headersSent=${this.headersSent}`);
1052
+ if (chunk) {
1053
+ this.write(chunk, encoding);
1054
+ }
1055
+ this._ended = true;
1056
+ // If connected, we can send request and end immediately
1057
+ if (this._connected) {
1058
+ if (!this.headersSent) {
1059
+ this._sendRequest();
1060
+ }
1061
+ // Call super.end only when connected
1062
+ super.end(undefined, undefined, callback);
1063
+ }
1064
+ else {
1065
+ // Socket not connected yet - _flushPendingWrites will handle ending
1066
+ // Store callback if provided
1067
+ if (callback) {
1068
+ this.once('finish', callback);
1069
+ }
1070
+ }
1071
+ return this;
1072
+ }
1073
+ abort() {
1074
+ if (this.aborted)
1075
+ return;
1076
+ this.aborted = true;
1077
+ this.emit('abort');
1078
+ this.destroy();
1079
+ }
1080
+ flushHeaders() {
1081
+ if (this._connected && !this.headersSent) {
1082
+ this._sendRequest();
1083
+ }
1084
+ }
1085
+ }
1086
+ export function createServer(optionsOrListener, requestListener) {
1087
+ return new Server(optionsOrListener, requestListener);
1088
+ }
1089
+ export function request(urlOrOptions, optionsOrCallback, callback) {
1090
+ let opts = {};
1091
+ let cb = callback;
1092
+ if (typeof urlOrOptions === 'string') {
1093
+ const url = new URL(urlOrOptions);
1094
+ opts = {
1095
+ protocol: url.protocol,
1096
+ hostname: url.hostname,
1097
+ path: url.pathname + url.search,
1098
+ port: url.port ? parseInt(url.port) : undefined
1099
+ };
1100
+ }
1101
+ else if (urlOrOptions instanceof URL) {
1102
+ opts = {
1103
+ protocol: urlOrOptions.protocol,
1104
+ hostname: urlOrOptions.hostname,
1105
+ path: urlOrOptions.pathname + urlOrOptions.search,
1106
+ port: urlOrOptions.port ? parseInt(urlOrOptions.port) : undefined
1107
+ };
1108
+ }
1109
+ else {
1110
+ opts = urlOrOptions;
1111
+ }
1112
+ // Handle (url, options, callback) or (url, callback) signatures
1113
+ if (typeof optionsOrCallback === 'function') {
1114
+ cb = optionsOrCallback;
1115
+ }
1116
+ else if (optionsOrCallback) {
1117
+ // Merge options
1118
+ opts = { ...opts, ...optionsOrCallback };
1119
+ }
1120
+ return new ClientRequest(opts, cb);
1121
+ }
1122
+ export function get(urlOrOptions, optionsOrCallback, callback) {
1123
+ const req = request(urlOrOptions, optionsOrCallback, callback);
1124
+ req.end();
1125
+ return req;
1126
+ }