import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; import { Logger } from './Logger'; const logger = Logger.getInstance().getLogger(); /** * A robust HTTP client wrapper built on Axios, providing features like * custom user-agent, proxy support, and centralized error handling. */ export class HttpClient { private client: AxiosInstance; private userAgent: string; /** * Initializes the HttpClient. * @param proxy Optional proxy URL (e.g., "http://user:pass@host:port"). * @param userAgent Optional custom User-Agent string. */ constructor(proxy?: string, userAgent?: string) { this.userAgent = userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 XbibzScraper/1.0.0'; const config: AxiosRequestConfig = { timeout: 30000, // Default timeout of 30 seconds headers: { 'User-Agent': this.userAgent, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Accept-Language': 'en-US,en;q=0.5', 'Connection': 'keep-alive', }, }; if (proxy) { // Use Axios built-in proxy support config.proxy = { protocol: new URL(proxy).protocol.replace(':', ''), host: new URL(proxy).hostname, port: parseInt(new URL(proxy).port), auth: { username: new URL(proxy).username, password: new URL(proxy).password, } } logger.info(`HttpClient configured with proxy: ${proxy}`); } this.client = axios.create(config); } /** * Performs a GET request. * @param url The URL to request. * @param config Optional Axios request configuration. * @returns A promise that resolves to the Axios response. */ public async get(url: string, config?: AxiosRequestConfig): Promise> { logger.debug(`GET request to: ${url}`); try { const response = await this.client.get(url, config); logger.debug({ status: response.status }, `GET request successful for: ${url}`); return response; } catch (error) { this.handleAxiosError(error); throw error; // Re-throw the error after logging/handling } } /** * Performs a POST request. * @param url The URL to request. * @param data The data to post. * @param config Optional Axios request configuration. * @returns A promise that resolves to the Axios response. */ public async post(url: string, data?: any, config?: AxiosRequestConfig): Promise> { logger.debug(`POST request to: ${url}`); try { const response = await this.client.post(url, data, config); logger.debug({ status: response.status }, `POST request successful for: ${url}`); return response; } catch (error) { this.handleAxiosError(error); throw error; } } /** * Centralized error handling for Axios errors. * @param error The error object. */ private handleAxiosError(error: any): void { if (axios.isAxiosError(error)) { const config: any = error.config || {}; const logDetails = { url: config.url, method: config.method, }; if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx logger.error({ ...logDetails, status: error.response.status, data: error.response.data, }, 'HTTP Error Response'); } else if (error.request) { // The request was made but no response was received logger.error(logDetails, 'No response received from server'); } else { // Something happened in setting up the request that triggered an Error logger.error({ message: error.message }, 'Request setup error'); } } else { logger.error({ error: error.message || error }, 'Non-Axios error during HTTP request'); } } }