From e15befa98d8d207b824e4c1cd97ae8aa6d12e075 Mon Sep 17 00:00:00 2001 From: Sergey Konstantinov Date: Mon, 30 Jan 2023 00:05:10 +0200 Subject: [PATCH] HTTP API continued --- .../10.md | 2 +- .../01.md | 16 ++--- .../02.md | 18 ++--- .../04-Раздел III. HTTP API и REST/03.md | 69 +++++++++++++++++++ .../04-Раздел III. HTTP API и REST/04.md | 1 + .../05-Раздел IV. HTTP API и REST/03.md | 27 -------- .../01.md | 0 7 files changed, 89 insertions(+), 44 deletions(-) rename src/ru/drafts/{05-Раздел IV. HTTP API и REST => 04-Раздел III. HTTP API и REST}/01.md (66%) rename src/ru/drafts/{05-Раздел IV. HTTP API и REST => 04-Раздел III. HTTP API и REST}/02.md (54%) create mode 100644 src/ru/drafts/04-Раздел III. HTTP API и REST/03.md create mode 100644 src/ru/drafts/04-Раздел III. HTTP API и REST/04.md delete mode 100644 src/ru/drafts/05-Раздел IV. HTTP API и REST/03.md rename src/ru/drafts/{04-Раздел III. Обратная совместимость => 05-Раздел IV. Обратная совместимость}/01.md (100%) diff --git a/src/ru/drafts/03-Раздел II. Паттерны проектирования API/10.md b/src/ru/drafts/03-Раздел II. Паттерны проектирования API/10.md index d52e82e..363cd91 100644 --- a/src/ru/drafts/03-Раздел II. Паттерны проектирования API/10.md +++ b/src/ru/drafts/03-Раздел II. Паттерны проектирования API/10.md @@ -80,7 +80,7 @@ PATCH /v1/orders/{id} * если два клиента делают одновременный запрос, но изменяют различные поля, конфликта доступа не происходит, и оба изменения применяются. Все эти соображения, однако, на поверку оказываются мнимыми: - * причины увеличенного расхода трафика мы разберём в следующей главе, и передача лишних полей к ним не относится (а если и относится, то это повод декомпозировать эндпойнт); + * причины увеличенного расхода трафика мы разбирали в главе «Описание конечных интерфейсов», и передача лишних полей к ним не относится (а если и относится, то это повод декомпозировать эндпойнт); * концепция передачи только изменившихся полей по факту перекладывает ответственность определения, какие поля изменились, на клиент; * это не только не снижает сложность имплементации этого кода, но и чревато его фрагментацией на несколько независимых клиентских реализаций; * существование клиентского алгоритма построения diff-ов не отменяет обязанность сервера уметь делать то же самое — поскольку клиентские разработчики могли ошибиться или просто полениться правильно вычислить изменившиеся поля; diff --git a/src/ru/drafts/05-Раздел IV. HTTP API и REST/01.md b/src/ru/drafts/04-Раздел III. HTTP API и REST/01.md similarity index 66% rename from src/ru/drafts/05-Раздел IV. HTTP API и REST/01.md rename to src/ru/drafts/04-Раздел III. HTTP API и REST/01.md index 81dc10f..f2cd7c5 100644 --- a/src/ru/drafts/05-Раздел IV. HTTP API и REST/01.md +++ b/src/ru/drafts/04-Раздел III. HTTP API и REST/01.md @@ -1,6 +1,6 @@ -### О концепции «HTTP API» и терминологии +### О концепции HTTP API и терминологии -Вопросы организации HTTP API — к большому сожалению, одни из самых «холиварных». Будучи одной из самых популярных и притом весьма непростых в понимании (в виду большого по объёму и фрагментированного на отдельные спецификации стандарта) технологий, стандарт HTTP обречён быть плохо понятым и превратно истолкованным миллионами разработчиков и многими тысячами учебных пособий. Поэтому, прежде чем переходить непосредственно к полезной части настоящего раздела, мы обязаны дать уточнения, о чём же всё-таки пойдёт речь. +Вопросы организации HTTP API — к большому сожалению, одни из самых «холиварных». Будучи одной из самых популярных и притом весьма непростых в понимании технологий (в виду большого по объёму и фрагментированного на отдельные RFC стандарта), спецификация HTTP обречена быть плохо понятой и превратно истолкованной миллионами разработчиков и многими тысячами учебных пособий. Поэтому, прежде чем переходить непосредственно к полезной части настоящего раздела, мы обязаны дать уточнения, о чём же всё-таки пойдёт речь. Так сложилось, что в настоящий момент сетевой стек, используемый для разработки распределённых API, практически полностью унифицирован в двух точках. Одна из них — это Internet protocol suite, состоящий из базового протокола IP и надстройки в виде TCP или UDP над ним. На сегодняшний день альтернативы TCP/IP используются в чрезвычайно ограниченном спектре задач, и средний разработчик практически не сталкивается ни с каким другим сетевым стеком. Однако у TCP/IP с прикладной точки зрения есть существенный недостаток — он оперирует поверх системы IP-адресов, которые плохо подходят для организации распределённых систем: * во-первых, люди не запоминают IP-адреса и предпочитают оперировать «говорящими» именами; @@ -8,16 +8,16 @@ Удобной (и опять же имеющей почти стопроцентное проникновение) абстракцией над 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. +Поскольку, с одной стороны, HTTP — простой и практически безальтернативный протокол, позволяющий осуществлять произвольные запросы к удаленным серверам по их доменным именам, и, с другой стороны, он предоставляет почти бесконечное количество разнообразных расширений над базовой функциональностью, он довольно быстро стал второй точкой, к которой сходятся сетевые технологии: практически все запросы к API внутри TCP/IP-сетей осуществляются по протоколу HTTP (и даже если используется альтернативный протокол, запросы в нём всё равно зачастую оформлены в виде HTTP-пакетов просто ради удобства). При этом, однако, в отличие от TCP/IP-уровня, каждый разработчик сам для себя решает, какой объём функциональности, предоставляемой протоколом и многочисленными расширениями к нему, он готов применить. В частности, gRPC и GraphQL работают поверх HTTP, но используют крайне ограниченное подмножество его возможностей. + +Тем не менее, *обычно* словосочетание «HTTP API» используется не просто в значении «любой API, использующий протокол HTTP»; говоря «HTTP API» мы *скорее* подразумеваем, что он используется не как дополнительный третий протокол транспортного уровня (как это происходит в GRPC и GraphQL), а именно как протокол уровня приложения, то есть составляющие протокола (такие как: URL, заголовки, HTTP-глаголы, статусы ответа, политики кэширования и т.д.) используются в соответствии с их семантикой, определённой в стандартах. *Обычно* также подразумевается, что в HTTP API использует какой-то из текстовых форматов передачи данных (JSON, XML) для описания вызовов. + В рамках настоящего раздела мы поговорим о дизайне сетевых API, обладающих следующими характеристиками: * протоколом взаимодействия является HTTP версий 1.1 и выше; * форматом данных является JSON (за исключением эндпойнтов, специально предназначенных для передачи файлов в других форматах); * в качестве идентификаторов ресурсов используется URL в соответствии с описанной в стандарте семантикой; - * спецификация протокола HTTP нигде в дизайне API не нарушается и не игнорируется специально. + * семантика вызовов HTTP-эндпойнтов соответствует спецификации, и стандарт нигде не нарушается специально. -Такое API мы будем для краткости называть просто «HTTP API». +Такое API мы будем для краткости называть просто «HTTP API» или «JSON-over-HTTP API». Мы понимаем, что такое использование терминологически некорректно, но писать каждый раз «JSON-over-HTTP эндпойнты, утилизирующие семантику, описанную в стандартах HTTP и URL» не представляется возможным. \ No newline at end of file diff --git a/src/ru/drafts/05-Раздел IV. HTTP API и REST/02.md b/src/ru/drafts/04-Раздел III. HTTP API и REST/02.md similarity index 54% rename from src/ru/drafts/05-Раздел IV. HTTP API и REST/02.md rename to src/ru/drafts/04-Раздел III. HTTP API и REST/02.md index 81f0a62..52f9ff5 100644 --- a/src/ru/drafts/05-Раздел IV. HTTP API и REST/02.md +++ b/src/ru/drafts/04-Раздел III. HTTP API и REST/02.md @@ -1,8 +1,10 @@ ### Мифология REST -В 2000 году один из авторов спецификаций HTTP и URI Рой Филдинг защитил докторскую диссертацию на тему «Архитектурные стили и дизайн архитектуры сетевого программного обеспечения», пятая глава которой была озаглавлена как «[Representational State Transfer (REST)](https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm]». +Прежде, чем перейти непосредственно к паттернам проектирования HTTP API, мы должны сделать ещё одно терминологическое отступление. Очень часто API, соответствующие данному нами в предыдущей главе определению, называют «REST API» или «RESTful API». В настоящем разделе мы эти термины не используем, поскольку оба этих термина неформальные и не несут никакого конкретного смысла. -Как нетрудно убедиться, прочитав эту главу, она представляет собой довольно абстрактный обзор распределённой сетевой архитектуры, вообще не привязанной ни к HTTP, ни к URL. Более того, она вовсе не посвящена правилам дизайна API — в этой главе Филдинг методично *перечисляет ограничения*, с которыми приходится сталкиваться разработчику распределённого сетевого программного обеспечения. Вот они: +Что такое «REST»? В 2000 году один из авторов спецификаций HTTP и URI Рой Филдинг защитил докторскую диссертацию на тему «Архитектурные стили и дизайн архитектуры сетевого программного обеспечения», пятая глава которой была озаглавлена как «[Representational State Transfer (REST)](https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm]». + +Как нетрудно убедиться, прочитав эту главу, она представляет собой абстрактный обзор распределённой сетевой архитектуры, вообще не привязанной ни к HTTP, ни к URL. Более того, она вовсе не посвящена правилам дизайна API — в этой главе Филдинг методично *перечисляет ограничения*, с которыми приходится сталкиваться разработчику распределённого сетевого программного обеспечения. Вот они: * клиент и сервер не знают внутреннего устройства друг друга (клиент-серверная архитектура); * сессия хранится на клиенте (stateless-дизайн); @@ -11,9 +13,9 @@ * сетевые системы являются многослойными, т.е. сервер может быть только прокси к другим серверам; * функциональность клиента может быть расширена через поставку кода с сервера. -На этом определение REST заканчивается. Дальше Филдинг конкретизирует некоторые аспекты имплементации систем в указанных ограничениях, но все они точно так же являются совершенно абстрактными. Буквально: «ключевая информационная абстракция в REST — ресурс; любая информация, которой можно дать наименование, может быть ресурсом». +На этом определение REST заканчивается. Дальше Филдинг конкретизирует аспекты имплементации систем в указанных ограничениях, но все они точно так же являются совершенно абстрактными. Буквально: «ключевая информационная абстракция в REST — ресурс; любая информация, которой можно дать наименование, может быть ресурсом». -Ключевой вывод, который следует из определения REST по Филдингу, вообще говоря, таков: *любое сетевое ПО в мире соответствует принципам REST*, за очень-очень редкими исключениями. +Ключевой вывод, который следует из определения REST по Филдингу-2000, вообще говоря, таков: *любое сетевое ПО в мире соответствует принципам REST*, за очень-очень редкими исключениями. В самом деле: * очень сложно представить себе систему, в которой не было бы хоть какой-нибудь стандартизации взаимодействия между компонентами, иначе её просто невозможно будет разрабатывать — в частности, как мы уже отмечали, почти всё сетевое взаимодействие в мире использует стек TCP/IP; @@ -22,13 +24,13 @@ * поскольку клиент представляет собой вычислительную машину, он всегда хранит хоть какое-то состояние и кэширует хоть какие-то данные; * наконец, code-on-demand вообще лукавое требование, поскольку всегда можно объявить данные, полученные по сети, «инструкциями» на некотором формальном языке, а код клиента — их интерпретатором. -Да, конечно, вышеприведённое рассуждение является софизмом, доведением до абсурда. Самое забавное в этом упражнении состоит в том, что мы можем довести его до абсурда и в другую сторону, объявив ограничения REST неисполнимыми. Например, очевидно, что требование code-on-demand противоречит требованию независимости клиента и сервера — клиент должен уметь интерпретировать код с сервера, написанный на вполне конкретном языке. Что касается правила на букву S («stateless»), то систем, в которых сервер *вообще не хранит никакого контекста клиента* в мире вообще практически нет, поскольку ничего полезного для клиента в такой системе сделать нельзя. (Что, кстати, постулируется в соответствующем разделе прямым текстом: «коммуникация … не может получать никаких преимуществ от того, что на сервере хранится какой-то контекст».) +Да, конечно, вышеприведённое рассуждение является софизмом, доведением до абсурда. Самое забавное в этом упражнении состоит в том, что мы можем довести его до абсурда и в другую сторону, объявив ограничения 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. +REST по Филдингу-2008 подразумевает, что клиент, получив каким-то образом ссылку на точку входа в REST API, далее должен быть в состоянии полностью выстроить взаимодействие с API, не обладая вообще никаким априорным знанием о нём, и уж тем более не должен содержать никакого специально написанного кода для работы с этим API. **NB**: оставляя за скобками тот факт, что Филдинг весьма вольно истолковал свою же диссертацию, просто отметим, что ни одна существующая система в мире не удовлетворяет описанию REST по Филдингу-2008. @@ -36,6 +38,6 @@ Хотим ли мы тем самым сказать, что REST является бессмысленной концепцией? Отнюдь нет. Мы только хотели показать, что она допускает чересчур широкую интерпретацию, в чём одновременно кроется и её сила, и её слабость. -С одной стороны, благодаря многообразию интерпретаций, разработчики API выстроили какое-то размытое, но всё-таки полезное представление о «правильной» архитектуре API. С другой стороны, за отсутствием чётких определений тема REST API превратилась в один из самых больших источников холиваров среди программистов — притом холиваров самого низкого технического уровня, поскольку популярное представление о REST не имеет вообще никакого отношения ни к тому REST, который описан в диссертации Филдинга (и тем более к тому REST, который Филдинг описал в своём манифесте 2008 года). +С одной стороны, благодаря многообразию интерпретаций, разработчики API выстроили какое-то размытое, но всё-таки полезное представление о «правильной» архитектуре HTTP API. С другой стороны, за отсутствием чётких определений тема REST API превратилась в один из самых больших источников холиваров среди программистов — притом холиваров совершенно бессмысленных, поскольку популярное представление о REST не имеет вообще никакого отношения ни к тому REST, который описан в диссертации Филдинга (и тем более к тому REST, который Филдинг описал в своём манифесте 2008 года). -Термин «REST API» в последующих главах мы использовать не будем, поскольку, как видно из написанного выше, он нам просто ни к чему — на все описанные Филдингом ограничения мы многократно ссылались по ходу предыдущих глав, поскольку, повторимся, распределённое сетевое API попросту невозможно разработать, не руководствуясь ими. Однако HTTP API (повторимся, подразумевая под этим JSON-over-HTTP эндпойнты, утилизирующие семантику, описанную в стандартах HTTP и URL), как мы его будем определять в последующих главах, фактически соответствует усреднённому представлению о «RESTful API», как его можно найти в многочисленных учебных пособиях. \ No newline at end of file +Термин «архитектурный стиль REST» и производный от него «REST API» в последующих главах мы использовать не будем, поскольку, как видно из написанного выше, в этом нет никакой нужды — на все описанные Филдингом ограничения мы многократно ссылались по ходу предыдущих глав, поскольку, повторимся, распределённое сетевое API попросту невозможно разработать, не руководствуясь ими. Однако HTTP API (подразумевая под этим JSON-over-HTTP эндпойнты, утилизирующие семантику, описанную в стандартах HTTP и URL), как мы его будем определять в последующих главах, фактически соответствует усреднённому представлению о «REST/RESTful API», как его можно найти в многочисленных учебных пособиях. \ No newline at end of file diff --git a/src/ru/drafts/04-Раздел III. HTTP API и REST/03.md b/src/ru/drafts/04-Раздел III. HTTP API и REST/03.md new file mode 100644 index 0000000..9e2976e --- /dev/null +++ b/src/ru/drafts/04-Раздел III. HTTP API и REST/03.md @@ -0,0 +1,69 @@ +### Преимущества и недостатки 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 — непонятной, а разработчики программного обеспечения — неидеальными, то промежуточные агенты могут трактовать метаданные запроса *неправильно*. Особенно это касается каких-то экзотических и сложных в имплементации стандартов. + +Если попытаться сформулировать главный принцип разработки HTTP API, то мы получим примерно следующее: **лучше бы ты разрабатывал API так, чтобы промежуточные агенты могли читать и интерпретировать мета-информацию запроса и ответа**. + +#### Вопросы производительности + +В пользу многих современных альтернатив HTTP API — таких как GraphQL, gRPC, Apache Thrift — часто приводят аргумент о низкой производительности JSON-over-HTTP API по сравнению с рассматриваемой технологией; конкретнее, называются следующие проблемы: + + 1. Избыточность формата: + * в JSON необходимо всякий раз передавать имена всех полей, даже если передаётся массив из большого количества одинаковых объектов; + * большое количество технических символов — кавычек, скобок, запятых — и необходимость экранировать служебные символы в строках. + 2. HTTP API в ответ на запрос к ресурсу возвращает представление ресурса целиком, хотя клиенту могут быть интересны только отдельные поля. + 3. Низкая производительность операций сериализации и десериализации данных. + 4. Передача бинарных данных требует дополнительного кодирования, например, через Base64. + 5. Низкая производительность самого протокола HTTP (в частности, невозможность мультиплексирования нескольких запросов и ответов по одному соединению). + +Будем здесь честны: большинство существующих имплементаций HTTP API действительно страдают от указанных проблем. Тем не менее, мы берём на себя смелость заявить, что все эти проблемы большей частью надуманы, и их решению не уделяют большого внимания потому, что указанные накладные расходы не являются сколько-нибудь заметными для большинства вендоров API. В частности: + + 1. Если мы говорим об избыточности формата, то необходимо сделать важную оговорку: всё вышесказанное верно, если мы не применяем сжатие. [Сравнения показывают](https://nilsmagnus.github.io/post/proto-json-sizes/), что использование gzip очень сильно нивелирует разницу в размере JSON документов относительно альтернативных бинарных форматов (а есть ещё и специально предназначенные для текстовых данных архиваторы, например, brotli). + + 2. Вообще говоря, если такая нужда появляется, то и в рамках HTTP API вполне можно регулировать список возвращаемых полей ответа, это вполне соответствует духу и букве стандарта. Однако, мы должны заметить, что экономия трафика на возврате частичных состояний (которую мы рассматривали подробно в главе «Частичные обновления») очень редко бывает оправдана. + + 3. Если использовать стандартные (де)сериализаторы JSON, разница по сравнению с бинарными форматами может оказаться действительно очень большой. Если, однако, эти накладные расходы являются проблемой, стоит обратиться к альтернативным сериализаторам — в частности, [simdjson](https://github.com/simdjson/simdjson). Благодаря оптимизированному низкоуровневому коду simdjson показывает отличную производительность, которой уж точно хватит для обычного HTTP API. + + 4. Вообще говоря, парадигма HTTP API подразумевает, что для бинарных данных (такие как изображения или видеофайлы) предоставляются отдельные эндпойнты. Передача бинарных данных в теле JSON-ответа необходима только в случаях, когда отдельный запрос за ними представляет собой проблему с точки зрения производительности. Такой проблемы фактически не существует в server-2-server взаимодействии, и даже в мобильных сетях она уже далеко не так остра. + + 5. Протокол HTTP 1.1 действительно неидеален с точки зрения мультиплексирования запросов. Однако альтернативные парадигмы организации API для решения этой проблемы опираются… на HTTP версии 2.0. Разумеется, и HTTP API можно построить поверх версии 2.0. + +На всякий случай ещё раз уточним: JSON-over-HTTP API *действительно* проигрывает с точки зрения производительности современным бинарным протоколам, таким как gRPC. Мы, однако, берём на себя смелость утверждать, что производительность хорошо спроектированного и оптимизированного HTTP API достаточна для абсолютного большинства предметных областей, и реальный выигрыш от перехода на альтернативные протоколы окажется незначителен. + +#### Преимущества и недостатки формата JSON + +Как нетрудно заметить, большинство претензий, предъявляемых к концепции HTTP API, относятся вовсе не к HTTP, а к использованию формата JSON. В самом деле, ничто не мешает разработать API, которое будет использовать любой бинарный формат вместо JSON (включая тот же Protobuf), и тогда разница между Protobuf-over-HTTP API и gRPC сведётся только к (не)возможности использовать то или иное стандартное программное обеспечение «из коробки». + +Однако, во многих случаях (включая настоящую книгу) разработчики предпочитают текстовый JSON бинарным Protobuf (Flatbuffers, Thrift, AVRO и т.д.) по очень простой причине: JSON очень легко и удобно читать. Во-первых, он текстовый и не требует дополнительной расшифровки; во-вторых, имена полей включены в сам файл. Если Protobuf невозможно прочитать без .proto-файла, то по JSON-документу почти всегда можно попытаться понять, что за данные в нём описаны. + +Помимо человекочитаемости у JSON есть ещё одно важное преимущество: он максимально формален. В нём нет никаких конструкций, которые могут быть по-разном истолкованы в разных архитектурах (с точностью до ограничений на длины чисел и строк), и при этом он удобно ложится в нативные структуры данных почти любого языка программирования. С этой точки зрения у нас фактически не было никакого другого выбора, какой ещё формат данных мы могли бы использовать при написании этой книги. В случае же разработки API менее общего назначения, мы рекомендуем подходить к выбору формата по тому же принципу: взвесить накладные расходы на подготовку и внедрение инструментов чтения бинарных форматов и расшифровки бинарных протоколов против накладных расходов на неоптимальную (де)сериализацию и использование разнородного ПО различного качества. \ No newline at end of file diff --git a/src/ru/drafts/04-Раздел III. HTTP API и REST/04.md b/src/ru/drafts/04-Раздел III. HTTP API и REST/04.md new file mode 100644 index 0000000..e76dc53 --- /dev/null +++ b/src/ru/drafts/04-Раздел III. HTTP API и REST/04.md @@ -0,0 +1 @@ +### Семантика и основные понятия \ No newline at end of file diff --git a/src/ru/drafts/05-Раздел IV. HTTP API и REST/03.md b/src/ru/drafts/05-Раздел IV. HTTP API и REST/03.md deleted file mode 100644 index 93b37a6..0000000 --- a/src/ru/drafts/05-Раздел IV. HTTP API и REST/03.md +++ /dev/null @@ -1,27 +0,0 @@ -### Преимущества и недостатки 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 — непонятной, а разработчики программного обеспечения — неидеальными, то промежуточные агенты могут трактовать метаданные запроса *неправильно*. Особенно это касается каких-то экзотических и сложных в имплементации стандартов. \ No newline at end of file diff --git a/src/ru/drafts/04-Раздел III. Обратная совместимость/01.md b/src/ru/drafts/05-Раздел IV. Обратная совместимость/01.md similarity index 100% rename from src/ru/drafts/04-Раздел III. Обратная совместимость/01.md rename to src/ru/drafts/05-Раздел IV. Обратная совместимость/01.md