Архитектуру REST часто путают с некой методикой именования URL на сайте, правилами применения HTTP-методов: GET, POST, PUT, DELETE. Но REST — мощнейший архитектурный стиль, затрагивающий всё приложение, концентрирующийся на производительности и совместимости с другими системами.
Между веб-сервером, на котором работает сайт, и браузером клиента находятся многочисленные промежуточные узлы, обрабатывающие и кэширующие передаваемую информацию. На сайт заходят поисковые роботы. Всем им безразличны архитектурные велосипеды — они воспринимают веб-сайт, как черный ящик с HTTP-интерфейсом. Если веб-сервер точно следует требованиям протокола, он работает эффективно.
Унифицированный программный интерфейс
Однаждына поддержку пришел проект с большим каталогом товаров. У каждого товара — своя страничка с фотографиями, описанием, отзывами и т.д. Всё как обычно. Но клиент жаловался на то, что на почту в большом объеме льются письма с информацией о выставлении товарам оценок. На что я обратил внимание: звездочки в голосовалке товаров были оформлены ссылками на php-скрипт. Скрипт запоминал оценку и высылал уведомление клиенту на почту. Откуда же столько оценок? Поисковый робот регулярно во время индексации сайта заходил на страничку товара, видел набор ссылок и начинал переходить по этим ссылкам, нечаянно запуская скрипт выставления оценки. К слову, он заходил по всем ссылкам равномерно, поэтому оценки получались средние.
Почему это происходило? Поисковый робот воспринимал GET-запросы, которые он делает, как безопасные и идемпотентные. Но обработчик был небезопасным и неидемпотентным.
Метод | Безопасный | Идемпотентный |
---|---|---|
GET | Да | Да |
HEAD | Да | Да |
OPTIONS | Да | Да |
PUT | Нет | Да |
DELETE | Нет | Да |
POST | Нет | Нет |
Безопасный запрос — это запрос, который не меняет состояние приложения.
Идемпотентный запрос — это запрос, эффект которого от многократного выполнения равен эффекту от однократного выполнения.
Так как POST-запросы небезопасные и неидемпотентные, браузер никогда не повторяет отправку формы POST-запросом автоматически.
Идемпотентный запрос можно повторять многократно, если нет уверенности, что предыдущий такой же запрос был выполнен. Рассмотрим пример с коллекцией книг в библиотеке. Если необходимо удалить книгу из библиотеки по определенному ISBN, используя для этого DELETE-запрос, можно будет послать повторный запрос на удаление. Результатом даже нескольких запросов будет удаление книги по данному ISBN — ожидаемый результат. Если необходимо поменять название у определенной книги PUT запросом, то без опасения можно послать несколько запросов. Результатом будет изменение названия книги на необходимое.
В то же время многократное обращение к функции добавления книги посредством POST-запроса приведет к добавлению нескольких книг. Именно поэтому повторные неидемпотентные запросы могут привести к неожиданным результатам.
Из принципов идемпотентности и безопасности следуют отношения между HTTP-методами и CRUD-операциями.
HTTP-метод | Операция |
---|---|
POST | Create |
GET | Read |
PUT | Update |
DELETE | Delete |
Унификация интерфейса позволяет не делать типовых ошибок и не нарушать семантику HTTP-запросов.
Влияет ли это на скорость? Да. Как через уменьшение количества ошибок, так и через возможность прозрачного кэширования безопасных методов.
Не храним состояние клиента
Еще один веб-сайт, реализованный на коммерческом движке интернет-магазина, хранил сессии в БД. Для генерирования каждой страницы он запускал сессии. В момент старта сессии он создавал запись в таблице БД, а когда заканчивал обрабатывать запрос клиента, сохранял сессионные данные. Сессии идентифицировал по кукам. Если приходил поисковый робот (а он обычно не запоминает куки), то каждая сгенерированная страничка порождала старт сессии и вставку в сессионную таблицу. Вместе с приличной посещаемостью веб-сайта это приводило к большой нагрузке на сессионную таблицу: постоянно шли запросы на создание сессии, на её обновление. А вставка больших сериализованных данных из сессии в БД занимала приличное количество времени и иногда подвешивала сайт.
А теперь вопрос: зачем стартовать сессию при прорисовке страницы, если большая часть информации на этой странице от пользователя не зависит? Не лучше было бы сгенерировать страницу вообще без пользовательских данных, не трогая сессии, а потом уже подгрузить оставшиеся элементы таким образом, чтобы сессия не запускалась до авторизации пользователя?
Сессии при неграмотном применении — зло. Они мешают производительности и масштабируемости проекта. Если их не получается избегать, то необходимо локализовывать их применение.
Кэширование
PHP-разработчики любят memcache. Когда возникают проблемы с производительностью, то предлагают закэшировать или тяжелые запросы к БД, или часть генерируемой страницы, или даже вообще весь контент страницы, если сессионные данные не мешаются. И это называется оптимизацией производительности.
Печальная правда состоит в том, что большинство проектов, которые начинают прикручивать memcache, могли бы этого не делать при их посещаемости, если бы следовали REST-архитектуре.
Протокол HTTP содержит все необходимые заголовки для организации кэширования как на стороне клиента, так и промежуточными звеньями между клиентом и веб-сервером. Эти заголовки позволят избегать лишних запросов к серверу, а также при необходимости размещать непосредственно перед веб-сервером специализированное ПО для кэширования (например, Squid), внося минимум дополнительной логики в само приложение.
Обычно кэширующие заголовки выставляют только для успешных запросов (200 OK), но также можно кэшировать и другие запросы, в том числе с ошибками:
- 301 (Moved Permanently): если содержимое ресурса переместилось по другому адресу, то это, вероятно на долго. Можно кэшировать эти запросы на значительный промежуток времени
- 400 (Bad Request): если пользователь прислал неверные данные, то повторная их отправка вызовет ту же ошибку. Следовательно, можно попросить клиента закэшировать ответ, чтобы избежать повторного обращения к этому ресурсу.
- 404 (Not Found): если ресурса нет, он был удален и его существование не планируется, то почему бы не порекомендовать клиенту закэшировать этот ответ.
- 405 (Method Not Allowed): если ресурс не поддерживает метод PUT, то, вероятно, до обновления веб-сервиса он и не будет его поддерживать. Кэшируем.
- 410 (Gone): ситуация такая же, как и с 404.
Правильно разбивая веб-приложение на компоненты, организуя прозрачное кэширование на стороне клиента, можно добиться значительных результатов. Но прежде всего необходимо представлять, как работает протокол HTTP, чтобы использовать все его возможности и глубже понимать принципы построения RESTful-приложений.