Безопасность. Обзоры. Ноутбуки. Звуки и карты. Windows

В русскоязычной части Интернета присутствует большое количество статей, посвященных веб-службам на основе SOAP и XML-RPC, но почему-то почти ничего нет про вполне заслуживающую внимания (но менее распространенную) архитектуру RESТ.

В данной статье описываются основы этой архитектуры, возможности и примеры её использования.

Что такое REST

REST (Representational state transfer) – это стиль архитектуры программного обеспечения для распределенных систем, таких как World Wide Web, который, как правило, используется для построения веб-служб. Термин REST был введен в 2000 году Роем Филдингом, одним из авторов HTTP-протокола. Системы, поддерживающие REST, называются RESTful-системами.

В общем случае REST является очень простым интерфейсом управления информацией без использования каких-то дополнительных внутренних прослоек. Каждая единица информации однозначно определяется глобальным идентификатором, таким как URL. Каждая URL в свою очередь имеет строго заданный формат.

А теперь тоже самое более наглядно:

Отсутствие дополнительных внутренних прослоек означает передачу данных в том же виде, что и сами данные. Т.е. мы не заворачиваем данные в XML, как это делает SOAP и XML-RPC, не используем AMF, как это делает Flash и т.д. Просто отдаем сами данные.

Каждая единица информации однозначно определяется URL – это значит, что URL по сути является первичным ключом для единицы данных. Т.е. например третья книга с книжной полки будет иметь вид /book/3, а 35 страница в этой книге - /book/3/page/35. Отсюда и получается строго заданный формат. Причем совершенно не имеет значения, в каком формате находятся данные по адресу /book/3/page/35 – это может быть и HTML, и отсканированная копия в виде jpeg-файла, и документ Microsoft Word.

Как происходит управление информацией сервиса – это целиком и полностью основывается на протоколе передачи данных. Наиболее распространенный протокол конечно же HTTP. Так вот, для HTTP действие над данными задается с помощью методов: GET (получить), PUT (добавить, заменить), POST (добавить, изменить, удалить), DELETE (удалить). Таким образом, действия CRUD (Create-Read-Updtae-Delete) могут выполняться как со всеми 4-мя методами, так и только с помощью GET и POST.

Вот как это будет выглядеть на примере:

GET /book/ - получить список всех книг
GET /book/3/ - получить книгу номер 3
PUT /book/ - добавить книгу (данные в теле запроса)

DELETE /book/3 – удалить книгу

ВАЖНОЕ ДОПОЛНЕНИЕ: Существуют так называемые REST-Patterns , которые различаются связыванием HTTP-методов с тем, что они делают. В частности, разные паттерны по-разному рассматривают POST и PUT. Однако, PUT предназначен для создания, реплейса или апдейта, для POST это не определено (The POST operation is very generic and no specific meaning can be attached to it) . Поэтому мой пример будет правильным и в таком виде, и в виде если поменять местами POST и PUT.

Вообще, POST может использоваться одновременно для всех действий изменения:
POST /book/ – добавить книгу (данные в теле запроса)
POST /book/3 – изменить книгу (данные в теле запроса)
POST /book/3 – удалить книгу (тело запроса пустое)

Это позволяет иногда обходить неприятные моменты, связанные с неприятием PUT и DELETE.

Использование REST для построения Web-сервисов.

Как известно, web-сервис – это приложение работающее в World Wide Web и доступ к которому предоставляется по HTTP-протоколу, а обмен информации идет с помощью формата XML. Следовательно, формат данных передаваемых в теле запроса будет всегда XML.

Для каждой единицы информации (info) определяется 5 действий. А именно:

GET /info/ (Index) – получает список всех объектов. Как правило, это упрощенный список, т.е. содержащий только поля идентификатора и названия объекта, без остальных данных.

GET /info/{id} (View) – получает полную информацию о объекте.

PUT /info/ или POST /info/ (Create) – создает новый объект. Данные передаются в теле запроса без применения кодирования, даже urlencode. В PHP тело запроса может быть получено таким способом:

Function getBody() {
if (!isset($HTTP_RAW_POST_DATA))
$HTTP_RAW_POST_DATA = file_get_contents("php://input");
return $HTTP_RAW_POST_DATA;
}

POST /info/{id} или PUT /info/{id} (Edit) – изменяет данные с идентификатором {id}, возможно заменяет их. Данные так же передаются в теле запроса, но в отличие от PUT здесь есть некоторый нюанс. Дело в том, что POST-запрос подразумевает наличие urldecoded-post-data. Т.е. если не применять кодирования – это нарушение стандарта. Тут кто как хочет – некоторые не обращают внимания на стандарт, некоторые используют какую-нибудь post-переменную.

DELETE /info/{id} (Delete) – удаляет данные с идентификатором {id}.

Еще раз отмечу, что в нашем примере /info/ - может и базироваться на какой-то другой информации, что может быть (и должно) быть отражено в URL:

/data/4/otherdata/6/info/3/ … и тому подобное.

Какие можно сделать из этого выводы:

Как видно, в архитектура REST очень проста в плане использования. По виду пришедшего запроса сразу можно определить, что он делает, не разбираясь в форматах (в отличие от SOAP, XML-RPC). Данные передаются без применения дополнительных слоев, поэтому REST считается менее ресурсоемким, поскольку не надо парсить запрос чтоб понять что он должен сделать и не надо переводить данные из одного формата в другой.

Практическое применение.

Самое главное достоинство сервисов в том, что с ними работать может какая угодно система, будь то сайт, flash, программа и др. так как методы парсинга XML и выполнения запросов HTTP присутствуют почти везде.

Архитектура REST позволяет серьезно упростить эту задачу. Конечно в реальности, того что описано не достаточно, ведь нельзя кому угодно давать возможность изменять информацию, то есть нужна еще авторизация и аутентификация. Но это достаточно просто разрешается при помощи различного типа сессий или просто HTTP Authentication.

Многие из вас уже наверняка знают о требованиях Джеффа Безоса к разработчикам в Amazon. Если вы не слышали об этом, ниже перечислены основные его положения:

  1. Отныне все группы разработчиков должны раскрывать свои данные и функциональные средства через сервисные интерфейсы;
  2. Группы разработчиков должны связываться друг с другом с помощью этих интерфейсов;
  3. Неважно, какую технологию используют разработчики: HTTP, Cobra, Pubsub, встроенные протоколы;
  4. Больше никаких форм межпроцессного взаимодействия не допускается: никаких прямых ссылок и считываний данных других групп, никакой модели общей памяти, никаких «лазеек». Единственная разрешенная форма связи осуществляется через служебные интерфейсы по сети;
  5. Все без исключения сервисные интерфейсы с самого начала должны проектироваться выгружаемыми. Это значит, что группа должна планировать интерфейс так, чтобы его можно было раскрыть остальным разработчикам. Исключения не допускаются;
  6. Все, кто не слушаются этих правил, будут уволены.

В итоге эти требования оказались ключом к успеху Amazon. Компания смогла создавать эластичные системы, а позднее могла предложить эти услуги в лице Amazon Web Services.

Принципы разработки RESTful API

Для разработки RESTful API нужно следовать следующим принципам:

Простота

Нужно убедиться в простоте базового URL для API. Например, если нужно разработать запрос для продуктов, должно получаться так:

/products /products/12345

Первый запрос к API - для всех продуктов, второй - для специфического продукта.

Используйте существительные, а не глаголы

Многие разработчики совершают эту ошибку. Обычно они забывают, что у нас есть HTTP методы для лучшего описания API, и в итоге используют глаголы в URL. Например, запрос для получения всех продуктов звучит так:

/products

А не так:

/getAllProducts

Так часто поступают при создании URL.

Правильные HTTP методы

В RESTful API существуют различные методы, которые описывают тип операции, которую будет осуществлять API.

  • GET - для получения ресурса или группы ресурсов;
  • POST - для создания ресурса или группы ресурсов;
  • PUT/PATCH - для обновления уже существующего ресурса или группы ресурсов;
  • DELETE - для удаления уже существующего ресурса или группы ресурсов.

Нужно обязательно убедиться в том, что вы используете верный HTTP метод для каждой операции.

Не забывайте о множественном числе

Эта тема еще стоит под вопросом. Некоторым нравится называть URL ресурсов во множественном числе, некоторым - в единственном. Пример:

/products /product

Мне нравится использовать множественное число, потому что в таком случае не создается путаница: работаем ли мы с одним ресурсом или с группой ресурсов? Также не нужно дополнять базовые URL, например, добавлением all: /product/all .

Кому-то может не нравиться мой подход. Мой единственный совет - делайте так, чтобы в проекте все было единообразно.

Параметры

Иногда нужен API, который должен работать не только по имени. В таком случае для разработки API понадобятся параметры запроса.

  • нужно использовать /products?name="ABC" , а не /getProductsByName ;
  • нужно использовать /products?type="xyz" , а не /getProductsByType .

В таком случае URL не будут слишком длинными, а структура останется простой.

Правильные HTTP коды

Существует множество HTTP кодов. Многие из нас используют только два из них: 200 и 500 ! Это плохая методика. Ниже перечислены часто используемые HTTP коды:

  • 200 OK - самый часто используемый код, свидетельствующий об успехе операции;
  • 201 CREATED - используется, когда с помощью метода POST создается ресурс;
  • 202 ACCEPTED - используется, чтобы сообщить, что ресурс принят на сервер;
  • 400 BAD REQUEST - используется, когда со стороны клиента допущена ошибка в вводе;
  • 401 UNAUTHORIZED / 403 FORBIDDEN - используются, если для выполнения операции требуется аутентификация пользователя или системы;
  • 404 NOT FOUND - используется, если в системе отсутствуют искомые ресурсы;
  • 500 INTERNAL SERVER ERROR - это никогда не используется просто так - в таком случае произошла ошибка в системе;
  • 502 BAD GATEWAY - используется, если сервер получил некорректный ответ от предыдущего сервера.

Версии

Версии API - важная вещь. Различные компании используют версии по-разному: кто-то - как даты, кто-то - как параметры запросов. Мне нравится указывать версии до названия ресурса. Пример:

/v1/products /v2/products

Мне также кажется, что стоит избегать использования /v1.2/products , так как это подразумевает, что API часто меняется. К тому же, точки в URL не так легко заметить. Так что чем проще, тем лучше.

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

Разбиение на страницы

Обязательно нужно разбивать запрос на страницы, если вы работаете с API, который в качестве ответа может предоставить огромный объем данных. Если сбалансированность нагрузки не обеспечена, пользователь может обрушить сервер.

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

В таком случае стоит использовать limit и offset . Пример: /products?limit=25&offset=50 . Также стоит установить эти настройки по умолчанию.

Поддерживаемые форматы

Также нужно выбирать то, как будет отвечать API. Большинство современных приложений используют JSON. Приложения старых версий должны пользоваться XML ответами.

Верные сообщения об ошибках

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

{ "error": { "message": "(#803) Some of the aliases you requested do not exist: products", "type": "OAuthException", "code": 803, "fbtrace_id": "FOXX2AhLh80" } }

Я также видел некоторые примеры, в которых люди помещают в сообщения ссылку на ошибку, в которой рассказывается о ней и о способах избавления от проблемы.

Open API Specification

Чтобы все группы разработчиков в компании подчинялись одним и тем же принципам, будет полезно использовать Open API Specification. Open API позволяет проектировать API и делиться ей с потребителями в более простой форме.

Заключение

Довольно очевидно, что для лучшей связи отлично подойдут API. Если же они неудачно спроектированы, это вызовет только больше путаницы. Так что выкладывайтесь на полную в разработке, а дальше уже последует этап реализации.

Зачем, например, заморачиваться с методом DELETE или там заголовком Accept? Не проще ли использовать метод GET и передавать все в параметрах, например, delete=true или format=json ? Вбил в браузере, и работает! А вот этот ваш DELETE так просто через браузер не пошлешь. На что я ответил примерно так.

Вот, допустим, у вас есть некоторые ресурсы. Для определенности, пусть это будут книги и пользователи. Что, собственно, означает иметь REST API для работы с этими ресурсами? В первом приближении, следующее. Если мы хотим получить какую-то книгу, то говорим GET /books/123 . Аналогично информация о пользователе получается запросом GET /users/456 . Вообще-то, в начале URL неплохо бы иметь что-то вроде /api/v1.0/ , но для краткости мы это опустим. По умолчанию данные отдаются, например, в JSON’е , но при желании мы можем передать Accept-заголовок с другим форматом. Для создания или обновления существующей книги следует использовать метод PUT, передав данные в теле запроса и указав формат этих данных в заголовке Content-type. Для удаления данных используется метод DELETE.

Внимательный читатель спросит, а для чего тогда нужен POST? Вообще, если делать все по науке, он должен использоваться для добавления элементов в сущность, словно она является неким контейнером, например, словарем. Однако на практике так обычно не делают, ведь при использовании API несколькими клиентами один клиент может изменить название книги, а второй — ее цену, в результате чего получится ерунда. Поэтому POST либо вообще не используют, либо используют в качестве замены методов PUT и DELETE. То есть, POST с каким-то телом запроса работает, как PUT, а без тела запроса — как DELETE. Это позволяет работать с клиентами, которые почему-то не умеют посылать PUT и DELETE.

Можно работать и сразу с целыми коллекциями. Для получения списка всех пользователей говорим GET /users , а для создания нового пользователя с автоматически сгенерированным id — POST /users . Как и ранее, в последнем случае данные передаются в теле запроса. Также можно перезаписать всю коллекцию, сказав PUT /users , и удалить сразу всех пользователей, сказав DELETE /users . Еще иногда требуется фильтрация по полям или пагинация, в этих случаях делают так:

GET /api/v1.0/users?fields=id,email,url&offset=100&limit=10&order_by=id

… или как-то так:

GET /api/v1.0/logs?from=2013-01-01+00:00:00&to=2013-12-31+23:59:59

Как бы, это все. Довольно однообразно и даже логично, не так ли? Так чем такой подход лучше описанного в начале поста?

В свое время я имел удовольствие работать над проектом, где API был устроен «простым и понятным» образом, на методах GET и POST, со всякими delete=1 и так далее. Смею вас заверить, что на самом деле вы этого не хотите. Потому что на практике работа с этим API превращается в какой-то кошмар.

Допустим, один программист занимается книгами, а второй пользователями. Первый решает, что для получения списка всех сущностей будет использоваться запрос GET /all_books , а второй решает перечислять только id и использовать URL GET /select_user_ids . Для удаления сущности первый программист решает использовать параметр del=true , а второй — delete=1 . Для экспорта данных в CSV первый программист делает поддержку export=text/csv , а второй — format=CSV . Потом выясняется, что некоторые библиотеки не умеют посылать GET-запросы со слишком длинными query string и ходить за данными на чтение начинают методом POST. А затем кто-то случайно удаляет через браузер всех пользователей в боевом окружении… И так далее, и тому подобное, полный бардак в общем.

Вы спросите, что же мешает привести все это безобразие в одному стандарту, например, использовать только del=1 и export=csv ? Так вот, REST — это и есть то самое приведение к одному стандарту , с учетом всяческих граблей типа случайного удаления данных через браузер и так далее. Притом у разных компаний этот стандарт одинаковый. Когда в команду разработчиков приходит новичок, вы просто говорите ему, что у вас всюду REST, а основные ресурсы — это пользователи и книги. Все, после этого одного предложения ваш новый коллега знает 90% API, безо всякого там чтения Wiki. Если вы хотите говорить с иностранцами, вы же просто используете общепринятый английский язык , а не изобретаете новый? Вот так же и здесь. Нельзя также не напомнить о пользе повторного использования протоколов и кода. А ведь для работы с REST, и HTTP вообще, написана куча библиотек и фреймворков.

Вы скажите «я, конечно, согласен, что REST такой весь из себя интуитивно понятный и общепринятый, но что, если я просто хочу загрузить через браузер список книг в формате CSV»? Тут важно понимать, что REST — это не о том, как сделать все через браузер . Предполагается, что должен быть клиент, который умеет работать с вашим API, вот через него и экспортируете. Но если по каким-то причинам это затруднительно, вы можете, например, использовать curl. Если у вас нелады с консолью, вы без труда найдете множество GUI-клиентов или, скажем, какой-нибудь плагин для Chrome, с аналогичным функционалом. Однако я все же советую попробовать curl. Пользоваться им совсем не так сложно, как вам может казаться. Всего-то нужно запомнить десяток параметров.

Так задаются дополнительные HTTP-заголовки:

H "Accept: text/csv" -H "Content-type: application/json"

Выбираем используемый метод:

X{GET|PUT|POST|DELETE}

Указываем тело запроса:

D "{"name":"Alex","url":"http://сайт/"}"

D @filename.json
# чтобы при этом не удалялись символы новой строки:
--data-binary @filename.json

Выводим заголовки из ответа сервера в stdout:

Говорим передавать данные в gzip’е:

Сохраняем тело ответа в указанный файл вместо stdout:

Наконец, для отключения буферизации используйте флаг -N . Может пригодится, если вы работаете с большими объемами данных или бесконечными потоками.

Теперь рассмотрим пару примеров.

Экспорт книг в формате CSV:

curl -H "Accept: text/csv" http://localhost/api/v1.0/books -o books.csv

Создание пользователя c выводом заголовков из ответа сервера в stdout:

curl -XPOST -H "Content-type: application/json" -d "{"name":"Alex"}" \
http://localhost/api/v1.0/users -D -

Удаление пользователя с заданным id:

curl -XDELETE http://localhost/api/v1.0/users/123

Несложно, правда ведь?

Несколько финальных замечаний, относящихся не совсем к REST. Во-первых, иногда от приложения требуется не только предоставлять доступ к некоторым ресурсам, но и выполнять какие-то команды. Таким командам имеет смысл выделять URL-адреса, начинающиеся с /commands/ . Например, запуск почтовой рассылки по всем пользователям будет выглядеть как-то так:

curl -XPOST -H "Content-type: application/json" \
-d "{"subject":"Good news, everyone!","body":"..."}" \
http://localhost/api/v1.0/commands/notify_all_users_via_email

Дополнение: Некоторые команды должны быть доступны только в тестовом окружении, для них можно выделить URL-адреса, начинающиеся с /debug/ .

Во-вторых, иногда требуется реализовать бесконечные потоки событий , или отправку текущего состояния, а затем обновлений к нему. Таким концам разумно выделить URL, начинающиеся, например, со /streams/ . Вот как примерно это должно работать:

curl -H "Accept: application/x-json-stream" \
http://localhost/api/v1.0/streams/users -N

{"type":"user","data":{"id":123,"name":"Alex","url":"http://сайт/"}}
{"type":"user","data":{"id":456,"name":"Bob","url":"http://ya.ru/"}}
...
{"type":"sync"}
{"type":"heartbeat"}
{"type":"heartbeat"}
{"type":"user_deleted","data":{"id":123}}
...

Нужно обратить внимание на несколько моментов. Здесь используется формат x-json-stream , то есть, поток JSON-объектов, разделенных символом \n. Если этот символ встречается в самом JSON-объекте, его, соответственно, следует кодировать. Некоторым клиентам может быть удобнее работать с честным JSON’ом, то есть, списком JSON-объектов. Предусмотреть поддержку сразу нескольких форматов довольно просто. Во втором случае список объектов должен начинаться с открывающейся квадратной скобки, а объекты должны разделяться запятыми. Для удобства работы со стримом нужно либо ставить после запятых символ \n, либо делать это на стороне клиента с помощью sed:

curl ... | sed "s/},/}\n/g"

Каждый объект имеет поле type и опциональное поле data. Объекты с типом heartbeat посылаются несмотря ни на что один раз в пять секунд. Если клиент не видит такого объекта в течение десяти секунд, он считает, что либо что-то сломалось на стороне сервера, либо что-то не так с сетью, и закрывает соединение. Объект с типом sync используется в стримах, посылающих некое состояние, а затем обновления к нему, для разделения первого от второго. Наконец, все остальные типы представляют собой полезную нагрузку. Поле data нужно по той причине, что вложенные данные также могут иметь поле type, что приводило бы к неразберихе.

В-третьих, когда вы пишите RESTful приложение, старайтесь с самого начала придерживаться некоторых соглашений. Например, с самого начала договоритесь, что имена полей в JSON-объектах должны всегда писаться в camelCase. Раз и навсегда запретите использовать в идентификаторах такие спецсимволы, как знак плюс и пробелы. Договоритесь, что в случае получения кода 301 клиент должен посылать точно такой же запрос на URL, указанный в заголовке Location. Примите соглашение о том, как будет передаваться автоматически сгенерированные id. Например, в Riak для этого используется заголовок Location . Подумайте о том, как вы будете сообщать о различных типах ошибок, в том числе временной недоступности БД, ошибках валидации полей и так далее. Пользователи почти наверняка предпочтут увидеть:

{"message":"validation_error","description":"..."}

… вместо кода 500 без каких-либо дополнительных пояснений. Если для вашего приложения важна точность представления чисел, договоритесь передавать все числа в виде строк, чтобы json-декодер не терял точность из-за преобразования строк во float’ы.

Но помните, хотя все написанное выше — это идеал, к которому стоит стремиться, на практике всем наплевать на стандарты . А значит, вас ждет много подпорок, слепленных на скорую руку, нежелание коллег переходить на более правильные версии API (зачем, если все работает?), и многие другие увлекательные вещи.

RESTful API может создаваться не только для сторонних сервисов. Он может использоваться одностраничными приложениями для работы с бэк-эндом. Вот несколько основных моментов, которые нужно знать при проектировании интерфейса.

URLs и действия

Ключевым принципом REST является деление вашего API на логические ресурсы. Управление этими ресурсами происходит с помощью HTTP-запросов с соответствующим методом - GET, POST, PUT, PATCH, DELETE.

Ресурс должен описываться существительным во множественном числе. Действия над ресурсами, обычно, определяются стратегией CRUD и соответствуют HTTP-методам следующим образом:

  • GET /api/users - получить список пользователей;
  • GET /api/users/123 - получить указанного пользователя;
  • POST /api/users - создать нового пользователя;
  • PUT /api/users/123 - обновить все данные указанного пользователя;
  • PATCH /api/users/123 - частично обновить данные пользователя;
  • DELETE /api/users/123 - удалить пользователя.

Если ресурс существует только в контексте другого ресурса, то URL может быть составным:

  • GET /api/posts/9/comments - получить список комментариев к записи №9;
  • GET /api/posts/9/comments/3 - получить комментарий №3 к записи №9.

Когда действие над объектом не соответствует CRUD операции, то его можно рассматривать как составной ресурс:

  • POST /api/posts/9/like - отметить запись №9 как понравившуюся;
  • DELETE /api/posts/9/like - снять отметку «понравилось» с записи №9.

Действия по созданию и обновлению ресурсов должны возвращать ресурс

Методы POST, PUT или PATCH могут изменять поля ресурса, которые не были включены в запрос (например, ID, дата создания или дата обновления). Чтобы не вынуждать пользователя API выполнять ещё один запрос на получение обновлённых данных, такие методы должны вернуть их в ответе.

Фильтры, сортировка и поиск

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

  • GET /api/users?state=active - список активных пользователей;
  • GET /api/tasks?state=open&sort=priority,-created_at - список невыполненных задач, отсортированных по приоритету и дате создания (сперва новые задачи).

Постраничная навигация

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

Пример заголовка:

Link: ; rel="next", ; rel="prev", ; rel="first", ; rel="last"

Возможные значения rel:

  • next - следующая страница результатов;
  • prev - предыдущая страница результатов;
  • first - первая страница результатов;
  • last - последняя страница результатов.

Важно следовать этим значениям, а не конструировать собственные URL-ы потому, что иногда постраничная навигация может основываться на сложных правилах, а не простом переборе страниц.

Переопределение HTTP-метода

Для совместимости с некоторыми серверами или клиентами, которые не поддерживают другие HTTP-методы кроме GET и POST, может быть полезным их эмуляция. Значение метода передаётся в заголовке X-HTTP-Method-Override , а сам он выполняется как POST-метод. GET-запросы не должны менять состояние сервера!

Коды HTTP-статуса

  • 200 OK - ответ на успешный запрос GET, PUT, PATCH или DELETE.
  • 201 Created - ответ на POST запрос, в результате которого произошло создание нового объекта. Ответ так же должен сопровождаться заголовком Location , указывающий на URL ресурса.
  • 204 No Content - ответ на успешно выполненный запрос, который ничего не возвращает (например, DELETE).
  • 404 Not Found - запрашиваемый объект не найден.
  • 500 Internal Server Error - ошибка на сервере.

В случае ошибок, в ответе может содержаться отладочная информация для разработчиков, если это возможно.

Если заметили ошибку, выделите фрагмент текста и нажмите Ctrl+Enter
ПОДЕЛИТЬСЯ:
Безопасность. Обзоры. Ноутбуки. Звуки и карты. Windows