Лучшая практика для ошибок в API RESTful

Каковы наилучшие методы для возврата кодов состояния HTTP в RESTful API? Я использую Laravel 4 для своей PHP-структуры.

В случае ошибки следует использовать

return Response::json('User Exists', 401); 

или

включить флаг для success

 return Response::json([ 'success' => false, 'data' => 'User Exists'], 401 ); 

или

используйте 200 вместо 4xx, полагаясь на success чтобы определить, есть ли ошибка

 return Response::json([ 'success' => false, 'data' => 'User Exists'], 200 ); 

И в случае успеха и нет необходимости возвращать какие-либо данные, вы все еще возвращаете что-нибудь?

Код API PHP

 public function getCheckUniqueEmail() { // Check if Email already exist in table 'users' $uniqueEmail = checkIfEmailExists(); // Return JSON Response if($uniqueEmail) { // Validation fail (user exists) return Response::json('User Exists', 401); } else { // Validation success // - Return anything? } } 

Когда вы посмотрите на список доступных кодов состояния HTTP , вы в какой-то момент поймете, что их много, но они используются сами по себе, они не могут действительно объяснить ошибку сами по себе.

Поэтому, чтобы ответить на ваш вопрос, есть две части. Один из них: как ваш API может объяснить причины ошибки и добавить полезную информацию, которую пользователь API (который в большинстве случаев является другим разработчиком) может читать и действовать. Вы должны добавить как можно больше информации, как машиночитаемые, так и общедоступные.

Другая часть: Как коды состояния HTTP помогают различать определенные состояния ошибки (и успеха)?

Эта последняя часть на самом деле сложнее, чем одна вещь. Есть очевидные случаи, когда 404 используется, чтобы сказать «не найден». И 500 для любых ошибок, которые являются серверными.

Я бы не использовал статус 401, если я действительно не хочу, чтобы операция была успешной, если есть аутентификационные данные аутентификации HTTP. 401 обычно запускает диалоговое окно в браузере, что плохо.

В случае уникального и уже существующего источника, статус «409 конфликт» представляется подходящим. И если создание пользователя преуспевает, статус «201 создан» тоже звучит неплохо.

Обратите внимание, что существует намного больше кодов состояния, некоторые из которых связаны с расширениями протокола HTTP (например, DAV), некоторые полностью нестандартизированы (например, статус «420 улучшить ваше спокойствие» из API Twitter). Посмотрите http://en.wikipedia.org/wiki/List_of_HTTP_status_codes, чтобы узнать, что было использовано до сих пор, и решите, хотите ли вы использовать что-то подходящее для ваших случаев ошибок.

По моему опыту, легко просто выбрать код состояния и использовать его, но это сложно сделать последовательно и в соответствии с существующими стандартами.

Однако я бы не остановился здесь, потому что другие могли жаловаться. 🙂 Выполнение интерфейсов RESTful – это сложная задача сама по себе, но чем больше интерфейсов существует, тем больше опыта собрано.

Редактировать:

Что касается управления версиями: считается неправильной практикой поместить тег версии в URL-адрес, например: example.com/api/v1/stuff Будет работать, но это не приятно.

Но в первую очередь: как ваш клиент указывает, какой вид представления он хочет получить, т.е. как он может решить либо получить JSON, либо XML? Ответ: с заголовком Accept . Он может отправить Accept: application/json для JSON и Accept: application/xml для XML. Он может даже принимать несколько типов, и именно сервер должен решить, что вернуть.

Если сервер не отвечает на более чем одно представление ресурса (JSON или XML, выбранный клиентом), для клиента действительно не так много выбора. Но по-прежнему хорошо, что клиент отправил хотя бы «application / json» в качестве своего единственного выбора, а затем возвратил заголовок Content-type: application/json в ответ. Таким образом, обе стороны дают понять, что они ожидают, что другая сторона увидит содержимое.

Теперь для версий. Если вы поместите версию в URL-адрес, вы фактически создадите разные ресурсы (v1 и v2), но на самом деле у вас есть только один ресурс (= URL) с различными методами для доступа к нему. Создание новой версии API должно происходить, когда происходит нарушение изменения параметров запроса и / или представление в ответе, которое несовместимо с текущей версией.

Поэтому, когда вы создаете API, который использует JSON, вы не имеете дело с универсальным JSON. Вы имеете дело с конкретной структурой JSON, которая как-то уникальна для вашего API. Вы можете и, вероятно, должны указывать это в Content-type отправленном сервером. Расширение «vendor» существует для этого: Content-type: application/vnd.IAMVENDOR.MYAPI+json расскажет миру, что базовая структура данных – это приложение / json, но именно ваша компания и ваш API действительно говорят, какая структура ждать. И именно там версия для API-запроса подходит: application/vnd.IAMVENDOR.MYAPI-v1+json .

Поэтому вместо того, чтобы Accept: application/vnd.IAMVENDOR.MYAPI-v1+json версию в URL-адресе, вы ожидаете, что клиент отправит заголовок Accept: application/vnd.IAMVENDOR.MYAPI-v1+json , и вы ответите Content-type: application/vnd.IAMVENDOR.MYAPI-v1+json . Это действительно ничего не меняет для первой версии, но давайте посмотрим, как все развивается, когда вступает в игру версия 2.

Подход URL-адресов создаст совершенно несвязанный набор новых ресурсов. Клиент будет задаваться вопросом, является ли example.com/api/v2/stuff тем же ресурсом, что и example.com/api/v1/stuff . Клиент мог создать некоторые ресурсы с помощью API v1, и он сохранил URL-адреса для этого материала. Как он должен обновить все эти ресурсы до v2? Ресурсы действительно не изменились, они одинаковы, единственное, что изменилось, это то, что они выглядят по-разному в v2.

Да, сервер может уведомить клиента, отправив перенаправление на URL-адрес v2. Но перенаправление не сигнализирует о том, что клиенту также необходимо обновить клиентскую часть API.

При использовании заголовка accept с версией URL-адрес ресурса одинаковый для всех версий. Клиент решает запросить ресурс с версией 1 или 2, и сервер может быть таким добрым и до сих пор отвечает на запросы версии 1 с ответами версии 1, но все запросы версии 2 с новыми и блестящими ответами версии 2.

Если сервер не может ответить на запрос версии 1, он может сообщить клиенту, отправив статус HTTP «406 Not Acceptable» (запрошенный ресурс способен генерировать контент не приемлемым в соответствии с заголовками Accept, отправленными в запросе).

Клиент может отправлять заголовок accept с включенными обеими версиями, что позволяет серверу отвечать тем, который ему больше всего нравится, т. Е. Смарт-клиент может реализовать версии 1 и 2 и теперь отправляет как заголовок accept, так и ожидает обновления сервера от версии 1 до 2. Сервер будет сообщать в каждом ответе, является ли он версией 1 или 2, и клиент может действовать соответственно – ему не нужно знать точную дату обновления версии сервера.

Подводя итог: для очень простого API с ограниченным, возможно, внутренним, использование даже с версией может быть излишним. Но вы никогда не знаете, будет ли это правдой через год. Всегда очень хорошая идея включить номер версии в API. И самое лучшее место для этого – внутри типа mime, который ваш API собирается использовать. Проверка единственной существующей версии должна быть тривиальной, но у вас есть возможность прозрачного обновления позже, не запутывая существующих клиентов.

Я бы не использовал 200 статусов для всего. Это просто сбивает с толку.

JQuery позволяет обрабатывать разные коды ответов по-разному, есть встроенные методы, которые уже используют их в ваших приложениях, и когда ваше приложение растет, вы можете предоставить API для других пользователей.

Редактировать: Также я настоятельно рекомендую посмотреть этот разговор о Laravel и разработке API:

http://kuzemchak.net/blog/entry/laracon-slides-and-video

Есть список HTTP-кода состояния при Illuminate\Http\Response которые распространяются на Symfony\Component\HttpFoundation\Response . Вы можете использовать это в своем классе.

Например:

 use Illuminate\Http\Response as LaravelResponse; ... return Response::json('User Exists', LaravelResponse::HTTP_CONFLICT); 

Это гораздо более читаемо.