207 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			207 lines
		
	
	
		
			5.9 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;
 | 
						|
 | 
						|
    // Check for unsupported fetch-specific options
 | 
						|
    if (
 | 
						|
      options.credentials ||
 | 
						|
      options.mode ||
 | 
						|
      options.cache ||
 | 
						|
      options.redirect ||
 | 
						|
      options.referrer ||
 | 
						|
      options.referrerPolicy ||
 | 
						|
      options.integrity
 | 
						|
    ) {
 | 
						|
      throw new Error(
 | 
						|
        'Fetch API specific options (credentials, mode, cache, redirect, referrer, referrerPolicy, integrity) are not supported in Node.js implementation',
 | 
						|
      );
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Fire the request and return a CoreResponse
 | 
						|
   */
 | 
						|
  async fire(): Promise<CoreResponse> {
 | 
						|
    const incomingMessage = await this.fireCore();
 | 
						|
    return new CoreResponse(incomingMessage, this.url, this.options);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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
 | 
						|
    let timeoutId: NodeJS.Timeout | null = null;
 | 
						|
    const request = requestModule.request(this.options, async (response) => {
 | 
						|
      // Handle hard timeout
 | 
						|
      if (this.options.hardDataCuttingTimeout) {
 | 
						|
        timeoutId = setTimeout(() => {
 | 
						|
          response.destroy();
 | 
						|
          done.reject(new Error('Request timed out'));
 | 
						|
        }, this.options.hardDataCuttingTimeout);
 | 
						|
      }
 | 
						|
 | 
						|
      // Always return the raw stream
 | 
						|
      done.resolve(response);
 | 
						|
    });
 | 
						|
 | 
						|
    // Set request timeout (Node.js built-in timeout)
 | 
						|
    if (this.options.timeout) {
 | 
						|
      request.setTimeout(this.options.timeout, () => {
 | 
						|
        request.destroy();
 | 
						|
        done.reject(new Error('Request timed out'));
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    // 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();
 | 
						|
      // Clear timeout on error
 | 
						|
      if (timeoutId) {
 | 
						|
        clearTimeout(timeoutId);
 | 
						|
        timeoutId = null;
 | 
						|
      }
 | 
						|
      done.reject(e);
 | 
						|
    });
 | 
						|
 | 
						|
    // Get response and handle response errors
 | 
						|
    const response = await done.promise;
 | 
						|
    
 | 
						|
    // Clear timeout on successful response
 | 
						|
    if (timeoutId) {
 | 
						|
      clearTimeout(timeoutId);
 | 
						|
      timeoutId = null;
 | 
						|
    }
 | 
						|
    
 | 
						|
    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();
 | 
						|
  }
 | 
						|
}
 |