Skip to main content
Glama
http-client.js13.3 kB
/** * HTTP客户端工具模块 * 提供HTTP请求封装和代理支持 */ import axios from 'axios'; import { logger } from './logger.js'; /** * HTTP客户端配置 */ class HttpClientConfig { constructor() { this.timeout = 30000; // 30秒 this.maxRetries = 3; this.retryDelay = 1000; // 1秒 this.retryMultiplier = 2; this.maxRedirects = 5; this.validateStatus = (status) => status >= 200 && status < 300; this.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; this.enableLogging = true; this.enableRetry = true; this.enableProxy = false; this.proxy = null; this.headers = {}; } } /** * HTTP客户端类 */ export class HttpClient { constructor(config = new HttpClientConfig()) { this.config = config; this.axiosInstance = null; this.interceptors = { request: [], response: [] }; this.retryCount = new Map(); // 用于跟踪每个请求的重试次数 this._initializeAxios(); this._setupInterceptors(); } /** * 初始化Axios实例 * @private */ _initializeAxios() { const axiosConfig = { timeout: this.config.timeout, maxRedirects: this.config.maxRedirects, validateStatus: this.config.validateStatus, headers: { 'User-Agent': this.config.userAgent, ...this.config.headers } }; // 配置代理 if (this.config.enableProxy && this.config.proxy) { axiosConfig.proxy = this.config.proxy; } this.axiosInstance = axios.create(axiosConfig); } /** * 设置拦截器 * @private */ _setupInterceptors() { // 请求拦截器 this.axiosInstance.interceptors.request.use( (config) => { // 执行自定义请求拦截器 this.interceptors.request.forEach(interceptor => { config = interceptor(config); }); if (this.config.enableLogging) { logger.debug('HTTP请求', { method: config.method?.toUpperCase(), url: config.url, headers: config.headers, data: config.data, timeout: config.timeout }); } return config; }, (error) => { if (this.config.enableLogging) { logger.error('HTTP请求拦截器错误', error); } return Promise.reject(error); } ); // 响应拦截器 this.axiosInstance.interceptors.response.use( (response) => { if (this.config.enableLogging) { logger.debug('HTTP响应', { status: response.status, statusText: response.statusText, headers: response.headers, data: response.data, url: response.config.url }); } // 执行自定义响应拦截器 this.interceptors.response.forEach(interceptor => { response = interceptor(response); }); return response; }, (error) => { if (this.config.enableLogging) { logger.error('HTTP响应错误', { message: error.message, code: error.code, config: error.config, response: error.response?.data }); } return Promise.reject(error); } ); } /** * 生成请求ID * @private * @returns {string} 请求ID */ _generateRequestId() { return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } /** * 计算重试延迟 * @private * @param {number} attempt - 尝试次数 * @returns {number} 延迟时间(毫秒) */ _calculateRetryDelay(attempt) { return this.config.retryDelay * Math.pow(this.config.retryMultiplier, attempt - 1); } /** * 是否应该重试 * @private * @param {Error} error - 错误对象 * @param {number} attempt - 尝试次数 * @returns {boolean} 是否应该重试 */ _shouldRetry(error, attempt) { if (!this.config.enableRetry || attempt >= this.config.maxRetries) { return false; } // 可重试的错误类型 const retryableErrors = [ 'ECONNRESET', 'ENOTFOUND', 'ECONNREFUSED', 'ETIMEDOUT', 'EPIPE', 'EHOSTUNREACH', 'EAI_AGAIN' ]; // 可重试的HTTP状态码 const retryableStatusCodes = [408, 429, 500, 502, 503, 504]; // 检查错误代码 if (error.code && retryableErrors.includes(error.code)) { return true; } // 检查HTTP状态码 if (error.response && retryableStatusCodes.includes(error.response.status)) { return true; } // 网络错误 if (error.message && error.message.toLowerCase().includes('network')) { return true; } return false; } /** * 执行重试逻辑 * @private * @param {Function} requestFunc - 请求函数 * @param {number} attempt - 尝试次数 * @param {string} requestId - 请求ID * @returns {Promise} 响应结果 */ async _executeWithRetry(requestFunc, attempt = 0, requestId = null) { const id = requestId || this._generateRequestId(); try { return await requestFunc(); } catch (error) { if (this._shouldRetry(error, attempt)) { const delay = this._calculateRetryDelay(attempt + 1); if (this.config.enableLogging) { logger.warn(`请求重试 ${attempt + 1}/${this.config.maxRetries}`, { requestId: id, error: error.message, retryDelay: delay }); } await this._sleep(delay); return this._executeWithRetry(requestFunc, attempt + 1, id); } throw error; } } /** * 睡眠函数 * @private * @param {number} ms - 毫秒数 * @returns {Promise} Promise */ _sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * GET请求 * @param {string} url - URL * @param {Object} config - 配置 * @returns {Promise} 响应结果 */ async get(url, config = {}) { const requestFunc = () => this.axiosInstance.get(url, config); return this._executeWithRetry(requestFunc); } /** * POST请求 * @param {string} url - URL * @param {*} data - 请求数据 * @param {Object} config - 配置 * @returns {Promise} 响应结果 */ async post(url, data, config = {}) { const requestFunc = () => this.axiosInstance.post(url, data, config); return this._executeWithRetry(requestFunc); } /** * PUT请求 * @param {string} url - URL * @param {*} data - 请求数据 * @param {Object} config - 配置 * @returns {Promise} 响应结果 */ async put(url, data, config = {}) { const requestFunc = () => this.axiosInstance.put(url, data, config); return this._executeWithRetry(requestFunc); } /** * DELETE请求 * @param {string} url - URL * @param {Object} config - 配置 * @returns {Promise} 响应结果 */ async delete(url, config = {}) { const requestFunc = () => this.axiosInstance.delete(url, config); return this._executeWithRetry(requestFunc); } /** * PATCH请求 * @param {string} url - URL * @param {*} data - 请求数据 * @param {Object} config - 配置 * @returns {Promise} 响应结果 */ async patch(url, data, config = {}) { const requestFunc = () => this.axiosInstance.patch(url, data, config); return this._executeWithRetry(requestFunc); } /** * HEAD请求 * @param {string} url - URL * @param {Object} config - 配置 * @returns {Promise} 响应结果 */ async head(url, config = {}) { const requestFunc = () => this.axiosInstance.head(url, config); return this._executeWithRetry(requestFunc); } /** * OPTIONS请求 * @param {string} url - URL * @param {Object} config - 配置 * @returns {Promise} 响应结果 */ async options(url, config = {}) { const requestFunc = () => this.axiosInstance.options(url, config); return this._executeWithRetry(requestFunc); } /** * 下载文件 * @param {string} url - 文件URL * @param {string} filePath - 保存路径 * @param {Object} config - 配置 * @returns {Promise} 响应结果 */ async download(url, filePath, config = {}) { const response = await this.get(url, { ...config, responseType: 'stream' }); const fs = await import('fs'); const writer = fs.createWriteStream(filePath); response.data.pipe(writer); return new Promise((resolve, reject) => { writer.on('finish', resolve); writer.on('error', reject); }); } /** * 上传文件 * @param {string} url - 上传URL * @param {string|Buffer|Stream} file - 文件 * @param {Object} config - 配置 * @returns {Promise} 响应结果 */ async upload(url, file, config = {}) { const FormData = (await import('form-data')).default; const formData = new FormData(); if (typeof file === 'string') { // 文件路径 const fs = await import('fs'); formData.append('file', fs.createReadStream(file)); } else { // Buffer或Stream formData.append('file', file); } return this.post(url, formData, { ...config, headers: { ...formData.getHeaders(), ...config.headers } }); } /** * 添加请求拦截器 * @param {Function} interceptor - 拦截器函数 */ addRequestInterceptor(interceptor) { this.interceptors.request.push(interceptor); } /** * 添加响应拦截器 * @param {Function} interceptor - 拦截器函数 */ addResponseInterceptor(interceptor) { this.interceptors.response.push(interceptor); } /** * 设置代理 * @param {Object} proxy - 代理配置 */ setProxy(proxy) { this.config.proxy = proxy; this.config.enableProxy = true; // 重新初始化Axios实例 this._initializeAxios(); this._setupInterceptors(); } /** * 清除代理 */ clearProxy() { this.config.proxy = null; this.config.enableProxy = false; // 重新初始化Axios实例 this._initializeAxios(); this._setupInterceptors(); } /** * 设置请求头 * @param {Object} headers - 请求头 */ setHeaders(headers) { this.config.headers = { ...this.config.headers, ...headers }; // 重新初始化Axios实例 this._initializeAxios(); this._setupInterceptors(); } /** * 获取当前配置 * @returns {HttpClientConfig} 配置对象 */ getConfig() { return { ...this.config }; } /** * 更新配置 * @param {Object} config - 配置对象 */ updateConfig(config) { this.config = { ...this.config, ...config }; // 重新初始化Axios实例 this._initializeAxios(); this._setupInterceptors(); } /** * 关闭客户端 */ close() { if (this.axiosInstance) { // 这里可以添加清理逻辑 this.axiosInstance = null; } } } /** * 创建HTTP客户端实例 * @param {Object} config - 配置对象 * @returns {HttpClient} HTTP客户端实例 */ export function createHttpClient(config = {}) { const clientConfig = new HttpClientConfig(); Object.assign(clientConfig, config); return new HttpClient(clientConfig); } /** * 默认的HTTP客户端实例 */ let defaultClient = null; /** * 获取默认的HTTP客户端 * @returns {HttpClient} HTTP客户端实例 */ export function getDefaultClient() { if (!defaultClient) { defaultClient = createHttpClient(); } return defaultClient; } /** * 快速GET请求 * @param {string} url - URL * @param {Object} config - 配置 * @returns {Promise} 响应结果 */ export async function get(url, config = {}) { const client = getDefaultClient(); return client.get(url, config); } /** * 快速POST请求 * @param {string} url - URL * @param {*} data - 请求数据 * @param {Object} config - 配置 * @returns {Promise} 响应结果 */ export async function post(url, data, config = {}) { const client = getDefaultClient(); return client.post(url, data, config); } /** * 快速PUT请求 * @param {string} url - URL * @param {*} data - 请求数据 * @param {Object} config - 配置 * @returns {Promise} 响应结果 */ export async function put(url, data, config = {}) { const client = getDefaultClient(); return client.put(url, data, config); } /** * 快速DELETE请求 * @param {string} url - URL * @param {Object} config - 配置 * @returns {Promise} 响应结果 */ export async function del(url, config = {}) { const client = getDefaultClient(); return client.delete(url, config); } /** * 快速PATCH请求 * @param {string} url - URL * @param {*} data - 请求数据 * @param {Object} config - 配置 * @returns {Promise} 响应结果 */ export async function patch(url, data, config = {}) { const client = getDefaultClient(); return client.patch(url, data, config); } export default { HttpClient, HttpClientConfig, createHttpClient, getDefaultClient, get, post, put, delete: del, patch };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/billyangbc/xiaohongshu-mcp-nodejs'

If you have feedback or need assistance with the MCP directory API, please join our Discord server