无尘阁日记

无尘阁日记

给Yii2的HttpClient模拟超时
2026-03-04

问:

$res = $client->createRequest()

->setUrl($url)// 设置接口地址

->setMethod('POST')

->setData([

'ProjectId' => $project->Id,

'ReportId' => $reportId,

'Dir' => str_replace(Yii::$app->basePath, '',$dir),

'ReportFileName' => $report_name,

'ReportFileNameWithTag' => $report_tag_name,

'Extension' => $ext

])

->setOptions([

'timeout' => 180, // 此处三个时间超时太长不合理,改到5分钟即可

'userAgent' => 'ZPY2.0',

'sslVerifyPeer' => false,

'sslCafile' => '',

'sslCapath' => '',

CURLOPT_CONNECTTIMEOUT => 180, // connection timeout

CURLOPT_TIMEOUT => 180, // data receiving timeout

])

->setFormat(\yii\httpclient\Client::FORMAT_JSON)

->addHeaders(['Content-Type' => 'application/json'])

->send();


这个我如何模拟超时?

答:

你想测试的是:客户端请求在 Yii2 httpclient 中触发 timeout 的情况
核心思路其实只有三种:

1 让服务端故意慢(最真实)
2 请求一个永远不返回的地址
3 人为把 timeout 调得很小

我给你几个最实用的办法。

一、最推荐:服务端 sleep(真实模拟)

如果你能控制接口服务端,最简单。

接口代码加:

sleep(200);

例如:

public function actionTest()
{
    sleep(200);
    return ['ok' => true];
}

你的客户端:

CURLOPT_TIMEOUT => 10

10秒后就会 timeout。

二、请求一个不会返回的地址(最简单)

比如:

$url = 'http://10.255.255.1/test';

这个 IP 常用于模拟网络阻塞。

请求代码不用变。

会触发:

Operation timed out

三、故意把 timeout 调小(开发最常用)

->setOptions([
    CURLOPT_CONNECTTIMEOUT => 1,
    CURLOPT_TIMEOUT => 1,
])

如果接口稍微慢一点就会超时。

四、模拟“连接超时”和“读取超时”

连接超时(服务器没响应)

CURLOPT_CONNECTTIMEOUT => 2

访问一个不存在的 IP。

读取超时(服务器很慢)

服务器:

sleep(30);

客户端:

CURLOPT_TIMEOUT => 5

五、抓取错误信息(非常重要)

很多人不知道 timeout 怎么判断。

if (!$res->isOk) {

    $error = $res->getContent();
    var_dump($res->statusCode);
}

或者:

try {

    $res = $request->send();

} catch (\Exception $e) {

    echo $e->getMessage();
}

六、推荐的完整测试代码

try {

    $res = $client->createRequest()
        ->setUrl($url)
        ->setMethod('POST')
        ->setData($data)
        ->setOptions([
            CURLOPT_CONNECTTIMEOUT => 3,
            CURLOPT_TIMEOUT => 3,
        ])
        ->send();

} catch (\Throwable $e) {

    echo "timeout测试成功\n";
    echo $e->getMessage();
}

七、企业级最真实的办法(推荐)

使用 nginx 限速tc 网络延迟

例如:

tc qdisc add dev eth0 root netem delay 3000ms

模拟 3 秒网络延迟。

八、很多人不知道的 Yii2 坑

你代码里有一个问题:

'timeout' => 180

这个 不是 curl timeout

真正起作用的是:

CURLOPT_TIMEOUT
CURLOPT_CONNECTTIMEOUT

九、最简单一句话模拟

把 timeout 改成:

CURLOPT_TIMEOUT => 1

然后请求一个正常接口。

基本就能稳定复现。


Yii2 yii\httpclient\Client 里,你代码中的:

->setOptions([
    'timeout' => 180,
])

很多人会误解,它其实不是 Yii 自己定义的参数,而是 传给底层 cURL 的一个 option。但它的作用和 CURLOPT_TIMEOUT同一个东西

不过需要搞清楚 三个不同的超时概念


一、timeout 的真实作用

'timeout' => 180

等价于:

CURLOPT_TIMEOUT => 180

意思是:

整个 HTTP 请求最长允许执行 180 秒

包括:

  • DNS 解析

  • TCP 建立连接

  • SSL 握手

  • 发送请求

  • 等待服务器响应

  • 接收响应数据

超过 180 秒就会被 cURL 强制终止。

二、三个常见超时参数区别

参数作用触发阶段
CURLOPT_CONNECTTIMEOUT建立连接超时TCP连接阶段
CURLOPT_TIMEOUT / timeout整个请求最大时间全流程
CURLOPT_TIMEOUT_MS毫秒级超时全流程

三、一个最清晰的理解方式

假设:

CURLOPT_CONNECTTIMEOUT = 5
CURLOPT_TIMEOUT = 20

流程就是:

0s -----------5s---------------------20s
   连接阶段         请求+响应阶段

情况:

1️⃣ 如果 5秒连不上服务器

报错:

Connection timed out

2️⃣ 如果 连接成功但服务器一直不返回

20秒 报:

Operation timed out

四、你代码里其实写重复了

你现在是:

'timeout' => 180,
CURLOPT_CONNECTTIMEOUT => 180,
CURLOPT_TIMEOUT => 180,

这里:

timeout = CURLOPT_TIMEOUT

所以 写了两次一样的东西

五、推荐生产写法

建议这样写最清晰:

->setOptions([
    CURLOPT_CONNECTTIMEOUT => 5,   // 建立连接最多5秒
    CURLOPT_TIMEOUT => 30,         // 整个请求最多30秒
])

如果是 内部服务调用

CONNECTTIMEOUT = 2
TIMEOUT = 10

就够了。

六、你代码还有两个小问题

'sslVerifyPeer' => false,
'sslCafile' => '',
'sslCapath' => '',

这些是 Guzzle 的参数
在 Yii2 httpclient 里 不会生效

正确写法是:

CURLOPT_SSL_VERIFYPEER => false
CURLOPT_SSL_VERIFYHOST => false

七、给你一份 Yii2 最标准 HTTP 请求模板

$res = $client->createRequest()
    ->setUrl($url)
    ->setMethod('POST')
    ->setData($data)
    ->addHeaders([
        'Content-Type' => 'application/json',
        'User-Agent' => 'ZPY2.0'
    ])
    ->setOptions([
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
    ])
    ->setFormat(\yii\httpclient\Client::FORMAT_JSON)
    ->send();

八、很多高级程序员才知道的一个坑

如果你写:

CURLOPT_TIMEOUT => 30

但服务器 一直在传数据(哪怕很慢)

请求 不会超时

因为 cURL 认为“数据还在流动”。

真正限制这个的是:

CURLOPT_LOW_SPEED_TIME
CURLOPT_LOW_SPEED_LIMIT