xin / http
一个基于 PSR-7/PSR-18 标准的轻量级、高性能 HTTP 客户端,支持 Guzzle、Amp 和 Workerman 等多种驱动。
v2.2.2
2026-04-28 13:02 UTC
Requires
- php: >=8.1
- nyholm/psr7: ^1.8
- psr/http-message: ^1.0 || ^2.0
- xin/support: ^1.0
Requires (Dev)
- amphp/http-client: ^5.3
- guzzlehttp/guzzle: ^8.0|^7.0|^6.0
- hyperf/guzzle: ^3.1
- symfony/var-dumper: ^7.4
- workerman/http-client: ^3.0
- workerman/workerman: ^5.1
README
一个基于适配器模式的轻量级 PHP HTTP 客户端库,支持多种底层实现(Guzzle、AmpPHP、Workerman),提供简洁的 API 和强大的拦截器机制。
✨ 特性
- 🚀 多适配器支持:自动识别或手动指定底层 HTTP 客户端(Guzzle、AmpPHP、Workerman)
- 🎯 简洁 API:链式调用,支持同步/异步请求
- 🔧 拦截器机制:灵活请求/响应拦截器,轻松处理认证、日志等横切关注点
- 📦 丰富的响应处理:JSON/XML 自动解析、流式响应、文件下载等
- ⚡ 异步支持:原生支持异步请求和 SSE 流式处理
- 🛡️ 完善的错误处理:友好的异常体系和错误检查方法
📋 环境要求
- PHP >= 8.1
- Composer
🚀 安装
composer require xin/http
可选依赖
根据需要使用不同的适配器:
# Guzzle 适配器(默认)
composer require guzzlehttp/guzzle
# AmpPHP 适配器(协程)
composer require amphp/http-client
# Workerman 适配器
composer require workerman/http-client workerman/workerman
💡 快速开始
基础用法
<?php
use Xin\HttpClient\HttpClient;
require_once 'vendor/autoload.php';
// GET 请求
$response = HttpClient::get('https://api.example.com/users');
// POST 请求
$response = HttpClient::post('https://api.example.com/users', [
'json' => ['name' => 'John', 'email' => 'john@example.com']
]);
// 获取响应数据
echo $response->body(); // 原始响应体
$data = $response->json(); // JSON 解析为数组
$status = $response->status(); // 状态码
$success = $response->successful(); // 是否成功 (2xx)
常用 HTTP 方法
// 同步请求
HttpClient::get($uri, $options = []);
HttpClient::post($uri, $options = []);
HttpClient::put($uri, $options = []);
HttpClient::patch($uri, $options = []);
HttpClient::delete($uri, $options = []);
HttpClient::head($uri, $options = []);
// 异步请求
HttpClient::getAsync($uri, $options = []);
HttpClient::postAsync($uri, $options = []);
HttpClient::putAsync($uri, $options = []);
📖 详细用法
创建客户端实例
// 使用默认单例
$client = HttpClient::default();
// 创建新实例
$client = HttpClient::create([
'timeout' => 30,
'connect_timeout' => 5,
]);
// 自定义配置
$client = new HttpClient([
'base_uri' => 'https://api.example.com',
'headers' => [
'Accept' => 'application/json',
],
]);
配置选项
// 设置全局默认配置
HttpClient::setDefaultOptions([
'timeout' => 30,
'verify' => false,
'headers' => [
'X-API-Key' => 'your-api-key',
],
]);
// 合并默认配置
HttpClient::mergeDefaultOptions([
'timeout' => 60,
]);
// 实例级别配置
$client = HttpClient::create();
$client->mergeOptions([
'timeout' => 10,
'headers' => ['Custom-Header' => 'value'],
]);
// 链式配置
$client->withTimeout(30)
->withConnectTimeout(5)
->withHeaders(['Authorization' => 'Bearer token'])
->withAllowRedirects(false)
->withVerify(true);
请求选项
$response = HttpClient::post('https://api.example.com/users', [
// Query 参数
'query' => [
'page' => 1,
'limit' => 20,
],
// JSON 数据
'json' => [
'name' => 'John',
'email' => 'john@example.com',
],
// 表单数据
'form_params' => [
'field' => 'value',
],
// 请求头
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer token',
],
// 超时设置
'timeout' => 30,
'connect_timeout' => 5,
// 其他 Guzzle 兼容选项
'allow_redirects' => true,
'verify' => false,
]);
响应处理
$response = HttpClient::get('https://api.example.com/users/1');
// 状态检查
$response->successful(); // 2xx 成功
$response->ok(); // 200 OK
$response->redirect(); // 3xx 重定向
$response->failed(); // 4xx/5xx 失败
$response->isClientError(); // 4xx 客户端错误
$response->isServerError(); // 5xx 服务器错误
// 获取数据
$response->body(); // 原始响应体字符串
$response->json(); // JSON 解析为数组
$response->json(false); // JSON 解析为对象
$response->xml(); // XML 解析为数组
$response->object(); // 自动解析 JSON/XML 为对象
$response->data('user.name'); // 点符号访问嵌套数据
$response->toArray(); // 转换为数组
// 响应信息
$response->status(); // HTTP 状态码
$response->headers(); // 所有响应头
$response->header('Content-Type'); // 指定响应头
$response->contentType(); // Content-Type
$response->cookies(); // Cookies
$response->url(); // 请求 URL
// 内容类型判断
$response->isJson(); // 是否为 JSON
$response->isXml(); // 是否为 XML
$response->isSSE(); // 是否为 SSE 流
$response->isStream(); // 是否为流式响应
$response->isAudio(); // 是否为音频
$response->isVideo(); // 是否为视频
// 数组式访问(针对 JSON 响应)
$name = $response['user']['name'];
$email = $response['user.email'];
错误处理
use Xin\HttpClient\Exceptions\ResponseException;
use Xin\HttpClient\Exceptions\RequestException;
// 方式 1: 自动抛出异常
try {
$response = HttpClient::get('https://api.example.com/not-found');
$response->throw(); // 失败时抛出异常
} catch (ResponseException $e) {
echo "HTTP Error: " . $e->getResponse()->status();
} catch (RequestException $e) {
echo "Request Error: " . $e->getMessage();
}
// 方式 2: 条件抛出
$response->throwIf($response->status() === 404);
// 方式 3: 除非 HTTP 错误才抛出
$response->throwUnlessHttpError();
// 方式 4: 错误回调
$response->onError(function ($response) {
Log::error("Request failed: " . $response->status());
});
// 方式 5: 带回调的 throw
$response->throw(function ($response, $exception) {
Log::error("Error: " . $exception->getMessage());
});
文件上传
// 同步上传
$response = HttpClient::upload('https://api.example.com/upload', [
'file' => fopen('/path/to/file.jpg', 'r'),
], [
'description' => 'File description',
]);
// 异步上传
$promise = HttpClient::uploadAsync('https://api.example.com/upload', [
'file' => fopen('/path/to/file.jpg', 'r'),
]);
$promise->then(function ($response) {
echo "Upload successful: " . $response->body();
});
文件下载
$response = HttpClient::get('https://example.com/large-file.zip');
// 保存到文件
$response->store('/path/to/save/file.zip');
// 保存解码后的数据
$response->storeAsData('/path/to/data.json');
流式响应
// SSE (Server-Sent Events) 流式处理
$response = HttpClient::post('https://api.example.com/chat', [
'json' => [
'message' => 'Hello',
'stream' => true,
],
]);
if ($response->isSSE()) {
foreach ($response->streamIterator() as $line) {
$data = json_decode($line, true);
echo $data['choices'][0]['delta']['content'] ?? '';
}
}
// 自定义流处理回调
$response->setOnStreamIteratorChunk(function ($chunk) {
echo $chunk;
flush();
});
异步请求
use Xin\HttpClient\Promise;
// 单个异步请求
$promise = HttpClient::getAsync('https://api.example.com/users');
$promise->then(
function ($response) {
echo "Success: " . $response->body();
},
function ($exception) {
echo "Error: " . $exception->getMessage();
}
);
// 并发请求
$promises = [
'users' => HttpClient::getAsync('https://api.example.com/users'),
'posts' => HttpClient::getAsync('https://api.example.com/posts'),
];
Promise::all($promises)->then(function ($results) {
$users = $results['users']->json();
$posts = $results['posts']->json();
});
🎭 Promise 详解
Xin\HttpClient\Promise 是一个符合 Promises/A+ 规范的异步编程工具,支持链式调用、错误处理和多种组合操作。
基本用法
use Xin\HttpClient\Promise;
// 创建 Promise
$promise = new Promise(function ($resolve, $reject) {
// 异步操作
if (/* 成功 */) {
$resolve($value);
} else {
$reject($error);
}
});
// 处理结果
$promise->then(
function ($value) {
// 成功回调
echo "Resolved: " . $value;
},
function ($reason) {
// 失败回调
echo "Rejected: " . $reason;
}
);
链式调用
HttpClient::getAsync('https://api.example.com/users/1')
->then(function ($response) {
return $response->json();
})
->then(function ($data) {
return $data['user']['name'];
})
->then(function ($name) {
echo "User name: " . $name;
})
->catch(function ($exception) {
echo "Error: " . $exception->getMessage();
});
错误处理
// catch - 捕获错误
HttpClient::getAsync('https://api.example.com/not-found')
->then(function ($response) {
return $response->throw()->json();
})
->catch(function ($exception) {
echo "Request failed: " . $exception->getMessage();
});
// otherwise - catch 的别名
$promise->otherwise(function ($error) {
Log::error($error->getMessage());
});
// finally - 无论成功或失败都执行
HttpClient::getAsync('https://api.example.com/data')
->then(function ($response) {
return $response->json();
})
->finally(function () {
echo "Request completed";
});
同步等待
// wait() - 阻塞等待 Promise 完成并返回结果
$promise = HttpClient::getAsync('https://api.example.com/users');
$response = $promise->wait();
echo $response->body();
// 如果 Promise 被拒绝,wait() 会抛出异常
try {
$result = $promise->wait();
} catch (Throwable $e) {
echo "Error: " . $e->getMessage();
}
状态检查
$promise = HttpClient::getAsync('https://api.example.com/users');
// 检查状态
$promise->isPending(); // 是否进行中
$promise->isFulfilled(); // 是否已成功
$promise->isRejected(); // 是否已失败
$promise->getState(); // 获取状态字符串 ('pending', 'fulfilled', 'rejected')
静态方法
Promise::resolve()
创建一个已解决的 Promise:
$promise = Promise::resolve($value);
$promise->then(function ($value) {
echo "Value: " . $value;
});
Promise::reject()
创建一个已拒绝的 Promise:
$promise = Promise::reject(new Exception('Error'));
$promise->catch(function ($error) {
echo "Error: " . $error->getMessage();
});
组合操作
Promise::all() - 全部成功
等待所有 Promise 成功,任何一个失败则整体失败:
$promises = [
'users' => HttpClient::getAsync('https://api.example.com/users'),
'posts' => HttpClient::getAsync('https://api.example.com/posts'),
'comments' => HttpClient::getAsync('https://api.example.com/comments'),
];
Promise::all($promises)
->then(function ($results) {
// $results 是一个数组,键名与输入相同
$users = $results['users']->json();
$posts = $results['posts']->json();
$comments = $results['comments']->json();
echo "All requests completed successfully!";
})
->catch(function ($error) {
// 任何一个请求失败都会触发这里
echo "One or more requests failed: " . $error->getMessage();
});
Promise::any() - 任一成功
等待第一个成功的 Promise,全部失败则整体失败:
$mirrors = [
HttpClient::getAsync('https://mirror1.example.com/data'),
HttpClient::getAsync('https://mirror2.example.com/data'),
HttpClient::getAsync('https://mirror3.example.com/data'),
];
Promise::any($mirrors)
->then(function ($response) {
// 使用最快响应的那个镜像
echo "Got response from fastest mirror: " . $response->url();
})
->catch(function ($error) {
// 所有镜像都失败了
echo "All mirrors failed: " . $error->getMessage();
});
Promise::race() - 竞速
等待第一个完成的 Promise(无论成功或失败):
$requests = [
HttpClient::getAsync('https://fast-api.example.com/data'),
HttpClient::getAsync('https://slow-api.example.com/data'),
];
Promise::race($requests)
->then(function ($response) {
echo "First response: " . $response->url();
})
->catch(function ($error) {
echo "First error: " . $error->getMessage();
});
Promise::allSettled() - 全部结算
等待所有 Promise 完成(无论成功或失败),返回每个 Promise 的状态和结果:
$promises = [
'success' => HttpClient::getAsync('https://api.example.com/users'),
'failure' => HttpClient::getAsync('https://api.example.com/not-found'),
];
Promise::allSettled($promises)
->then(function ($results) {
foreach ($results as $key => $result) {
if ($result['status'] === 'fulfilled') {
echo "$key succeeded: " . $result['value']->status();
} else {
echo "$key failed: " . $result['reason']->getMessage();
}
}
});
// 输出示例:
// success succeeded: 200
// failure failed: HTTP 404 Not Found
实际应用场景
场景 1:批量 API 请求
$userIds = [1, 2, 3, 4, 5];
$promises = [];
foreach ($userIds as $id) {
$promises[$id] = HttpClient::getAsync("https://api.example.com/users/{$id}");
}
Promise::all($promises)
->then(function ($responses) use ($userIds) {
$users = [];
foreach ($userIds as $id) {
$users[] = $responses[$id]->json();
}
return $users;
})
->then(function ($users) {
echo "Fetched " . count($users) . " users";
})
->catch(function ($error) {
echo "Failed to fetch some users: " . $error->getMessage();
});
场景 2:带超时的请求
$mainRequest = HttpClient::getAsync('https://api.example.com/slow-endpoint');
$timeoutPromise = new Promise(function ($resolve, $reject) {
\Revolt\EventLoop::delay(5.0, function () use ($reject) {
$reject(new \RuntimeException('Request timeout'));
});
});
Promise::race([$mainRequest, $timeoutPromise])
->then(function ($response) {
echo "Response: " . $response->body();
})
->catch(function ($error) {
echo "Error: " . $error->getMessage();
});
场景 3:重试机制
function requestWithRetry($url, $maxRetries = 3) {
$attempt = 0;
$tryRequest = function () use ($url, $maxRetries, &$attempt, &$tryRequest) {
$attempt++;
return HttpClient::getAsync($url)
->then(function ($response) {
if ($response->failed()) {
throw new \Exception("HTTP {$response->status()}");
}
return $response;
})
->catch(function ($error) use ($maxRetries, &$attempt, &$tryRequest) {
if ($attempt < $maxRetries) {
echo "Attempt {$attempt} failed, retrying...\n";
return $tryRequest();
}
throw $error;
});
};
return $tryRequest();
}
requestWithRetry('https://api.example.com/unstable-endpoint')
->then(function ($response) {
echo "Success after retries: " . $response->status();
})
->catch(function ($error) {
echo "All retries failed: " . $error->getMessage();
});
场景 4:并行数据聚合
// 从多个 API 获取数据并聚合
$dashboardData = Promise::all([
'stats' => HttpClient::getAsync('https://api.example.com/stats'),
'notifications' => HttpClient::getAsync('https://api.example.com/notifications'),
'recent_activity' => HttpClient::getAsync('https://api.example.com/activity'),
])->then(function ($responses) {
return [
'stats' => $responses['stats']->json(),
'notifications' => $responses['notifications']->json(),
'activity' => $responses['recent_activity']->json(),
];
});
// 在事件循环中等待结果
$data = $dashboardData->wait();
print_r($data);
🔌 拦截器
拦截器允许你在请求发送前和响应接收后执行自定义逻辑。
请求拦截器
use Xin\HttpClient\HttpClient;
// 添加请求拦截器
HttpClient::default()->addRequestInterceptor(function (array $options) {
// 添加认证头
$options['headers']['Authorization'] = 'Bearer ' . getToken();
// 添加请求 ID
$options['headers']['X-Request-ID'] = uniqid();
// 记录日志
Log::info("Sending request to: " . $options['uri']);
return $options; // 必须返回修改后的 options
});
// 移除拦截器
$client->removeRequestInterceptor($callback);
响应拦截器
// 添加响应拦截器
HttpClient::default()->addResponseInterceptor(function ($response) {
// 自动解析 JSON
if ($response->isJson()) {
$response->decoded();
}
// 记录响应日志
Log::info("Response status: " . $response->status());
return $response; // 必须返回 response
});
// 移除拦截器
$client->removeResponseInterceptor($callback);
内置拦截器
use Xin\HttpClient\Interceptors\Requests\BodyJsonParametersInterceptor;
use Xin\HttpClient\Interceptors\Responses\DecodedInterceptor;
// 自动将数组 body 转为 JSON
HttpClient::default()->addRequestInterceptor(new BodyJsonParametersInterceptor());
// 自动解码 JSON/XML 响应
HttpClient::default()->addResponseInterceptor(new DecodedInterceptor());
🔄 适配器
自动选择适配器
库会自动检测环境并选择合适的适配器:
- Workerman - 如果在 Workerman 环境中
- AmpPHP - 如果安装了 AmpPHP
- Guzzle - 默认回退方案
手动指定适配器
// 通过配置指定
$client = HttpClient::create([
'adapter' => 'guzzle', // 或 'workerman', 'ampphp'
]);
// 使用类名
$client = HttpClient::create([
'adapter' => \Xin\HttpClient\Adapters\GuzzleHttpClient::class,
]);
// 动态切换适配器
$client->useAdapter('workerman');
$client->useAdapter(new AmpHttpClientAdapter($options));
创建特定适配器
// Guzzle 适配器
$client = HttpClient::createGuzzleHttpClient([
'timeout' => 30,
]);
// Workerman 适配器
$client = HttpClient::createWorkermanHttpClient([
'timeout' => 30,
]);
// AmpPHP 适配器
$client = HttpClient::createAmpHttpClient([
'timeout' => 30,
]);
🎨 高级用法
在类中使用
use Xin\HttpClient\HasHttpClient;
class ApiService
{
use HasHttpClient;
protected $baseUri = 'https://api.example.com';
public function getUsers()
{
return $this->client()->get('/users')->json();
}
public function createUser(array $data)
{
return $this->client()->post('/users', [
'json' => $data,
])->throw()->json();
}
// 自定义 HTTP 客户端配置
protected function httpClientOptions(): array
{
return [
'timeout' => 30,
'base_uri' => $this->baseUri,
'headers' => [
'Accept' => 'application/json',
],
];
}
// 请求拦截器
protected function onHttpClientRequest(array $options): array
{
$options['headers']['Authorization'] = 'Bearer ' . $this->getToken();
return $options;
}
}
Cookie 管理
$client = HttpClient::create([
'cookies' => true, // 启用 Cookie
]);
$response = $client->get('https://example.com/login', [
'form_params' => [
'username' => 'user',
'password' => 'pass',
],
]);
// 获取 Cookies
$cookies = $client->cookies();
// 携带 Cookie 访问
$response = $client->get('https://example.com/dashboard');
获取底层客户端
// 获取原生 HTTP 客户端实例
$guzzleClient = HttpClient::default()->client();
// 可以直接使用原生客户端
$psr7Response = $guzzleClient->send($psr7Request);
📝 完整示例
RESTful API 调用
<?php
use Xin\HttpClient\HttpClient;
use Xin\HttpClient\Interceptors\Requests\BodyJsonParametersInterceptor;
require_once 'vendor/autoload.php';
// 配置客户端
$api = HttpClient::create([
'base_uri' => 'https://api.example.com/v1',
'timeout' => 30,
'headers' => [
'Accept' => 'application/json',
],
]);
// 添加拦截器自动处理 JSON
$api->addRequestInterceptor(new BodyJsonParametersInterceptor());
$api->addRequestInterceptor(function (array $options) {
$options['headers']['Authorization'] = 'Bearer YOUR_API_TOKEN';
return $options;
});
try {
// 获取资源列表
$users = $api->get('/users', [
'query' => ['page' => 1, 'limit' => 20],
])->throw()->json();
// 创建资源
$newUser = $api->post('/users', [
'json' => [
'name' => 'John Doe',
'email' => 'john@example.com',
],
])->throw()->json();
// 更新资源
$api->put("/users/{$newUser['id']}", [
'json' => ['name' => 'Jane Doe'],
])->throw();
// 删除资源
$api->delete("/users/{$newUser['id']}")->throw();
echo "All operations completed successfully!";
} catch (\Exception $e) {
echo "Error: " . $e->getMessage();
}
AI 流式对话(SSE)
<?php
use Xin\HttpClient\HttpClient;
require_once 'vendor/autoload.php';
$client = HttpClient::create([
'base_uri' => 'https://api.openai.com/v1',
'headers' => [
'Authorization' => 'Bearer YOUR_API_KEY',
'Content-Type' => 'application/json',
],
]);
$response = $client->post('/chat/completions', [
'json' => [
'model' => 'gpt-4',
'messages' => [
['role' => 'user', 'content' => 'Hello!'],
],
'stream' => true,
],
]);
if ($response->isSSE()) {
foreach ($response->streamIterator() as $line) {
if (str_starts_with($line, 'data: ')) {
$data = json_decode(substr($line, 6), true);
$content = $data['choices'][0]['delta']['content'] ?? '';
echo $content;
flush();
}
}
}
📚 API 参考
HttpClient 主要方法
| 方法 | 说明 |
|---|---|
HttpClient::default() | 获取默认单例 |
HttpClient::create($options) | 创建新实例 |
HttpClient::get/post/put/delete/patch/head() | 同步请求 |
HttpClient::getAsync/postAsync/...() | 异步请求 |
HttpClient::upload() | 文件上传 |
HttpClient::request($method, $uri, $options) | 通用请求方法 |
$client->mergeOptions($options) | 合并配置 |
$client->with*() | 链式配置方法 |
$client->addRequestInterceptor() | 添加请求拦截器 |
$client->addResponseInterceptor() | 添加响应拦截器 |
$client->getOptions() | 获取配置 |
$client->client() | 获取底层客户端 |
Response 主要方法
| 方法 | 说明 |
|---|---|
$response->body() | 获取响应体 |
$response->json($asArray) | JSON 解析 |
$response->xml($asArray) | XML 解析 |
$response->data($key) | 获取解码数据 |
$response->status() | 状态码 |
$response->successful() | 是否成功 |
$response->failed() | 是否失败 |
$response->throw() | 失败时抛出异常 |
$response->headers() | 获取响应头 |
$response->cookies() | 获取 Cookies |
$response->store($path) | 保存到文件 |
$response->streamIterator() | 获取流迭代器 |
$response->isJson/isXml/isSSE() | 类型判断 |
🤝 贡献
欢迎提交 Issue 和 Pull Request!
📄 许可证
Apache-2.0 License