Теоретически, RESTful приложение может использовать любой stateless протокол для обмена сообщениями между клиентом и сервером. Практически, RESTful приложения базируются на протоколе HTTP, среди методов которого есть методы GET
, POST
, PUT
и DELETE
.
POST
vs PUT
Изучая вопрос по статьям и форумам в Интернете, я встретил утверждения о том, что HTTP-метод PUT
должен использоваться как для изменения, так и для создания ресурсов. В то же время, для создания ресурсов используется метод POST
(см. Let's have a REST, часть I). В чем здесь дело? Когда и для чего нужно использовать PUT
, а когда - POST
?
Найти ответ помогла спецификация протокола HTTP (RFC 2616). Согласно RFC 2616, метод POST
используется с URL, адресующим ресурс, являющийся в некотором смысле родителем или контейнером для того ресурса, который будет создан в результате обработки запроса POST
:
The posted entity is subordinate to that URL in the same way that a file is subordinate to a directory containing it, a news article is subordinate to a newsgroup to which it is posted, or a record is subordinate to a database.
Ожидается, что сервер в ответ на запрос POST
создаст объект из полученных данных и вернет URL созданного объекта.
Примеры URL для запросов POST
:
http://<server>/books POST создать книгу
http://<server>/users POST создать пользователя
Тогда как ресурсы, представляющие конкретные книги и пользователей, адресуются при помощи уникальных идентификаторов:
http://<server>/books/33 GET получить представление книги
http://<server>/users/7 GET получить представление пользователя
Итак, HTTP-запрос POST
на создание новой книги имеет URL http://<server>/books
и содержит данные (представление, в терминах REST) для создания новой книги. Сервер, успешно обработав запрос, возвращает URL созданного ресурса http://<server>/books/34
, где 34 - идентификатор нового ресурса.
Для создания другой книги нужно снова выполнить запрос POST
c URL http://<server>/books
и другим представлением. И сервер вернет URL нового созданного ресурса, возможно, http://<server>/books/35
.
В отличие от HTTP-запроса с методом POST
, запрос с методом PUT
выполняется с URL конкретного ресурса, например, http://<server>/books/33
или http://<server>/users/7
:
The PUT method requests that the enclosed entity be stored under the supplied Request-URL.
Изменение существующего на сервере ресурса - типичное использование запроса PUT
. Однако, в случае отсутствия на сервере адресуемого ресурса, ресурс с таким адресом должен быть создан из данных запроса PUT
!
Запрос PUT
c URL http://<server>/books/33
изменит существующий ресурс - книгу с номером 33. А запрос PUT
c URL http://<server>/books/45
, адресующий несуществующий ресурс, приведет к созданию нового ресурса с идентификатором 45.
The fundamental difference between the POST and PUT requests is reflected in the different meaning of the Request-URL. The URL in a POST request identifies the resource that will handle the enclosed entity. ... In contrast, the URL in a PUT request identifies the entity enclosed with the request -- the user agent knows what URL is intended and the server MUST NOT attempt to apply the request to some other resource.
Решение о том, использовать ли метод PUT
для создания ресурсов, остается за разработчиком приложения.
Идемпотентность
Заслуживает внимания, что спецификация протокола HTTP относит метод PUT
, вместе с GET
и DELETE
, к идемпотентным методам. Свойство идемпотентности состоит в том, что побочный эффект выполнения N>0 идентичных запросов точно такой же, как от выполнения одного запроса.
Для GET
это означает, что первое и последующие чтения (получение представления) одного и того же ресурса оказывают одно и то же воздействие на читаемый ресурс - никакого. Для DELETE
это означает, что первый и последующие идентичные запросы подтверждают факт отсутствия ресурса на сервере. Нет разницы, был ли ресурс удален по первому запросу или не существовал на момент первого запроса.
Идемпотентность PUT
означает, что результат запроса PUT
обязан зависеть только от данных, передаваемых вместе с запросом, и не зависеть от состояния сервера. Только в этом случае первый и последующие идентичные запросы PUT
всегда приведут к одному и тому же результату.
Вам приходилось обновлять страницу в браузере после того, как вы отослали на сервер заполненную HTML-форму? Браузер предупреждает вас о том, что обновление страницы может вызвать побочные эффекты, и просит подтвердить ваше намерение. Это происходит потому, что метод POST
, используемый для отправки HTML-формы, не является идемпотентным! А это значит, повторная отправка идентичного запроса может привести к нежелательным для пользователя результатам.
Если отправленная вами форма была формой заказа в интернет-магазине, то захотите ли вы отправить ее еще раз, рискуя созданием еще одного такого же заказа?
Например, первая отправка HTML-формы запросом POST
с URL http://magazin/orders
создает на сервере заказ, и сервер возвращает URL этого заказа http://magazin/orders/12345
. Повторная отправка той же формы туда же создает на сервере другой заказ с точно таким же содержанием, как и первый, и сервер возвращает его URL http://magazin/orders/12346
.
Чтобы избежать повторной отправки формы при обновлении страницы, имеет смысл в конце обработчика формы перенаправить (redirect) браузер на безопасный URL.
Хорошая аналогия = ограниченная аналогия
Имеем интерфейс RESTful приложения для работы с книгами:
http://<server>/books GET получение списка книг
http://<server>/books POST создание новой книги
http://<server>/books/<id> GET получение детальной информации о книге
http://<server>/books/<id> PUT обновление или создание книги
http://<server>/books/<id> DELETE удаление книги
Может возникнуть соблазн провести аналогию с манипулированием данными в БД при помощи запросов SQL. Команды INSERT
(с подзапросом), SELECT
, UPDATE
и DELETE
могут воздействовать как на отдельную строку, так и на все строки таблицы. Так почему бы, проектируя интерфейс RESTful приложения, не предусмотреть также следующие запросы:
http://<server>/books PUT изменение всех книг
http://<server>/books DELETE удаление всех книг
Не стоит этого делать. Такие запросы a) расходятся с рекомендациями RFC 2616 и b) очень опасны. Обновление всех книг на сервере так, чтобы они стали одинаковы, и удаление всех книг - это не то, что вы захотите делать каждый день. Это операции, которые чреваты потерей данных и большими проблемами для системы, находящейся в эксплуатации.
Браузерное приложение vs web-сервис
Два типа RESTful приложений - это браузерные web-приложения и web-сервисы.
В браузерном web-приложении полученные с сервера HTML-представления ресурсов отображаются в браузере, и с ними напрямую работает пользователь. Переходы по гиперссылкам выполняются при помощи HTTP-метода GET
, отправка HTML-форм - при помощи HTTP-метода POST
, при этом в теле запроса передаются данные формы.
Язык HTML (в том числе, HTML5) не предусматривает использование HTTP-методов PUT
и DELETE
ни с гиперссылками, ни с формами. Поэтому запросы PUT
и DELETE
приходится имитировать в HTML-приложениях, претендующих на звание RESTful. Фреймворк Ruby on Rails делает это, передавая в запросе POST
скрытый параметр _method, принимающий значения 'PUT
' или 'DELETE
'. Сервер выбирает обработчик запроса, анализируя значение этого параметра. (То есть, для диспетчеризации запроса серверу приходится заглядывать в тело запроса, что нарушает одно из требований модели REST - требование самоописательности запроса.)
Другой интересный практический момент - это получение разных представлений одной и той же сущности. Посмотрим на фрагмент RESTful инерфейса для работы с книгами:
http://<server>/books GET получение списка книг
http://<server>/books/<id> GET получение детальной информации о книге
Запрос GET
c URL http://
позволяет получить представление книги, которое будет отображено как HTML-страница в браузере. Но что будет отображено в браузере: информация о книге, которую пользователь сможет только прочесть, но не изменить, или форма для редактирования информации о книге?
В разных контекстах может оказаться уместным одно или другое представление, то есть, они оба нужны и важны. Следовательно, для получения каждого из них необходим собственный URL, каждое из них - самостоятельный ресурс в терминах REST. Вот как принято идентифицировать ресурсы в Ruby on Rails приложениях:
/books GET представление списка книг, только-чтение
/books/new GET представление для создания книги, пустая HTML-форма
/books/<id> GET представление книги, только-чтение
/books/<id>/edit GET представление для изменения книги, HTML-форма, заполненная данными книги
Просто и красиво, не так ли?
Web-сервисы, в отличие от браузерных web-приложений, для реалиазции RESTful поведения не нуждаются в костылях, вроде скрытого поля _method в HTML-формах. Все методы HTTP находятся в распоряжении клиента web-сервиса.
Однако, web-сервисы находятся в натянутых отношениях с другим требованием REST - требованием о том, что ответ сервера должен содержать ссылки на другие ресурсы приложения, тем самым предлагая клиенту доступ к другим представлениям. Клиент web-сервиса - не человек, а программа, поэтому действия такого клиента строго детерминированы и вряд ли предполагают исследование других представлений через гиперссылки, полученные в ответе от сервера. Поэтому гиперссылки в ответах RESTful web-сервиса могут и вовсе не встречаться, а клиентская программа может конструировать нужные ей URL самостоятельно.
В заключительной части я рассмотрю маленькое RESTful приложение.
Комментариев нет:
Отправить комментарий