Это главы 36 раздела «HTTP API & REST» моей книги «API». Второе издание книги будет содержать три новых раздела: «Паттерны API», «HTTP API и REST», «SDK и UI‑библиотеки». Если эта работа была для вас полезна, пожалуйста, оцените книгу на GitHub, Amazon или GoodReads. English version on Substack.
После трёх вступительных глав с прояснением основных терминов и понятий (такова, увы, цена популярности технологии) у читателя может возникнуть резонный вопрос — а почему вообще существует такая дихотомия: какие-то API полагаются на стандартную семантику HTTP, а какие-то полностью от неё отказываются в пользу новоизобретённых стандартов. Например, если мы посмотрим на формат ответа в JSON-RPC, то мы обнаружим, что он легко мог бы быть заменён на стандартные средства протокола HTTP. Вместо
HTTP/1.1 200 OK { "jsonrpc": "2.0", "id", "error": { "code": -32600, "message": "Invalid request" } }
сервер мог бы ответить просто 400 Bad Request
(с передачей идентификатора запроса, ну скажем, в заголовке X-OurCoffeeAPI-Request-Id
). Тем не менее, разработчики протокола посчитали нужным разработать свой собственный формат.
Такая ситуация (не только конкретно с JSON-RPC, а почти со всеми высокоуровневыми протоколами поверх HTTP) сложилась по множеству причин, включая разнообразные исторические (например, невозможность использовать многие возможности HTTP из ранних реализаций XMLHttpRequest
в браузерах). Однако, новые варианты RPC-протоколов, использующих абсолютный минимум возможностей HTTP, продолжают появляться и сегодня.
Чтобы разрешить этот парадокс, обратим внимание на одно принципиальное различие между использованием протоколов уровня приложения (как в нашем примере с JSON-RPC) и чистого HTTP: если ошибка 400 Bad Request
является прозрачной для практически любого сетевого агента, то ошибка в собственном формате JSON-RPC таковой не является — во-первых, потому что понять её может только агент с поддержкой JSON-RPC, а, во-вторых, что более важно, в JSON-RPC статус запроса не является метаинформацией. Протокол HTTP позволяет прочитать такие детали, как метод и URL запроса, статус операции, заголовки запроса и ответа, не читая тело запроса целиком. Для большинства протоколов более высокого уровня, включая JSON-RPC, это не так: даже если агент и обладает поддержкой протокола, ему необходимо прочитать и разобрать тело ответа.
Каким образом эта самая возможность читать метаданные нам может быть полезна? Современный стек взаимодействия между клиентом и сервером является (как и предсказывал Филдинг) многослойным. Мы можем выделить множество агентов разного уровня, которые, так или иначе, обрабатывают сетевые запросы и ответы:
-
разработчик пишет код поверх какого-то фреймворка, который отправляет запросы;
-
фреймворк базируется на API языка программирования, компилятор или интерпретатор которого, в свою очередь, полагается на API операционной системы;
-
запрос доходит до сервера, возможно, через промежуточные HTTP-прокси;
-
сервер, в свою очередь, тоже представляет собой несколько слоёв абстракции в виде фреймворка, языка программирования и ОС;
-
перед конечным сервером, как правило, находится веб-сервер, проксирующий запрос, а зачастую и не один;
-
в современных облачных архитектурах HTTP-запрос, прежде чем дойти до конечного обработчика, пройдёт через несколько абстракций в виде прокси и гейтвеев.
Главное преимущество, которое предоставляет следование букве стандарта HTTP — возможность положиться на то, что промежуточные агенты, от клиентских фреймворков до API-гейтвеев, умеют читать метаданные запроса и выполнять какие-то действия с их использованием — настраивать политику перезапросов и таймауты, логировать, кэшировать, шардировать, проксировать и так далее — без необходимости писать какой-то дополнительный код.
Если попытаться сформулировать главный принцип разработки HTTP API, то мы получим примерно следующее: лучше бы ты разрабатывал API так, чтобы промежуточные агенты могли читать и интерпретировать мета-информацию запроса и ответа.
В отличие от большинства альтернативных технологий, основной импульс развития которых исходит от какой-то одной крупной IT-компании (Facebook, Google, Apache Software Foundation), инструменты для HTTP API разрабатываются множеством различных компаний и коллабораций. Соответственно, вместо гомогенного, но ограниченного в возможностях фреймворка, для HTTP API мы имеем множество самых разных инструментов, таких как:
-
различные прокси и API-гейтвеи (nginx, Envoy);
-
различные форматы описания спецификаций (в первую очередь, OpenAPI) и связанные инструменты для работы со спецификациями (Redoc, Swagger UI) и кодогенерации;
-
ПО для разработчиков, позволяющее удобным образом разрабатывать и отлаживать клиенты API (Postman, Insomnia) и так далее.
Конечно, большинство этих инструментов применимы и для работы с API, реализующими альтернативные парадигмы. Однако именно способность промежуточных агентов считывать метаданные HTTP запросов позволяет легко строить сложные конвейеры типа экспортировать access-логи nginx в Prometheus и из коробки получить удобные мониторинги статус-кодов ответов в Grafana.
Отдельно заметим что HTTP API является на сегодняшний день выбором по умолчанию при разработке публичных API. В силу озвученных выше причин, как бы ни был устроен технологический стек партнёра, интегрироваться с HTTP API он сможет без особых усилий. При этом распространённость технологии понижает и порог входа, и требования к квалификации инженеров партёра.
Главным недостатком HTTP API является то, что промежуточные агенты, от клиентских фреймворков до API-гейтвеев, умеют читать метаданные запроса и выполнять какие-то действия с их использованием — настраивать политику перезапросов и таймауты, логировать, кэшировать, шардировать, проксировать и так далее — даже если вы их об этом не просили. Более того, так как стандарты HTTP являются сложными, концепция REST — непонятной, а разработчики программного обеспечения — неидеальными, то промежуточные агенты (и разработчики партнёра!) могут трактовать метаданные запроса неправильно. Особенно это касается каких-то экзотических и сложных в имплементации стандартов. Как правило, одной из причин разработки новых RPC-фреймворков декларируется стремление обеспечить простоту и консистентность работы с протоколом, чтобы таким образом уменьшить поле для потенциальных ошибок в реализации интеграции с API.
Вопросы производительности
В пользу многих современных альтернатив HTTP API — таких как GraphQL, gRPC, Apache Thrift — часто приводят аргумент о низкой производительности JSON-over-HTTP API по сравнению с рассматриваемой технологией; конкретнее, называются следующие проблемы:
-
Избыточность формата:
-
в JSON необходимо всякий раз передавать имена всех полей, даже если передаётся массив из большого количества одинаковых объектов;
-
большое количество технических символов — кавычек, скобок, запятых — и необходимость экранировать служебные символы в строках.
-
-
HTTP API в ответ на запрос к ресурсу возвращает представление ресурса целиком, хотя клиенту могут быть интересны только отдельные поля.
-
Низкая производительность операций сериализации и десериализации данных.
-
Передача бинарных данных требует дополнительного кодирования, например, через Base64.
-
Низкая производительность самого протокола HTTP (в частности, невозможность мультиплексирования нескольких запросов и ответов по одному соединению).
Будем здесь честны: большинство существующих имплементаций HTTP API действительно страдают от указанных проблем. Тем не менее, мы берём на себя смелость заявить, что все эти проблемы большей частью надуманы, и их решению не уделяют большого внимания потому, что указанные накладные расходы не являются сколько-нибудь заметными для большинства вендоров API. В частности:
-
Если мы говорим об избыточности формата, то необходимо сделать важную оговорку: всё вышесказанное верно, если мы не применяем сжатие. Сравнения показывают, что использование gzip практически нивелирует разницу в размере JSON документов относительно альтернативных бинарных форматов (а есть ещё и специально предназначенные для текстовых данных архиваторы, например, brotli).
-
Вообще говоря, если такая нужда появляется, то и в рамках HTTP API вполне можно регулировать список возвращаемых полей ответа, это вполне соответствует духу и букве стандарта. Однако, мы должны заметить, что экономия трафика на возврате частичных состояний (которую мы рассматривали подробно в главе «Частичные обновления») очень редко бывает оправдана.
-
Если использовать стандартные десериализаторы JSON, разница по сравнению с бинарными форматами может оказаться действительно очень большой. Если, однако, эти накладные расходы являются проблемой, стоит обратиться к альтернативным десериализаторам — в частности, simdjson. Благодаря оптимизированному низкоуровневому коду simdjson показывает отличную производительность, которой может не хватить только совсем уж экзотическим API.
-
Вообще говоря, парадигма HTTP API подразумевает, что для бинарных данных (такие как изображения или видеофайлы) предоставляются отдельные эндпойнты. Передача бинарных данных в теле JSON-ответа необходима только в случаях, когда отдельный запрос за ними представляет собой проблему с точки зрения производительности. Такой проблемы фактически не существует в server-2-server взаимодействии и в протоколе HTTP 2.0 и выше.
-
Протокол HTTP 1.1 действительно неидеален с точки зрения мультиплексирования запросов. Однако альтернативные парадигмы организации API для решения этой проблемы опираются… на HTTP версии 2.0. Разумеется, и HTTP API можно построить поверх версии 2.0.
На всякий случай ещё раз уточним: JSON-over-HTTP API действительно проигрывает с точки зрения производительности современным бинарным протоколам. Мы, однако, берём на себя смелость утверждать, что производительность хорошо спроектированного и оптимизированного HTTP API достаточна для абсолютного большинства предметных областей, и реальный выигрыш от перехода на альтернативные протоколы окажется незначителен.
Преимущества и недостатки формата JSON
Как нетрудно заметить, большинство претензий, предъявляемых к концепции HTTP API, относятся вовсе не к HTTP, а к использованию формата JSON. В самом деле, ничто не мешает разработать API, которое будет использовать любой бинарный формат вместо JSON (включая те же Protocol Buffers), и тогда разница между Protobuf-over-HTTP API и gRPC сведётся только к (не)использованию подробных URL, статус-кодов и заголовков запросов и ответов (и вытекающей отсюда (не)возможности использовать то или иное стандартное программное обеспечение «из коробки»).
Однако, во многих случаях (включая настоящую книгу) разработчики предпочитают текстовый JSON бинарным Protobuf (Flatbuffers, Thrift, Avro и т.д.) по очень простой причине: JSON очень легко и удобно читать. Во-первых, он текстовый и не требует дополнительной расшифровки; во-вторых, имена полей включены в сам файл. Если сообщение в формате protobuf невозможно прочитать без .proto
-файла, то по JSON-документу почти всегда можно попытаться понять, что за данные в нём описаны. В совокупности с тем, что при разработке HTTP API мы также стараемся следовать стандартной семантике всей остальной обвязки, в итоге мы получаем API, запросы и ответы к которому (по крайней мере в теории) удобно читаются и интуитивно понятны.
Помимо человекочитаемости у JSON есть ещё одно важное преимущество: он максимально формален. В нём нет никаких конструкций, которые могут быть по-разному истолкованы в разных архитектурах (с точностью до ограничений на длины чисел и строк), и при этом он удобно ложится в нативные структуры данных (индексные и ассоциативные массивы) почти любого языка программирования. С этой точки зрения у нас фактически не было никакого другого выбора, какой ещё формат данных мы могли бы использовать при написании примеров кода для этой книги.
В случае же разработки API менее общего назначения, мы рекомендуем подходить к выбору формата по тому же принципу:
-
взвесить накладные расходы на подготовку и внедрение инструментов чтения бинарных форматов и расшифровки бинарных протоколов против накладных расходов на неоптимальную передачу данных;
-
оценить, хватит ли вам качественного, но ограниченного в возможностях набора программного обеспечения, идущего в комплекте с альтернативным форматом, или вам важнее возможность использовать широкий спектр инструментов, умеющих работать с HTTP API, пусть они и не всегда высокого качества;
-
прикинуть, насколько дорого вам обойдутся разработчики, способные работать с этим форматом.
ссылка на оригинал статьи https://habr.com/ru/articles/740828/
Добавить комментарий