php中curl请求第三方接口的使用
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源的数据量很大或发送频率很高,请考虑添加适当的缓冲机制,以避免服务器过载。
最后,请注意,此示例代码是一个简单的起点,您可能需要根据您的具体需求和环境对其进行调整和优化。
*/
?>