1
0
mirror of https://github.com/twirl/The-API-Book.git synced 2025-06-24 22:36:43 +02:00

proofreading

This commit is contained in:
Sergey Konstantinov
2023-07-04 10:04:09 +03:00
parent 682c864c93
commit 735c9c5918
39 changed files with 214 additions and 200 deletions

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@ -4497,7 +4497,7 @@ ProgramContext.dispatch = (action) => {
<p>Появление доменных имён потребовало разработки клиент-серверных протоколов более высокого, чем TCP/IP, уровня, и для передачи текстовых (гипертекстовых) данных таким протоколом стал <a href="https://www.w3.org/Protocols/HTTP/AsImplemented.html">HTTP 0.9</a>, разработанный Тимом Бёрнерсом-Ли опубликованный в 1991 году. Помимо поддержки обращения к узлам сети по именам, HTTP также предоставил ещё одну очень удобную абстракцию, а именно назначение собственных адресов эндпойнтам, работающим на одном сетевом узле.</p>
<p>Протокол был очень прост и всего лишь описывал способ получить документ, открыв TCP/IP соединение с сервером и передав строку вида <code>GET адрес_документа</code>. Позднее протокол был дополнен стандартом URL, позволяющим детализировать адрес документа, и далее протокол начал развиваться стремительно: появились новые глаголы помимо <code>GET</code>, статусы ответов, заголовки, типы данных и так далее.</p>
<p>HTTP появился изначально для передачи размеченного гипертекста, что для программных интерфейсов подходит слабо. Однако HTML со временем эволюционировал в более строгий и машиночитаемый XML, который быстро стал одним из общепринятых форматов описания вызовов API. С начала 2000-х XML начал вытесняться более простым и интероперабельным JSON, и сегодня, говоря об HTTP API, чаще всего имеют в виду такие интерфейсы, в которых данные передаются в формате JSON по протоколу HTTP.</p>
<p>Поскольку, с одной стороны, HTTP был простым и понятным протоколом, позволяющим осуществлять произвольные запросы к удаленным серверам по их доменным именам, и, с другой стороны, быстро оброс почти бесконечным количеством разнообразных расширений над базовой функциональностью, он довольно быстро стал второй точкой, к которой сходятся сетевые технологии: практически все запросы к API внутри TCP/IP-сетей осуществляются по протоколу HTTP (и даже если используется альтернативный протокол, запросы в нём всё равно зачастую оформлены в виде HTTP-пакетов просто ради удобства). При этом, однако, в отличие от TCP/IP-уровня, каждый разработчик сам для себя решает, какой объём функциональности, предоставляемой протоколом и многочисленными расширениями к нему, он готов применить. В частности, gRPC и GraphQL работают поверх HTTP, но используют крайне ограниченное подмножество его возможностей.</p>
<p>Поскольку, с одной стороны, HTTP был простым и понятным протоколом, позволяющим осуществлять произвольные запросы к удаленным серверам по их доменным именам, и, с другой стороны, быстро оброс почти бесконечным количеством разнообразных расширений над базовой функциональностью, он стал второй точкой, к которой сходятся сетевые технологии: практически все запросы к API внутри TCP/IP-сетей осуществляются по протоколу HTTP (и даже если используется альтернативный протокол, запросы в нём всё равно зачастую оформлены в виде HTTP-пакетов просто ради удобства). При этом, однако, в отличие от TCP/IP-уровня, каждый разработчик сам для себя решает, какой объём функциональности, предоставляемой протоколом и многочисленными расширениями к нему, он готов применить. В частности, gRPC и GraphQL работают поверх HTTP, но используют крайне ограниченное подмножество его возможностей.</p>
<p>Тем не менее, <em>обычно</em> словосочетание «HTTP API» используется не просто в значении «любой API, использующий протокол HTTP»; говоря «HTTP API» мы <em>скорее</em> подразумеваем, что он используется не как дополнительный третий протокол транспортного уровня (как это происходит в GRPC и GraphQL), а именно как протокол уровня приложения, то есть составляющие протокола (такие как: URL, заголовки, HTTP-глаголы, статусы ответа, политики кэширования и т.д.) используются в соответствии с их семантикой, определённой в стандартах. <em>Обычно</em> также подразумевается, что в HTTP API использует какой-то из текстовых форматов передачи данных (JSON, XML) для описания вызовов.</p>
<p>В рамках настоящего раздела мы поговорим о дизайне сетевых API, обладающих следующими характеристиками:</p>
<ul>
@ -4548,7 +4548,7 @@ ProgramContext.dispatch = (action) => {
<p>Соображения выше распространяются не только на программное обеспечение, но и на его создателей. Представление разработчиков о HTTP API, увы, также фрагментировано. Практически любой программист как-то умеет работать с HTTP API, но редко при этом досконально знает стандарт или хотя бы консультируется с ним при написании кода. Это ведёт к тому, что добиться качественной и консистентной реализации логики работы с HTTP API может быть сложнее, нежели при использовании альтернативных технологий — причём это соображение справедливо как для партнёров-интеграторов, так и для самого провайдера API.</p>
<p>Отдельно заметим что HTTP API является на сегодняшний день выбором по умолчанию при разработке публичных API. В силу озвученных выше причин, как бы ни был устроен технологический стек партнёра, интегрироваться с HTTP API он сможет без особых усилий. При этом распространённость технологии понижает и порог входа, и требования к квалификации инженеров партнёра.</p>
<h5><a href="#chapter-34-paragraph-3" id="chapter-34-paragraph-3" class="anchor">3. Идеология разработки</a></h5>
<p>Современные HTTP API унаследовали парадигму разработки ещё с тех времён, когда по протоколу HTTP в основном передавали гипертекст. В ней считается, что HTTP-запрос представляет собой операцию, выполняемую над некоторым объектом (ресурсом), который идентифицируется с помощью URL. Большинство альтернативных технологий придерживаются других парадигм; чаще всего URL в них идентифицирует <em>функцию</em>, которую необходимо выполнить с передачей указанных параметров. Подобная семантика не то чтобы противоречит HTTP — выполнение удалённых процедур хорошо описывается протоколом — но делает использование стандартных возможностей протокола бессмысленным (например, <code>Range</code>-заголовки) или вовсе опасным (возникает двусмысленность интерпретации, скажем, смысла заголовка <code>ETag</code>).</p>
<p>Современные HTTP API унаследовали парадигму разработки ещё с тех времён, когда по протоколу HTTP в основном передавали гипертекст. В ней считается, что HTTP-запрос представляет собой операцию, выполняемую над некоторым объектом (ресурсом), который идентифицируется с помощью URL. Большинство альтернативных технологий придерживаются других парадигм; чаще всего URL в них идентифицирует <em>функцию</em>, которую необходимо выполнить с передачей указанных параметров. Подобная семантика не то чтобы противоречит HTTP — выполнение удалённых процедур хорошо описывается протоколом — но делает использование стандартных возможностей протокола бессмысленным (например, <code>Range</code>-заголовки) или вовсе опасным (возникает неоднозначность интерпретации, скажем, заголовка <code>ETag</code>).</p>
<p>С точки зрения клиентской разработки следование парадигме HTTP часто требует реализации дополнительного слоя абстракции, который превращает вызов методов на объектах в HTTP-операции над нужными ресурсам. RPC-технологии в этом плане удобнее для интеграции. (Впрочем, любой достаточно сложный RPC API все равно потребует промежуточного уровня абстракции, а, например, GraphQL в нём нуждается изначально.)</p>
<h5><a href="#chapter-34-paragraph-4" id="chapter-34-paragraph-4" class="anchor">4. Вопросы производительности</a></h5>
<p>В пользу многих современных альтернатив HTTP API — таких как GraphQL, gRPC, Apache Thrift — часто приводят аргумент о низкой производительности JSON-over-HTTP API по сравнению с рассматриваемой технологией; конкретнее, называются следующие проблемы:</p>
@ -5107,7 +5107,7 @@ X-OurCoffeeAPI-Version: 1
<p>Простых ответов на вопросы выше у нас, к сожалению, нет. В рамках настоящей книги мы придерживаемся следующего подхода: сигнатура вызова в первую очередь должна быть лаконична и читабельна. Усложнение сигнатур в угоду абстрактным концепциям нежелательно. Применительно к указанным проблемам это означает, что:</p>
<ol>
<li>Метаданные операции не должны менять смысл операции; если запрос доходит до конечного микросервиса вообще без заголовков, он всё ещё должен быть выполним, хотя какая-то вспомогательная функциональность может деградировать или отсутствовать.</li>
<li>Мы используем указание версии в path по одной простой причине: все остальные способы сделать это имеют смысл если и только если при изменении мажорной версии протокола номенклатура URL останется прежней. Но, если номенклатура ресурсов может быть сохранена, то нет никакой нужды нарушать обратную совместимость нет.</li>
<li>Мы используем указание версии в path по одной простой причине: все остальные способы сделать это имеют смысл, если и только если при изменении мажорной версии протокола номенклатура URL останется прежней. Но, если номенклатура ресурсов может быть сохранена, то нет никакой нужды нарушать обратную совместимость.</li>
<li>Иерархия ресурсов выдерживается там, где она однозначна (т.е., если сущность низшего уровня абстракции однозначно подчинена сущности высшего уровня абстракции, то отношения между ними будут выражены в виде вложенных путей).
<ul>
<li>Если есть сомнения в том, что иерархия в ходе дальнейшего развития API останется неизменной, лучше завести новый верхнеуровневый префикс, а не вкладывать новые сущности в уже существующие.</li>
@ -5252,7 +5252,7 @@ If-Match: &#x3C;ревизия>
<li>Неизвестная серверная ошибка (т.е. сервер сломан настолько, что диагностика ошибки невозможна).</li>
</ol>
<p>Исходя из общих соображений, соблазнительной кажется идея назначить каждой из ошибок свой статус-код. Скажем, для ошибки (4) напрашивается код <code>403</code>, а для ошибки (11) — <code>429</code>. Не будем, однако, торопиться, и прежде зададим себе вопрос <em>с какой целью</em> мы хотим назначить тот или иной код ошибки.</p>
<p>В нашей системе в общем случае присутствуют три агента: пользователь приложения, само приложение (клиент) и сервер. Каждому из этих акторов необходимо понимать ответ на три вопроса относительно ошибки (причём для каждого из акторов ответ может быть разным):</p>
<p>В нашей системе в общем случае присутствуют три агента: пользователь приложения, само приложение (клиент) и сервер. Каждому из этих акторов необходимо понимать ответ на четыре вопроса относительно ошибки (причём для каждого из акторов ответ может быть разным):</p>
<ol>
<li>Кто допустил ошибку (конечный пользователь, разработчик клиента, разработчик сервера или какой-то промежуточный агент, например, программист сетевого стека).
<ul>
@ -5458,7 +5458,7 @@ Retry-After: 5
</li>
</ol>
<p>Для того, чтобы успешно развивать API, необходимо уметь отвечать именно на этот вопрос: почему ваши потребители предпочтут выполнять те или иные действия <em>программно</em>. Вопрос этот не праздный, поскольку, по опыту автора настоящей книги, именно отсутствие у руководителей продукта и маркетологов экспертизы в области работы с API и есть самая большая проблема развития API.</p>
<p>Конечный пользователь взаимодействует не с вашим API напрямую, с приложениями, которые поверх API написали разработчики в интересах какого-то стороннего бизнеса (причём иногда в цепочке между вами и конечным пользователем находится ещё и более одного разработчика). С этой точки зрения целевая аудитория API — это некоторая пирамида, напоминающая пирамиду Маслоу:</p>
<p>Конечный пользователь взаимодействует не с вашим API напрямую, а с приложениями, которые поверх API написали разработчики в интересах какого-то стороннего бизнеса (причём иногда в цепочке между вами и конечным пользователем находится ещё и более одного разработчика). С этой точки зрения целевая аудитория API — это некоторая пирамида, напоминающая пирамиду Маслоу:</p>
<ul>
<li>основание пирамиды — это пользователи; они ищут удовлетворение каких-то своих потребностей и не думают о технологиях;</li>
<li>средний ярус пирамиды — бизнес-заказчики; соотнеся потребности пользователей с техническими возможностями, озвученными разработчиками, бизнес строит продукты;</li>
@ -5761,9 +5761,9 @@ Retry-After: 5
<p>Проблема же API-ключей заключается в том, что они <em>не позволяют</em> надёжно идентифицировать ни приложение, ни владельца.</p>
<p>Если API предоставляется с какими-то бесплатными лимитами, то велик соблазн завести множество ключей, оформленных на разных владельцев, чтобы оставаться в рамках бесплатных лимитов. Вы можете повышать стоимость заведения таких мультиаккаунтов, например, требуя привязки номера телефона или кредитной карты, однако и то, и другое — в настоящий момент широко распространённая услуга. Выпуск виртуальных телефонных номеров или виртуальных кредитных карт (не говоря уже о нелегальных способах приобрести краденые) всегда будет дешевле, чем честная оплата использования API — если, конечно, это не API выпуска карт или номеров. Таким образом, идентификация пользователя по ключам (если только ваш API не является чистым B2B и для его использования нужно подписать физический договор) никак не освобождает от необходимости перепроверять, действительно ли пользователь соблюдает правила и не заводит множество ключей для одного приложения.</p>
<p>Другая опасность заключается в том, что ключ могут банально украсть у добросовестного партнёра; в случае клиентских и веб-приложений это довольно тривиально.</p>
<p>Может показаться, что в случае предоставления серверных API проблема воровства ключей неактуальна, но, на самом деле, это не так. Предположим, что партнёр предоставляет свой собственный публичный сервис, который «под капотом» использует ваше API. Это часто означает, что в сервисе партнёра есть эндпойнт, предназначенный для конечных пользователей, который внутри делает запрос к API и возвращает результат, и этот эндпойнт может использоваться злоумышленником как эквивалент API. Конечно, можно объявить такой фрод проблемой партнёра, однако было бы, во-первых, наивно ожидать от каждого партнёра реализации собственной антифрод-системы, которая позволит выявлять таких недобросовестных пользователей, и, во-вторых, это попросту неэффективно: очевидно, что централизованная система борьбы с фродерами всегда будет более эффективной, нежели множество частных любительских реализаций. К томе же, и серверные ключи могут быть украдены: это сложнее, чем украсть клиентские, но не невозможно. Популярный API рано или поздно столкнётся с тем, что украденные ключи будут выложены в свободный доступ (или владелец ключа просто будет делиться им со знакомыми по доброте душевной).</p>
<p>Может показаться, что в случае предоставления серверных API проблема воровства ключей неактуальна, но, на самом деле, это не так. Предположим, что партнёр предоставляет свой собственный публичный сервис, который «под капотом» использует ваше API. Это часто означает, что в сервисе партнёра есть эндпойнт, предназначенный для конечных пользователей, который внутри делает запрос к API и возвращает результат, и этот эндпойнт может использоваться злоумышленником как эквивалент API. Конечно, можно объявить такой фрод проблемой партнёра, однако было бы, во-первых, наивно ожидать от каждого партнёра реализации собственной антифрод-системы, которая позволит выявлять таких недобросовестных пользователей, и, во-вторых, это попросту неэффективно: очевидно, что централизованная система борьбы с фродерами всегда будет более эффективной, нежели множество частных любительских реализаций. К тому же, и серверные ключи могут быть украдены: это сложнее, чем украсть клиентские, но не невозможно. Популярный API рано или поздно столкнётся с тем, что украденные ключи будут выложены в свободный доступ (или владелец ключа просто будет делиться им со знакомыми по доброте душевной).</p>
<p>Так или иначе, встаёт вопрос независимой валидации: каким образом можно проконтролировать, действительно ли API используется потребителем в соответствии с пользовательским соглашением.</p>
<p>Мобильные приложения удобно отслеживаются по идентификатору приложения в соответствующем сторе (Google Play, App Store и другие), поэтому разумно требовать от партнёров идентифицировать приложение при подключении API. Вебсайты с некоторой точностью можно идентифицировать по заголовкам <code>Referer</code> или <code>Origin</code> (и для надёжности можно так же потребовать от партнёра указывать домен сайта при инициализации API).</p>
<p>Мобильные приложения удобно отслеживаются по идентификатору приложения в соответствующем сторе (Google Play, App Store и другие), поэтому разумно требовать от партнёров идентифицировать приложение при подключении API. Вебсайты с некоторой точностью можно идентифицировать по заголовкам <code>Referer</code> или <code>Origin</code> (и для надёжности можно также потребовать от партнёра указывать домен сайта при инициализации API).</p>
<p>Эти данные сами по себе не являются надёжными; важно то, что они позволяют проводить кросс-проверки:</p>
<ul>
<li>если ключ был выпущен для одного домена, но запросы приходят с <code>Referer</code>-ом другого домена — это повод разобраться в ситуации и, возможно, забанить возможность обращаться к API с этим <code>Referer</code>-ом или этим ключом;</li>

Binary file not shown.