php中curl请求第三方接口的使用

3797次阅读 301人点赞 作者: WuBin 发布时间: 2024-05-29 16:57:23
扫码到手机查看

curl请求非SSE

使用POST方式 从服务端发起请求

// 传递的post参数
$paramsStr = json_encode([...数组...], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);

$curl = curl_init();
curl_setopt_array($curl, array(
    CURLOPT_URL => '请求的接口地址',
    CURLOPT_TIMEOUT => 30,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_SSL_VERIFYPEER  => false,
    CURLOPT_SSL_VERIFYHOST  => false,
    CURLOPT_CUSTOMREQUEST => 'POST',
    
    CURLOPT_POSTFIELDS => $paramsStr,

    // 根据第三方的要求设置请求头
    CURLOPT_HTTPHEADER => array(
        'Content-Type: application/json',
        "Authorization: Bearer {$apikey}"
    )

));
$response = curl_exec($curl);
curl_close($curl);
$res = json_decode($response, true);
return $res;

由于是一次性的,最终结果直接输出到浏览器即可。

curl请求SSE

注意这里有可能出现报错问题!

如果报错:SSL certificate problem: unable to get local issuer certificate  问题参考

简单说就是:cURL错误60通常表示SSL证书问题。本地开发环境下务必要设置:

 curl_setopt_array($curl, array(
            CURLOPT_SSL_VERIFYPEER  => false,
            CURLOPT_SSL_VERIFYHOST  => false,
            ....
));

上面两项决定了是否验证SSL证书,设置为false则为不验证。

指定SSL证书路径:你可以通过设置cURL选项CURLOPT_CAINFO来指定SSL证书的路径。你需要提供一个有效的CA证书文件的绝对路径。例如:

curl_setopt($ch, CURLOPT_CAINFO, '/path/to/cacert.pem');
注意,在生产环境中禁用SSL证书验证是不安全的,仅在开发和测试阶段使用。

示例

当使用php脚本做桥梁,从第三方请求SSE接口然后以SSE的流形式实时返回前端。我们先看一个简单的例子:

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');

// 可以定义一个函数来发送事件
function sendEvent($id, $event, $data) {
    echo "id: $id" . PHP_EOL;
    echo "event: $event" . PHP_EOL;
    echo "data: $data" . PHP_EOL . PHP_EOL;
    ob_flush();
    flush();
}

// 发送初始化的id
sendEvent(time(), 'init', 'initialized');

// 模拟一个无限循环以持续发送事件
while (true) {
    // 这里可以添加你的逻辑来检测事件并发送它们
    sendEvent(time(), 'message', 'Hello, EventSource!');

    // 为了简化,每隔一定时间发送一次事件
    sleep(1);
}
?>

再来看一个通过脚本直接实现将获取的SSE转化为本地SSE输出的例子

<?php
$API_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxx';
$URL_TALKING = 'xxxxxxxxxxxxxxxxxxxxx';
$query = '我被人打了,我还手了,请问该如何维权';

// 假设你有一个要发送的数据数组
$params = [
    "model" => 'farui-plus', 
    'prompt' => $query,
    'input' => [
        'prompt' => $query,
        'message' =>  [
            [
                "role" => "system",
                "content" => 'You are a helpful assistant.'
            ],
            [
                "role" => "user",
                "content" => $query
            ]
        ]
    ],
    'parameters' => [
        "result_format" => "message"
    ]
];

ob_start();

// 初始化cURL
$ch = curl_init();

// 设置cURL选项
curl_setopt($ch, CURLOPT_URL, $URL_TALKING); // SSE服务器URL
curl_setopt($ch, CURLOPT_POST, true); // 发送POST请求
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params)); // POST数据
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 返回响应内容,而不是直接输出
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 跟随重定向
curl_setopt($ch, CURLOPT_HEADER, false); // 不返回头部信息
// 设置请求头
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    "Authorization: Bearer {$API_KEY}",
    'X-DashScope-SSE: enable'
]); 
/**
 * 它用于指定一个回调函数,这个回调函数将会接收 cURL 请求的数据。
 * 这个回调函数应该有两个参数,第一个是 data,第二个是 回调函数
 * 函数会在 cURL 接收到数据时被调用。
 */
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $chunk) {
    // 将SSE响应直接输出给浏览器
    echo $chunk;
    ob_flush(); // 刷新输出缓冲区
    flush(); // 发送缓冲区内容到浏览器
    usleep(1000);
    return strlen($chunk);
});

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

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

关于对CURLOPT_WRITEFUNCTION的解释,请参考这里:https://www.wubin.work/blog/articles/528

这里不深究对第三方SSE内容的格式化和输出,主要列出我实际项目中用到的最终代码:

可供参考的SSE-header头设置

// 缓冲区没开启就开启
if (ob_get_level() <= 0) {
    ob_start();
}

set_time_limit(0);
ini_set('output_buffering', 'off');
// ini_set('zlib.output_compression', false);

header('Content-Type: text/event-stream; Charset=UTF-8');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
header('X-Accel-Buffering: no');
$paramsStr = json_encode([..传参数组..], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);

// 初始化cURL
$ch = curl_init();

curl_setopt_array($ch, array(
    CURLOPT_URL => '第三方接口地址',
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $paramsStr, // 携带的post参数
    CURLOPT_RETURNTRANSFER => true,
    // 如果有重定向,则跟随重定向
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_HEADER => false,
    
    // 第三方要求的header
    CURLOPT_HTTPHEADER => array(
        'Content-Type: application/json',
        "Authorization: Bearer {$apikey}",
        'X-DashScope-SSE: enable'
    )

));

curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $chunk) {
    // 输出间隔时间
    sleep(1);

    // 将SSE响应直接输出给浏览器
    echo $chunk;


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

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

if(curl_error($ch)) {
    $errMsg = 'Error: ' . curl_error($ch);
    echo  $errMsg
}

// 关闭cURL资源
curl_close($ch);
注意,对SSE每次轮询结果的格式化,推荐使用正则匹配+循环的方式,因为不同第三方在最后输出的格式不一定一样,比如通常使用\n\n或者PHP_EOL进行分隔,但是其最终结果中并不包含换行符。
$event = <<<EOD
id:2
event:result
:HTTP_STATUS/200
data:{"output":{"choices":[{"message":{"content":"【相关法条】\n[1]《中华人民","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":22,"input_tokens":11,"output_tokens":11},"request_id":"353b00ab-222a-9bae-ad03-ef66bc500c9b"}
EOD;

$event = trim($event);
$patterns = array(
    // 匹配id:1
    'id'          =>    '/id:[\d]+/',  
    // 匹配event:result 
    'event'       =>    '/event:[\w]+/',
    // 匹配 :HTTP_STATUS/200
    'http_status' =>    '/:HTTP_STATUS\/[\d]+/',
    // 匹配 data
    'data'        =>    '/data:[\s\S]*/'
);

$resultArray = [];
$outputRow = [];

// 遍历每个正则表达式模式
foreach ($patterns as $patternKey => $pattern) {
    // 执行正则表达式匹配,并将结果存储到 $matches 数组中
    preg_match($pattern, $event, $matches);
    $line = isset($matches[0]) ? $matches[0] : '';
    if($line) {
        // 使用冒号分割键和值
        list($key, $value) = explode(':', $line, 2);
        $key = trim($key);
        $value = trim($value);

        // 当key为空并且包含http等字符的时候 获取http状态码
        if(empty($key) && stripos($value, 'HTTP_STATUS') !== false) {
            $key = 'http_status';
            $value = explode('/', $value)[1];
        }

        // 对于data键,我们需要进一步处理JSON字符串
        if ($key === 'data') {
            $value = json_decode($value, true); // 将JSON字符串转换为数组
        }
    } else {
        $value = '';
    }

    $resultArray[$key] = $value;
}

$choices = $resultArray['data']['output']['choices'][0];
$usage = $resultArray['data']['usage'];
$totalTokens = $usage['total_tokens'];

// 将最终结果进行格式化
$outputRow = [
    'id' => $resultArray['id'],
    'event' => 'message',
    'http_status' => "HTTP_STATUS/{$resultArray['http_status']}",
    'data' => [
        'role' => $choices['message']['role'],
        'message' => $choices['message']['content'],
        'finish_reason' => $choices['finish_reason'],
        'total_tokens' => $totalTokens
    ]
];
return $outputRow;

/* 最终结果
[
    'id' => 2,
    'event' => 'message',
    'http_status' => "HTTP_STATUS/200",
    'data' => [
        'role' => 'assistant',
        'message' => '【相关法条】\n[1]《中华人民"',
        'finish_reason' => 'null',
        'total_tokens' => 22
    ]
]
*/

请求SSE的其他方法记录

获取并解析SSE的响应

<?php
// 初始化cURL会话
$ch = curl_init();
 
// 设置cURL选项
curl_setopt($ch, CURLOPT_URL, 'http://your-sse-server.com/events'); // 替换为你的SSE服务器地址
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 
// 启用SSE流模式
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Accept: text/event-stream',
    'Cache-Control: no-cache'
));
 
// 执行cURL请求
$response = curl_exec($ch);
 
// 解析SSE响应
$events = explode("n", $response);
foreach ($events as $event) {
    if (strpos($event, 'event: ') === 0) {
        $eventParts = explode(":", $event, 2);
        $eventName = trim(end($eventParts));
        $data = '';
        continue;
    }
    if (strpos($event, 'data: ') === 0) {
        $data .= trim(substr($event, 6));
        if (substr($data, -2) == 'n\n') {
            $data = trim(substr($data, 0, -2));
            echo "Received event '{$eventName}': {$data}n";
            $data = '';
        }
        continue;
    }
    if ($event == "n") {
        // 处理完整事件
        continue;
    }
}
 
// 关闭cURL会话
curl_close($ch);
?>

使用while循环实现SSE的输出

<!-- 若您想要使用PHP来请求另一个SSE(Server-Sent Events)源,并将这些事件以SSE的形式实时推送给您的客户端,您需要创建一个PHP脚本来作为中间代理。这个脚本会向SSE源发起请求,并实时将接收到的数据转发给连接该PHP脚本的客户端。

以下是一个简单的PHP脚本示例,展示了如何实现这一功能:

php -->
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');

// 连接到SSE源
$sseUrl = 'http://your-sse-source-url.com/events';
$ch = curl_init($sseUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_exec($ch);
curl_setopt($ch, CURLOPT_TIMEOUT, 0);

// 无限循环以持续读取SSE源并转发给客户端
while (true) {
    // 重置cURL的选项以准备下一次读取
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        'Accept: text/event-stream',
        'Cache-Control: no-cache',
        'Connection: keep-alive'
    ));

    // 执行请求以读取SSE源的新数据
    $data = curl_exec($ch);

    // 检查是否有数据可读
    if ($data === false) {
        // 可能是连接断开,重新连接SSE源
        curl_close($ch);
        $ch = curl_init($sseUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_NOBODY, true);
        curl_exec($ch);
        continue;
    }

    // 发送数据给客户端
    echo $data;
    flush(); // 刷新输出缓冲区

    // 稍微延迟一下,避免过于频繁地发送数据
    usleep(1000);
}

// 关闭cURL连接(这行代码实际上永远不会被执行,因为上面的循环是无限的)
curl_close($ch);

/*
在这个脚本中,我们首先设置了适当的HTTP头部,以便告诉客户端这是一个SSE流。然后,我们使用cURL来连接到SSE源,并在一个无限循环中读取数据。每次我们读取到新的数据时,就将其发送(echo)给客户端,并使用flush()来确保数据被立即发送给客户端。

注意,这个脚本假设SSE源是长连接的,并且会不断地推送事件。如果SSE源在任何时候断开连接,我们的脚本将尝试重新连接。

安全性注意事项:

确保您的服务器环境支持长时间运行的脚本。一些服务器配置可能会限制脚本执行的时间。
如果SSE源需要身份验证,请确保在cURL请求中包含正确的身份验证信息。
如果SSE源的数据量很大或发送频率很高,请考虑添加适当的缓冲机制,以避免服务器过载。
最后,请注意,此示例代码是一个简单的起点,您可能需要根据您的具体需求和环境对其进行调整和优化。
*/
?>

点赞 支持一下 觉得不错?客官您就稍微鼓励一下吧!
关键词:sse
推荐阅读
  • uniapp实现被浏览器唤起的功能

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

    9352次阅读 603人点赞 发布时间: 2022-12-14 16:34:53 立即查看
  • Vue

    盘点Vue2和Vue3的10种组件通信方式

    Vue中组件通信方式有很多,其中Vue2和Vue3实现起来也会有很多差异;本文将通过选项式API组合式API以及setup三种不同实现方式全面介绍Vue2和Vue3的组件通信方式。

    4072次阅读 302人点赞 发布时间: 2022-08-19 09:40:16 立即查看
  • JS

    几个高级前端常用的API

    推荐4个前端开发中常用的高端API,分别是MutationObserver、IntersectionObserver、getComputedstyle、getBoundingClientRect、requ...

    14279次阅读 934人点赞 发布时间: 2021-11-11 09:39:54 立即查看
  • PHP

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

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

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

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

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

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

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

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

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

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

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

    Vue+html2canvas截图空白的问题

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

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

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

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

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