xin/http

一个基于 PSR-7/PSR-18 标准的轻量级、高性能 HTTP 客户端,支持 Guzzle、Amp 和 Workerman 等多种驱动。

Maintainers

Package info

gitee.com/liuxiaojinla/php-httpclient

pkg:composer/xin/http

Statistics

Installs: 219

Dependents: 3

Suggesters: 0

v2.2.2 2026-04-28 13:02 UTC

This package is auto-updated.

Last update: 2026-04-28 13:15:22 UTC


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());

🔄 适配器

自动选择适配器

库会自动检测环境并选择合适的适配器:

  1. Workerman - 如果在 Workerman 环境中
  2. AmpPHP - 如果安装了 AmpPHP
  3. 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

👤 作者

657306123@qq.com