This commit is contained in:
Your Name
2026-10-23 11:39:56 +08:00
parent 86476920cf
commit ff98014fa2
11 changed files with 766 additions and 20 deletions
@@ -42,6 +42,7 @@ final class DifyRequiredAttachmentUpload
array $options = []
): array {
$temporary = [];
$multi = null;
$skipped = [];
$index = 0;
$total = 0;
@@ -78,6 +79,11 @@ final class DifyRequiredAttachmentUpload
}
}
unset($item);
// Keep libcurl's connection cache only for this preparation. Each
// transfer still owns a fresh easy handle, so source downloads never
// inherit the Dify upload headers or request body.
$multi = curl_multi_init();
if ($multi === false) self::fail('CURL_UNAVAILABLE');
$prepared = [];
$uploadedIndices = [];
$cache = [];
@@ -99,7 +105,7 @@ final class DifyRequiredAttachmentUpload
if (isset($item['path'])) {
self::copyLocal($item['path'], $path, $maxBytes, $deadline, $shouldAbort);
} else {
$exists = self::download($item['url'], $item['parts'], $path, $maxBytes, $deadline, $shouldAbort, $options);
$exists = self::download($item['url'], $item['parts'], $path, $maxBytes, $deadline, $shouldAbort, $options, $multi);
}
self::check($deadline, $shouldAbort);
if (!$exists) {
@@ -113,7 +119,7 @@ final class DifyRequiredAttachmentUpload
'audio/wav' => 'wav', 'audio/mpeg' => 'mp3', 'text/plain' => 'txt'][$mime] ?? 'bin';
}
$cache[$cacheKey] = self::upload($endpoint, $apiKey, $user, $path, $mime,
'attachment-' . $index . '.' . $extension, $deadline, $shouldAbort, $options['ca_file']);
'attachment-' . $index . '.' . $extension, $deadline, $shouldAbort, $options['ca_file'], $multi);
}
}
if ($cache[$cacheKey] === null) {
@@ -155,6 +161,7 @@ final class DifyRequiredAttachmentUpload
'failed_index' => $index, 'attachment_count' => $total, 'skipped' => $skipped];
} finally {
foreach ($temporary as $path) if (is_file($path)) @unlink($path);
if ($multi !== null && $multi !== false) curl_multi_close($multi);
}
}
@@ -340,7 +347,7 @@ final class DifyRequiredAttachmentUpload
}
}
private static function download(string $url, array $parts, string $path, int $maxBytes, float $deadline, ?callable $shouldAbort, array $options): bool
private static function download(string $url, array $parts, string $path, int $maxBytes, float $deadline, ?callable $shouldAbort, array $options, $multi): bool
{
self::check($deadline, $shouldAbort);
$host = trim($parts['host'], '[]');
@@ -391,7 +398,7 @@ final class DifyRequiredAttachmentUpload
return $written;
};
if (!curl_setopt_array($ch, $curlOptions)) self::fail('REQUIRED_FILES_DOWNLOAD_FAILED');
$result = self::execute($ch, $deadline, $shouldAbort);
$result = self::execute($multi, $ch, $deadline, $shouldAbort);
if ($writeError !== '') self::fail($writeError);
if ($result['errno'] === CURLE_OPERATION_TIMEDOUT) self::fail('REQUIRED_FILES_TIMEOUT');
if ($result['errno'] !== CURLE_OK) self::fail('REQUIRED_FILES_DOWNLOAD_FAILED');
@@ -426,7 +433,7 @@ final class DifyRequiredAttachmentUpload
&& ($remaining === 0 || (ord($packed[$bytes]) & (255 << (8 - $remaining))) === (ord($base[$bytes]) & (255 << (8 - $remaining))));
}
private static function upload(string $endpoint, string $apiKey, string $user, string $path, string $mime, string $name, float $deadline, ?callable $shouldAbort, string $caFile): string
private static function upload(string $endpoint, string $apiKey, string $user, string $path, string $mime, string $name, float $deadline, ?callable $shouldAbort, string $caFile, $multi): string
{
self::check($deadline, $shouldAbort);
$ch = curl_init();
@@ -444,7 +451,7 @@ final class DifyRequiredAttachmentUpload
return strlen($chunk);
};
if (!curl_setopt_array($ch, $options)) self::fail('REQUIRED_FILES_UPLOAD_FAILED');
$result = self::execute($ch, $deadline, $shouldAbort);
$result = self::execute($multi, $ch, $deadline, $shouldAbort);
if ($result['errno'] === CURLE_OPERATION_TIMEDOUT) self::fail('REQUIRED_FILES_TIMEOUT');
if ($result['errno'] !== CURLE_OK || $tooLarge || !in_array($result['status'], [200, 201], true)) self::fail('REQUIRED_FILES_UPLOAD_FAILED');
$decoded = json_decode($body, true);
@@ -475,10 +482,9 @@ final class DifyRequiredAttachmentUpload
return $options;
}
/** curl_multi polling observes cancellation even while no headers/body arrive. */
private static function execute($ch, float $deadline, ?callable $shouldAbort): array
/** Request-local pool retains connections; polling also observes silent cancellation. */
private static function execute($multi, $ch, float $deadline, ?callable $shouldAbort): array
{
$multi = curl_multi_init();
$added = false;
try {
if (curl_multi_add_handle($multi, $ch) !== CURLM_OK) self::fail('REQUIRED_FILES_PREPARE_FAILED');
@@ -495,7 +501,6 @@ final class DifyRequiredAttachmentUpload
return ['errno' => $errno, 'status' => (int) curl_getinfo($ch, CURLINFO_HTTP_CODE)];
} finally {
if ($added) curl_multi_remove_handle($multi, $ch);
curl_multi_close($multi);
}
}