sh-axios
v1.0.8
Published
A flexible, extensible HTTP client library with adapter support, retry strategy, and error handling
Downloads
41
Maintainers
Readme
sh-axios
一个灵活、可扩展的 HTTP 客户端库,支持多种适配器、重试策略和错误处理。
特性
- ✅ 多种适配器支持:默认、curl-cffi-proxy、tls-bridge 等
- ✅ 可配置的重试策略:支持自定义重试逻辑和不可重试错误码
- ✅ 完善的错误处理:统一的错误类型和详细的错误信息
- ✅ 代理和会话支持:支持代理配置和 TLS 会话复用
- ✅ 流式上传/下载:支持大文件流式传输
- ✅ TypeScript 支持:完整的类型定义
- ✅ 易于扩展:可以轻松添加新的适配器
安装
# 安装库和 peer dependencies
npm install sh-axios axios
# 或
yarn add sh-axios axios依赖版本:
axios:^1.7.0(peer dependency,必须安装)node-tls-client:^2.1.0(dependency,仅 TLS 适配器需要)
注意:axios 是 peer dependency,需要在使用此库的项目中安装。
开发
构建
# 使用 npm/yarn
yarn build
# 或使用 Windows 脚本
scripts\build.bat
# 或
npm run build:win开发模式(监视模式)
# 使用 npm/yarn
yarn dev
# 或使用 Windows 脚本
scripts\dev.bat
# 或
npm run dev:win清理
# 使用 npm/yarn
yarn clean
# 或使用 Windows 脚本
scripts\clean.bat
# 或
npm run clean:win发布
# 使用 Windows 脚本(推荐)
scripts\publish.bat
# 或
npm run publish:win发布前准备:
- 更新
package.json中的版本号 - 确保已登录 npm:
npm login - 运行发布脚本
快速开始
方式 1:面向对象 API(推荐,更简洁)
import { createTlsClient, HttpError } from 'sh-axios';
import { ClientIdentifier } from 'node-tls-client';
// 创建客户端(一步到位)
const client = createTlsClient({
clientIdentifier: ClientIdentifier.chrome_120,
defaultRetryOptions: {
maxRetries: 3,
nonRetryableStatusCodes: [400, 401, 403, 404, 422], // 可配置
},
});
try {
// 发送 GET 请求
const response = await client.get('https://api.example.com/data', {
proxyUrl: 'socks5://127.0.0.1:1080',
tlsSessionKey: 'my-session-key',
});
console.log('Response:', response.data);
} catch (error) {
if (error instanceof HttpError) {
console.error('HTTP Error:', error.errorCode, error.message);
}
}
// 发送 POST 请求
const response = await client.post('https://api.example.com/data', {
name: 'test',
});方式 2:函数式 API(兼容参考实现)
import { createTlsClient, httpRequest, HttpError } from 'sh-axios';
import { ClientIdentifier } from 'node-tls-client';
// 创建 axios 实例
const axiosInstance = createTlsClient({
clientIdentifier: ClientIdentifier.chrome_120,
}).getAxiosInstance();
try {
// 使用 httpRequest 发送请求(完全兼容参考实现)
const response = await httpRequest(axiosInstance, {
method: 'GET',
url: 'https://api.example.com/data',
proxyUrl: 'socks5://127.0.0.1:1080',
tlsSessionKey: 'my-session-key',
});
console.log('Response:', response.data);
} catch (error) {
if (error instanceof HttpError) {
console.error('HTTP Error:', error.errorCode, error.message);
}
}使用不同的适配器
import {
createDefaultClient,
createTlsClient,
createCurlCffiProxyClient,
createTlsBridgeClient
} from 'sh-axios';
import { ClientIdentifier } from 'node-tls-client';
// 1. 使用 TLS 适配器(node-tls-client,不支持流式上传)
const tlsClient = createTlsClient({
clientIdentifier: ClientIdentifier.chrome_120,
forceHttp1: true,
});
// 2. 使用 curl-cffi-proxy 适配器(Python 服务)
import { Impersonate } from 'sh-axios';
const curlCffiClient = createCurlCffiProxyClient({
proxyServiceUrl: 'http://localhost:8004',
defaultImpersonate: Impersonate.CHROME_110, // 使用统一枚举
});
// 3. 使用 tls-bridge 适配器(Go 服务)
const tlsBridgeClient = createTlsBridgeClient({
bridgeServiceUrl: 'http://localhost:8005',
defaultImpersonate: Impersonate.CHROME_120, // 使用统一枚举
});
// 4. 使用默认适配器(axios 原生)
const defaultClient = createDefaultClient();配置代理和会话
// 使用代理
const response = await client.get('https://api.example.com/data', {
proxyUrl: 'socks5://127.0.0.1:1080',
});
// 使用会话(TLS 会话复用)
// 注意:如果适配器不支持会话复用,tlsSessionKey 会被静默忽略(不报错)
// 这样可以统一使用方的编码,在不同适配器间无缝切换
const response = await client.get('https://api.example.com/data', {
proxyUrl: 'socks5://127.0.0.1:1080',
tlsSessionKey: 'my-session-key', // 如果适配器不支持,会静默忽略
});自定义重试策略
// 全局配置
const client = new HttpClient({
defaultRetryOptions: {
maxRetries: 5,
baseDelayMs: 2000,
nonRetryableStatusCodes: [400, 401, 403, 404, 422], // 可配置
shouldRetry: (error: HttpError, attempt: number) => {
// 自定义重试逻辑(error 是 HttpError 类型)
if (error.errorCode === 429) {
return { delayMs: 5000 }; // 限流时等待 5 秒
}
return error.retryable; // 使用 HttpError 的 retryable 标志
},
beforeRetry: async (error: HttpError, attempt: number) => {
// 重试前钩子(使用 RetryDecision 枚举)
if (error.errorCode === 401) {
// 刷新 token
await refreshToken();
return RetryDecision.RETRY; // 使用枚举,类型安全
}
return RetryDecision.PASSTHROUGH; // 继续按 shouldRetry 判定
},
},
});
// 单次请求配置
const response = await client.get('https://api.example.com/data', {}, {
maxRetries: 1,
nonRetryableStatusCodes: [400, 401, 403, 404, 422, 429], // 覆盖默认配置
});流式上传
import FormData from 'form-data';
const formData = new FormData();
formData.append('file', fs.createReadStream('large-file.zip'));
const response = await client.post('https://api.example.com/upload', formData, {
streamRequest: true, // 启用流式上传
proxyUrl: 'socks5://127.0.0.1:1080',
tlsSessionKey: 'my-session-key',
});流式下载
const response = await client.get('https://api.example.com/download', {
responseType: 'stream',
proxyUrl: 'socks5://127.0.0.1:1080',
});
// response.data 是一个 ReadableStream
const stream = response.data;
stream.pipe(fs.createWriteStream('output.zip'));完整配置示例
查看以下示例文件了解所有可用配置选项:
examples/basic-usage.ts- 基础使用示例examples/advanced-usage.ts- 高级功能示例(流式上传/下载、错误处理等)examples/full-config-example.ts- 完整配置示例
client.post 完整配置示例
const response = await client.post(
'https://api.example.com/data', // url
{ name: 'test', age: 18 }, // data
{
// ========== HttpRequestConfig(扩展了 AxiosRequestConfig)==========
// 代理和会话配置
proxyUrl: 'socks5://127.0.0.1:1080',
tlsSessionKey: 'my-session-key', // 如果适配器不支持,会静默忽略
streamRequest: false, // 是否使用流式上传
// AxiosRequestConfig 的所有标准配置
headers: { 'Content-Type': 'application/json' },
params: { page: 1, limit: 10 },
timeout: 30000,
responseType: 'json', // 'json' | 'text' | 'blob' | 'arraybuffer' | 'stream'
withCredentials: true,
auth: { username: 'user', password: 'pass' },
maxRedirects: 5,
validateStatus: (status) => status >= 200 && status < 300,
onUploadProgress: (progressEvent) => { /* ... */ },
onDownloadProgress: (progressEvent) => { /* ... */ },
signal: new AbortController().signal,
// ... 更多 AxiosRequestConfig 选项
} as HttpRequestConfig,
{
// ========== RetryOptions(可选,覆盖全局配置)==========
maxRetries: 5,
baseDelayMs: 2000,
maxDelayMs: 20000,
nonRetryableStatusCodes: [400, 401, 403, 404, 422, 429],
shouldRetry: (error, attempt) => {
if (error.response?.status === 429) {
return { delayMs: 5000 };
}
return true;
},
beforeRetry: async (error: HttpError, attempt: number) => {
if (error.errorCode === 401) {
// await refreshToken();
return RetryDecision.RETRY; // 使用枚举
}
return RetryDecision.PASSTHROUGH; // 使用枚举
},
} as RetryOptions
);API 文档
HttpClient
构造函数
new HttpClient(config?: HttpClientConfig)配置选项:
defaultConfig?: AxiosRequestConfig- 默认请求配置defaultRetryOptions?: RetryOptions- 默认重试配置logger?: Logger- 日志记录器debug?: boolean- 是否启用调试模式
方法
get<T>(url: string, config?: HttpRequestConfig, retryOptions?: RetryOptions): Promise<HttpResponse<T>>post<T>(url: string, data?: any, config?: HttpRequestConfig, retryOptions?: RetryOptions): Promise<HttpResponse<T>>put<T>(url: string, data?: any, config?: HttpRequestConfig, retryOptions?: RetryOptions): Promise<HttpResponse<T>>delete<T>(url: string, config?: HttpRequestConfig, retryOptions?: RetryOptions): Promise<HttpResponse<T>>patch<T>(url: string, data?: any, config?: HttpRequestConfig, retryOptions?: RetryOptions): Promise<HttpResponse<T>>request<T>(config: HttpRequestConfig, retryOptions?: RetryOptions): Promise<HttpResponse<T>>setAdapter(adapterType: string, adapterFactory: Function, options?: any): voidgetAxiosInstance(): AxiosInstance
RetryOptions
import { HttpError, RetryDecision } from 'sh-axios';
interface RetryOptions {
maxRetries?: number; // 最大重试次数(默认 3)
baseDelayMs?: number; // 基础延迟(毫秒,默认 1000)
maxDelayMs?: number; // 最大延迟(毫秒,默认 10000)
shouldRetry?: (error: HttpError, attempt: number) => boolean | { delayMs?: number };
beforeRetry?: (error: HttpError, attempt: number) => Promise<RetryDecision>;
nonRetryableStatusCodes?: number[]; // 不可重试的状态码(默认 [400, 401, 403, 404, 422])
}
// RetryDecision 枚举
enum RetryDecision {
RETRY = 'retry', // 立即重试
FAIL = 'fail', // 立即失败
PASSTHROUGH = 'passthrough', // 透传(继续按 shouldRetry 判断)
}HttpRequestConfig
扩展了 AxiosRequestConfig,添加了:
proxyUrl?: string | null- 代理 URLtlsSessionKey?: string- TLS 会话 key(如果适配器不支持,会静默忽略)streamRequest?: boolean- 是否使用流式上传
注意:HttpRequestConfig 包含 AxiosRequestConfig 的所有标准选项,如:
headers,params,timeout,responseType,withCredentials,authmaxRedirects,validateStatus,onUploadProgress,onDownloadProgresssignal,xsrfCookieName,xsrfHeaderName, 等等
详细配置示例请参考 examples/full-config-example.ts。
适配器池(AdapterPool)
推荐使用场景:需要频繁切换适配器或预先创建所有适配器的场景(如 sora 项目)
基本使用
import { createAdapterPool, AdapterType, httpRequest } from 'sh-axios';
import { ClientIdentifier } from 'node-tls-client';
// 初始化适配器池(预先创建所有适配器实例)
const pool = createAdapterPool({
// TLS 适配器配置
tls: {
clientIdentifier: ClientIdentifier.chrome_120,
},
// curl-cffi-proxy 适配器配置(必须提供 proxyServiceUrl)
curlCffiProxy: {
proxyServiceUrl: 'http://localhost:8004',
defaultImpersonate: 'chrome110',
},
// tls-bridge 适配器配置(必须提供 bridgeServiceUrl)
tlsBridge: {
bridgeServiceUrl: 'http://localhost:8005',
defaultImpersonate: 'chrome120',
},
// 默认适配器(可选)
default: {},
// 全局配置(应用到所有实例)
globalAxiosConfig: {
timeout: 30_000,
baseURL: 'https://api.example.com',
},
});
// 动态切换使用不同的适配器
const tlsInstance = pool.getAdapter(AdapterType.TLS);
const curlCffiInstance = pool.getAdapter(AdapterType.CURL_CFFI_PROXY);
// 使用适配器发送请求
if (tlsInstance) {
const response = await httpRequest(tlsInstance, {
method: 'GET',
url: '/data',
proxyUrl: 'socks5://127.0.0.1:1080',
tlsSessionKey: 'my-session-key',
});
}在 sora 项目中使用
// 初始化时创建适配器池
class IOSProtocolAdapter {
private adapterPool: AdapterPool;
constructor() {
this.adapterPool = createAdapterPool({
tls: {
clientIdentifier: ClientIdentifier.safari_ios_18_0,
},
curlCffiProxy: {
proxyServiceUrl: process.env.CURL_CFFI_PROXY_URL || 'http://localhost:8004',
},
tlsBridge: {
bridgeServiceUrl: process.env.TLS_BRIDGE_URL || 'http://localhost:8005',
},
});
}
// 根据配置动态选择适配器
private async getAxiosInstance(adapterType: string) {
return this.adapterPool.requireAdapter(adapterType);
}
}运行时更新配置
// 更新 TLS 适配器的指纹
pool.updateAdapter(AdapterType.TLS, {
clientIdentifier: ClientIdentifier.safari_ios_18_0,
});
// 重新获取实例
const updatedInstance = pool.getAdapter(AdapterType.TLS);API 参考
getAdapter(adapterType): 获取适配器实例(如果未配置返回 undefined)requireAdapter(adapterType): 获取适配器实例(如果未配置抛出错误)hasAdapter(adapterType): 检查适配器是否已配置getAvailableAdapters(): 获取所有已配置的适配器类型updateAdapter(adapterType, config): 更新适配器配置并重新创建实例destroy(): 销毁所有实例(清理资源)
适配器
默认适配器
使用 axios 原生功能,支持基本的代理配置。
TLS 适配器(node-tls-client)
使用 node-tls-client 库,支持:
- TLS 指纹模拟
- 代理支持
- 会话复用(通过 tlsSessionKey)
- ⚠️ 不支持流式上传(FormData 会被完整读入内存)
Curl-cffi-proxy 适配器
使用 Python curl-cffi-proxy 服务,支持:
- TLS 指纹模拟
- 代理支持
- 会话复用(通过 session_key)
- 流式上传/下载
TLS-Bridge 适配器
使用 Go tls-bridge 服务,支持:
- TLS 指纹模拟
- 代理支持
- 会话复用(通过 session_key)
- 真正的流式上传(不读入内存)
错误处理
所有错误都会抛出 HttpError 实例,包含:
errorCode: number- 错误码(HTTP 状态码或负数)retryable: boolean- 是否可重试originalError?: any- 原始错误对象metadata?: Record<string, any>- 额外元数据
import { HttpError } from 'sh-axios';
try {
const response = await client.get('https://api.example.com/data');
console.log('Success:', response.data);
} catch (error) {
if (error instanceof HttpError) {
console.log('Error code:', error.errorCode);
console.log('Error message:', error.message);
console.log('Retryable:', error.retryable);
console.log('Metadata:', error.metadata);
// 根据错误码处理
switch (error.errorCode) {
case 400:
console.error('Bad Request');
break;
case 401:
console.error('Unauthorized - 需要认证');
break;
case 403:
console.error('Forbidden - 权限不足');
break;
case 404:
console.error('Not Found');
break;
case 429:
console.error('Too Many Requests - 请求过于频繁');
break;
case 500:
case 502:
case 503:
console.error('Server Error - 服务器错误');
break;
default:
if (error.errorCode < 0) {
console.error('Network Error - 网络错误');
}
}
} else {
console.error('Unknown error:', error);
}
}配置说明
不可重试错误码
默认不可重试的错误码:[400, 401, 403, 404, 422]
可以通过配置覆盖:
// 全局配置
const client = new HttpClient({
defaultRetryOptions: {
nonRetryableStatusCodes: [400, 401, 403, 404, 422, 429], // 添加 429
},
});
// 单次请求配置(优先级更高)
await client.get('https://api.example.com/data', {}, {
nonRetryableStatusCodes: [400, 401, 403, 404], // 覆盖全局配置
});设计说明
本库提供双重 API 设计,满足不同场景:
- 面向对象 API(推荐):使用
HttpClient类,更简洁易用 - 函数式 API:使用
httpRequest函数,完全兼容参考实现
更多文档
- API.md - 完整的 API 文档
- CHANGELOG.md - 更新日志
- examples/ - 示例代码
- basic-usage.ts - 基础使用示例
- advanced-usage.ts - 高级功能示例
- full-config-example.ts - 完整配置示例
扩展适配器
实现自定义适配器:
import { BaseAdapter, AdapterType, AdapterMetadata } from 'sh-axios';
class MyCustomAdapter extends BaseAdapter {
readonly metadata: AdapterMetadata = {
type: AdapterType.CUSTOM,
capabilities: {
supportsProxy: true,
supportsSessionReuse: false,
supportsStreaming: true,
supportsImpersonate: false,
},
factory: (opts) => {
// 创建适配器函数
return async (config) => {
// 实现适配器逻辑
};
},
};
protected configureProxy(config, proxyConfig) {
// 实现代理配置
return config;
}
isAdapterFor(axiosInstance) {
// 判断是否使用此适配器
return false;
}
}许可证
MIT
