Практические уроки по программированию
66,11
рейтинг
8 января в 11:29

Разработка → Что такое RESTful на самом деле перевод

А ваше приложение — RESTful? Чтобы ответить на этот вопрос нужно сначала разобраться что такое RESTful. Бытует мнение, что отдавать правильные коды ответов в HTTP — это уже RESTful. Или делать правильные идемпотентные HTTP-запросы — это вообще очень RESTful. Мы в Хекслете сделали практический курс по протоколу HTTP (отличия версий, отправка форм, аутентификация, куки и пр.), и в нем мы стараемся рассказать о правильном использовании запросов, но нужно понимать, что RESTful это не про HTTP, это вообще не про протоколы интернета. Современный веб и взаимодействие между браузером и сервером с помощью HTTP и URI могут удовлетворять принципам RESTful, а могут и не удовлетворять.

В сегодняшнем переводе — простое и понятное описание RESTful, и какой должна быть система, чтобы ее можно было так называть.



Если вы занимаетесь веб-разработкой, вы скорее всего слышали о REST. Но если вы такой же как я, то обычно вы притворяетесь и вежливо киваете когда у вас спрашивают, делаете ли вы все RESTful. Я использую HTTP, эээ, значит — это RESTful, так? Недавно я решил наконец выяснить, что означает это модное словечко, которое звучит так умиротворяюще («restful» с англ. «успокоительный», «спокойный»).

Что такое REST?


REST — это аббревиатура от Representational State Transfer («передача состояния представления»). Это согласованный набор архитектурных принципов для создания более масштабируемой и гибкой сети. Эти принципы отвечают на ряд вопросов. Какие у системы компоненты? Как они должны взаимодействовать друг с другом? Как быть уверенным, что можно заменять различные части системы в любое время? Как система может масштабироваться для обслуживания миллиардов пользователей?

Рой Филдинг первым использовал термин REST в 2000 году в своей докторской диссертации «Архитектурные стили и дизайн программных сетевых архитектур». На момент публикации диссертации Всемирная паутина (Web) была уже очень популярна. Филдинг, по существу, шагнул назад и проанализировал черты, которые сделали Web более успешным, чем конкурирующие протоколы Интернета. Затем он выработал концепцию фреймворка для создания сетевой коммуникации, подобной браузеру. Так что REST — это общий набор принципов, характерных не только для Web. Он может быть применен к другим видам сетей, таким как встроенные системы. REST — не протокол, так как он не задаёт деталей реализации.

Ограничения Филдинга


В диссертации Филдинга есть группа архитектурных ограничений, которым должна удовлетворять система, соответствующая требованиям RESTful. Ниже я даю краткий обзор каждого из этих ограничений и рассуждаю как Web удовлетворяет им, на основании его основных технологий: HTTP, HTML, и URI. (Если вы не знакомы с URI, считайте его «URL». Они отличаются, но в нашем обсуждении это не важно). Давайте разберём каждое из ограничений Филдинга.

Клиент-сервер

Первое ограничение указывает, что сеть должна состоять ​​из клиентов и серверов. Сервер — это компьютер, который имеет требуемые ресурсы, а клиент — это компьютер, которому нужно взаимодействовать с ресурсами, хранящимися на сервере. Когда вы копаетесь в интернете, ваш компьютер выступает в роли клиента и отправляет HTTP-запросы серверу, чтобы получать доступ к информации и работать с ней. RESTful-система должна производить операции в клиент-серверной модели, даже если компонент периодически ведёт себя то как клиент, то как сервер.

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

Отсутствие состояния

Понятие «без состояния» не означает, что серверы и клиенты его не имеют, у них просто нет необходимости отслеживать состояние друг друга. Когда клиент не взаимодействует с сервером, сервер не имеет представления о его существовании. Сервер также не ведёт учет прошлых запросов. Каждый запрос рассматривается как самостоятельный.

Единообразие интерфейса

Ограничение гарантирует, что между серверами и клиентами существует общий язык, который позволяет каждой части быть заменяемой или изменяемой, без нарушения целостности системы. Это достигается через 4 субограничения: идентификацию ресурсов, манипуляцию ресурсами через представления, «самодостаточные» сообщения и гипермедиа.

1-е ограничение интерфейса: определение ресурсов

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

Web использует URI для идентификации ресурсов, а HTTP — в качестве стандарта коммуникации. Чтобы получить ресурс, хранящийся на сервере, клиент делает к URI HTTP-GET-запрос, который идентифицирует этот ресурс. Каждый раз, когда вы набираете в браузере какой-то адрес, браузер делает GET-запрос на этот URI. Если браузер принимает в ответ 200 OK и HTML-документ обратно, то браузер рендерит страницу в окне, и вы ее видите.

2-е ограничение интерфейса: управление ресурсами через представления

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

Давайте рассмотрим блог в качестве примера. Когда пользователь создаёт новый пост в блоге, его компьютер должен сообщить серверу, что в блог нужно добавить новую запись. Чтобы это выполнить, он посылает HTTP-POST-запрос или PUT-запрос с содержимым в виде новой записи в блоге. Сервер возвращает ответ, указывающий, что запись создана или была проблема. В не-REST мире клиент может в буквальном смысле давать инструкции операциям, вроде «добавить новую строку» и «присвоить записи название», вместо обычной отправки представления того, каким он видит конечный ресурс.

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

Чтобы понять, как это касается WEB, давайте проанализируем набор HTTP запросов и ответов.

Когда пользователь набирает www.example.com в адресной строке веб-браузера, браузер отправляет соответствующий HTTP-запрос:

GET / HTTP/1.1
Host: www.example.com


Это самодостаточное сообщение, потому что оно передаёт серверу какой был использован HTTP-метод и какой протокол (HTTP 1.1).

Сервер может послать ответ вроде такого:

HTTP / 1.1 200 OK
Content-Type: text/html
<!DOCTYPE html>
Home Page


Hello World!
Check out the Recurse Center!





Это самодостаточное сообщение, потому что оно сообщает клиенту, как интерпретировать текст сообщения (благодаря Content-type = text/html). У клиента есть всё, что необходимо, в этом одном сообщении для обработки его соответствующим образом.

Представьте себе альтернативный способ коммуникации, когда сервер отправляет двоичный код в одном ответе, а затем отдельное сообщение, которое говорит клиенту, как интерпретировать бинарный код, не важно изображение это или фрагмент кода. Если два ответа каким-то образом доставятся в неверном порядке, или между ними будет вставлено третье сообщение, то клиент запутается и не сможет правильно интерпретировать эти сообщения.

4-е ограничение интерфейса: гипермедиа

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

HTML — это один из видов гипермедиа. Чтобы лучше это понять, давайте еще раз посмотрим на ответ сервера выше.
<a href= “http://www.recurse.com”> Check out the Recurse Center!</a>
сообщает клиенту, что он должен сделать GET-запрос к www.recurse.com, если пользователь нажимает на ссылку.
<img src="awesome-pic.jpg">
говорит клиенту немедленно сделать GET-запрос к www.example.com/awesome-pic.jpg, чтобы тот отобразил изображение для пользователя.

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

Другие ограничения Филдинга: кэширование, система слоёв и код по требованию


Есть еще три ограничения Филдинга, которые мы здесь рассмотрим кратко.

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

Система слоёв предполагает наличие большего количества компонентов, чем клиент и сервер. В системе может быть больше одного слоя. Тем не менее, каждый компонент ограничен способностью видеть только соседний слой и взаимодействовать только с ним. Прокси — это дополнительный компонент, он ретранслирует HTTP-запросы на серверы или другие прокси. Прокси-серверы могут быть полезны для балансировки нагрузки и проверок безопасности. Прокси действует как сервер для начального клиента, который посылает запрос, а затем как клиент, когда ретранслирует эту просьбу. Шлюз — это еще один дополнительный компонент, он переводит HTTP-запрос в другой протокол, распространяет этот запрос, а затем переводит полученный ответ обратно в HTTP. Клиент может обращаться со шлюзом, как с обычным сервером. Пример шлюза — система, которая загружает файлы с FTP-сервера.

Код по требованию — единственное опциональное ограничение, которое предполагает отправку сервером исполняемого кода клиенту. Это то, что происходит в HTML-теге
<script>
. Когда HTML-документ загружается, браузер автоматически выбирает на сервере JavaScript и исполняет его локально.

* * *

В целом, система RESTful — это любая сеть, которая отвечает ограничениям Филдинга. RESTful-система должна быть достаточно гибкой для различных сценариев использования, масштабируемой для размещения большого количества пользователей и компонентов, а также адаптируемой с течением времени. Мы рассмотрели, насколько Веб отвечает требованиям RESTful, когда дело касается взаимодействия человека и браузера. Разработка Web-интерфейсов на основе RESTful-архитектуры для межкомпьютерных взаимодействий — это намного более сложная тема (для дополнительной информации смотрите перечисленные ниже ресурсы). Помните, что REST — это теоретический дизайн и тот Web, которая существует сегодня, все еще иногда не дотягивает до теории.

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

К прочтению



Перевод: Наталия Басс
Автор: @freetonik Lauren Long
Hexlet
рейтинг 66,11
Практические уроки по программированию
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Комментарии (103)

  • 0
    Что-то я не пойму…
    Во-первых, как отсутствие состояния в ограничениях Филдинга сочетается с «изменением состояния ресурса» в пункте 1?
    Во-вторых, как отсутствие необходимости «беспокоиться о том, что случайно закэшируется только часть необходимой информации» сочетается с понятием гипермедиа?
    Или более обще, как самодостаточность каждого сообщения из пункта 3 сочетается с понятием гипермедиа?
    • 0
      Отсутствия состояния у протокола обмена между клиентом и сервером. Нет, например, каких-то хендшейков, определенной последовательности запросов, ожиданий клиента или сервера, что сервер или клиент помнят какие-то предыдущие запросы или ответы.

      Самодостаточность означает, что что каждое сообщение логически полное, но при этом может содержать гипермедиа ссылки на другие ресурсы.
      • +6
        Правильно ли я понимаю, что везде, где есть авторизация, это уже не RESTful? То есть у клиента появляется зависимость от предыдущих запросов, которое можно считать состоянием.
        • +7
          Современная авторизация предполагает получение от сервера авторизации сертификата на доступ к ресурсам (тикет в OAuth, например), который в дальнейшем указывается в заголовке В КАЖДОМ ЗАПРОСЕ на получение/изменение данных. Т.е., каждый запрос содержит полную информацию, достаточную для его обработки (т.о., обрабатывается без учёта какого-либо состояния). Получается честный stateless.
          • +1
            Извиняюсь за невнимательность, перечитав поняла, что каждый запрос независим, и ключ авторизации стоит рассматривать как дополнительный параметр запроса.
          • 0
            Немного не понял. А чем это отличается в плане хранения состояния от обычной авторизации, например, в PHP? При логине выдаем клиенту идентификатор сессии, клиент хранит его у себя и при каждом запросе отправляет на сервер. Сервер может проверить валидность этого идентификатора, потому что хранит сессии у себя.
            • 0
              Сертификат содержит идентификатор пользователя, а ид сессии — нет. Малой кровью чистый RESTful реализуется с помощью HTTP-аутентификации. Хотя лично я не считаю большим отклонением от принципов процедуру логина, если в сессиях подобных php хранить исключительно идентификатор пользователя.
              • +1
                Сертификат содержит идентификатор пользователя

                В OAuth это совершенно не обязательно, кстати. Тикет может быть непрозрачным для сервера ресурсов.
                • +1
                  Что, к сожалению, позволяет взламывать сессию путём подбора ключей без необходимости не только взламывать пароль пользователя, но даже знать его логин.
                  • 0
                    Что, к сожалению, позволяет взламывать сессию путём подбора ключей без необходимости не только взламывать пароль пользователя, но даже знать его логин.

                    А при чем тут вообще сессия? Тикеты OAuth не имеют никакого отношения к сессиям.
                  • 0
                    Сессия в PHP живёт 24 минуты.
                    И если есть защита от http-флуда, то, как можно подобрать ключ за это время?
                    • +1
                      Сессия живёт столько, сколько указано в настройках.
          • 0
            Кстати, заинтересовался, а как «современная авторизация», соответствующая HTTP REST должна решать проблему, когда приходит HTTP POST запрос с кучей данных и устаревшим тикетом?
            • 0
              В идеале сервер должен отдать ошибку 401 сразу после разбора устаревшего тикета, отменив приём тела запроса. Клиент при этом должен осуществить попытку обновления тикета на сервере авторизации и, если это удаётся, повторяет изначальный запрос с данными. Всё это происходит прозрачно для пользователя (т.е., время истечения валидность тикета — это не «время сессии», а «уровень безопасности, соответствующий частоте обновления тикетов»).
              • 0
                А почему именно 401, а не 403?
                • +4
                  Коды ошибок HTTP пронумерованы так, чтобы проверки на возникновение этих ошибок происходили в порядке возрастания этих кодов (хотя это и не всегда так, но для единиц и десятков, как правило, именно так — каждый последующий номер является более специфичным).

                  Т.е., на сервере сначала идёт парсинг запроса (выделение имён и значений параметров, заголовков, т.п.) Если на этом этапе случилась ошибка — сервер вернёт 400 («я вообще ничего не понял, что ты спрашиваешь, и мне плевать, кто ты»). Если ошибки не было — производим этап парсинга заголовков. Если на этом этапе была выяснена невалидность тикета (просрочннность, например), то, по-сути, тикета нет и уже не важно, кто именно там в тикете указан — сервер просто не имеет права доверять его содержимому, и потому сигнализирует об его отсутствии — 401 («я не понял, кто ты»). Если же тикет присутствует и валиден, но данному конкретному юзеру запрещён доступ к данному конкретному ресурсу, то сервер возвращает 403 («запрос корректен, я понял, кто ты, но тебе запрещенно лезть к этому конкретному ресурсу»).
                  • –1
                    Что такое 401 и 403 я знаю, есть куча расшифровок и описаний в RFC. Я о другом!

                    Вы на ситуацию:
                    когда приходит HTTP POST запрос с кучей данных и устаревшим тикетом?


                    Предлагаете ответить с помощью 401, что в корне не верно, сами же написали, что:
                    401 («я не понял, кто ты»)


                    Вместо этого более подходящий способ: 403, т.е. ваши же слова:
                    403 («запрос корректен, я понял, кто ты, но тебе запрещенно лезть к этому конкретному ресурсу»)


                    Вполне логично, что сервер запрещает доступ к ресурсу, токен же протух!
                    • +3
                      «Протухший токен» = «отсутствующий токен» («протухшесть» токена находится за гранью понимания обработчика запросов на изменение данных — это кухня сервиса авторизации/аутентификации). Ваш вариант не позволяет различать ограничения бизнес-логики (отсутствие прав у пользователя, к примеру, о котором нужно сообщить пользователю) и необходимость отрефрешить токен (что должно произойти без уведомления пользователя автоматически). Также ваш вариант содержит и более тонкие проблемы — например, возможность «прощупать» протухшим токеном существование определённых ресурсов без доступа к ним.
                      • 0
                        >>возможность «прощупать» протухшим токеном существование определённых ресурсов без доступа к ним.
                        принято! Это единственный аргумент, который принуждает использовать 401, хоть это и не совсем корректно
                        • 0
                          Коды ошибок в протоколе нужны только для дифференциации стратегий их обработки, не стОит наделять их излишне глубоким смыслом («корректность/некорректностью») в отрыве от этой дифференциации. Также коды ошибок созданы так, чтобы поощрять «послойную» архитектуру сервера, обрабатывающего запросы (каждый слой архитектуры резервирует свои непересекающиеся коды ошибок, ошибка на одном слое отменяет обработку на всех последующих). Если принять эти принципы, то код 401 для рефреша токена становится логичным, очевидным, удобным.
                        • +1
                          Абсолютно корректно. Что истекший токен, что невалидный (при попытке брутфорса, например), что вообще отсутствующий суть одна — клиент не аутенфицирован (не подтвердил свою личность), сервер не может определить кто является клиентом, а значит не может определить авторизирован (имеет права) он на запрашиваемое действие с ресурсом, или нет.
                  • 0
                    Про нумерацию кодов ошибок я, всё-таки, слегка преувеличил. Схема уж очень нелинейная: http://i.stack.imgur.com/whhD1.png
                • 0
                  401 — я не могу тебя узнать (авторизовать, креденшелы невалидны, либо отсутствуют), для доступа к ресурсу я должен знать кто ты.
                  403 — я тебя узнал (авторизовал), но у тебя чувак нет доступа к этому ресурсу.

                  В некоторых случаях 403 можно заменить на 404 статус код. Это выгодно когда клиент даже знать не должен о том что есть что-то к чему у него нет доступа. Но это уже отдельные юзкейсы.
        • 0
          Зависит от способа реализации авторизации. Хотя, скорее, вы про аутентификацию :)
          • 0
            Да, вы правы, имелась в виду аутентификация.
        • 0
          .
      • 0
        А, то есть самодостаточность из пункта 3 — это полнота на уровне протокола, а не на уровне получаемого пользователем документа?
        • 0
          Нет, документ, например, html или json, должен быть синтаксически полным. Но при этом имеет право ссылаться на другие документы.
  • 0
    Исходя из статьи делаю вывод, что GET по адресу для создания новой сущности на сервере и получение в ответе 200 {status: 404, message: 'No ok'} есть верный RESTful?
    • 0
      Что б уж совсем был RESTful, для создания сущности лучше все же POST.
      • +5
        Я о том, что очень важные условия RESTful в статье не упомянули.
    • 0
      В самом начале статьи написано: «RESTful это не про HTTP, это вообще не про протоколы интернета. ». далее эта мысль развёртывается. таким образом ваш случай RESTful но не http REST
      • 0
        Интересный вывод. Будет о чем похоливарить на работе )
        • 0
          В оригинальной диссертации, если я правильно понял о чём это, написано:

          5.3.2 Connector View
          REST does not restrict communication to a particular protocol,… but the architecture also includes seamless access to resources that originate on pre-existing network servers, including FTP...
    • 0
      Это семантически неверная реализация RESTful над HTTP :) Ну и с некоторым http-ориентированным сетевым софтом типа браузеров или прокси могут быть проблемы,
      • 0
        Хорошо бы инфу именно в направлении RESTful над HTTP, так как не встречал еще RESTful не над HTTP.
        • 0
          Об этом много инфы (хотя зачастую противоречивой в деталях), тут именно описание что такое RESTful в принципе.
          • 0
            Таки наоборот, подобную данной статье инфу я уже несколько раз находил (даже на хабре), а вот подробного и непротиворичивого описания RESTful на HTTP еще нет )
            • 0
              А его и не будет, потому что RESTful — лишь принципы построения архитектуры распределенных систем, из них не вытекает одна единственная реализация на HTTP.
  • +1
    На самом деле основная путаница заключается в вопросе «Что считать ресурсом?».

    К примеру, добавление комментария к посту — что тут ресурс? Пусть это будет /post. Тогда post на этот ресурс — это изменение поста. Или даже создание нового, что еще запутаннее.
    • 0
      toster.ru/q/192177#answer_526723
    • 0
      В вашем примере попробуйте осуществить обратное действие (GET или DELETE), тогда станет ясно, как это должно выглядеть.
    • +1
      А это вы решаете как организовать свои ресурсы. Например при REST над HTTP вы можете решить, что пост с комментариями один ресурс и добавлять комментарии с помощью метода PATCH, а можете решить, что комментарии отдельные ресурсы и добавлять их при помощи метода POST, ссылаясь на пост.
      • +1
        Так вот в том-то всё и дело — нет никаких внятных правил, все лепят как попало, из-за этого и путаница.
        • +1
          Путаница обычно из-за других вещей, типа той же аутентификации.
          • 0
            Что ещё за путаница с аутентификацией?
            • 0
              Например, как в рамках рестфул реализовать привычную сессионную аутентификацию, когда клиент один раз отправляет аутентификационную информацию, получает сессиионную кук, а потом пользуется ею пока сам не разлогинится или сервер его не разлогинит.
              • 0
                Не понял проблему. Вы всю процедуру, вкратце, описали, в чем проблема?
                • +1
                  В том, что такая реализация перестаёт быть stateless.
                  • 0
                    Ерунда. Проблема состояний — это проблема знания о состоянии контрагента. Здесь такой проблемы нет.
                    • 0
                      Есть. Клиент делает запрос, указывая полученную от сервера куку, предполагая, что на сервере есть сессия с таким идентификтором. Сервер отклоняет запрос, если предположение клиента неверно.
                      • 0
                        Правильно. Отклоняет запрос, если он неверный. Это ровно то же самое, как-если бы клиент просто передан-неправильные параметры. Клиенту на нужно знать, в каком состоянии сервер.
                        • 0
                          Серверу нужно знать историю запросов клиента — отправлял он верные аутентификационные данные за последнее время или нет.
                          • 0
                            зачем? т.е. мне интересно узнать что вы имеете в виду под этим, ну и зачем конкретно
                            • 0
                              Обычный сценарий:
                              — клиент отправляет на /login имя и пароль
                              — сервер создаёт сессию
                              — сервер проверяет валидность имени и пароля
                              — если всё хорошо, записывает в сессию признак, что клиент аутентифицировался
                              — отправляет клиенту результат и сессионную куку
                              — клиент в следующем значимом запросе отправляет эту куку
                              — сервер поднимает сессию по куке и проверяет есть ли в ней признак аутентификации, то есть отправлял ли клиент валидные аутентификационные данные
                              — если всё ок, то запрос выполняется
                          • +1
                            Нет, не нужно. Каждый запрос автономен. Запрос просто должен содержать правильные параметры.

                            Знание состояния — это знание до_запроса, чего вот сейчас ожидает сервер или клиент. Клиент не знает, есть ли кука на сервере, даи не нужно емуэто знать. Он просто шлет её и всё.

                            Это как отправка комментария к статье — то, что на сервере есть ранее созданная статья, вовсе не означает, что сервер находится в каком-то там состоянии «готовности к приему комментариев».
                            • 0
                              Поддерживаю, начал тоже было в том комменте писать про комментарии и статьи, но решил уточнить что конкретно имеется в виду.

                              VolCh на токенах ситуация примерно аналогичная

                              — на метод логина отправляем данные (по сути это не логин, а создание нового токена, POST /tokens, если удобно).
                              — проверяем логин-пароль, если все ок — отдаем сущность нового токена, которым затем подписываем каждый запрос.
                              — при последующих запросах просто проверяем наличие переданного в запросе токена где-либо и достаем из токена идентификатор пользователя — это не ломает stateless, токен такая же сущность, что и статья и другие, и если они не найдены — возвращаем ошибку, либо работаем с ними, если все ок.
                              — чтобы сделать логаут — DELETE /tokens/{token}
                              • 0
                                Непонятно, чем такое поведение принципиально отличается от установление TLS сессии (где в качестве токена выступает полученный secret) или http2, где есть состояние на клиенте и сервере, которые могут быть в любой момент сброшены, т. к. предназначены они для оптимизации.

                                Или, может, стоит запретить использование HTTP/1.1? Там же есть keep-alive…
                • 0
                  Проблема такая: по REST вы шлете только и только запрос только и только его. До этого про Вас сервер «НИЧЕГО» не слышал. И на основании запроса он говорит либо результат либо «Приходи вчера», т.е. ошибку.
                  Если же Вы будете использовать обычную сессионную аутентификацию, к примеру http auth digest, то это ошибка архитектуры! Это не по REST! Т.к. требуется предварительный handshake, а в REST такого нету! В REST «без запоминания состояния».

                  Еще раз: В запросе должно быть ВСЕ чтобы выполнить его или сказать об ошибке.

                  Единственный handshake между клиентом и сервером это может быть на уровне TCP-протокола и только.

                  Именно поэтому многие реализуют запросы в виде тело + N-минутный токен. на основании этого можно получить либо ошибку либо результат
                  • 0
                    Единственный handshake между клиентом и сервером это может быть на уровне TCP-протокола и только.
                    Т. е. вы отрицаете допустимость использования TLS или, скажем, HTTP2?
                    • 0
                      Это не stateless протоколы в общем случае. Нельзя построить REST на их основе. Можно поверх них построить прикладной протокол, используя их в качестве сеансового уровня, но обычно под REST HTTP понимают всё же REST как подмножество HTTP.
                      • 0
                        Это не stateless протоколы в общем случае.
                        Я это и имею ввиду. Как и TCP, если уж на то пошло. Или использование аутентификации и bearer token после (stateful для клиента, как минимум). Или использование Set-Cookie. Нет почти ничего stateless в реальном мире.

                        REST, как подмножество HTTP звучит тоже довольно странно, т. к. в RESTful любят всякие PUT/DELETE/PATCH, которые могут не поддерживаться по дороге. А PATCH вообще не содержит целостного представления обновляемого ресурса.
              • 0
                Выше же был ответ на этот вопрос — токены в каждом запросе
                • 0
                  Где брать токены простой html-форме?
                  • 0
                    Так в смысле, простой html-форме? Тогда я не особо понимаю о чем вы.

                    Если вы про то, что вернувшийся с сервера токен html-форма никак не подхватит, то насколько я помню, нигде ведь не говорится, что restful подход реализуется средствами простых html-форм, поэтому либо прибегаем к каким-то дополнительным инструментам, помимо чистого html, либо как-то извращаемся и немного ломаем понятия.

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

                    Поэтому, если отбросить все нюансы кук (все, в том числе описанные в диссере Филдинга), взять необходимость реализации аутентификации на чистых формах, то при большом желании сервер при логине может установить куку с тем самым токеном, а клиент их будет отправлять обратно, то есть каких-то принципиальных отличий между токеном в заголовке и кукой в таком исключительном случае я, по крайней мере, не вижу, потому как все состояние как таковое хранится на клиенте.
                    • 0
                      HTTP в целом stateless протокол, предусматривающий в том числе и аутентификацию несколькими способами, но в силу ряда причин эти способы мало используются, особенно на серверах, где основной текстовый контент отдаётся в html, а основные клиенты — браузеры.

                      В том же OAuth есть два сервера: сервер ресурсов и сервер аутентификации. Токен выдаёт сервер аутентификации, процесс его выдачи не входит в логику работы сервера ресурсов, последний ожидает токен в каждом запросе, для него токен — аутентифицирующая информация типа логина и пароля. С классическими же формами такое разделение плохо работает.
                      • 0
                        Так проблема же не в классических формах, а в том, что из них пытаются сделать клиентскую оболочку для restful
                        • 0
                          Проблема в том, прежде всего, что где-то в 90-х, емнип, стало популярно использовать сессии на стороне сервера для, прежде всего, аутентификации и авторизации. Один раз отправили пароль, получили куку, а по ней на сервере сессия с, как минимум, идентификатором пользователя.
                          • 0
                            Так популярно и пусть.
                            Это же не отменяет того, что я написал выше — нет смысла делать что-то, из чего это сделать напрямую нельзя.
                            Путаница — может быть, я уже понял это исходя из диалога. Проблема — пока не вижу её из того, что вы пишете относительно «как в рамках рестфул реализовать привычную сессионную аутентификацию, когда клиент один раз отправляет..»
        • 0
          Нет никаких внятных правил о том, как будет выглядеть то, что вы ещё не спроектировали. Проектирование — отдельная работа (идущая перед реализацией), которую никакой справочник паттернов не заменит. Увы, в программировании необходимо понимать то, что делаешь.
    • 0
      www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_2_1_1

      www.restapitutorial.com/lessons/restfulresourcenaming.html

      Ну а вообще, если всё перевести в плоскость коллекций, то становится проще в этом плане. И добавление комментария к посту становится добавлением комментария к списку комментариев, который имеет ссылку на пост.
  • +2
    К сожалению REST это больше buzzword и в лучше случае список рекомендаций, чем реальная база для разработки API на HTTP.

    Да и статью следовало бы назвать «Определение REST от Рой Филдинга (автора термина)».
    • –1
      Для удовлетворения принципов этого «баззворда» разработали HTTP. Не стОит называть всё то, что абстрактнее вашего уровня восприятия, баззвордами. REST — это не религия, не магия, это развитые принципы обеспечения масштабируемости (и на вычислительные ресурсы, и на количество пользователей) инфо-систем, родившиеся из конкретных реальных проблем и конкретных изначально частных их решений. HTTP — это прикладной протокол, а не транспортный.
      • +4
        Для удовлетворения принципов этого «баззворда» разработали HTTP.
        WAT? Что вы хотели этим сказать?

        Во-первых, HTTP — не REST. Или вы может быть скажете, что такие вещи как Cookie и Keep-Alive полностью соответствуют REST?

        Во-вторых, вы уверены, что HTTP именно под принципы REST разрабатывали? Особенно с учетом огромной разницы в годах появления?

        В-тертьих, у конкретно REST есть все основные признкаи «buzzword»: 1) есть много-много разных определений, 2) соответствут система условиям REST в каждом конкретном случае или нет является свпорным вопросм, 3) термин (не решение!) продвигается на лево и направо.
        • +1
          Да, термин REST появился позже создания протокола HTTP, как результат переосмысления того, каким сложились его текущие реализации, каковы проблемы и каким он должен получиться в итоге. Ваше недоумение относительно хронографии возникновения терминов правомерно.
      • 0
        Для удовлетворения принципов этого «баззворда» разработали HTTP
        Кстати, да — Рой Филдинг является не только автором концепции REST, но и одним из авторов спецификации HTTP (по крайней мере с версии 1.0, где были только GET, POST и HEAD) и одним из основателей проекта Apache.
  • 0
    >Каждый ресурс должен быть уникально обозначен постоянным идентификатором

    Я выбираю запросом список строк, которые удовлетворяют какому-то требованию. Что тут будет идентификатором ресурса? Ресурс — это список строк. При следующем запросе этот список будет другой, даже если требования будут те же. В чём смысл идентификатора такого ресурса, как его можно использовать, и как его реализовать?
    • +3
      В вашем случае ресурс динамический, меняется не один ресурс на другой, а состояние одного и того же ресурса. Это не конкретное фиксированное множество строк, а множество, удовлетворяющее ваш запрос на текущий момент времени. Представьте URL, по которому сервер возвращает текущее время. То, что при каждом запросе будет приходить новое время, не значит, что вы будете получать разные ресурсы. Вы всё так же будете получать запрашиваемый ресурс — текущее время. В вашем случае (в случае http) идентификатором ресурса будет URL вместе с вашими требованиями. Допустим `http://example.com/users?city=moscow`. Требование к постоянству идентификатора говорит о том, что вы не можете просто взять и поменять например имя параметра city на place, не оставив по прежнему адресу информации о новом размещении ресурса, и не можете поменять смысл параметра (например, вместо пользователей из Москвы начать показывать пользователей, у которых Москва — любимый город).
      • 0
        А если требования сложные, передаются в POST — то что будет идентификатором?
        URL будет примерно такой: http://example.com/users_by_query
        • +1
          передаются в POST

          То это не REST. Выбор между GET и POST осуществляется не на основе количества параметров, а на основе семантики запроса. Вы же сами почти полное предложение по-английски написали: «GET users_by_query».
          • 0
            А как в рамках REST описывать такие запросы, которые получают много параметров и возвращают какой-то ресурс?
            • +2
              Также, как запросы, которые получают мало параметров или не получают вообще. «GET users_by_query?filter=123» — вполне нормальный вариант.
              • 0
                А 100 килобайт параметров как передать?
                • +1
                  СтОит попробовать пересмотреть архитектуру вашей информационной системы. Возможно то, что вы называете параметрами, является данными (другими ресурсами).
            • 0
              Прежде чем приступить к реализации подобного решения с множеством параметров спросите себя, а кто будет пользователем вашего API. Если человек, то это лучше всего сделать через URI, если же машина, то ей пофиг, но программировать удобнее через мини-URI и остальное в теле запроса
    • 0
      например, ввести сущность userfilter («фильтр по пользователям»), создать экземпляр этой сущности, получить ее id и отдать отфильтрованных пользователей по GET /userfilter/{id}/users
      • +1
        Зачем? Вы фильтруете список пользователей, так и применяйте к нему фильтр, а не к фильтру передавайте список.

        GET /users?filter=some-filter-name-or-id
        • 0
          я, конечно, допускаю, что я ошибаюсь, но, по-моему, в RESTful HTTP нет операции применения чего либо к чему-либо, как это характерно для RPC. Я именно беру объект фильтр в идентификатором id и беру у него дочернюю коллекцию users. Я имею право так делать, т.к. я именно так определил entity userfilter. И у меня есть только операции получения entity и списка entity, но никаких операций применения чего-либо у чему-либо
          • 0
            Повторюсь, что я имел в виду только лишь само название итогового URI и его интерпретацию с точки зрения разработки, ни о каком явном применении речи не шло. Так как ну по сути своей, разницы между /users?filter={some-filter-name-or-id} и GET /userfilter/{id}/users нет никакой.

            Но мне в любом случае непонятна такая логика, когда у фильтра что-то есть дочернее. Может, у нас разные понятия фильтров или я чего-то не знаю из реализации проекта.
            • 0
              Более разумным выглядит GET /users/filter/{id}
              • 0
                С чего более разумным? Более разумным будет, если после коллекции будет идти идентификатор сущности из этой коллекции, а не совершенно левая по отношению к этому списку сущность или их коллекция.
                • 0
                  Не совершенно левая, а связанная отношением многие-к-одному с коллекцией
      • –1
        Это крайне плохой вариант. Конечно, если фильтрация выполняется долго, только так и можно поступать. Но если есть возможность за один запрос получить требуемое, такие нагромождения вредны. К тому же это близко к нарушению принципа «Отсутствие состояния».
        Кроме того, GET имеет свойство idempotent'ности, а вызов ещё раз того же «GET /users?filter=some-filter-name-or-id » возвратит не тот же результат.
        • 0
          Эмм?
          Вас почти ничего не спасет от изменения списка при фильтрации пользователей, на то он и список, чтобы изменяться и идемпотентность GET тут не причем, тем более что идемпотентность имеет несколько другой смысл (отсутствие любых сайд-эффектов), а не просто «сделал запрос и получил всегда абсолютно идентичный предыдущему запросу ответ».
          Под почти ничего имею в виду использование в запросе временных границ, которые каким-либо образом ограничивают выборку новых записей. Старые записи, конечно же, могут пропасть.

          Да и каким образом это близко к нарушению stateless?
          • +1
            stateless — мы ведь должны первым запросом создать на сервере ресурс (фильтр), и второй запрос подразумевает, что выполнен первый. То есть сервер приобретает свойство иметь состояние. (сервер или клиент — мне кажется, в данном случае состояние именно у сервера).

            Насчёт идемпотентности я согласен. Остаётся первый вопрос — два запроса вместо одного.

            На свой исходный вопрос я получил, наверно, ответ в https://cloud.google.com/bigquery/docs/reference/v2/jobs/query.
            • 0
              Я понял про что вы, но состояния здесь нет. Это аналогично примеру выше, с постом и комментами.

              Если мы не создадим фильтр (сервер вернул ошибку при создании), то и второго запроса быть не должно, вот и все.

              Нет разницы между успешным созданием поста и отправкой на него комментария через секунду и отправкой комментария через год — что в первом, что во втором случае между созданием поста и отправкой комментария не возникнет состояния, это будет 2 (с постом 3) абсолютно независимых запроса.
              • 0
                Но всё же разумно ли делать два запроса вместо одного? Пример https://cloud.google.com/bigquery/docs/reference/v2/jobs/query работает так: если ответ на запрос (POST) есть — возвращает его, если ещё нет (за заданное время) — возвращает идентификатор задания (то есть фильтр). На самом деле всегда возвращает ресурс, часть которого и ответ, и идентификатор. Получается, что именно то что я в первом посте написал и выполняется. Правильно ли гугл построил это запрос в рамках REST?
                • 0
                  Где конкретно два запроса, с юзерфильтрами? why not? Мое замечание было лишь по поводу того, что название ресурса было не совсем логичным.

                  Если система создана с условием, что пользователи системы могут создавать пользовательские фильтры и сохранять их, например, какая-нибудь там финансовая система с фильтром финансовых показателей по куче полей — решили прикрутить фичу «сохранить фильтр под названием», то пусть, никто не запрещает. Только тогда логика должна быть такая, что есть несколько ресурсов — фильтры и список финансовых показателей, т.е. какие-нибудь там /filters и /finance-results

                  Делаете POST на /filters с теми полями, которые вам разрешил сервер, получаете например имя фильтра или его id в случае успешного создания или 400 ошибку в случае плохого. Впоследствии, фильтр можно будет изменить по запросу PUT/PATCH /filters/{filter-id-or-name}. И там уже снова, либо шибки валидации, либо 404, в случае, когда фильтр с таким названием или идентификатором не найден.

                  Затем варианта два с половиной:
                  — Либо делаем просто GET /finance-results и получаем например первые 10 записей сверху
                  — Либо делаем запрос типа GET /finance-results?filter={filter-id-or-name}, который уже применяет фильтр, указанный пользователем в запросе.
                  — Если же вдруг по каким-то причинам фильтра не существует, или клиент указал неправильное название фильтра — там уже на усмотрение тех, кто создает систему, но я бы вернул либо пустой список, либо ошибку, что фильтра не существует, вот и все.

                  Если же вопрос про два запроса был про гугл… Не знаком с Google BigQuery, но в таком сценарии, как вы описываете, ничего плохо я, опять же, не вижу.

                  Формально, в данном случае, вы создаете сущность Job с неким query и возвращает он вам также Job (только в случае успешной валидации), только по мере обработки этой сущности, Job либо заполнен остальными полями (результат выполнения Query), либо нет. Если же создание Job не прошло валидацию, состояния никакого здесь нет — просто вернул список ошибок и все, клиент при этом может делать все что угодно, но логичным действием, конечно же, будет новый запрос на создание Job с исправлениями (а не запрос на какой-нибудь адрес проверки статуса Job, который не создан), и гугл снова его независимо от других запросов на создание его провалидирует.

                  Затем вы успешно создали валидное задание с каким-то запросом для гугла. Гугл в своей очереди эти запросы выполняет и ему в общем-то, плевать кто конкретно создал этот Job и правильный ли в этом Job указан query, вернет ли он что-то или же нет, ему также не требуется никаких знаний о предыдущих и будущих запросах.

                  Всё просто — получил, выполнил, вернул.
      • 0
        А если кто-то изменит userfilter между генерацией и вторым запросом?
        • 0
          ровно та же ситуация, как кто-то добавит пост к блогу до того, как вы сами добавили первый пост и, к вашему удивлению, список постов окажется не пуст, хотя вы этого не ожидали. Защита в обоих случаях может быть предложена одинаковая — разрешение изменения только создателем. Для разрешения конфликтов конкурентных изменений создателем (например из соседних вкладок) протокол допускает передачу версии объекта.
  • +1
    Вапще вопросы про HTTP REST возникают из-за смешения уровней проектирования. RESTful-интерфейс может быть реализован и над HTTP, и над протоколом уровнем выше, и над протоколом уровнем выше.

    Например, самый спорный вопрос — авторизация и аутентификация, в RESTful реализуется с помощью передачи в каждом запросе необходимых данных состояния (сеанса). Но запрос, полученный сервером не является HTTP-запросом.

    С точки зрения архитектуры, перед RESTful-сервером стоит, как минимум, HTTP-сервер, а чаще ещё и интерпретатор скриптов. То есть, HTTP запросы, передаваемые HTTP-серверу не обязаны быть RESTful, они обязаны стать RESTful во время перехода вверх по архитектуре.

    Например, HTTP-сервер может получать только GET и POST, но он может симулировать другие методы с помощью дополнительного параметра или заголовка. Но HTTP-сервер обязан передать выше запрос, преобразованный таким образом, чтобы применяемый метод передавался единообразно. То есть, каким бы способом мы не указали желаемый метод для HTTP-сервера, он или использует исходный, или возьмёт замещаемый метод из заголовка, или возьмёт его из параметра запроса. В этот момент, HTTP-сервер нарушит принципы RESTful, но ведь он и не обязан их соблюдать. Зато, стоящий дальше RESTful сервер получит полный и единообразный запрос, который соответствует принципам RESTful.

    Ещё пример, клиент в ходе работы использует некие защищённые данные сеанса. С точки зрения RESTful мы не можем сохранять состояние. Однако, мы можем построить архитектуру таким образом, чтобы RESTful-компонентам и не приходилось это делать. Например, таким образом: клиент отправляет HTTP-серверу запрос с идентификатором сеанса. HTTP-сервер получает данные сеанса из хранилища (файла, базы, памяти… не важно) и присоединяет их к запросу, который получает RESTful-сервер. С точки зрения RESTful-сервера некий клиент прислал ему полный запрос, не требующий дополнительной обработки, который можно просто выполнить, ведь данные полны, и их достаточно. В ответ мы отправляем клиенту и результат, и изменённые пользовательские данные сеанса. При окончательной отправке клиенту данные сеанса HTTP-сервер сохраняет в хранилище и заменяет идентификатором. С точки зрения RESTful-сервера мы ничего не нарушили.

    HTTP-сервер и RESTful-сервер здесь — скорее логические, чем фактические понятия. Они могут быть реализованы в рамках одного исполняемого компонента. Но могут быть и фактически двумя разными программными и даже аппаратными серверами. Протокол их общения друг с другом тоже произвольный. В рамках одного компонента это может быть и вовсе простой вызов функций, где запрос представляется в виде структуры в памяти. Точно так же можно разделить RESTful-клиент и HTTP-клиент.

    Вапще, ещё начиная с модели OSI вся сеть представляет из себя конвейер, или, скорее, матрёшку. Когда кто-то бунтует по поводу того, зачем ему знать OSI, я отвечаю — это основа, показывающая, как проектировать сетевые приложения. Главный принцип — если что-то не реализуется на одном уровне, реализуй уровень выше, сделай матрёшку поменьше, чтобы вложить в исходную. Так и с RESTful, его ограничения достаточно строги для реализации на уровне HTTP, но никто не запрещает сделать уровень над HTTP, и реализовать его по принципу RESTful.
  • +2
    Уже давно вижу статьи, «срывающие покровы с RESTful».

    Считаю, что на RESTful как и на паттернах проектирования не нужно зацикливаться. Руководствуйтесь здравым смыслом. API должен быть прост, понятен и удобен для пользователя. Это гораздо важнее строгого соответствия какой бы то ни было парадигме.
    • 0
      Попытка соответствия какой-либо парадигме без понимания принципов этой парадигмы и решаемых ею проблем — культ Карго. «Руководствуйтесь здравым смыслом» = «пользуйтесь методами и инструментами, в которых компетентны, выделяйте отдельное время для освоения новых подходов прежде чем применять их в рабочих проектах.»

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

Самое читаемое Разработка