npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sh-axios

v1.0.8

Published

A flexible, extensible HTTP client library with adapter support, retry strategy, and error handling

Downloads

41

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

发布前准备

  1. 更新 package.json 中的版本号
  2. 确保已登录 npm:npm login
  3. 运行发布脚本

快速开始

方式 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): void
  • getAxiosInstance(): 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 - 代理 URL
  • tlsSessionKey?: string - TLS 会话 key(如果适配器不支持,会静默忽略)
  • streamRequest?: boolean - 是否使用流式上传

注意HttpRequestConfig 包含 AxiosRequestConfig 的所有标准选项,如:

  • headers, params, timeout, responseType, withCredentials, auth
  • maxRedirects, validateStatus, onUploadProgress, onDownloadProgress
  • signal, 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 设计,满足不同场景:

  1. 面向对象 API(推荐):使用 HttpClient 类,更简洁易用
  2. 函数式 API:使用 httpRequest 函数,完全兼容参考实现

更多文档

扩展适配器

实现自定义适配器:

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