Обработка ошибок
Всё, что бросает SDK, наследует S5wException, а класс исключения говорит, что случилось.
Ветвитесь по классу; текст сообщения — для логов, он может меняться.
Три вида сбоев
| Что случилось | Исключение | Повтор поможет? |
|---|---|---|
| сервер не ответил: нет сети, DNS, отказ соединения, таймаут | TransportException |
да, позже |
| сервер ответил, но не как S5W: не JSON, не те поля | ResponseException |
нет — неверный адрес или несовместимая версия |
| сервер отказал: ответ с кодом ошибки | ApiException и его наследники |
зависит от кода, см. ниже |
Полная иерархия и соответствие кодам — в справочнике Исключения.
Ключ не работает или сервер недоступен
Это разные ситуации, и реагировать на них нужно по-разному: мёртвый ключ надо заменить, а недоступный сервер — подождать.
use Flytachi\Winter\SDK\S5w\Exception\ForbiddenException;
use Flytachi\Winter\SDK\S5w\Exception\TransportException;
use Flytachi\Winter\SDK\S5w\Exception\UnauthorizedException;
try {
$s5w->check();
} catch (UnauthorizedException) {
// 401: сервер не знает такой ключ — ошибка в конфигурации
} catch (ForbiddenException) {
// 403: ключ известен, но отключён или истёк — нужен новый, повтор не поможет
} catch (TransportException $e) {
// сервер не ответил — можно повторить позже
if ($e->isTimeout()) {
// именно таймаут, а не отказ соединения
}
}Для проверок здоровья приложения есть короткая форма:
if (!$s5w->isValid()) {
// ключ отклонён (401 или 403)
}isValid() возвращает false только когда сервер отказал ключу. Если сервер недоступен,
он по-прежнему бросает TransportException — «не удалось узнать» не то же самое, что «нет».
ForbiddenException бывает и с рабочим ключом: если ключ BASIC используется для folders(),
files() или uploads(), которым нужен FULL.
Нет такого файла или папки
use Flytachi\Winter\SDK\S5w\Exception\NotFoundException;
try {
$file = $s5w->files()->get($id);
} catch (NotFoundException) {
return ResponseEntity::notFound();
}Часто проще вообще не ловить исключение:
$file = $s5w->files()->find($id); // null вместо исключения
$folder = $s5w->folders()->find('x');Файл из чужого бакета выглядит точно так же, как несуществующий: сервер нарочно не различает эти случаи.
Несуществующая папка — это 422, а не 404
Если в upload(), list() или move() указана папка, которой нет, сервер отвечает 422:
это ошибка в параметрах запроса, а не «ресурс не найден». Ловите ValidationException.
Ошибки валидации
ValidationException (422) приходит, когда параметры не прошли проверку. У него есть ошибки
по полям — их удобно показать в форме:
use Flytachi\Winter\SDK\S5w\Exception\ValidationException;
try {
$s5w->folders()->create($request->name);
} catch (ValidationException $e) {
$e->errors();
// ["name" => ["must not be blank", "size must be between 1 and 100", …]]
$e->errorsOf('name');
// ["must not be blank", "size must be between 1 and 100", …]
}Бывает, что ошибка относится не к одному полю, а ко всему запросу — нет папки, опции картинки
для не-картинки, хеш не совпал при завершении загрузки. Тогда errors() пустой, а суть — в
сообщении:
S5W v1/files: 422 Folder "no-such" does not exist
S5W v1/files: 422 Image options were given, but this file is not an imageИмя уже занято
ConflictException (409) — при создании или переименовании папки с занятым именем и при
переименовании или переносе файла, если в папке назначения есть файл с таким именем.
use Flytachi\Winter\SDK\S5w\Exception\ConflictException;
try {
$s5w->files()->rename($file, 'report.pdf');
} catch (ConflictException) {
$s5w->files()->rename($file, 'report-' . date('Ymd') . '.pdf');
}Загрузка конфликта не даёт никогда: при совпадении имени сервер сам сохранит файл как
report (1).pdf.
Не хватает места
PayloadTooLargeException (413) при загрузке означает, что квота бакета заполнена:
use Flytachi\Winter\SDK\S5w\Exception\PayloadTooLargeException;
try {
$s5w->files()->upload($path, folder: 'backups');
} catch (PayloadTooLargeException) {
$free = $s5w->check()->storage->free;
$logger->error("Бакет заполнен, свободно {$free} байт");
}Сбои загрузки частями
Загрузка частями сама переживает короткие сбои: повторяет часть до трёх раз, каждый раз спрашивая сервер, сколько он получил. Наружу выходят только два особых исключения:
use Flytachi\Winter\SDK\S5w\Exception\UploadInterruptedException;
use Flytachi\Winter\SDK\S5w\Exception\UploadUnconfirmedException;
try {
$file = $s5w->uploads()->send($path, folder: 'videos');
} catch (UploadInterruptedException $e) {
// повторы кончились; сессия жива 24 часа — можно продолжить
$file = $s5w->uploads()->resume($e->sessionId(), $path);
} catch (UploadUnconfirmedException $e) {
// все байты отправлены и сборка запрошена, но ответ потерялся, а сессии уже нет:
// файл, возможно, создан, но его id неизвестен
$logger->error("Проверьте файл {$e->name()} вручную");
}| Исключение | Что значит | Что делать |
|---|---|---|
UploadInterruptedException |
сеть или сервер так и не ответили после всех попыток | resume($e->sessionId(), $path) позже |
UploadUnconfirmedException |
ответ на сборку потерялся, сессии нет | найти файл по имени в папке или загрузить заново, если дубликат допустим |
любой другой ApiException |
сервер отклонил сами данные (квота, папка, размер части) | исправить причину; SDK уже отменил сессию |
Почему SDK не ищет файл сам в случае UploadUnconfirmedException: поиск по имени может
найти чужой файл с таким же именем, а гадать опаснее, чем честно сообщить. Подробнее — в
разделе Конвейер загрузки.
Поймать всё
use Flytachi\Winter\SDK\S5w\Exception\ApiException;
use Flytachi\Winter\SDK\S5w\S5wException;
try {
$s5w->files()->upload($path);
} catch (ApiException $e) {
$logger->warning($e->getMessage(), [
'status' => $e->status(), // 404, 409, 422…
'method' => $e->method(), // "POST"
'path' => $e->path(), // "v1/files"
'errors' => $e->errors(),
]);
} catch (S5wException $e) {
// сеть, неожиданный ответ, файл не читается с диска
$logger->error($e->getMessage());
}У всех исключений getPrevious() содержит исходную причину, если она была — например,
исключение HTTP-клиента при обрыве связи.
Повторы: что повторять, а что нет
| Исключение | Повторять? |
|---|---|
TransportException, ServerException (5xx) |
да, с паузой |
UploadInterruptedException |
да, через resume() |
UnauthorizedException, ForbiddenException |
нет — нужен другой ключ |
ValidationException, ConflictException, NotFoundException |
нет — нужны другие параметры |
PayloadTooLargeException |
нет, пока не освободится место |
ResponseException |
нет — проверьте адрес сервера |
Обычные вызовы можно повторять автоматически при сетевом сбое — параметр retries в
S5wConfig. Учтите, что delete() не идемпотентен: если первая попытка удалила файл, а ответ
потерялся, повтор получит 404. Загрузка одним запросом не повторяется никогда, даже с
retries — повтор мог бы сохранить файл дважды.
См. также
- Исключения — иерархия, коды, методы
- Большие файлы — продолжение после сбоя
- Формат ошибок на сервере