163 lines
5.0 KiB
TypeScript
163 lines
5.0 KiB
TypeScript
import * as plugins from './plugins.js';
|
|
import * as types from './types.js';
|
|
import { CoreResponse } from './response.js';
|
|
import { CoreRequest as AbstractCoreRequest } from '../core_base/request.js';
|
|
|
|
// Keep-alive agents for connection pooling
|
|
const httpAgent = new plugins.agentkeepalive.HttpAgent({
|
|
keepAlive: true,
|
|
maxFreeSockets: 10,
|
|
maxSockets: 100,
|
|
maxTotalSockets: 1000,
|
|
});
|
|
|
|
const httpAgentKeepAliveFalse = new plugins.agentkeepalive.HttpAgent({
|
|
keepAlive: false,
|
|
});
|
|
|
|
const httpsAgent = new plugins.agentkeepalive.HttpsAgent({
|
|
keepAlive: true,
|
|
maxFreeSockets: 10,
|
|
maxSockets: 100,
|
|
maxTotalSockets: 1000,
|
|
});
|
|
|
|
const httpsAgentKeepAliveFalse = new plugins.agentkeepalive.HttpsAgent({
|
|
keepAlive: false,
|
|
});
|
|
|
|
/**
|
|
* Node.js implementation of Core Request class that handles all HTTP/HTTPS requests
|
|
*/
|
|
export class CoreRequest extends AbstractCoreRequest<types.ICoreRequestOptions, CoreResponse> {
|
|
private requestDataFunc: ((req: plugins.http.ClientRequest) => void) | null;
|
|
|
|
constructor(
|
|
url: string,
|
|
options: types.ICoreRequestOptions = {},
|
|
requestDataFunc: ((req: plugins.http.ClientRequest) => void) | null = null
|
|
) {
|
|
super(url, options);
|
|
this.requestDataFunc = requestDataFunc;
|
|
}
|
|
|
|
/**
|
|
* Fire the request and return a CoreResponse
|
|
*/
|
|
async fire(): Promise<CoreResponse> {
|
|
const incomingMessage = await this.fireCore();
|
|
return new CoreResponse(incomingMessage, this.url);
|
|
}
|
|
|
|
/**
|
|
* Fire the request and return the raw IncomingMessage
|
|
*/
|
|
async fireCore(): Promise<plugins.http.IncomingMessage> {
|
|
const done = plugins.smartpromise.defer<plugins.http.IncomingMessage>();
|
|
|
|
// Parse URL
|
|
const parsedUrl = plugins.smarturl.Smarturl.createFromUrl(this.url, {
|
|
searchParams: this.options.queryParams || {},
|
|
});
|
|
|
|
this.options.hostname = parsedUrl.hostname;
|
|
if (parsedUrl.port) {
|
|
this.options.port = parseInt(parsedUrl.port, 10);
|
|
}
|
|
this.options.path = parsedUrl.path;
|
|
|
|
// Handle unix socket URLs
|
|
if (CoreRequest.isUnixSocket(this.url)) {
|
|
const { socketPath, path } = CoreRequest.parseUnixSocketUrl(this.options.path);
|
|
this.options.socketPath = socketPath;
|
|
this.options.path = path;
|
|
}
|
|
|
|
// Determine agent based on protocol and keep-alive setting
|
|
if (!this.options.agent) {
|
|
// Only use keep-alive agents if explicitly requested
|
|
if (this.options.keepAlive === true) {
|
|
this.options.agent = parsedUrl.protocol === 'https:' ? httpsAgent : httpAgent;
|
|
} else if (this.options.keepAlive === false) {
|
|
this.options.agent = parsedUrl.protocol === 'https:' ? httpsAgentKeepAliveFalse : httpAgentKeepAliveFalse;
|
|
}
|
|
// If keepAlive is undefined, don't set any agent (more fetch-like behavior)
|
|
}
|
|
|
|
// Determine request module
|
|
const requestModule = parsedUrl.protocol === 'https:' ? plugins.https : plugins.http;
|
|
|
|
if (!requestModule) {
|
|
throw new Error(`The request to ${this.url} is missing a viable protocol. Must be http or https`);
|
|
}
|
|
|
|
// Perform the request
|
|
const request = requestModule.request(this.options, async (response) => {
|
|
// Handle hard timeout
|
|
if (this.options.hardDataCuttingTimeout) {
|
|
setTimeout(() => {
|
|
response.destroy();
|
|
done.reject(new Error('Request timed out'));
|
|
}, this.options.hardDataCuttingTimeout);
|
|
}
|
|
|
|
// Always return the raw stream
|
|
done.resolve(response);
|
|
});
|
|
|
|
// Write request body
|
|
if (this.options.requestBody) {
|
|
if (this.options.requestBody instanceof plugins.formData) {
|
|
this.options.requestBody.pipe(request).on('finish', () => {
|
|
request.end();
|
|
});
|
|
} else {
|
|
// Write body as-is - caller is responsible for serialization
|
|
const bodyData = typeof this.options.requestBody === 'string'
|
|
? this.options.requestBody
|
|
: this.options.requestBody instanceof Buffer
|
|
? this.options.requestBody
|
|
: JSON.stringify(this.options.requestBody); // Still stringify for backward compatibility
|
|
request.write(bodyData);
|
|
request.end();
|
|
}
|
|
} else if (this.requestDataFunc) {
|
|
this.requestDataFunc(request);
|
|
} else {
|
|
request.end();
|
|
}
|
|
|
|
// Handle request errors
|
|
request.on('error', (e) => {
|
|
console.error(e);
|
|
request.destroy();
|
|
done.reject(e);
|
|
});
|
|
|
|
// Get response and handle response errors
|
|
const response = await done.promise;
|
|
response.on('error', (err) => {
|
|
console.error(err);
|
|
response.destroy();
|
|
});
|
|
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Static factory method to create and fire a request
|
|
*/
|
|
static async create(
|
|
url: string,
|
|
options: types.ICoreRequestOptions = {}
|
|
): Promise<CoreResponse> {
|
|
const request = new CoreRequest(url, options);
|
|
return request.fire();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convenience exports for backward compatibility
|
|
*/
|
|
export const isUnixSocket = CoreRequest.isUnixSocket;
|
|
export const parseUnixSocketUrl = CoreRequest.parseUnixSocketUrl; |