使用curl里面关于CURLOPT配置中的WRITEFUNCTION读取sse流

8702次阅读 698人点赞 作者: WuBin 发布时间: 2024-05-30 09:15:22
扫码到手机查看

CURLOPT_WRITEFUNCTION

CURLOPT_WRITEFUNCTION 是 PHP 中的 cURL 库的一个选项,它用于指定一个回调函数,这个回调函数将会接收 cURL 下载的数据。这个回调函数应该有两个参数,第一个是 data,第二个是 mimetype。

这个选项的值应该是一个有两个参数的函数名,这个函数会在 cURL 接收到数据时被调用。

以下是一些使用 CURLOPT_WRITEFUNCTION 的示例:

function write_data($data) {
    $file = fopen('output.txt', 'a');
    fwrite($file, $data);
    fclose($file);
    return strlen($data);
}

$ch = curl_init('http://example.com');
curl_setopt($ch, CURLOPT_WRITEFUNCTION, 'write_data');
curl_exec($ch);
curl_close($ch);

在这个例子中,我们创建了一个函数 write_data,这个函数接收数据并将其写入到 output.txt 文件中。然后我们用 curl_setopt 函数将 CURLOPT_WRITEFUNCTION 的值设置为 'write_data'。

$ch = curl_init('http://example.com');
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($handle, $data) {
    $file = fopen('output.txt', 'a');
    fwrite($file, $data);
    fclose($file);
    return strlen($data);
});
curl_exec($ch);
curl_close($ch);

在这个例子中,我们使用了一个匿名函数作为 CURLOPT_WRITEFUNCTION 的值。这个函数的功能和上一个例子中的 write_data 函数一样,也是接收数据并写入到 output.txt 文件中。

注意:在这两个例子中,我们都在 curl_exec 函数执行期间将数据写入到 output.txt 文件中。如果你需要在多线程环境中使用这个功能,你可能需要使用其他方法来保证数据的同步写入,因为 curl_multi 可能会在多个线程中调用你的回调函数,这可能会导致数据竞争和数据损坏。

CURLOPT_WRITEDATA 

在 PHP 的 cURL 扩展中,CURLOPT_WRITEDATA 是一个选项,它用于指定一个资源(比如文件句柄或者字符串变量)来接收 cURL 下载的数据。当你使用 CURLOPT_WRITEFUNCTION 选项来自定义数据写入的方式时,这个资源通常会作为该函数的第一个参数传递。

如果你没有设置 CURLOPT_WRITEFUNCTION,那么 CURLOPT_WRITEDATA 将默认为 STDOUT,这意味着数据会被直接输出到标准输出。

以下是一些使用 CURLOPT_WRITEDATA 的示例:

示例 1:将数据写入文件

$fileHandle = fopen('output.txt', 'w');

$ch = curl_init('http://example.com');
curl_setopt($ch, CURLOPT_WRITEDATA, $fileHandle);
curl_exec($ch);

fclose($fileHandle);
curl_close($ch);

在这个例子中,我们打开一个文件句柄 $fileHandle,并将其设置为 CURLOPT_WRITEDATA 的值。cURL 将自动把下载的数据写入到这个文件中。

示例 2:将数据写入字符串变量

$responseData = '';

$ch = curl_init('http://example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 这一行不是必需的,但通常用于确保数据不被直接输出
curl_setopt($ch, CURLOPT_WRITEDATA, fopen('php://temp', 'r+'));
$response = curl_exec($ch);

// 获取临时流中的数据
rewind($responseData);
$responseData = stream_get_contents($responseData);

curl_close($ch);

echo $responseData; // 输出下载的数据

在这个例子中,我们使用 php://temp 来创建一个临时流,并将其设置为 CURLOPT_WRITEDATA 的值。当 cURL 下载完数据后,我们可以从临时流中读取数据到字符串变量 $responseData 中。

请注意,CURLOPT_RETURNTRANSFER 和 CURLOPT_WRITEDATA 通常是互斥的。如果你设置了 CURLOPT_RETURNTRANSFER 为 true,cURL 会将响应内容返回给 curl_exec() 函数,而不是写入到 CURLOPT_WRITEDATA 指定的资源中。在大多数情况下,你只需要使用其中一个选项。如果你使用 CURLOPT_WRITEFUNCTION 自定义了数据写入,CURLOPT_RETURNTRANSFER 将被忽略。

最后,记得在完成后关闭所有的文件句柄和 cURL 句柄,以释放资源。

开发中遇到的问题

CURL 的 WRITEFUNCTION 回调需要返回准确处理的数据长度

现在有这么个情况

curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $chunk) {
    
    if (strpos($chunk, '{"code"') !== false) {
       $chunk = $this->getStreamMsg('抱歉,服务器繁忙,请稍后重试。');
  }

    echo $chunk;

    ob_flush(); // 刷新输出缓冲区
    flush(); // 发送缓冲区内容到浏览器
    return strlen($chunk);
}

// 执行cURL请求

curl_exec($ch);

if(curl_error($ch)) {
    $errMsg = 'Error: ' . curl_error($ch);
    $this->throwError('这里出现报错了' . $errMsg);
}

// 关闭cURL资源
curl_close($ch);

以下是当第三方返回错误的时候,$chunk打印的内容:

{"code":"InvalidApiKey","message":"Invalid API-key provided.","request_id":"0d381c05-aefb-95eb-9f5f-a749b75a120a"}

这段代码 本地是检测 返回的字符串中是否含有{”code" 字符串,如果有的话代表这是个第三方传输报错。这里执行之后,会发现,出现了如下错误:

(getStreamMsg就只是一个简单构建自定义的sse流字符串的函数)发现不仅输出了,自定义的错误,同时也执行了curl_error。

经过排查,发现问题出在错误处理的流程上。

当使用 getStreamMsg() 替换 $chunk 后,实际上改变了返回给 curl 的数据长度。在 WRITEFUNCTION 回调中,返回值 strlen($chunk) 必须准确反映处理的数据长度,否则会导致 curl 认为数据传输出错。

因为我使用了$chunk =$this->getStreamMsg(),对$chunk重新赋值,因此改变了$chunk的原有长度!导致报错,从而执行了curl_error流程!!

做如下修改:

 curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $chunk) {
            
    // 简单检查是否包含错误码
    if (strpos($chunk, '{"code"') !== false) {
        $errorMsg = $this->originalStream('抱歉,服务器繁忙,请稍后重试。');
        echo $errorMsg;
    } else {
        echo $chunk;
    }
           
    ob_flush(); // 刷新输出缓冲区
    flush(); // 发送缓冲区内容到浏览器
    return strlen($chunk);
});

// 执行cURL请求
curl_exec($ch);

if(curl_error($ch)) {
    $errMsg = 'Error: ' . curl_error($ch);
    $this->throwError($errMsg);
    // echo $errMsg;
}

主要改动:

1. 保持原始 $chunk 不变

2. 只在输出时根据条件选择输出错误信息还是原始内容

3. 返回值始终使用原始 $chunk 的长度

如此修改后就不会影响数据的传输了。

CURL 的 WRITEFUNCTION 回调需要返回准确处理的数据长度,这是用来确认数据是否完整传输的重要依据。当我们改变了 $chunk 的内容但返回的仍是原始长度时,就会导致长度不匹配,从而触发 curl_error。

针对这个问题,再让我们改一下:

 curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $chunk) {
            
    // 简单检查是否包含错误码
    if (strpos($chunk, '{"code"') !== false) {
        $errorMsg = $this->originalStream('抱歉,服务器繁忙,请稍后重试。');
        echo $errorMsg;
        ob_flush();
        flush(); 
        return 0;
    } else {
        ob_flush();
        flush(); 
        echo $chunk;
        return strlen($chunk);
    }
            
});

// 执行cURL请求
curl_exec($ch);

if(curl_error($ch)) {
    $errMsg = 'Error: ' . curl_error($ch);
    $this->throwError($errMsg);
    // echo $errMsg;
}

这次我们在检测到传输回来的数据,出现问题的时候,直接return 0;

在 CURL 的 WRITEFUNCTION 回调中:

1. return 0 表示写入操作失败,会导致 CURL 传输中断,并且会触发 curl_error()

2. return strlen($chunk) 表示写入成功,继续处理后续数据

所以在本次修改代码中:

- 当检测到 {"code"} 时返回 0,会立即中断传输并触发 curl_error()

- 这种情况下 curl_error($ch) 会返回类似 "Failed writing body" 的错误

所以,想要让返回成功,并且让请求不中断,一定要给了什么数据,就要return回相应的长度!

WRITEFUNCTION 回调中遇到错误,会断开链接

另外,如果WRITEFUNCTION 回调中执行的函数抛错了,也同样会中断sse流的接收!
 curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $chunk) {
    
    // 比如这里解析错误了  那么会直接中断链接!走curl_error流程!
    $json = json_decode($chunk, true);
    echo $json['some_attr'];
    return strlen($chunk);
});

// 执行cURL请求
curl_exec($ch);

if(curl_error($ch)) {
    $errMsg = 'Error: ' . curl_error($ch);
    $this->throwError($errMsg);
}
另外,如果某些sb服务器设置禁止了程序抛错,那么使用try-catch也无法捕获回调中的错误!出现的效果就是请求直接中断,并且走curl_error流程!如果在禁止抛错的服务器上,发现SSE输出,最后结果不完整,不全的,那么大概率就是回调中执行的代码遇到错误了,导致链接的中断!!

点赞 支持一下 觉得不错?客官您就稍微鼓励一下吧!
关键词:curl,CURLOPT-WRITEFUNCTION
推荐阅读
  • python基础-操作列表和迭代器

    python基础笔记-操作列表和迭代器的相关方法

    6247次阅读 131人点赞 发布时间: 2024-06-13 13:26:27 立即查看
  • uniapp实现被浏览器唤起的功能

    当用户打开h5链接时候,点击打开app若用户在已经安装过app的情况下直接打开app,若未安装过跳到应用市场下载安装这个功能在实现上主要分为两种场景,从普通浏览器唤醒以及从微信唤醒。

    10621次阅读 704人点赞 发布时间: 2022-12-14 16:34:53 立即查看
  • PHP

    【正则】一些常用的正则表达式总结

    在日常开发中,正则表达式是非常有用的,正则表达式在每个语言中都是可以使用的,他就跟JSON一样,是通用的。了解一些常用的正则表达式,能大大提高你的工作效率。

    14637次阅读 577人点赞 发布时间: 2021-10-09 15:58:58 立即查看
  • 【中文】免费可商用字体下载与考证

    65款免费、可商用、无任何限制中文字体打包下载,这些字体都是经过长期验证,经得住市场考验的,让您规避被无良厂商起诉的风险。

    13434次阅读 1082人点赞 发布时间: 2021-07-05 15:28:45 立即查看
  • Vue

    Vue3开发一个v-loading的自定义指令

    在vue3中实现一个自定义的指令,有助于我们简化开发,简化复用,通过一个指令的调用即可实现一些可高度复用的交互。

    17474次阅读 1408人点赞 发布时间: 2021-07-02 15:58:35 立即查看
  • JS

    关于手机上滚动穿透问题的解决

    当页面出现浮层的时候,滑动浮层的内容,正常情况下预期应该是浮层下边的内容不会滚动;然而事实并非如此。在PC上使用css即可解决,但是在手机端,情况就变的比较复杂,就需要禁止触摸事件才可以。

    15882次阅读 1284人点赞 发布时间: 2021-05-31 09:25:50 立即查看
  • Vue

    Vue+html2canvas截图空白的问题

    在使用vue做信网单页专题时,有海报生成的功能,这里推荐2个插件:一个是html2canvas,构造好DOM然后转canvas进行截图;另外使用vue-canvas-poster(这个截止到2021年3月...

    31276次阅读 2464人点赞 发布时间: 2021-03-02 09:04:51 立即查看
  • Vue

    vue-router4过度动画无效解决方案

    在初次使用vue3+vue-router4时候,先后遇到了过度动画transition进入和退出分别无效的情况,搜遍百度没没找到合适解决方法,包括vue-route4有一些API都进行了变化,以前的一些操...

    27311次阅读 2106人点赞 发布时间: 2021-02-23 13:37:20 立即查看
交流 收藏 目录