diff --git a/src/ru/drafts/04-Раздел III. HTTP API и REST/03.md b/src/ru/drafts/04-Раздел III. HTTP API и REST/03.md index 9e2976e..0261bcc 100644 --- a/src/ru/drafts/04-Раздел III. HTTP API и REST/03.md +++ b/src/ru/drafts/04-Раздел III. HTTP API и REST/03.md @@ -30,10 +30,17 @@ Главное преимущество, которое вам предоставляет следование букве стандарта HTTP — возможность положиться на то, что промежуточные агенты, от клиентских фреймворков до API-гейтвеев, умеют читать метаданные запроса и выполнять какие-то действия с их использованием — настраивать политику перезапросов и таймауты, логировать, кэшировать, шардировать, проксировать и так далее — без необходимости писать какой-то дополнительный код. -Главным недостатком HTTP API является то, что промежуточные агенты, от клиентских фреймворков до API-гейтвеев, умеют читать метаданные запроса и выполнять какие-то действия с их использованием — настраивать политику перезапросов и таймауты, логировать, кэшировать, шардировать, проксировать и так далее — даже если вы их об этом не просили. Более того, так как стандарты HTTP являются сложными, концепция REST — непонятной, а разработчики программного обеспечения — неидеальными, то промежуточные агенты могут трактовать метаданные запроса *неправильно*. Особенно это касается каких-то экзотических и сложных в имплементации стандартов. - Если попытаться сформулировать главный принцип разработки 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 являются сложными, концепция REST — непонятной, а разработчики программного обеспечения — неидеальными, то промежуточные агенты могут трактовать метаданные запроса *неправильно*. Особенно это касается каких-то экзотических и сложных в имплементации стандартов. + #### Вопросы производительности В пользу многих современных альтернатив HTTP API — таких как GraphQL, gRPC, Apache Thrift — часто приводят аргумент о низкой производительности JSON-over-HTTP API по сравнению с рассматриваемой технологией; конкретнее, называются следующие проблемы: @@ -52,18 +59,18 @@ 2. Вообще говоря, если такая нужда появляется, то и в рамках HTTP API вполне можно регулировать список возвращаемых полей ответа, это вполне соответствует духу и букве стандарта. Однако, мы должны заметить, что экономия трафика на возврате частичных состояний (которую мы рассматривали подробно в главе «Частичные обновления») очень редко бывает оправдана. - 3. Если использовать стандартные (де)сериализаторы JSON, разница по сравнению с бинарными форматами может оказаться действительно очень большой. Если, однако, эти накладные расходы являются проблемой, стоит обратиться к альтернативным сериализаторам — в частности, [simdjson](https://github.com/simdjson/simdjson). Благодаря оптимизированному низкоуровневому коду simdjson показывает отличную производительность, которой уж точно хватит для обычного 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-over-HTTP API *действительно* проигрывает с точки зрения производительности современным бинарным протоколам. Мы, однако, берём на себя смелость утверждать, что производительность хорошо спроектированного и оптимизированного HTTP API достаточна для абсолютного большинства предметных областей, и реальный выигрыш от перехода на альтернативные протоколы окажется незначителен. #### Преимущества и недостатки формата JSON -Как нетрудно заметить, большинство претензий, предъявляемых к концепции HTTP API, относятся вовсе не к HTTP, а к использованию формата JSON. В самом деле, ничто не мешает разработать API, которое будет использовать любой бинарный формат вместо JSON (включая тот же Protobuf), и тогда разница между Protobuf-over-HTTP API и gRPC сведётся только к (не)возможности использовать то или иное стандартное программное обеспечение «из коробки». +Как нетрудно заметить, большинство претензий, предъявляемых к концепции HTTP API, относятся вовсе не к HTTP, а к использованию формата JSON. В самом деле, ничто не мешает разработать API, которое будет использовать любой бинарный формат вместо JSON (включая тот же Protobuf), и тогда разница между Protobuf-over-HTTP API и gRPC сведётся только к (не)использованию подробных URL, статус-кодов и заголовков запросов и ответов (и вытекающей отсюда (не)возможности использовать то или иное стандартное программное обеспечение «из коробки»). -Однако, во многих случаях (включая настоящую книгу) разработчики предпочитают текстовый JSON бинарным Protobuf (Flatbuffers, Thrift, AVRO и т.д.) по очень простой причине: JSON очень легко и удобно читать. Во-первых, он текстовый и не требует дополнительной расшифровки; во-вторых, имена полей включены в сам файл. Если Protobuf невозможно прочитать без .proto-файла, то по JSON-документу почти всегда можно попытаться понять, что за данные в нём описаны. +Однако, во многих случаях (включая настоящую книгу) разработчики предпочитают текстовый JSON бинарным Protobuf (Flatbuffers, Thrift, Avro и т.д.) по очень простой причине: JSON очень легко и удобно читать. Во-первых, он текстовый и не требует дополнительной расшифровки; во-вторых, имена полей включены в сам файл. Если сообщение в формате protobuf невозможно прочитать без .proto-файла, то по JSON-документу почти всегда можно попытаться понять, что за данные в нём описаны. В совокупности с тем, что при разработке HTTP API мы также стараемся следовать стандартной семантике всей остальной обвязки, в итоге мы получаем API, запросы и ответы к которому (по крайней мере в теории) удобно читаются и интуитивно понятны. -Помимо человекочитаемости у JSON есть ещё одно важное преимущество: он максимально формален. В нём нет никаких конструкций, которые могут быть по-разном истолкованы в разных архитектурах (с точностью до ограничений на длины чисел и строк), и при этом он удобно ложится в нативные структуры данных почти любого языка программирования. С этой точки зрения у нас фактически не было никакого другого выбора, какой ещё формат данных мы могли бы использовать при написании этой книги. В случае же разработки API менее общего назначения, мы рекомендуем подходить к выбору формата по тому же принципу: взвесить накладные расходы на подготовку и внедрение инструментов чтения бинарных форматов и расшифровки бинарных протоколов против накладных расходов на неоптимальную (де)сериализацию и использование разнородного ПО различного качества. \ No newline at end of file +Помимо человекочитаемости у JSON есть ещё одно важное преимущество: он максимально формален. В нём нет никаких конструкций, которые могут быть по-разном истолкованы в разных архитектурах (с точностью до ограничений на длины чисел и строк), и при этом он удобно ложится в нативные структуры данных почти любого языка программирования. С этой точки зрения у нас фактически не было никакого другого выбора, какой ещё формат данных мы могли бы использовать при написании примеров кода для этой книги. В случае же разработки API менее общего назначения, мы рекомендуем подходить к выбору формата по тому же принципу: взвесить накладные расходы на подготовку и внедрение инструментов чтения бинарных форматов и расшифровки бинарных протоколов против накладных расходов на неоптимальную передачу данных и использование разнородного ПО при разработке 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 index e76dc53..ce73f4d 100644 --- a/src/ru/drafts/04-Раздел III. HTTP API и REST/04.md +++ b/src/ru/drafts/04-Раздел III. HTTP API и REST/04.md @@ -1 +1,66 @@ -### Семантика и основные понятия \ No newline at end of file +### Семантика протокола HTTP + +Итак, после трёх вступительных глав с прояснением основных терминов и понятий (такова, увы, цена популярности технологии) мы перейдём, наконец, непосредственно к вопросам дизайна HTTP API. + +В описании семантики и формата протокола мы будем руководствоваться свежевышедшим [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html), который заменил аж девять предыдущих RFC, описывавших разные аспекты технологии (правда, большое количество различной дополнительной функциональсти всё ещё покрывается отдельными стандартами, например, метод `PATCH` так и не вошёл в основной стандарт и всё ещё регулируется [RFC 5789](https://www.rfc-editor.org/rfc/rfc5789)). + +HTTP-запрос представляет собой применение определённого глагола к URL с указанием версии протокола и передачей дополнительной мета-информации в заголовках и, возможно, каких-то данных в теле запроса: + +``` +POST /v1/orders HTTP/2.0 +Host: our-api-host.tld +Content-Type: application/json + +{ + "coffee_machine_id": 123, + "currency_code": "MNT", + "price": "10.23", + "recipe": "lungo", + "offer_id": 321, + "volume": "800ml" +} +``` + +Ответом на HTTP-запрос будет являться конструкция, состоящая из статус-кода ответа, сообщения, заголовков и, возможно, тела ответа. + +``` +201 Created +Location: /v1/orders/123 +Content-Type: application/json + +{ + "id": 123 +} +``` + +### URL + +URL — единица адресации в HTTP API (некоторые евангелисты технологии даже используют термин «пространство URL» как синоним для Мировой паутины). Предполагается, что API в парадигме REST должно использовать систему адресов столь же гранулярную, как и предметная область; иными словами, у любых сущностей, которыми мы можем манипулировать независимо, должен быть свой URL. При этом + +Формат URL регулируется [отдельным стандартом](https://url.spec.whatwg.org/), который развивает независимое сообщество Web Hypertext Application Technology Working Group (WHATWG). + +URL принято раскладывать на составляющие, каждая из которых имеет свою семантику (и может отсутствовать): + * схема — протокол обращения (в нашем случае всегда `https:`); + * хост (домен или IP-адрес), к которому обращён запрос — самая крупная единица адресации; + * домен может включать в себя поддомены; + * порт; + * путь — часть URL между доменным именем (с портом) и символами `?`, `#` или концом строки + * путь традиционно считается способом выразить строгую иерархию сущностей; если сущность `leaf` полностью вложена в сущность `root`, этот факт отражают в виде path `/root/leaf`; + * путь принято разбивать по символу `/` и работать с каждой частью как отдельным токеном — но стандарт этого вообще-то не предписывает; + * пути с символом `/` и без символа `/` в конце (скажем `/root/leaf` и `/root/leaf/`) вообще говоря являются разными (и URL, отличающиеся только наличием/отсутствием слэша, считаются разными URL), хотя практически нам неизвестны аргументы в пользу того, чтобы не считать такие пути эквивалентными; + * пути могут содержать секции `.` и/или `..`, которые предлагается трактовать аналогичным символам в путях на файловой системе (и, соответственно, считать URL `/root/leaf`, `/root/./leaf`, `/root/branch/../leaf` эквивалентными), но стандарт этого вновь не предписывает; + * query — часть URL после знака `?` до знака `#` или конца строки; + * query принято раскладывать на пары ключ=значение, разделённые символом `&`; следует вновь иметь в виду, что стандарт не предписывает query строго соответствовать этому формату; + * фрагмент (якорь) — часть URL после знака `#`; + * фрагмент традиционно рассматривается как адресация внутри запрошенного документа, поэтому многими агентами опускается при выполнении запроса; + * два URL с различными значениями фрагмента могут считаться одинаковыми — а могут не считаться, зависит от контекста. + +В HTTP-запросах, как правило (но не обязательно) схема, хост и порт опускаются (и считаются совпадающими с параметрами соединения). + +**NB**: в стандарте также перечислены разнообразные исторические наслоения (например, передача логинов и паролей в URL или использование не-UTF кодировки), которые нам в рамках вопросов дизайна API неинтересны. Также стандарт содержит правила сериализации, нормализации и сравнения URL, которые могут оказаться полезны. + +### Заголовки + +Заголовки — это *метаинформация*, привязанная к запросу или ответу. Она может описывать какие-то свойства передаваемых данных (например, `Content-Length`), дополнительные сведения о клиенте или сервере (`User-Agent`, `Date`), или просто содержать какие-то поля, не относящиеся непосредственно к смыслу запроса/ответа (например, `Authorization`). + +Важное свойство заголовков — это возможность считывать их до того, как получено тело сообщения. Таким образом, заголовки могут, во-первых, сами по себе влиять на обработку запроса или ответа, и ими можно относительно легко манипулировать при проксировании. \ No newline at end of file