mirror of
https://github.com/twirl/The-API-Book.git
synced 2025-05-31 22:09:37 +02:00
HTTP continued
This commit is contained in:
parent
84f609fe13
commit
4b727eef02
@ -33,36 +33,45 @@ Content-Type: application/json
|
||||
}
|
||||
```
|
||||
|
||||
### URL
|
||||
#### Важное замечание о консистентности
|
||||
|
||||
URL — единица адресации в HTTP API (некоторые евангелисты технологии даже используют термин «пространство URL» как синоним для Мировой паутины). Предполагается, что API в парадигме REST должно использовать систему адресов столь же гранулярную, как и предметная область; иными словами, у любых сущностей, которыми мы можем манипулировать независимо, должен быть свой URL. При этом
|
||||
Один и тот же параметр в разных ситуациях может находиться в разных частях запроса. Скажем, идентификатор партнёра, совершающего запрос, может быть передан:
|
||||
* в имени поддомена `<partner-id>.domain.tld`;
|
||||
* как часть пути `/v1/<partner-id>/orders`;
|
||||
* как query-параметр `/v1/orders?partner_id=<partner-id>`;
|
||||
* как заголовок
|
||||
|
||||
Формат URL регулируется [отдельным стандартом](https://url.spec.whatwg.org/), который развивает независимое сообщество Web Hypertext Application Technology Working Group (WHATWG).
|
||||
```
|
||||
GET /v1/orders
|
||||
X-ApiName-Partner-Id: <partner-id>
|
||||
```
|
||||
|
||||
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-запросах, как правило (но не обязательно) схема, хост и порт опускаются (и считаются совпадающими с параметрами соединения).
|
||||
```
|
||||
POST /v1/orders/retrieve
|
||||
{
|
||||
"partner_id": <partner-id>
|
||||
}
|
||||
```
|
||||
|
||||
**NB**: в стандарте также перечислены разнообразные исторические наслоения (например, передача логинов и паролей в URL или использование не-UTF кодировки), которые нам в рамках вопросов дизайна API неинтересны. Также стандарт содержит правила сериализации, нормализации и сравнения URL, которые могут оказаться полезны.
|
||||
Возможны и более экзотические варианты: размещение параметра в схеме запроса или `Content-Type` ответа.
|
||||
|
||||
### Заголовки
|
||||
Однако при перемещении параметра между различными составляющими запроса мы столкнёмся с тремя неприятными явлениями:
|
||||
* некоторые виды параметров чувствительны к регистру (путь, query-параметры, имена полей в JSON), некоторые нет (домен, имена заголовков);
|
||||
* при этом со *значениями* заголовков и вовсе неразбериха: часть из них по стандарту обязательно нечувствительна к регистру (в частности, `Content-Type`), а часть, напротив, обязательно чувствительна (например, `ETag`);
|
||||
* наборы допустимых символов и правила экранирования также различны для разных частей запроса
|
||||
* для path, например, стандарта экранирования символов `/`, `?` и `#` нет совсем;
|
||||
* для разных частей запросов используется разный кейсинг:
|
||||
|
||||
* `kebab-case` для домена, заголовков и пути;
|
||||
* `snake_case` для query-параметров;
|
||||
* `snake_case` или `camelCase` для тела запроса;
|
||||
|
||||
Заголовки — это *метаинформация*, привязанная к запросу или ответу. Она может описывать какие-то свойства передаваемых данных (например, `Content-Length`), дополнительные сведения о клиенте или сервере (`User-Agent`, `Date`), или просто содержать какие-то поля, не относящиеся непосредственно к смыслу запроса/ответа (например, `Authorization`).
|
||||
При этом использование и `snake_case`, и `camelCase` для доменного имени невозможно, знак подчеркивания в доменных именах недопустим, а заглавные буквы будут приведены к строчным.
|
||||
|
||||
Важное свойство заголовков — это возможность считывать их до того, как получено тело сообщения. Таким образом, заголовки могут, во-первых, сами по себе влиять на обработку запроса или ответа, и ими можно относительно легко манипулировать при проксировании — и многие сетевые агенты действительно это делают, добавляя или модифицируя заголовки по своему усмотрению (в частности, современные веб-браузеры добавляют к запросам целую коллекцию заголовков: `User-Agent`, `Origin`, `Accept-Language`, `Connection`, `Referer`, `Sec-Fetch-*` и так далее, а современное ПО веб-серверов, в свою очередь, автоматически добавляет или модифицирует такие заголовки как `X-Powered-By`, `Date`, `Content-Length`, `Content-Encoding`, `X-Forwarded-For`).
|
||||
Чисто теоретически возможно использование `kebab-case` во всех случаях, но в большинстве языков программирования имена переменных в `kebab-case` недопустимы, что приведёт к неудобству работы с таким API.
|
||||
|
||||
Подобное вольное обращение с заголовками создаёт определённые проблемы, если ваш API предусматривает передачу дополнительных полей метаданных, поскольку придуманные вами имена полей могут случайно совпасть с какими-то из существующих стандартных имён (или ещё хуже — в будущем появится новое стандартное поле, совпадающее с вашим). Долгое время во избежание подобных коллизий использовался префикс `X-`; уже более 10 лет как эта практика объявлена устаревшей и не рекомендуется к использованию (см. подробный разбор вопроса в [RFC 6648](https://www.rfc-editor.org/rfc/rfc6648)), однако отказа от этого префикса по факту не произошло (и многие нестандартные, но широко распространённые заголовки, например, `X-Forwarded-For`, его всё ещё содержат). Таким образом, использование `X-` вероятность коллизий снижает, но не устраняет. Тот же RFC вполне разумно предлагает использовать вместо `X-` префикс в виде имени компании. (Мы со своей стороны склонны рекомендовать использовать оба префикса в формате `X-ApiName-Field`; префикс `X-` для читабельности [чтобы отличать специальные заголовки от стандартных], а префикс с именем компании или API — чтобы не произошло коллизий с каким-нибудь другим нестандартным префиксом.)
|
||||
Короче говоря, ситуация с кейсингом настолько плоха и запутанна, что консистентного и удобного решения попросту нет. В этой книге мы придерживаемся следующего правила: токены даются в том кейсинге, который является общепринятым для той секции запроса, в которой находится токен; если положение токена меняется, то меняется и кейсинг. (Мы далеки от того, чтобы рекомендовать этот подход всюду; наша общая рекомендация, скорее — не умножать энтропию и пытаться минимизировать такого рода коллизии.)
|
||||
|
||||
**NB**. Вообще говоря, JSON исходно — это JavaScript Object Notation, а в языке JavaScript кейсинг по умолчанию - `camelCase`. Мы, тем не менее, позволим себе утверждать, что JSON уже перестал быть форматом данных, привязанным к JavaScript, и давно используется для организации взаимодействия агентов, реализованных на любых языках программирования. Использование `snake_case` по крайней мере позволяет легко перебрасывать параметр из query в тело и обратно, что, обычно, является наиболее частотным кейсом при разработке HTTP API.
|
25
src/ru/drafts/04-Раздел III. HTTP API и REST/05.md
Normal file
25
src/ru/drafts/04-Раздел III. HTTP API и REST/05.md
Normal file
@ -0,0 +1,25 @@
|
||||
### URL
|
||||
|
||||
URL — единица адресации в HTTP API (некоторые евангелисты технологии даже используют термин «пространство URL» как синоним для Мировой паутины). Предполагается, что API в парадигме REST должно использовать систему адресов столь же гранулярную, как и предметная область; иными словами, у любых сущностей, которыми мы можем манипулировать независимо, должен быть свой URL.
|
||||
|
||||
Формат URL регулируется [отдельным стандартом](https://url.spec.whatwg.org/), который развивает независимое сообщество Web Hypertext Application Technology Working Group (WHATWG). Считается, что концепция URL (вместе с понятием универсального имени ресурса, URN) составляет более общую сущность URI (универсальный идентификатор ресурса). Разница между URL и URN заключается в том, что URL позволяет *найти* некоторый ресурс в рамках некоторого протокола доступа, в то время как URN — «внутреннее» имя объекта, которое само по себе никак не помогает получить к нему доступ.
|
||||
|
||||
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, которые в целом полезно знать разработчику HTTP API.
|
9
src/ru/drafts/04-Раздел III. HTTP API и REST/06.md
Normal file
9
src/ru/drafts/04-Раздел III. HTTP API и REST/06.md
Normal file
@ -0,0 +1,9 @@
|
||||
### Заголовки
|
||||
|
||||
Заголовки — это *метаинформация*, привязанная к запросу или ответу. Она может описывать какие-то свойства передаваемых данных (например, `Content-Length`), дополнительные сведения о клиенте или сервере (`User-Agent`, `Date`), или просто содержать поля, не относящиеся непосредственно к смыслу запроса/ответа (например, `Authorization`).
|
||||
|
||||
Важное свойство заголовков — это возможность считывать их до того, как получено тело сообщения. Таким образом, заголовки могут, во-первых, сами по себе влиять на обработку запроса или ответа, и ими можно относительно легко манипулировать при проксировании — и многие сетевые агенты действительно это делают, добавляя или модифицируя заголовки по своему усмотрению (в частности, современные веб-браузеры добавляют к запросам целую коллекцию заголовков: `User-Agent`, `Origin`, `Accept-Language`, `Connection`, `Referer`, `Sec-Fetch-*` и так далее, а современное ПО веб-серверов, в свою очередь, автоматически добавляет или модифицирует такие заголовки как `X-Powered-By`, `Date`, `Content-Length`, `Content-Encoding`, `X-Forwarded-For`).
|
||||
|
||||
Подобное вольное обращение с заголовками создаёт определённые проблемы, если ваш API предусматривает передачу дополнительных полей метаданных, поскольку придуманные вами имена полей могут случайно совпасть с какими-то из существующих стандартных имён (или ещё хуже — в будущем появится новое стандартное поле, совпадающее с вашим). Долгое время во избежание подобных коллизий использовался префикс `X-`; уже более 10 лет как эта практика объявлена устаревшей и не рекомендуется к использованию (см. подробный разбор вопроса в [RFC 6648](https://www.rfc-editor.org/rfc/rfc6648)), однако отказа от этого префикса по факту не произошло (и многие нестандартные, но широко распространённые заголовки, например, `X-Forwarded-For`, его всё ещё содержат). Таким образом, использование префикса `X-` вероятность коллизий снижает, но не устраняет. Тот же RFC вполне разумно предлагает использовать вместо `X-` префикс в виде имени компании. (Мы со своей стороны склонны рекомендовать использовать оба префикса в формате `X-ApiName-Field`; префикс `X-` для читабельности [чтобы отличать специальные заголовки от стандартных], а префикс с именем компании или API — чтобы не произошло коллизий с каким-нибудь другим нестандартным префиксом.)
|
||||
|
||||
Помимо прочего заголовки используются как управляющие конструкции — это т.н. «content negotiation», т.е. договорённость клиента и сервера о формате ответа (через заголовки `Accept*`) и условные запросы, позволяющие сэкономить трафик на возврате ответа целиком или частично (через заголовки `If-*`-заголовки, такие как `If-Range`, `If-Modified-Since` и так далее).
|
Loading…
x
Reference in New Issue
Block a user