diff --git a/server/app/common/service/DifyChatService.php b/server/app/common/service/DifyChatService.php index 745e7b5b7..a60cc2b76 100644 --- a/server/app/common/service/DifyChatService.php +++ b/server/app/common/service/DifyChatService.php @@ -726,6 +726,21 @@ class DifyChatService ]; } + /** + * libcurl 7.32+ exposes CURLOPT_XFERINFOFUNCTION, while CentOS/RHEL 7 commonly + * ships libcurl 7.29 with only CURLOPT_PROGRESSFUNCTION. Resolve the option + * by name so loading this class never evaluates an undefined PHP constant. + */ + private static function curlProgressOption(): ?int + { + foreach (['CURLOPT_XFERINFOFUNCTION', 'CURLOPT_PROGRESSFUNCTION'] as $name) { + if (defined($name)) { + return (int) constant($name); + } + } + return null; + } + /** * @param array $payload * @param callable(string):mixed $onDelta @@ -783,7 +798,7 @@ class DifyChatService self::consumeStreamBytes($protocol, $buffer, $chunk, $state, $onDelta); return $state['callback_error'] ? 0 : strlen($chunk); }; - $progress = static function () use (&$state, $shouldAbort): int { + $progress = static function (...$unused) use (&$state, $shouldAbort): int { if ($shouldAbort !== null && $shouldAbort()) { $state['client_aborted'] = true; return 1; @@ -791,7 +806,7 @@ class DifyChatService return 0; }; - curl_setopt_array($ch, [ + $curlOptions = [ CURLOPT_URL => $url, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, @@ -807,9 +822,13 @@ class DifyChatService ], CURLOPT_HEADERFUNCTION => $header, CURLOPT_WRITEFUNCTION => $write, - CURLOPT_NOPROGRESS => false, - CURLOPT_XFERINFOFUNCTION => $progress, - ]); + ]; + $progressOption = self::curlProgressOption(); + if ($progressOption !== null) { + $curlOptions[CURLOPT_NOPROGRESS] = false; + $curlOptions[$progressOption] = $progress; + } + curl_setopt_array($ch, $curlOptions); curl_exec($ch); $errno = curl_errno($ch); diff --git a/server/tests/DifyChatStreamContractTest.php b/server/tests/DifyChatStreamContractTest.php index 22257344a..fe05c63c1 100644 --- a/server/tests/DifyChatStreamContractTest.php +++ b/server/tests/DifyChatStreamContractTest.php @@ -95,6 +95,25 @@ $explicitOpenAi = callDifyStreamPrivate('buildRequestSpecs', [ difyStreamExpect(count($explicitDify) === 1 && $explicitDify[0]['protocol'] === 'dify', 'explicit Dify endpoint never changes protocol'); difyStreamExpect(count($explicitOpenAi) === 1 && $explicitOpenAi[0]['protocol'] === 'openai', 'explicit OpenAI endpoint never changes protocol'); +$progressOption = callDifyStreamPrivate('curlProgressOption', []); +$expectedProgressOption = null; +foreach (['CURLOPT_XFERINFOFUNCTION', 'CURLOPT_PROGRESSFUNCTION'] as $optionName) { + if (defined($optionName)) { + $expectedProgressOption = (int) constant($optionName); + break; + } +} +difyStreamExpect( + $progressOption === $expectedProgressOption, + 'stream cancellation selects the newest cURL progress callback available at runtime' +); +$serviceSource = file_get_contents(dirname(__DIR__) . '/app/common/service/DifyChatService.php'); +difyStreamExpect( + is_string($serviceSource) + && str_contains($serviceSource, "'CURLOPT_XFERINFOFUNCTION', 'CURLOPT_PROGRESSFUNCTION'"), + 'stream cancellation retains the libcurl 7.29 progress callback fallback' +); + $retryableStream = [ 'errno' => 0, 'http_code' => 200,