15 тривіальних фактів про правильній роботі з протоколом HTTP

Увага! Реклама! Пост оплачений Капітаном Очевидність!

Нижче під катом ви знайдете 15 пунктів, що описують правильну організацію ресурсів, доступних за протоколом HTTP — сайтів, «ручок» бекенду, API та інша. «Правильний» тут означає «відповідний рекомендацій і специфікаціям». Велика частина ниженаписанного майже дослівно перекладено з офіційних стандартів, рекомендацій і best practices від IETF і W3C.



Ви не знайдете тут абсолютно нічого неочевидного. Ні, серйозно, кожен веб-розробник теоретично ці 15 пунктів освоїти десь в районі junior developer-а та/або другого-третього курсу університету.

Однак на практиці виявляється, що безліч веб-розробників ці ази таки не засвоїло. Читаєш документацію іншим API і плачеш. Впевнений, що кожен читач таки знайде в цьому списку щось нове для себе.



1. URL ідентифікує ресурс — деяку спільну сутність. Файл — ресурс. Ручка, яка щось шукає — ресурс. Виклик методу — не ресурс. Якщо ви хочете рубонути з гармати на Місяць, то ось так робити не треба:
GET /?method=торохнути&to=Луна

Заведіть ресурс «шарахалка», і тоді у вас все буде логічно:
POST /шарахалка/?to=Луна


Чому POST, а не GET? Читай нижче.

2. URL складається з схеми (протоколу), хоста, шляху (path), запити (query) і фрагмента. Шлях використовується для організації ієрархічних ресурсів, запит — для неиерархических ресурсів і для параметрів операції. Фрагмент ідентифікує підлеглий ресурс, який не має прямого URL.

Scheme Host Path Query Fragment
↓ ↓ ↓ ↓ ↓
nyashnye-kotiki.xxx/breeds/maine-coon/?deliver_to=Moscow#photo

Якщо на вашому сайті «Няшные котики» є каталог по породам, то його цілком логічно організувати у вигляді частин path, оскільки кожен котик належить рівно до однієї породи. А ось доставляти одного котика можна в кілька міст, тому фільтр «з доставкою в місто N» слід організувати через query.

3. Звернення HTTP складається з застосування методу (дієслова) до URL. Результатом такого застосування повинно бути — сюрприз-сюрприз! — те, що в дієслові написано. Тобто GET повертає подання ресурсу, DELETE видаляє і т. п.

4. Методи GET, HEAD, OPTIONS — безпечні. Передбачається, що виклик цих методів стану ресурсу не змінює. Тому багато мережеві агенти — такі, наприклад, як префетчер посилань у браузері або месенджері — вважають себе вправі за таким посиланнями ходити без явного волевиявлення користувача. ИЧСХ, ніяких стандартів не порушують.

5. За замовчуванням методи GET і HEAD кешуються, OPTIONS, POST, PUT, PATCH, DELETE — ні. Тому якщо ви гупнули по Місяцю методом POST, ви можете бути (майже) впевнені, що цей запит не виконається. Якщо ви шарахаете методом GET, який-небудь проміжний проксі може РАПТОВО віддати вам відповідь з кешу, і кулях в реальності не відбудеться.



6. Операції GET, PUT, DELETE симетричні. PUT кладе щось URL-у (створюючи новий ресурс або переписуючи старий), GET з цього URL-у повертає уявлення того, що поклав PUT, DELETE видаляє ресурс.
Метод HEAD синонимичен за семантикою методом GET, але не повертає тіло відповіді, а тільки його заголовки (метаінформацію про ресурс).

7. POST використовується в тому випадку, якщо у вас немає URL, до якого ви хочете застосувати операцію. Наприклад, якщо ви користувач пише нове повідомлення в тредик на форумі, він може сам обчислити його id і зробити:
PUT /threads/php-rulezz/messages/100500

Якщо клієнту генерувати id не дозволено, йому доведеться робити POST на ресурс рівнем вище по ієрархії:
POST /threads/php-rulezz/messages

І цей ресурс сам створить нове повідомлення.
Зверніть увагу, якщо ви помилково або внаслідок мережевих проблем повторіть POST запит — створиться друге повідомлення в тред, ідентичний першому. PUT ви можете робити хоч 100500 раз, результат не зміниться. Це властивість називається идемпотентностью.
Гаразд створення постів на форумі. От якщо ви робите важку і дорогу операцію по запиту користувача — дуже рекомендується виконувати идемпотентный запит. А то може вийти як на картинці:

Зрозуміло, використання идемпотентного PUT породжує свої проблеми — зокрема, як вирішувати конфлікти. Доведеться більше програмувати, зате результат буде більш надійним і безпечним.

8. PUT може використовуватися як для створення нових ресурсів, так і для оновлення старих. Однак у разі використання PUT для перезапису передбачається, що в тілі запиту передається закодований ресурс цілком. Якщо ж ви хочете модифікувати ресурс, тобто змінити його внутрішнє подання без повної перезапису, то для цього був придуманий метод PATCH. Цей метод uncacheable, небезпечний і неидемпотентный.

9. Коди відповіді потрібні в першу чергу для того, щоб клієнт міг зрозуміти, що йому робити далі. 3хх говорить, що для успішного виконання запиту потрібно виконати додаткову дію. 4хх каже, що клієнт, складаючи запит, зробив щось неправильно і, звичайно, про те, що благати марно — повторне виконання запиту все одно викине помилку. У 4хх вкрай рекомендується включати інформацію про те, що конкретно клієнт зробив не так. 5хх говорить про те, що клієнт все зробив правильно — проблема на стороні сервера.

Зазвичай при успішному виконанні операції сервер відповідає на GET — 200, на PUT — 201 Created (якщо ресурс створений) або 200 (ресурс оновлено), на DELETE — 204 (операція успішна, повертати нічого), на POST — 200 або 201 (у другому випадку в заголовку, зазвичай Location, вказується URL створеного ресурсу).

10. Кілька популярних помилок про HTTP статуси:
  • статусу 401 Unauthorized зобов'язаний супроводжуватися заголовком WWW-Authenticate і, таким чином, застосовний тільки тоді, коли клієнт автентифікований допомогою HTTP-аутентифікації; у всіх інших випадках слід використовувати 403 Forbidden;
  • статуси 3xx — це не тільки редиректи; вони показують, що клієнт повинен виконати додаткову дію, інакше запит не може вважатися успішним; наприклад, за статусом 304 Not Modified клієнт повинен взяти актуальну версію ресурсу з кешу;
  • статус 404, як не дивно, один з небагатьох 4xx статусів, які клієнт має право повторювати — він означає, що ресурсу зараз немає, але цілком можливо, що він з'явиться; взагалі 404 — статус невизначеності, який використовується, якщо сервер не хоче розкривати механіку помилки; для того, щоб відображати клієнту, що без додаткових дій з його боку ресурс не з'явиться, слід використовувати 410 Gone (ресурс був видалений) або загальний статус 400.



11. Існує особливий підклас URL-ів, які кодують в собі і ресурс, і дія над ним. В англомовній літературі їх прийнято називати Capability URLs. Класичний приклад такого URL — посилання на відновлення паролів, а також всілякі «секретні» прямі посилання на всілякі ресурси.

12. Оскільки основна небезпека при роботі з Capability URL — можливість їх витікання, слід максимально закрити можливості випадково такий URL знайти або перехопити:
  • для генерації секретних частин URL повинен використовуватися сильний генератор випадкових рядків (наприклад, UUID), що виключає можливості знайти Capability URL перебором; зрозуміло, що URL не повинен генеруватися детермінованим способом типу md5(username) і такі URL не можна пропускати через скорочувачі посилань;
  • Capability URLs повинні працювати тільки по HTTPS;
  • сторінки, доступні через Capability URL, повинні бути закриті wildcard-му від індексації роботами.


13. Повинні бути передбачені заходи мінімізації можливої шкоди:
користувач, що створив Capability URL (наприклад, расшаривший документ), повинен мати можливість зробити зворотну операцію, тобто відкликати URL;
Capability URLs повинні протухати з часом; чим небезпечніше надається доступ, тим коротшим повинен бути термін життя URL.

14. Нарешті, самі «секретні» сторінки повинні бути захищені від зливання даних сторонніх агентів:
  • на них не повинно бути ніяких third-party скриптів і картинок, бажано — на рівні CSP;
  • на них не повинно бути посилань на third-party сайти; якщо вони необхідні, то потрібно приховувати referrer, наприклад, через rel=«noreferrer»;
  • взагалі бажано через Referrer Policy налаштувати приховування referrer-а;
  • бажано відразу після заходу користувача через History API змінювати URL в адресному рядку браузера, щоб його не можна було підглянути через плече;
  • якщо посилання припускає якусь дію (наприклад, зміну пароля), то на секретній сторінці повинна бути форма (кнопка, скрипт), яку потрібно відіслати, щоб здійснити дію, причому ця форма має бути підписана CSRF-токеном (інакше префетчер браузера / поштового клієнта / месенджера зможе відновити пароль за юзера).


15. Все описане вище існує в стандартах виключно у формі рекомендації, і примусити кого-небудь до суворого виконання цих рекомендацій не можна. Я вже не перший раз розповідаю про всю цю тривию, і часто чую у відповідь: «так плювати я на все це хотів, придумали якийсь непотрібної нісенітниці; як у мене працювали всі сервіси тільки на GET, так і далі будуть, мучтеся зі своїми PUT-ами і DELETE-ми самі».

Зрозуміло, ви можете писати свій сервіс самі. Але майте, будь ласка, на увазі, що між вашим сервером і вашим клієнтом, навіть якщо вони знаходяться фізично поряд в одному ДЦ, є безліч інших мережних агентів — браузерів, проксі, роутерів, імплементацій HTTP-протоколу в різних мовах програмування і різних ОС, DPI-обладнання провайдерів і так далі. Всі ці агенти плюс-мінус імплементують протокол HTTP з оглядкою на RFC.

Якщо раптом браузер запрефетчит GET-посилання і шарахне по Місяці — це буде ваша вина, марно писати виробнику. Якщо у вас гроші переводяться GET-запитом, а імплементація протоколу HTTP у вашій мові програмування, не дочекавшись відповіді від сусіднього роутера, вирішить повторити запит і проведе транзакцію двічі — це буде знову ваша вина.

Але навіть не це головне. Припустимо, ваші HTTP-пакети гуляють у суворо контрольованому середовищі. Як ви збираєтеся пояснювати іншим розробникам, які рекомендації ви порушили і чому? Як ваш колега повинен зрозуміти, що ось цей GET-запит повторювати не можна, а статус 400 зовсім не означає клієнтську помилку? Відступаючи від рекомендацій, ви, фактично, кожен раз створюєте якийсь свій діалект HTTP із власною семантикою. Не забудьте його хоча б задокументувати ;)

Список літератури:
(В розробці останнього документа ваш покірний слуга брав певну участь.)

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.