Ваша модель даних почала стабілізуватися, і ви можете створити публічний API для своєї веб-програми. Ви розумієте, що важко внести значні зміни у свій API після того, як він випущений, і хочете отримати якомога більше правильного результату. Нажаль не існує загальноприйнятого стандарту, який би працював у всіх випадках. В Інтернеті немає дефіциту думок про дизайн API та у вас залишається купа вибору про те які формати ви повинні прийняти? Як робити безпеку та автентифікацію? Чи додавати керування версіями до вашого API?
Ключові вимоги до API
Багато думок про дизайн API, знайдених в Інтернеті, є науковими дискусіями, що обертаються навколо суб’єктивних інтерпретацій нечітких стандартів на відміну від того, що має сенс у реальному світі. Мета цієї публікації — описати найкращі практики прагматичного API, розробленого для сучасних веб-додатків. Я не намагаюся задовольнити стандарт, якщо він не здається правильним у тому чи іншому випадку. Щоб легше керувати процесом прийняття рішень, я записав деякі вимоги, до яких має прагнути API:
- Він має використовувати веб-стандарти там, де це має сенс
- Він має бути зручним для розробника та доступним для перегляду через адресний рядок браузера
- Він має бути простим, інтуїтивно зрозумілим і послідовним, щоб зробити користування їм не лише легким, але й приємним
- Він має забезпечити достатню гнучкість для роботи більшості інтерфейсів
- Він має бути ефективним, зберігаючи баланс з іншими вимогами
API — це інтерфейс користувача розробника. Як і будь-який інтерфейс користувача, важливо ретельно продумати сценарії роботи користувача!
Використовуйте RESTful URL-адреси та дії
Якщо щось і набуло широкого поширення, так це принципи RESTful. Вперше вони були представлені Роєм Філдінгом у розділі 5 його дисертації про мережеву архітектуру програмного забезпечення .
Ключові принципи REST включають поділ вашого API на логічні ресурси. Цими ресурсами маніпулюють за допомогою запитів HTTP, де метод (GET, POST, PUT, PATCH, DELETE) має певне значення.
Але що я можу зробити ресурсом? Ну, це мають бути іменники, які мають сенс з точки зору споживача API, а не дієслова. Щоб було зрозуміло: іменник — це річ, дієслово — це те, що ви з ним робите.
Але будьте уважні: хоча ваші внутрішні моделі можуть акуратно зіставлятися з ресурсами, це не обов’язково є зіставлення один до одного. Головне тут — уникнути витоку нерелевантних деталей реалізації до вашого API! Ваші ресурси API повинні мати сенс з точки зору споживача API.
Після того, як ви визначили свої ресурси, вам потрібно визначити, які дії до них застосовуються та як вони будуть зіставлені з вашим API. Принципи RESTful надають стратегії для обробки дій CRUD за допомогою методів HTTP, зіставлених таким чином:
- GET /tickets – Отримує список квитків
- GET /tickets/12 – отримує певний квиток
- POST /tickets – створює нову заявку
- PUT /tickets/12 – оновлює квиток №12
- PATCH /tickets/12 – частково оновлює квиток №12
- DELETE /tickets/12 – видаляє квиток №12
Чудова перевага REST полягає в тому, що ви використовуєте існуючі методи HTTP для впровадження значної функціональності лише в одній кінцевій точці /tickets . Немає умов іменування методів, яких слід дотримуватися, а структура URL-адреси чиста та зрозуміла.
Ім’я кінцевої точки має бути одниною чи множиною? Тут діє правило «make it simple». Хоча ваш внутрішній перфекционист скаже вам, що невірно описувати один екземпляр ресурсу у множині, прагматично буде робити URL-адреси послідовно і завжди використовувати множину. Відсутність необхідності мати справу з дивною множиною (особа/люди, гусак/гуси) покращує життя споживача API, а постачальнику API легше реалізувати (оскільки більшість сучасних фреймворків нативно оброблятимуть /tickets і /tickets/12 під загальний контролер).
Але як ви ставитеся до відносин? Якщо відношення може існувати лише в межах іншого ресурсу, принципи RESTful надають корисні вказівки. Розглянемо це на прикладі роботи API для сервісу квитків. Робота з квитками складається з кількох повідомлень. Ці повідомлення можна логічно зіставити з кінцевою точкою /tickets наступним чином:
- GET /tickets/12/messages – Отримує список повідомлень для квитка №12
- GET /tickets/12/messages/5 – Отримує повідомлення №5 для квитка №12
- POST /tickets/12/messages – створює нове повідомлення в заявці №12
- PUT /tickets/12/messages/5 – оновлює повідомлення №5 для квитка №12
- PATCH /tickets/12/messages/5 – частково оновлює повідомлення №5 для квитка №12
- DELETE /tickets/12/messages/5 – видаляє повідомлення №5 для квитка №12
Альтернатива 1 : якщо відношення може існувати незалежно від ресурсу, має сенс просто включити його ідентифікатор у вихідне представлення ресурсу. Тоді споживач API повинен буде досягти кінцевої точки відношення.
Альтернатива 2 : якщо спільно з ресурсом зазвичай запитується незалежно існуюче відношення, тоді API може запропонувати функціональні можливості для автоматичного вбудовування представлення відношення та уникнення повторного звернення до API. Чистий API і одне звернення до сервера. Мені подобається такий підхід.
А як щодо дій, які не вписуються у світ операцій CRUD?
Ось де все може стати нечітким. Існує кілька підходів:
- Реструктуруйте дію, щоб вона виглядала як поле ресурсу. Це працює, якщо дія не приймає параметри. Наприклад, дія активації може бути зіставлена з логічним активованим полем і оновлена через PATCH до ресурсу.
- Ставтеся до нього як до підресурсу з принципами RESTful. Наприклад, API GitHub дозволяє вам додавати зірочку за допомогою PUT /gists/:id/star і знімати зірочку за допомогою DELETE /gists/:id/star.
- Іноді у вас дійсно немає способу відобразити дію в розумній структурі RESTful. Наприклад, пошук за кількома ресурсами насправді не має сенсу застосовувати до певної кінцевої точки ресурсу. У цьому випадку /search матиме найбільший сенс, навіть якщо це не ресурс. Це нормально – просто робіть те, що правильно з точки зору споживача API, і переконайтеся, що це чітко задокументовано, щоб уникнути плутанини.
SSL всюди – завжди
Завжди використовуйте SSL. Без винятків. Сьогодні ваші веб-інтерфейси API можна отримати з будь-якого місця, де є Інтернет (наприклад, бібліотеки, кафе, аеропорти тощо). Не всі вони безпечні. Багато з них взагалі не шифрують комунікації, що дозволяє легко підслуховувати або видати себе за іншу особу, якщо облікові дані автентифікації викрадено.
Ще одна перевага постійного використання SSL полягає в тому, що гарантований зашифрований зв’язок спрощує автентифікацію – ви можете обійтися простими маркерами доступу замість того, щоб підписувати кожен запит API.
Єдине, на що слід звернути увагу, — доступ до URL-адрес API без використання SSL. Не перенаправляйте їх на аналоги SSL. Натомість видайте серйозну помилку! Коли діє автоматичне перенаправлення, погано налаштований клієнт може несвідомо передавати параметри запиту через незашифровану кінцеву точку. Важка помилка гарантує, що цю помилку буде виявлено на ранній стадії та клієнт налаштовано належним чином.
Документація
API хорош настільки, наскільки хороша його документація. Документи мають бути легко доступними та загальнодоступними. Більшість розробників перевірять документи, перш ніж спробувати будь-яку спробу інтеграції. Коли документи приховано у файлі PDF або вимагають входу, їх не лише важко знайти, але й нелегко шукати.
У документах мають бути наведені приклади повних циклів запитів/відповідей. Бажано, щоб запити були прикладами, які можна вставити – або посиланнями, які можна вставити в браузер, або прикладами curl, які можна вставити в термінал. GitHub і Stripe чудово справляються з цим.
Випустивши загальнодоступний API, ви зобов’язалися не порушувати роботу без попередження. Документація має містити будь-які графіки припинення підтримки та деталі, пов’язані з видимими зовнішніми оновленнями API. Оновлення слід доставляти через блог (тобто журнал змін) або список розсилки (бажано обидва!).
Керування версіями
Завжди змінюйте версію свого API. Контроль версій допомагає швидше виконувати ітерацію та запобігає потраплянню недійсних запитів на оновлені кінцеві точки. Це також допомагає згладити будь-які основні переходи версій API, оскільки ви можете продовжувати пропонувати старі версії API протягом певного періоду часу.
Існують різні думки щодо того, чи слід включати версію API в URL-адресу чи в заголовок. Академічно кажучи, це, ймовірно, має бути в заголовку. Однак версія має бути в URL-адресі, щоб забезпечити можливість перегляду веб-переглядачем ресурсів між версіями (пам’ятаєте вимоги до API?) і спрощувати роботу розробника.
Мені дуже подобається підхід Stripe до керування версіями API – URL-адреса має основний номер версії (v1), але API має підверсії на основі дати, які можна вибрати за допомогою спеціального заголовка запиту HTTP. У цьому випадку основна версія забезпечує структурну стабільність API в цілому, тоді як підверсії враховують менші зміни (застарілі поля, зміни кінцевих точок тощо).
API ніколи не буде повністю стабільним. Зміни неминучі. Важливо те, як цією зміною керувати. Добре задокументовані та оголошені багатомісячні графіки припинення підтримки можуть бути прийнятною практикою для багатьох API. Це зводиться до того, що є розумним, враховуючи галузь користування і специфіку можливих споживачів API.
Фільтрування результатів, сортування та пошук
Найкраще, щоб URL-адреси базових ресурсів були якомога меншими. Складні фільтри результатів, вимоги до сортування та розширений пошук (якщо обмежено одним типом ресурсу) можна легко реалізувати як параметри запиту поверх основної URL-адреси. Давайте розглянемо їх докладніше:
Фільтрування : використовуйте унікальний параметр запиту для кожного поля, яке реалізує фільтрацію. Наприклад, запитуючи список квитків у кінцевій точці /tickets, ви можете обмежити їх лише тими, що знаходяться у відкритому стані. Це можна зробити за допомогою запиту на зразок GET /tickets?state=open . Тут стан є параметром запиту, який реалізує фільтр.
Сортування : Подібно до фільтрації, сортування за загальним параметром можна використовувати для опису правил сортування. Задовольнити складні вимоги до сортування, дозволивши параметру сортування включати список полів, розділених комами, кожне з яких має можливий унарний мінус, щоб означати порядок сортування за спаданням. Давайте розглянемо кілька прикладів:
- GET /tickets?sort=-priority – отримує список квитків у порядку спадання пріоритету
- GET /tickets?sort=-priority,created_at – отримує список квитків у порядку спадання пріоритету. У рамках певного пріоритету першими замовляються старіші квитки
Пошук: іноді простих фільтрів недостатньо, і вам потрібна потужність повнотекстового пошуку. Можливо, ви вже використовуєте ElasticSearch або іншу технологію пошуку на основі Lucene. Коли повнотекстовий пошук використовується як механізм отримання екземплярів ресурсу для певного типу ресурсу, він може бути представлений в API як параметр запиту в кінцевій точці ресурсу. Скажімо, q . Пошукові запити мають передаватись безпосередньо до пошукової системи, а вихідні дані API мають бути у тому самому форматі, що й звичайний список.
Поєднуючи їх разом, ми можемо створювати такі запити:
- GET /tickets?sort=-updated_at – отримати нещодавно оновлені квитки
- GET /tickets?state=closed&sort=-updated_at – Отримати нещодавно закриті квитки
- GET /tickets?q=return&state=open&sort=-priority,created_at – Отримати відкриті квитки з найвищим пріоритетом, у яких згадується ключ слово “return”
Псевдоніми для типових запитів
Щоб зробити використання API приємнішим для звичайного споживача, подумайте про упаковку наборів умов у легкодоступні шляхи RESTful. Наприклад, наведений вище запит щодо нещодавно закритих квитків можна запакувати як GET /tickets/recently_closed
Обмеження полів, які повертає API
Споживачу API не завжди потрібне повне представлення ресурсу. Можливість вибору та вибору повернених полів значною мірою допомагає користувачеві API мінімізувати мережевий трафік і прискорити власне використання API.
Використовуйте параметр запиту полів , який включає список полів, розділених комами. Наприклад, наступний запит отримає достатньо інформації, щоб відобразити відсортований список відкритих заявок:
GET /tickets?fields=id,subject,updated_at&state=open&sort=-updated_at
Примітка . Цей підхід також можна поєднати з автозавантаженням пов’язаних ресурсів :
GET /tickets?embed=customer&fields=id,customer.id,customer.name
Оновлення та створення мають повертати представлення ресурсу
Виклик PUT, POST або PATCH може вносити зміни до полів основного ресурсу, які не були частиною наданих параметрів (наприклад: мітки часу created_at або updated_at). Щоб запобігти тому, щоб споживач API знову звертався до API для оновленого представлення, попросіть API повертати оновлене (або створене) представлення як частину відповіді.
У випадку POST, який призвів до створення, використовуйте код статусу HTTP 201 і додайте заголовок Location, який вказує на URL-адресу нового ресурсу. Обидва вони мають бути на додаток до включення новоствореного представлення ресурсу як тіла відповіді.
Ви повинні користувати HATEOAS?
Існує багато неоднозначних думок щодо того, чи повинен користувач API створювати посилання, чи слід надавати посилання на API. Принципи проектування RESTful визначають HATEOAS, який грубо стверджує, що взаємодія з кінцевою точкою має бути визначена в метаданих, які постачаються з вихідним представленням, а не на основі інформації поза діапазоном.
Хоча веб загалом працює за принципами типу HATEOAS (де ми переходимо на головну сторінку веб-сайту та переходимо за посиланнями на основі того, що бачимо на сторінці), я не думаю, що ми ще готові до HATEOAS на API. Під час перегляду веб-сайту рішення про те, які посилання будуть натиснуті, приймаються під час виконання. Однак за допомогою API рішення щодо того, які запити надсилатимуться, приймаються під час написання коду інтеграції API, а не під час виконання. Чи можна рішення відкласти на час? Звісно, однак, йдучи таким шляхом, ви не зможете отримати багато, оскільки код все одно не зможе впоратися зі значними змінами API без поломок. Тим не менш, я вважаю, що HATEOAS багатообіцяючий, але ще не готовий до показу в найкращий час. Необхідно докласти ще трохи зусиль, щоб визначити стандарти та інструменти навколо цих принципів, щоб його потенціал був повністю реалізований.
Наразі найкраще припустити, що користувач має доступ до документації та включити ідентифікатори ресурсів у вихідне представлення, яке споживач API використовуватиме під час створення посилань. Дотримання ідентифікаторів має декілька переваг: дані, що надходять через мережу, зведені до мінімуму, а дані, що зберігаються споживачами API, також мінімізовані (оскільки вони зберігають невеликі ідентифікатори на відміну від URL-адрес, які містять ідентифікатори).
Крім того, враховуючи, що ця публікація підтримує номери версій в URL-адресі, у довгостроковій перспективі для споживача API має більше сенсу зберігати ідентифікатори ресурсів, а не URL-адреси. Зрештою, ідентифікатор є стабільним у різних версіях, але URL-адреса, яка його представляє, ні!
Робимо вивід даних тільки як JSON
XML не є чудовим вибором для API. Він багатослівний, його важко проаналізувати, його важко прочитати, його модель даних несумісна з моделюванням даних у більшості мов програмування, а його переваги розширюваності не мають значення, коли основними потребами вихідного представлення є серіалізація з внутрішнього представлення. Я міг би продовжувати…
Я не збираюся докладати багато зусиль, щоб пояснити це. Важливо зазначити, що сьогодні вам буде важко знайти будь-який основний API, який все ще підтримує XML. Ви теж не повинні його підтримувати.
Тим не менш, якщо ваша клієнтська база складається з великої кількості корпоративних клієнтів, вам все одно може знадобитися підтримувати XML. Якщо вам доведеться це зробити, ви опинитеся перед новим запитанням:
Тип медіа має змінюватися на основі заголовків Accept чи на основі URL-адреси? Щоб забезпечити можливість перегляду в браузері, він має бути в URL-адресі. Найрозумнішим варіантом тут було б додати розширення .json або .xml до URL-адреси кінцевої точки хоча це і не рекомендується стандартом. Можливо також зробити це за допомогою заголовку Content-Type (Наприклад: Content-Type: application/json)
kebab-case vs snake_case vs camelCase для імен полів
Якщо ви використовуєте JSON ( нотація об’єктів JavaScript ) як основний формат представлення, «правильним» буде дотримання правил іменування JavaScript, а це означає верблюжий регістр для імен полів! Якщо потім ви підете шляхом створення клієнтських бібліотек різними мовами, найкраще використовувати в них ідіоматичні угоди про іменування — camelCase для C# і Java, snake_case для python і ruby або так званий kebab-case найбільше рекомендований для URL.
Особисто я користуюся для API kebab-case або snake_case тому що вони читабельні набагато краще ніж camelCase але між kebab-case та snake_case я більш рекомендую користувати kebab-case тому що з практики він більш читабельний для користувачів API. Але важливо пам’ятати що використання кількох типів назв у вашому API це те що вкрай ніяк неприйнятне і вносить хаос та руйнування в мозок користувачів та багато проклять на вашу голову.
Багато популярних JSON API використовують snake_case. Я підозрюю, що це пов’язано з тим, що бібліотеки серіалізації на стороні сервера дотримуються умов імен базової мови, у яку вони вбудовані. Можливо, нам потрібно, щоб бібліотеки серіалізації JSON обробляли перетворення умов імен.
За замовчуванням використовується гарне форматування результату і обов’язкова підтримка gzip
API, який забезпечує стислий пробільний вихід, не дуже цікавий для перегляду з браузера. Хоча якийсь параметр запиту (наприклад: ?pretty=true) може бути наданий для вмикання якісного форматування. API, який виконує стандартний друк за замовчуванням, набагато доступніший. Вартість додаткової передачі даних є незначною, особливо якщо порівнювати з вартістю нереалізації gzip.
Розглянемо деякі випадки використання: що робити, якщо користувач API налагоджує код і має роздрукувати дані, отримані від API, – вони будуть доступні для читання за замовчуванням. Або якщо споживач взяв URL-адресу, яку генерував їхній код, і натиснув її прямо з браузера, її можна буде прочитати за замовчуванням. Це дрібниці. Маленькі речі, які роблять API приємним у використанні!
Але як щодо додаткової передачі даних?
Давайте подивимося на це на прикладі реального світу. Я отримав деякі дані з API GitHub , який за замовчуванням використовує красивий друк. Я також зроблю кілька порівнянь gzip:
$ curl https://api.github.com/users/ragivald > with-whitespace.txt $ ruby -r json -e 'puts JSON.parse(STDIN.read)' < with-whitespace.txt > without-whitespace.txt $ gzip -c with-whitespace.txt > with-whitespace.txt.gz $ gzip -c without-whitespace.txt > without-whitespace.txt.gz
Вихідні файли мають такі розміри:
- without-whitespace.txt – 1221 байт
- with-whitespace.txt – 1290 байт
- without-whitespace.txt.gz – 477 байт
- with-whitespace.txt.gz – 480 байт
У цьому прикладі пробіли збільшили розмір виводу на 5,7%, коли gzip не запущено, і на 0,6%, коли gzip запущено. З іншого боку, сам акт gzipping забезпечив понад 60% економії пропускної здатності . Оскільки вартість якісного друку є відносно невеликою, найкраще використовувати якісний друк за замовчуванням і переконатися, що стиснення gzip підтримується!
Не використовуйте оболонку за умовчанням, але зробіть це можливим, коли це необхідно
Багато API загортають свої відповіді в оболонки(envelope) таким чином:
{ "data" : { "id" : 123, "name" : "John" } }
Для цього є кілька обґрунтувань: це полегшує включення додаткових метаданих або інформації про розбиття на сторінки, деякі клієнти REST не дозволяють легкий доступ до заголовків HTTP, а запити JSONP не мають доступу до заголовків HTTP. Однак із стандартами, які швидко приймаються, такими як CORS і заголовок посилання з RFC 5988, обгортання починає ставати непотрібним.
Ми можемо перевірити API на майбутнє, залишивши без обгорток за замовчуванням і обгортаючи лише у виняткових випадках.
Як використовувати обгортання у виняткових випадках?
Є 2 ситуації, коли обгортання справді потрібно: якщо API має підтримувати міждоменні запити через JSONP або якщо клієнт не може працювати з заголовками HTTP.
Для підтримки міждоменного JSONP: ці запити постачаються з додатковим параметром запиту (зазвичай називається callback або jsonp ), що представляє назву функції зворотного виклику. Якщо цей параметр присутній, API має перейти в режим повної обгортки, де він завжди відповідає кодом статусу HTTP 200 і передає справжній код статусу в корисне навантаження JSON. Будь-які додаткові заголовки HTTP, які були б передані разом із відповіддю, мають бути зіставлені з полями JSON, наприклад:
callback_function({ status_code: 200, next_page: "https://..", response: { ... actual JSON response body ... } })
Для підтримки обмежених HTTP-клієнтів: дозвольте спеціальний параметр запиту ?envelope=true , який ініціював би обгортання без функції зворотного виклику JSONP.
JSON кодування у тілі запитів POST, PUT і PATCH
Якщо ви дотримуєтеся підходу, описаного в цій публікації, ви використовуєте JSON для всіх вихідних даних API. Розглянемо JSON для введення API.
Багато API використовують кодування URL-адрес у своїх тілах запитів API. Кодування URL-адреси – це саме те, як це звучить – тіла запиту, де пари ключів і значень кодуються з використанням тих самих угод, як і для кодування даних у параметрах запиту URL-адреси. Це просто, широко підтримується та виконує роботу.
Однак кодування URL-адреси має кілька проблем, які роблять його проблематичним. Він не має поняття типів даних. Це змушує API аналізувати цілі числа та логічні значення з рядків. Крім того, він не має реальної концепції ієрархічної структури. Хоча існують деякі домовленості, які можуть побудувати певну структуру з пар ключів-значень (наприклад, додавання [ ] до ключа для представлення масиву), це не порівняти з рідною ієрархічною структурою JSON.
Якщо API простий, я вважаю, що кодування URL може бути достатньо. Але я б стверджував, що це не узгоджується з вихідним форматом.
Для API на основі JSON вам також слід дотримуватися JSON для введення API.
API, який приймає запити POST, PUT і PATCH у кодуванні JSON, також має вимагати, щоб заголовок Content-Type мав значення application/json або викидав код статусу HTTP 415 Unsupported Media Type.
Пагінація
API, які люблять обгортки, зазвичай включають дані розбиття на сторінки в самій обгортці. І я не звинувачую їх – донедавна не було кращих варіантів. Правильний спосіб включити деталі розбиття на сторінки сьогодні – це використовувати заголовок посилання, представлений у RFC 8288.
API, який використовує заголовок Link, може повертати набір готових посилань, тому користувач API не повинен створювати посилання самостійно. Це особливо важливо, коли розбивка на сторінки базується на курсорі . Ось приклад правильного використання заголовка посилання, взятий із документації GitHub :
Link: <https://api.github.com/user/repos?page=3&per_page=100>; rel="next", <https://api.github.com/user/repos?page=50&per_page=100>; rel="last"
Але це не повне рішення, оскільки багато API люблять повертати додаткову інформацію про розбивку на сторінки, наприклад підрахунок загальної кількості доступних результатів. API, який вимагає надсилання підрахунку, може використовувати спеціальний HTTP-заголовок, наприклад X-Total-Count .
Автоматичне завантаження представлень пов’язаних ресурсів
Є багато випадків, коли користувач API потребує завантаження даних, пов’язаних із запитуваним ресурсом (або посилання на нього). Замість того, щоб вимагати від споживача багаторазового звернення до API для отримання цієї інформації, можна було б отримати значний приріст ефективності, дозволивши повертати пов’язані дані та завантажувати їх разом із вихідним ресурсом за запитом.
Однак, оскільки це суперечить деяким принципам RESTful, ми можемо мінімізувати наше відхилення, роблячи це лише на основі параметра запиту embed (або expand ).
У цьому випадку embed буде розділеним комами списком полів, які потрібно вставити. Крапкова нотація може використовуватися для посилання на підполя. Наприклад:
GET /tickets/12?embed=customer.name,assigned_user
Це поверне квиток із вбудованими додатковими деталями, наприклад:
{ "id" : 12, "subject" : "I have a question!", "summary" : "Hi, ....", "customer" : { "name" : "Bob" }, assigned_user: { "id" : 42, "name" : "Jim", } }
Звичайно, здатність реалізувати щось подібне залежить від внутрішньої складності. Таке вбудовування може легко призвести до проблеми вибору N+1 .
Заміна методу HTTP
Деякі HTTP-клієнти можуть працювати лише з простими запитами GET і POST. Щоб збільшити доступність для цих обмежених клієнтів, API потрібен спосіб заміни методу HTTP. Хоча тут немає жодних жорстких стандартів, популярна умова полягає в прийнятті заголовка запиту X-HTTP-Method-Override із значенням рядка, що містить одне з PUT, PATCH або DELETE.
Зауважте, що заголовок перевизначення має прийматися лише для запитів POST. Запити GET ніколи не повинні змінювати дані на сервері!
Обмеження швидкості
Щоб запобігти зловживанням, стандартною практикою є додавання певного типу обмеження швидкості до API. RFC 6585 представив код статусу HTTP 429 Забагато запитів , щоб це врахувати.
Однак може бути дуже корисно повідомити споживача про його ліміт, перш ніж він його фактично досягне. У цій сфері наразі відсутні стандарти, але існує низка популярних угод, які використовують заголовки відповідей HTTP .
Додайте як мінімум такі заголовки:
- X-Rate-Limit-Limit – кількість дозволених запитів у поточному періоді
- X-Rate-Limit-Remaining – кількість запитів, що залишилися в поточному періоді
- X-Rate-Limit-Reset – кількість секунд, що залишилися в поточному періоді
Чому для X-Rate-Limit-Reset замість мітки часу використовується кількість секунд, що залишилися?
Позначка часу містить різну корисну, але непотрібну інформацію, таку як дата та можливо часовий пояс. Споживач API справді просто хоче знати, коли він зможе знову надіслати запит і скільки секунд відповідає на це запитання з мінімальною додатковою обробкою з їхнього боку. Це також дозволяє уникнути проблем, пов’язаних із перекосом годинника .
Деякі API використовують мітку часу UNIX (секунди з початку епохи) для X-Rate-Limit-Reset. Не робіть цього!
Чому це погана практика використовувати мітку часу UNIX для X-Rate-Limit-Reset?
У специфікації HTTP вже вказано використання форматів дати RFC 1123 (наразі використовується в HTTP-заголовках Date , If-Modified-Since та Last-Modified ). Якщо ми повинні вказати новий заголовок HTTP, який приймає певну мітку часу, він повинен відповідати угодам RFC 1123 замість використання міток часу UNIX.
Аутентифікація
RESTful API має бути без стану. Це означає, що автентифікація запиту не повинна залежати від файлів cookie або сеансів. Натомість кожен запит має надавати певні облікові дані для автентифікації.
Завжди використовуючи SSL, облікові дані автентифікації можна спростити до випадково згенерованого маркера доступу, який доставляється в полі імені користувача HTTP Basic Auth. Чудова особливість цього полягає в тому, що він повністю доступний для перегляду в браузері – браузер просто відобразить запит на введення облікових даних, якщо він отримає код статусу 401 Unauthorized від сервера.
Однак цей метод автентифікації з використанням маркера через базову автентифікацію прийнятний лише у випадках, коли користувачеві доцільно скопіювати маркер із інтерфейсу адміністрування в середовище споживача API. У випадках, коли це неможливо, слід використовувати OAuth 2 для забезпечення безпечної передачі маркерів третій стороні. OAuth 2 використовує маркери носія, а також потребує SSL як базовий стандарт шифрування.
API, який має підтримувати JSONP, потребуватиме третього методу автентифікації, оскільки запити JSONP не можуть надсилати облікові дані базової автентифікації HTTP або маркери носія. У цьому випадку можна використовувати спеціальний параметр запиту access_token . Примітка. Використання параметра запиту для токена викликає невід’ємну проблему безпеки, оскільки більшість веб-серверів зберігають параметри запиту в журналах сервера.
Наскільки це варте, всі три методи, наведені вище, є лише способами транспортування маркера через кордон API. Сам фактичний базовий токен може бути ідентичним.
Кешування
HTTP забезпечує вбудовану структуру кешування! Все, що вам потрібно зробити, це включити кілька додаткових вихідних заголовків відповідей і провести невелику перевірку, коли ви отримаєте кілька заголовків вхідних запитів.
Є 2 підходи: ETag і Last-Modified
ETag : під час генерації відповіді додайте HTTP-заголовок ETag, що містить хеш або контрольну суму представлення. Це значення має змінюватися кожного разу, коли змінюється представлення виводу. Тепер, якщо вхідний HTTP-запит містить заголовок If-None-Match із відповідним значенням ETag, API має повертати код статусу 304 Not Modified замість вихідного представлення ресурсу.
Last-Modified : це в основному працює як ETag, за винятком того, що він використовує мітки часу. Заголовок відповіді Last-Modified містить мітку часу у форматі RFC 1123 , яка перевіряється на If-Modified-Since. Зауважте, що специфікація HTTP містить 3 різні прийнятні формати дати, і сервер повинен бути готовий прийняти будь-який із них.
Помилки
Подібно до того, як сторінка помилки HTML показує відвідувачу корисне повідомлення про помилку, API має надавати корисне повідомлення про помилку у відомому форматі витратних матеріалів. Представлення помилки не повинно відрізнятися від представлення будь-якого ресурсу, лише з власним набором полів.
API має завжди повертати розумні коди стану HTTP. Помилки API зазвичай поділяються на 2 типи: коди статусу серії 400 для проблем клієнта та коди статусу серії 500 для проблем сервера. Як мінімум, API має стандартизувати, щоб усі помилки серії 400 надходили з витратним представленням помилок JSON. Якщо це можливо (тобто якщо балансувальники навантаження та зворотні проксі-сервери можуть створювати власні тіла помилок), це має поширюватися на коди стану серії 500.
Тіло помилки JSON має містити кілька речей для розробника — корисне повідомлення про помилку, унікальний код помилки (який можна знайти в документах для отримання додаткової інформації) і, можливо, детальний опис. Вихідне представлення JSON для чогось подібного виглядатиме так:
{ "code" : 1234, "message" : "Something bad happened :(", "description" : "More details about the error here" }
Помилки перевірки для запитів PUT, PATCH і POST потребують розбивки полів. Це найкраще моделювати, використовуючи фіксований код помилки верхнього рівня для помилок підтвердження та надаючи докладні помилки в додатковому полі помилок , наприклад:
{ "code" : 1024, "message" : "Validation Failed", "errors" : [ { "code" : 5432, "field" : "first_name", "message" : "First name cannot have fancy characters" }, { "code" : 5622, "field" : "password", "message" : "Password cannot be blank" } ] }
Коди стану HTTP
HTTP визначає низку значущих кодів стану, які можна повернути з вашого API. Це можна використовувати, щоб допомогти споживачам API відповідним чином скеровувати свої відповіді. Я підготував короткий список тих, якими ви точно повинні скористатися:
- 200 OK – відповідь на успішне GET, PUT, PATCH або DELETE. Можна також використовувати для POST, який не призводить до створення.
- 201 Створено – відповідь на повідомлення POST, результатом якого є створення. Має поєднуватися із заголовком Location, який вказує на розташування нового ресурсу
- 204 Немає вмісту – відповідь на успішний запит, який не повертатиме тіло (наприклад, запит DELETE)
- 304 Not Modified – Використовується, коли використовуються заголовки кешування HTTP
- 400 Неправильний запит – запит неправильно сформований, наприклад, якщо тіло не аналізується
- 401 Неавторизовано – коли дані автентифікації відсутні або надані недійсні. Також корисно викликати спливаюче вікно авторизації, якщо API використовується з браузера
- 403 Заборонено – коли автентифікація пройшла успішно, але автентифікований користувач не має доступу до ресурсу
- 404 Не знайдено – Коли запитується неіснуючий ресурс
- 405 Method Not Allowed – коли запитується метод HTTP, який не дозволений для автентифікованого користувача
- 410 Gone – вказує на те, що ресурс у цій кінцевій точці більше не доступний. Корисно як загальна відповідь для старих версій API
- 415 Непідтримуваний тип медіа — якщо в запиті надано неправильний тип вмісту
- 422 Unprocessable Entity – Використовується для помилок перевірки
- 429 Забагато запитів – коли запит відхилено через обмеження швидкості
Підсумок
API — це інтерфейс користувача для розробників. Докладіть зусиль, щоб він був не просто функціональним, але й приємним у використанні і вам буде безмежна вдячність майбутніх користувачів вашого API. Головне пам’ятайте API для користувачів, а не навпаки.