Большие файлы
Видео, архивы, дампы баз весят сотни мегабайт и больше. files()->upload() справится с ними
и сам, а uploads()->send() даёт то, что нужно для долгой загрузки: прогресс, id сессии и
возможность продолжить с места обрыва — даже после перезапуска процесса.
Загрузить с прогрессом
$file = $s5w->uploads()->send(
'/backups/db-2026-11-07.sql',
folder: 'backups',
onProgress: fn(int $sent, int $total) => printf("%d%%\n", $sent * 100 / $total),
);0%
8%
16%
…
99%
100%send() режет файл на части того размера, который просит сервер (сейчас 8 МиБ), отправляет
их по порядку и собирает файл в конце. Возвращает тот же File, что и обычная загрузка.
onProgress получает два числа: $sent — сколько байт уже на сервере, $total —
размер файла. Он вызывается:
| Когда | $sent |
|---|---|
| в начале, до первой части | 0 |
| после каждой части, которую подтвердил сервер | новое смещение |
| после восстановления, если часть дошла, а ответ потерялся | тоже вызывается — прогресс не теряется |
при resume() — первым вызовом |
место, с которого продолжаем |
| при дедупликации (передавать ничего не пришлось) | один вызов сразу с $sent === $total |
$total всегда больше нуля — пустую загрузку сервер не принимает, — так что деление
безопасно.
Сохранить id сессии
Если процесс может прерваться посреди загрузки — задача в очереди, воркер, который
перезапускают при деплое, — сохраните id сессии сразу после её открытия. Для этого есть
onStart:
use Flytachi\Winter\SDK\S5w\Entity\UploadSession;
$file = $s5w->uploads()->send(
$path,
folder: 'videos',
onStart: fn(UploadSession $session) => $job->saveUploadId($session->id),
onProgress: fn(int $sent, int $total) => $job->saveProgress($sent, $total),
);
$job->markDone($file->id);onStart вызывается один раз, до передачи первого байта. Если содержимое оказалось в бакете
и передавать ничего не нужно (дедупликация), сессии нет и onStart не вызывается.
Продолжить после сбоя
Сбой связи посреди загрузки
Обрыв одной части send() переживает сам: ждёт, спрашивает сервер, сколько тот получил, и
продолжает с этого места. Если связь так и не вернулась — по умолчанию три попытки на часть с
паузами 1 и 2 секунды, — send() бросает UploadInterruptedException. Сессия при этом остаётся
на сервере, и её можно продолжить:
use Flytachi\Winter\SDK\S5w\Exception\UploadInterruptedException;
try {
$file = $s5w->uploads()->send($path, folder: 'videos');
} catch (UploadInterruptedException $e) {
$logger->warning("Загрузка прервана на {$e->offset()} из {$e->size()} байт");
// позже — когда сеть вернётся
$file = $s5w->uploads()->resume($e->sessionId(), $path);
}Перезапуск процесса
Если процесс упал или его перезапустили, исключения уже не будет — но будет id сессии,
который вы сохранили в onStart. По нему и продолжайте:
$uploadId = $job->uploadId();
if ($uploadId !== null) {
$file = $s5w->uploads()->resume(
$uploadId,
$path,
onProgress: fn(int $sent, int $total) => $job->saveProgress($sent, $total),
);
}resume() спрашивает сервер, сколько байт он уже держит, проверяет, что файл того же размера,
что был заявлен при старте, и отправляет остаток. Первый вызов onProgress покажет, с какого
места продолжили, — например 45%.
Что может пойти не так при resume() |
Что будет |
|---|---|
| сессии уже нет: прошло больше суток без новых частей, её отменили или уже завершили | NotFoundException — начните загрузку заново |
| передан другой файл (размер не совпадает) | S5wException с текстом … not the same file |
Сессия живёт 24 часа
Незавершённая сессия удаляется сервером через 24 часа после последней полученной части. Пока сессия не завершена, место в квоте бакета она не занимает.
Дедупликация: не передавать то, что уже есть
Перед открытием сессии send() считает SHA-256 файла и сообщает его серверу. Если такое
содержимое в бакете уже есть, сервер сразу создаёт файл, и передавать ничего не нужно:
$first = $s5w->uploads()->send('/backups/big.sql', folder: 'backups'); // передаёт 100 МБ
$second = $s5w->uploads()->send('/backups/big.sql', folder: 'backups'); // ~0,3 с, ничего не передаёт
$second->deduplicated; // true
$second->name; // "big (1).sql"Хеш считается одним проходом по файлу на диске — для гигабайтного файла это около секунды, и это окупается, если файл уже загружался. Если вы знаете, что содержимое новое, отключите проверку:
$s5w->uploads()->send($path, deduplicate: false);С опциями обработки картинок (imageOptions) хеш не отправляется никогда: в бакете хранится
обработанный результат, и хеш оригинала с ним не совпадёт. Подробнее — в разделе
Конвейер загрузки.
Из памяти
Для больших данных, которые уже лежат в памяти, есть sendContent() — те же параметры, что
у send(), только вместо пути байты и обязательное имя:
$file = $s5w->uploads()->sendContent($archiveBytes, 'export.zip', folder: 'exports');resume() работает только с файлом на диске — данные из памяти после перезапуска процесса
всё равно потеряны.
Таймауты
Каждая часть отправляется отдельным запросом с таймаутом transferTimeout (по умолчанию 300
секунд на часть). Для обычного канала этого с запасом: часть в 8 МиБ на скорости 10 МБ/с
уходит меньше чем за секунду. Если канал очень медленный, увеличьте значение:
$config = (new S5wConfig($url, $key))->withTransferTimeout(900);Число попыток на часть (3) не настраивается — это константа Uploads::ATTEMPTS.
Что выбрать
| Нужно | Вызов |
|---|---|
| просто загрузить, неважно какого размера | files()->upload() |
| показать прогресс | uploads()->send(…, onProgress: …) |
| продолжить после перезапуска | uploads()->send(…, onStart: …) + uploads()->resume() |
| свой цикл, файл приходит кусками | Загрузка по шагам |
См. также
Uploads::send()иUploads::resume()— все параметры- Конвейер загрузки — как устроены повторы и восстановление
- Загрузка частями на сервере — протокол
/v1/uploads