You've already forked The-API-Book
mirror of
https://github.com/twirl/The-API-Book.git
synced 2025-08-10 21:51:42 +02:00
HTTP API: the beginning
This commit is contained in:
@@ -8,4 +8,16 @@
|
||||
|
||||
В рамках этого раздела мы попытаемся описать те задачи проектирования API и подходы к их решению, которые представляются нам наиболее важными. Мы не претендуем здесь на то, чтобы охватить *все* проблемы и тем более — все решения, и скорее фокусируемся на описании подходов к решению типовых задач с их достоинствами и недостатками. Мы понимаем, что читатель, знакомый с классическими трудами «банды четырёх», Гради Буча и Мартина Фаулера ожидает от раздела с названием «Паттерны API» большей системности и ширины охвата, и заранее просим у него прощения.
|
||||
|
||||
**NB**: первый из подобных паттернов — это API-first подход к разработке ПО, который мы описали во введении.
|
||||
**NB**: первые два паттерна, о которых необходимо упомянуть — это API-first подход к разработке ПО и организация аутентификации пользователей, которые мы описали во введении.
|
||||
|
||||
#### Принципы решения типовых проблем проектирования API
|
||||
|
||||
Прежде, чем излагать сами паттерны, нам нужно понять, чем же разработка API отличается от разработки обычных приложений. Ниже мы сформулируем три важных принципа, на которые будем ссылаться в последующих главах.
|
||||
|
||||
Рассматривая API как мост, связывающий два разных программных контекста, мы в большинстве случаев ожидаем, что стороны каньона функционируют независимо и изолированно друг от друга — причём чем крупнее контексты и сложнее каналы передачи данных, тем более независимо и изолированно они работают. В частности:
|
||||
|
||||
1. Чем более распределена и многосоставна система, чем более общий канал связи используется для коммуникации — тем более вероятны ошибки в процессе взаимодействия. В частности, в наиболее интересном нам кейсе распределённых многослойных клиент-серверных систем возникновение исключения на клиенте (потеря контекста, т.е. перезапуск приложения), на сервере (конвейер выполнения запроса выбросил исключение на каком-то шаге), в канале связи (соединение полностью или частично потеряно) или любом промежуточном агенте (например, промежуточный веб-сервер не дождался ответа бэкенда и вернул ошибку гейтвея) — норма жизни, и все системы должны проектироваться таким образом, что **в случае возникновения исключения любого рода клиенты API должны быть способны восстановить своё состояние** и продолжить корректно работать.
|
||||
|
||||
2. Чем больше различных партнёров подключено к API, тем больше вероятность того, что какие-то из предусмотренных вами механизмов обеспечения корректности взаимодействия будет имплементирован неправильно. Иными словами, **вы должны ожидать не только физических ошибок, связанных с состоянием сети или перегруженностью сервера, но и логических, связанных с неправильным использованием API** (и, в частности, предотвращать возможный отказ в обслуживании одних партнёров из-за ошибок в коде других партнёров).
|
||||
|
||||
3. Любая из частей системы может вносить непредсказуемые задержки исполнения запросов, причём достаточно высокого — секунды, десятки секунд — порядка. Даже если вы полностью контролируете среду исполнения и сеть, задержку может вносить само клиентское приложение, которое может быть просто написано неоптимальным образом или же работать на слабом или перегруженном устройстве. Если выполнение какой-то задачи через API требует последовательного исполнения цепочки вызовов, то **необходимо предусматривать механизмы синхронизации промежуточного состояния**.
|
||||
|
@@ -1,11 +1,24 @@
|
||||
#### Принципы решения типовых проблем проектирования API
|
||||
### Аутентификация партнёров и авторизация вызовов API
|
||||
|
||||
Прежде, чем излагать сами паттерны, нам нужно понять, чем же разработка API отличается от разработки обычных приложений. Ниже мы сформулируем три важных принципа, на которые будем ссылаться в последующих главах.
|
||||
Прежде, чем мы перейдём к обсуждению технических проблем и их решений, мы не можем не остановиться на важном вопросе авторизации вызовов API и аутентификации осуществляющих вызов клиентов (*AA* — Authorization & Authentication). Исходя из всё того же принципа мультипликатора («API умножает как возможности, так и проблемы») организация AA — одна из самых насущных проблем провайдера API, особенно публичного. Тем удивительнее тот факт, что в настоящий момент не существует какого стандартного подхода к ней — почти каждый крупный сервис разрабатывает какой-то свой интерфейс для решения этих задач, причём зачастую достаточно архаичный.
|
||||
|
||||
Рассматривая API как мост, связывающий два разных программных контекста, мы в большинстве случаев ожидаем, что стороны каньона функционируют независимо и изолированно друг от друга — причём чем крупнее контексты и сложнее каналы передачи данных, тем более независимо и изолированно они работают. В частности:
|
||||
Если отвлечься от технических деталей имплементации (в отношении которых мы ещё раз настоятельно рекомендуем не изобретать велосипед и использовать стандартные подходы и протоколы безопасности), то, по большому счёту, есть два основных способа авторизовать выполнение некоторой операции через API:
|
||||
* завести в системе специальный тип аккаунта «робот» и выполнять операции от имени робота;
|
||||
* авторизовать вызывающую систему (бэкенд или тип клиента) как единое целое (обычно для этого используются ключи, подписи или сертификаты).
|
||||
|
||||
1. Чем более распределена и многосоставна система, чем более общий канал связи используется для коммуникации — тем более вероятны ошибки в процессе взаимодействия. В частности, в наиболее интересном нам кейсе распределённых многослойных клиент-серверных систем возникновение исключения на клиенте (потеря контекста, т.е. перезапуск приложения), на сервере (конвейер выполнения запроса выбросил исключение на каком-то шаге), в канале связи (соединение полностью или частично потеряно) или любом промежуточном агенте (например, промежуточный веб-сервер не дождался ответа бэкенда и вернул ошибку гейтвея) — норма жизни, и все системы должны проектироваться таким образом, что **в случае возникновения исключения любого рода клиенты API должны быть способны восстановить своё состояние** и продолжить корректно работать.
|
||||
Разница между двумя подходами заключается в гранулярности доступа:
|
||||
* если клиент API выполняет запросы от имени пользователя системы, то и выполнять он может только те операции, которые разрешены конкретному пользователю;
|
||||
* если клиент API авторизуется ключом, то он может выполнять запросы фактически от имени любого пользователя.
|
||||
|
||||
2. Чем больше различных партнёров подключено к API, тем больше вероятность того, что какие-то из предусмотренных вами механизмов обеспечения корректности взаимодействия будет имплементирован неправильно. Иными словами, **вы должны ожидать не только физических ошибок, связанных с состоянием сети или перегруженностью сервера, но и логических, связанных с неправильным использованием API** (и, в частности, предотвращать возможный отказ в обслуживании одних партнёров из-за ошибок в коде других партнёров).
|
||||
Первая система, таким образом, является более гранулярной (робот может быть «виртуальным сотрудником» организации, то есть иметь доступ только к ограниченному набору данных) и вообще является естественным выбором для тех API, которые являются дополнением к существующему сервису для конечных пользователей (и, таким образом, использовать уже существующие системы AA). Недостатки же этого способа вытекают из его достоинств.
|
||||
|
||||
3. Любая из частей системы может вносить непредсказуемые задержки исполнения запросов, причём достаточно высокого — секунды, десятки секунд — порядка. Даже если вы полностью контролируете среду исполнения и сеть, задержку может вносить само клиентское приложение, которое может быть просто написано неоптимальным образом или же работать на слабом или перегруженном устройстве. Если выполнение какой-то задачи через API требует последовательного исполнения цепочки вызовов, то **необходимо предусматривать механизмы синхронизации промежуточного состояния**.
|
||||
1. Необходимо организовать какой-то процесс безопасной авторизации пользователя-робота (например, через получение для него токенов из какого-то веб-интерфейса), поскольку стандартная логин-парольная схема логина (тем более двухфакторная) слаба применима к клиенту API.
|
||||
2. Необходимо сделать для пользователей-роботов исключения из почти всех систем безопасности:
|
||||
* роботы выполняют намного больше запросов, чем обычные люди, и могут делать это в параллель (в том числе с разных IP-адресов, расположенных в разных дата-центрах);
|
||||
* роботы не принимают куки и не могут решить капчу;
|
||||
* робота нельзя профилактически разлогинить и/или инвалидировать его токен (это чревато простоем бизнеса партнёра), поэтому для роботов часто приходится изобретать токены с большим временем жизни и/или процедуру «подновления» токена.
|
||||
3. Наконец, вы столкнётесь с очень большими проблемами, если вам всё-таки понадобится дать роботу возможность выполнять операцию от имени другого пользователя (поскольку такую возможность придётся тогда либо выдать и обычным пользователям, либо каким-то образом скрыть её и разрешить только роботам).
|
||||
|
||||
Если же API не предоставляется как сервис для конечных пользователей, авторизация клиентов через API-ключи более проста в имплементации. При этой схеме предполагается, что, если запрос подписан правильно (передан правильный API-ключ или сертификат, валидна цифровая подпись), то передающий клиент может выполнять любые операции. Здесь можно добиться гранулярности уровня эндпойнта (т.е. партнёр может выставить для ключа набор эндпойнтов, которые можно с ним вызывать), но более гранулярные системы (когда ключу выставляются ещё и ограничения на уровне бизнес-сущностей) уже намного сложнее в разработке и применяются редко.
|
||||
|
||||
Обе схемы, в общем-то, можно свести друг к другу (если разрешить роботным пользователям выполнять операции от имени любых других пользователей, мы фактически получим авторизацию по ключу; если создать по API-ключу какой-то ограниченный сегмент данных в рамках которого выполняются запросы, то фактически мы получим систему аккаунтов пользователей), и иногда можно встретить гибридные схемы (когда запрос авторизуется и API-ключом, и токеном пользователя). В рамках этой главы мы скорее хотели бы показать, в чём принципиальное различие двух «чистых» подходов, нежели давать обзор всех возможных схем.
|
@@ -1,6 +1,6 @@
|
||||
### Стратегии синхронизации
|
||||
|
||||
Начнём рассмотрение паттернов проектирования API с последнего из ограничений, описанных в предыдущей главе — необходимости синхронизировать состояния. Представим, что конечный пользователь размещает заказ на приготовление кофе через наш API. Пока этот запрос путешествует от клиента в кофейню и обратно, многое может произойти; например, рассмотрим следующую последовательность событий.
|
||||
Перейдём теперь к техническим проблемам, стоящим перед разработчикам API, и начнём с последней из описанных во вводной главе — необходимости синхронизировать состояния. Представим, что конечный пользователь размещает заказ на приготовление кофе через наш API. Пока этот запрос путешествует от клиента в кофейню и обратно, многое может произойти. Например, рассмотрим следующую последовательность событий.
|
||||
|
||||
1. Клиент отправляет запрос на создание нового заказа.
|
||||
2. Из-за сетевых проблем запрос идёт до сервера очень долго, а клиент получает таймаут:
|
||||
|
23
src/ru/drafts/05-Раздел IV. HTTP API и REST/01.md
Normal file
23
src/ru/drafts/05-Раздел IV. HTTP API и REST/01.md
Normal file
@@ -0,0 +1,23 @@
|
||||
### О концепции «HTTP API» и терминологии
|
||||
|
||||
Вопросы организации HTTP API — к большому сожалению, одни из самых «холиварных». Будучи одной из самых популярных и притом весьма непростых в понимании (в виду большого по объёму и фрагментированного на отдельные спецификации стандарта) технологий, стандарт HTTP обречён быть плохо понятым и превратно истолкованным миллионами разработчиков и многими тысячами учебных пособий. Поэтому, прежде чем переходить непосредственно к полезной части настоящего раздела, мы обязаны дать уточнения, о чём же всё-таки пойдёт речь.
|
||||
|
||||
Так сложилось, что в настоящий момент сетевой стек, используемый для разработки распределённых API, практически полностью унифицирован в двух точках. Одна из них — это Internet protocol suite, состоящий из базового протокола IP и надстройки в виде TCP или UDP над ним. На сегодняшний день альтернативы TCP/IP используются в чрезвычайно ограниченном спектре задач, и средний разработчик практически не сталкивается ни с каким другим сетевым стеком. Однако у TCP/IP с прикладной точки зрения есть существенный недостаток — он оперирует поверх системы IP-адресов, которые плохо подходят для организации распределённых систем:
|
||||
* во-первых, люди не запоминают IP-адреса и предпочитают оперировать «говорящими» именами;
|
||||
* во-вторых, IP-адрес является технической сущностью, связанной с узлом сети, а разработчики приложений хотели бы иметь возможность добавлять и изменять узлы, не нарушая работы своих приложений.
|
||||
|
||||
Удобной (и опять же имеющей почти стопроцентное проникновение) абстракцией над IP-адресами оказалась система доменных имён, позволяющий назначить узлам сети человекочитаемые синонимы и манипулировать ими по усмотрению разработчика. Появление доменных имён потребовало разработки клиент-серверных протоколов более высокого, чем TCP/IP, уровня, и для передачи текстовых (гипертекстовых) данных таким протоколом стал [HTTP 0.9](https://www.w3.org/Protocols/HTTP/AsImplemented.html), разработанный Тимом Бёрнерсом-Ли опубликованный в 1991 году. Протокол был очень прост и всего лишь описывал способ получить документ, открыв TCP/IP соединение с сервером и передав строку вида `GET адрес_документа`. Позднее протокол был дополнен стандартом URL, позволяющим детализировать адрес документа, и далее протокол начал развиваться стремительно: появились новые глаголы помимо `GET`, статусы ответов, заголовки, типы данных и так далее.
|
||||
|
||||
Поскольку, с одной стороны, HTTP — простой и практически безальтернативный протокол, позволяющий обращаться к узлам сети по доменным именам, и, с другой стороны, он предоставляет почти бесконечное количество разнообразных расширений над базовой функциональностью, он довольно быстро стал вторым аттрактором, к которому сходятся сетевые технологии: практически все запросы к API внутри TCP/IP-сетей осуществляются по протоколу HTTP (и даже если используется альтернативный протокол, запросы в нём всё равно зачастую оформлены в виде HTTP-пакетов просто ради удобства). При этом, однако, в отличие от TCP/IP-уровня, каждый разработчик сам для себя решает, какой объём функциональности, предоставляемой протоколом и многочисленными расширениями к нему, он готов применить. В частности, GRPC и GraphQL работают поверх HTTP, но используют крайне ограниченное подмножество его возможностей.
|
||||
|
||||
Тем не менее, *обычно* словосочетание «HTTP API» используется не просто в значении «любой API, использующий протокол HTTP»; говоря «HTTP API» мы *скорее* подразумеваем, что он используется не как дополнительный третий протокол транспортного уровня (как это происходит в GRPC и GraphQL), а именно как протокол уровня приложения, то есть составляющие протокола (такие как: URL, заголовки, HTTP-глаголы, статусы ответа, политики кэширования и т.д.) используются в соответствии с их семантикой, определённой в стандартах. Как нетрудно заметить, определение это скорее количественное (какие конкретно из доступных возможностей используются) нежели качественное.
|
||||
|
||||
HTTP появился изначально для передачи размеченного гипертекста, что для программных интерфейсов подходит слабо. Однако HTML быстро эволюционировал в более строгий и машиночитаемый XML, который быстро стал одним из общепринятых форматов описания вызовов API. С начала 2000-х XML начал вытесняться более простым и интероперабельным JSON, и сегодня говоря о HTTP API, чаще всего имеют в виду такие интерфейсы, в которых данные передаются в формате JSON по протоколу HTTP.
|
||||
|
||||
В рамках настоящего раздела мы поговорим о дизайне сетевых API, обладающих следующими характеристиками:
|
||||
* протоколом взаимодействия является HTTP версий 1.1 и выше;
|
||||
* форматом данных является JSON (за исключением эндпойнтов, специально предназначенных для передачи файлов в других форматах);
|
||||
* в качестве идентификаторов ресурсов используется URL в соответствии с описанной в стандарте семантикой;
|
||||
* спецификация протокола HTTP нигде в дизайне API не нарушается и не игнорируется специально.
|
||||
|
||||
Такое API мы будем для краткости называть просто «HTTP API».
|
41
src/ru/drafts/05-Раздел IV. HTTP API и REST/02.md
Normal file
41
src/ru/drafts/05-Раздел IV. HTTP API и REST/02.md
Normal file
@@ -0,0 +1,41 @@
|
||||
### Мифология REST
|
||||
|
||||
В 2000 году один из авторов спецификаций HTTP и URI Рой Филдинг защитил докторскую диссертацию на тему «Архитектурные стили и дизайн архитектуры сетевого программного обеспечения», пятая глава которой была озаглавлена как «[Representational State Transfer (REST)](https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm]».
|
||||
|
||||
Как нетрудно убедиться, прочитав эту главу, она представляет собой довольно абстрактный обзор распределённой сетевой архитектуры, вообще не привязанной ни к HTTP, ни к URL. Более того, она вовсе не посвящена правилам дизайна API — в этой главе Филдинг методично *перечисляет ограничения*, с которыми приходится сталкиваться разработчику распределённого сетевого программного обеспечения. Вот они:
|
||||
|
||||
* клиент и сервер не знают внутреннего устройства друг друга (клиент-серверная архитектура);
|
||||
* сессия хранится на клиенте (stateless-дизайн);
|
||||
* данные должны размечаться как кэшируемые или некэшируемые;
|
||||
* интерфейсы взаимодействия между компонентами должны быть стандартизированы;
|
||||
* сетевые системы являются многослойными, т.е. сервер может быть только прокси к другим серверам;
|
||||
* функциональность клиента может быть расширена через поставку кода с сервера.
|
||||
|
||||
На этом определение REST заканчивается. Дальше Филдинг конкретизирует некоторые аспекты имплементации систем в указанных ограничениях, но все они точно так же являются совершенно абстрактными. Буквально: «ключевая информационная абстракция в REST — ресурс; любая информация, которой можно дать наименование, может быть ресурсом».
|
||||
|
||||
Ключевой вывод, который следует из определения REST по Филдингу, вообще говоря, таков: *любое сетевое ПО в мире соответствует принципам REST*, за очень-очень редкими исключениями.
|
||||
|
||||
В самом деле:
|
||||
* очень сложно представить себе систему, в которой не было бы хоть какой-нибудь стандартизации взаимодействия между компонентами, иначе её просто невозможно будет разрабатывать — в частности, как мы уже отмечали, почти всё сетевое взаимодействие в мире использует стек TCP/IP;
|
||||
* раз есть интерфейс взаимодействия, значит, под него всегда можно мимикрировать, а значит, требование независимости имплементации клиента и сервера всегда выполнимо;
|
||||
* раз можно сделать альтернативную имплементацию сервера — значит, можно сделать и многослойную архитектуру, поставив дополнительный прокси между клиентом и сервером;
|
||||
* поскольку клиент представляет собой вычислительную машину, он всегда хранит хоть какое-то состояние и кэширует хоть какие-то данные;
|
||||
* наконец, code-on-demand вообще лукавое требование, поскольку всегда можно объявить данные, полученные по сети, «инструкциями» на некотором формальном языке, а код клиента — их интерпретатором.
|
||||
|
||||
Да, конечно, вышеприведённое рассуждение является софизмом, доведением до абсурда. Самое забавное в этом упражнении состоит в том, что мы можем довести его до абсурда и в другую сторону, объявив ограничения REST неисполнимыми. Например, очевидно, что требование code-on-demand противоречит требованию независимости клиента и сервера — клиент должен уметь интерпретировать код с сервера, написанный на вполне конкретном языке. Что касается правила на букву S («stateless»), то систем, в которых сервер *вообще не хранит никакого контекста клиента* в мире вообще практически нет, поскольку ничего полезного для клиента в такой системе сделать нельзя. (Что, кстати, постулируется в соответствующем разделе прямым текстом: «коммуникация … не может получать никаких преимуществ от того, что на сервере хранится какой-то контекст».)
|
||||
|
||||
Наконец, сам Филдинг внёс дополнительную энтропию в вопрос, выпустив в 2008 году [разъяснение](https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven), что же он имел в виду. В частности, в этой статье утверждается, что:
|
||||
* разработка REST API должна фокусироваться на описании медиатипов, представляющих ресурсы; при этом клиент вообще ничего про эти медиатипы знать не должен;
|
||||
* в REST API не должно быть фиксированных имён ресурсов и операций над ними, клиент должен извлекать эту информацию из ответов сервера.
|
||||
|
||||
Короче говоря, REST по Филдингу-2008 подразумевает, что клиент, получив каким-то образом ссылку на точку входа в REST API, далее должен быть в состоянии полностью выстроить взаимодействие с API, не обладая вообще никаким априорным знанием о нём, и уж тем более не должен содержать никакого специально написанного кода для работы с этим API.
|
||||
|
||||
**NB**: оставляя за скобками тот факт, что Филдинг весьма вольно истолковал свою же диссертацию, просто отметим, что ни одна существующая система в мире не удовлетворяет описанию REST по Филдингу-2008.
|
||||
|
||||
Нам неизвестно, почему из всех обзоров абстрактной сетевой архитектуры именно диссертация Филдинга обрела столь широкую популярность; очевидно другое: теория Филдинга, преломившись в умах миллионов программистов (включая самого Филдинга), превратилась в целую инженерную субкультуру. Путём редукции абстракций REST применительно конкретно к протоколу HTTP и стандарту URL родилась химера «RESTful API», [конкретного смысла которой никто не знает](https://restfulapi.net/).
|
||||
|
||||
Хотим ли мы тем самым сказать, что REST является бессмысленной концепцией? Отнюдь нет. Мы только хотели показать, что она допускает чересчур широкую интерпретацию, в чём одновременно кроется и её сила, и её слабость.
|
||||
|
||||
С одной стороны, благодаря многообразию интерпретаций, разработчики API выстроили какое-то размытое, но всё-таки полезное представление о «правильной» архитектуре API. С другой стороны, за отсутствием чётких определений тема REST API превратилась в один из самых больших источников холиваров среди программистов — притом холиваров самого низкого технического уровня, поскольку популярное представление о REST не имеет вообще никакого отношения ни к тому REST, который описан в диссертации Филдинга (и тем более к тому REST, который Филдинг описал в своём манифесте 2008 года).
|
||||
|
||||
Термин «REST API» в последующих главах мы использовать не будем, поскольку, как видно из написанного выше, он нам просто ни к чему — на все описанные Филдингом ограничения мы многократно ссылались по ходу предыдущих глав, поскольку, повторимся, распределённое сетевое API попросту невозможно разработать, не руководствуясь ими. Однако HTTP API (повторимся, подразумевая под этим JSON-over-HTTP эндпойнты, утилизирующие семантику, описанную в стандартах HTTP и URL), как мы его будем определять в последующих главах, фактически соответствует усреднённому представлению о «RESTful API», как его можно найти в многочисленных учебных пособиях.
|
27
src/ru/drafts/05-Раздел IV. HTTP API и REST/03.md
Normal file
27
src/ru/drafts/05-Раздел IV. HTTP API и REST/03.md
Normal file
@@ -0,0 +1,27 @@
|
||||
### Преимущества и недостатки HTTP API
|
||||
|
||||
У читателя может возникнуть резонный вопрос — а почему вообще существует такая дихотомия: какие-то API полагаются на стандартную семантику HTTP, а какие-то полностью от неё отказываются в пользу новоизобретённых стандартов. Например, если мы посмотрим на [формат ответа в JSON-RPC](https://www.jsonrpc.org/specification#response_object), то мы обнаружим, что он легко мог бы быть заменён на стандартные средства протокола HTTP. Вместо
|
||||
|
||||
```
|
||||
200 OK
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id",
|
||||
"error": {
|
||||
"code": -32600,
|
||||
"message": "Invalid request"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
сервер мог бы ответить просто `400 Bad Request` (с передачей идентификатора запроса, ну скажем, в заголовке `X-Request-ID`). Тем не менее, разработчики протокола посчитали нужным разработать свой собственный формат.
|
||||
|
||||
Такая ситуация (не только конкретно с JSON-RPC, а почти со всеми высокоуровневыми протоколами поверх HTTP) сложилась по множеству причин, включая разнообразные исторические (например, невозможность использовать многие возможности HTTP из ранних реализаций `XMLHttpRequest` в браузерах). Однако, нужно отметить одно принципиальное различие между использованием протоколов уровня приложения (как в нашем примере с JSON-RPC) и чистого HTTP: если ошибка `400 Bad Request` является прозрачной для практически любого сетевого агента, то ошибка в собственном формате JSON-RPC таковой не является — во-первых, потому что понять её может только агент с поддержкой JSON-RPC, а, во-вторых, что более важно, в JSON-RPC статус запроса *не является метаинформацией*. Протокол HTTP позволяет прочитать такие детали, как метод и URL запроса, статус операции, заголовки запроса и ответа, *не читая тело запроса целиком*. Для большинства протоколов более высокого уровня это не так: даже если агент и обладает поддержкой протокола, ему необходимо прочитать запрос и ответ целиком и разобрать их.
|
||||
|
||||
Иными словами, **при разработке HTTP мы предоставляем возможность промежуточным агентам читать метаданные запросов и ответов**, и в этом заключается принципиальное отличие той, будем честны, не очень-то чётко очерченной технологии, которую мы для удобства называем «HTTP API».
|
||||
|
||||
Каким образом эта самая возможность читать метаданные нам может быть полезна? Современный стек взаимодействия между клиентом и сервером является (как и предсказывал Филдинг) многослойным. Разработчик пишет код поверх какого-то фреймворка, который отправляет запросы; фреймворк базируется на API языка программирования, которое, в свою очередь, обращается к API операционной системы. Далее запрос (возможно, через промежуточные HTTP-прокси) доходит до сервера, который, в свою очередь, тоже представляет собой несколько слоёв абстракции в виде фреймворка, языка программирования и ОС; к тому же, перед конечным сервером, как правило, находится веб-сервер, проксирующий запрос, а зачастую и не один. В современных облачных архитектурах HTTP-запрос, прежде чем дойти до конечного обработчика, пройдёт через несколько абстракций в виде прокси и гейтвеев. Если бы все эти агенты трактовали мета-информацию о запросе одинаково, это позволило бы обрабатывать многие ситуации оптимальнее — тратить меньше ресурсов и писать меньше кода.
|
||||
|
||||
Главное преимущество, которое вам предоставляет следование букве стандарта HTTP — возможность положиться на то, что промежуточные агенты, от клиентских фреймворков до API-гейтвеев, умеют читать метаданные запроса и выполнять какие-то действия с их использованием — настраивать политику перезапросов и таймауты, логировать, кэшировать, шардировать, проксировать и так далее — без необходимости писать какой-то дополнительный код.
|
||||
|
||||
Главным недостатком HTTP API является то, что промежуточные агенты, от клиентских фреймворков до API-гейтвеев, умеют читать метаданные запроса и выполнять какие-то действия с их использованием — настраивать политику перезапросов и таймауты, логировать, кэшировать, шардировать, проксировать и так далее — даже если вы их об этом не просили. Более того, так как стандарты HTTP являются сложными, концепция REST — непонятной, а разработчики программного обеспечения — неидеальными, то промежуточные агенты могут трактовать метаданные запроса *неправильно*. Особенно это касается каких-то экзотических и сложных в имплементации стандартов.
|
Reference in New Issue
Block a user