PSR-7 у прикладах

Стандарт PSR-7 успішно завершено. Цього тижня було додано останні штрихи. Тепер версія 0.6.0 пакета http-message package готова до використання. Спробуйте дотримуватись цього стандарту у своїх додатках.

HTTP повідомлення

HTTP – досить простий протокол. Саме тому він успішно використовується протягом багатьох років. Повідомлення в ньому мають таку структуру:

Заголовки є парою ключ-значення. Ключі є чутливими до регістру. Значення є рядками. Один заголовок може мати кілька значень. У цьому випадку зазвичай значення представлені списком, розділеним комами. Тіло повідомлення (Message body) – це рядок. Хоча зазвичай воно обробляється сервером та клієнтом як потік, щоб зменшити споживання пам'яті та знизити навантаження під час обробки. Це дуже важливо, коли передаються великі набори даних, і особливо, коли передаються файли. Наприклад, PHP «з коробки» представляє вхідне тіло запиту як потік php://input і використовує вихідний буфер (теж формально потік), щоб повернути відповідь. Рядок повідомлення (message line) – це місце, яке відрізняє HTTP запит від відповіді. Рядок повідомлення у запиту (далі рядок запиту) має такий формат:

"scheme" у http запиті буде або http, або https. "path" - це теж всім зрозуміла частина. Але що таке “authority”?

де пароль є необов'язковим. Фактично, у існуючих специфікаціях рекомендується взагалі не використовувати пароль в URI. Краще примусово запросити пароль клієнта. Рядок запиту – це набір пар ключ-значення, розділених амперсандами:

Залежно від мови реалізації, вона також може моделювати списки або масиви:

PHP перетворює цей рядок на двовимірний масив:

Отже,якби гнучкості у формуванні мети запиту нам виявилося б недостатньо, URI надав би свою. На щастя, відповіді HTTP сервера простіше. Рядок відповіді виглядає так:

"VERSION", як говорилося раніше, це зазвичай 1.0 або, частіше, 1.1. "status" - це число від 100 до 599 включно; «reason» — пояснення статусу, стандартне кожному за статусу. Отже, це був швидкий огляд HTTP повідомлень. Давайте подивимося, як PSR-7 моделює їх.

Заголовки повідомлень

Назви заголовків повідомлень спочатку реєстронезалежні. На жаль, більшість мов та бібліотек приводять їх до одного регістру. Як приклад, PHP зберігає їх у масиві $_SERVER у верхньому регістрі, з префіксом HTTP_, і підставляючи _for – (це відповідності з Common Gateway Interface (CGI) специфікацією). PSR-7 спрощує доступ до заголовків, надаючи об'єктно-орієнтований шар над ними

Вся вищезазначена логіка залежить від цього, як зазначений заголовок; accept, ACCEEPT або навіть aCCePt будуть коректними іменами заголовка та повернуть однаковий результат. PSR-7 передбачає, що розбір всіх заголовків поверне структуру як масиву:

Коли структура визначена, користувачі точно знатимуть, що отримають і можуть обробляти заголовки зручним способом – незалежно від реалізації. Але що якщо ви захочете додати заголовки до повідомлення, наприклад, щоб створити запит і передати його HTTP клієнту? Повідомлення в PSR-7 змодельовані у вигляді об'єктів-значень; це означає, що будь-яка зміна може – це, фактично, інше значення. Таким чином, визначення нового заголовка створить у результаті новий об'єкт повідомлення.

Якщо потрібно просто оновити значення, ви можете просто перевизначити його:

Якщо ви хочете додатиінше значення до вже існуючого заголовка, ви можете надійти так:

Або навіть видалити заголовок:

Тіло повідомлення

Даний приклад, і всі наступні приклади роботи з повідомленнями HTTP в даному пості будуть використовувати phly/http - бібліотеку, написану мною і відображає розвиток PSR-7. У цьому випадку Stream реалізує StreamableInterface. По суті, ви отримуєте тонкий, об'єктно-орієнтований інтерфейс для взаємодії з тілом повідомлення, який дозволяє додати до нього інформацію, прочитати її та багато іншого. Бажаєте змінити повідомлення? Створіть нове тіло повідомлення:

Досі ми розглядали функції, спільні для будь-яких повідомлень. Тепер я збираюся зупинитись на відповідях зокрема. У відповіді є статус код і пояснювальна фраза:

Це легко запам'ятати. Тепер, якщо ми самі формуємо відповідь? Пояснювальна фраза вважається необов'язковою (але водночас стандартною кожному за статус коду). Для неї інтерфейс передбачає специфічний відповіді мутатор withStatus():

Повторююсь, повідомлення змодельовані як об'єкти-значення; зміна будь-якого значення створить у результаті новий екземпляр, який має бути прив'язаний до відповіді чи запиту. Але в більшості випадків ви просто перепризначатимете поточний екземпляр.

$uri в даному випадку буде екземпляром UriInterface, і дозволить вам використовувати URI:

Так само, як і HTTP повідомлення, URI представлені у вигляді об'єктів-значень, і зміна будь-якої частини URI змінює його значення, методи, що мутують, повертають новий екземпляр:

Так як зміна URI означає створення нового екземпляра, то якщо ви хочете, щоб зміни відобразилися у вашому запиті, вам необхідно повідомити про ці зміни об'єкту запиту; і, як і з кожнимповідомленням, якщо необхідно змінити метод або URI у конкретному примірнику, слід використовувати такі методи:

Серверні запити

Припустимо, що ви пишете API і хочете приймати запити у форматі JSON; виконання цього може виглядати так:

Приклад вище показує кілька функцій. По-перше, він показує вилучення заголовка із запиту, та розгалуження логіки, засноване на цьому заголовку. По-друге, він показує формування об'єкта запиту у разі помилки (функція emit() є гіпотетичною, вона приймає об'єкт запиту та дає заголовки та тіло запиту). Нарешті, приклад демонструє отримання тіла запиту, десеріалізацію та впровадження його знову на запит.

Інша особливість серверних запитів – це атрибути. Вони призначені для збереження значень, отриманих з поточного запиту. Частий приклад використання – це збереження результатів маршрутизації (розподіл URI на пари ключ/значення). Робота з атрибутами складається з наступних методів:

  • getAttribute($name, $default = null) для отримання певного атрибуту та повернення значення за промовчанням, якщо атрибут не знайдено.
  • getAttributes() отримання всіх атрибутів.
  • withAttribute($name, $value) для повернення нового екземпляра ServerRequestInterface, який містить цей атрибут.
  • withoutAttribute(($name) для повернення екземпляра ServerRequestInterface без зазначеного атрибута.
Як приклад давайте розглянемо Aura Router з нашим екземпляром запиту:

Примірник запиту використовується для впорядкування даних і передачі маршруту. Потім результати маршрутизації використовуються створення екземпляра відповіді.

Варіанти використання

Тепер після швидкої екскурсії різними компонентамиPSR-7, повернемося до конкретних прикладів використання.

Завдяки тому, що повідомлення та URI змодельовані як об'єкти-значення, це так само означає, що розробники можуть створювати базові екземпляри запитів та URI та створювати окремі запити та URI з них:

Що PSR-7 пропонує, це стандартний спосіб взаємодії з запитами, які ви відправляєте клієнтом, і відповідями, які отримуєте. Реалізуючи об'єкти-значення, ми відкриваємо можливість деяким цікавим варіантам використання із прицілом на спрощення шаблону «скидання запиту» — зміна запиту завжди дає в результаті новий екземпляр, дозволяючи нам мати базовий екземпляр із відомим станом, який ми завжди можемо розширити.

Зв'язуюча ланка

Я довго на цьому зупинятись, т.к. вже робив це у статті. Основна ідея, якщо коротко, полягає в наступному:

Функція приймає два HTTP повідомлення, і виконує деякі перетворення з ними (які можуть включати делегування до наступного доступного зв'язувальної ланки). Зазвичай ці ланки повертають об'єкт відповіді. Інший варіант, який часто використовується – це лямбда висловлювання (дякую Ларрі Гарфілду, який надіслав мені на пошту цей термін!):

У лямбда ланці ви складаєте одне в іншому:

І нарешті, є метод, що просувається Rack і WSGI, в якому кожна ланка є об'єктом і проходить до виходу:

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

Фреймворки

Одна річ, яку фреймворки пропонують багато років – це … шар абстракції над HTTP повідомленнями.Мета PSR-7 надати загальний набір інтерфейсів для фреймворків, щоб останні могли використовувати однакові абстракції. Це дозволить розробникам писати перевикористовується, незалежний від фреймворку код або, принаймні, це те, що я хотів би побачити! Розглянемо Zend Framework 2. Він визначає інтерфейс Zend\Stdlib\DispatchableInterface, який є базовим для будь-якого контролера, який ви збираєтеся використовувати у фреймворку:

Це саме описана нами вище проміжна ланка; одна єдина відмінність полягає в тому, що воно використовує специфічні для даного фреймворку реалізації HTTP повідомлень. Що якщо натомість воно буде підтримувати PSR-7? Більшість реалізацій HTTP повідомлень у фреймворках побудовано таким чином, що ви можете змінити стан повідомлення у будь-який час. Іноді це може бути не зовсім правильно, особливо якщо припустити, що стан повідомлення може бути недійсним. Але це, мабуть, єдиний недолік цього методу. Сообщения по стандарту PSR-7 є об'єктами-значеннями. Таким чином, вам не потрібно повідомляти додаток будь-яким чином про будь-які зміни в повідомленнях. Це робить реалізацію більш явною та простою для відстеження у вашому коді (і покроково у відладчику, і з використанням статичних аналізаторів коду). Як приклад, якщо ZF2 буде оновлено відповідно до PSR-7, розробники не будуть зобов'язані повідомляти MvcEvent про будь-які зміни, які вони хочуть передати клієнтам, що здійснюють:

Наведений вище код ясно показує, що ми змінюємо стан програми. Використання об'єктів-значень робить більш простою одну особливу практику: розподіл підзапитів або реалізація Hierarchical MVC (HMVC). У цьому випадку ви можете створювати нові запити на основіпоточному, без інформування про цю програму, і будучи впевненими, що стан програми не зміниться. Загалом, для більшості фреймворків використання PSR-7 повідомлень переведе до переносної абстракції над HTTP повідомленнями. Це дасть змогу реалізувати універсальну проміжну ланку. Адаптація повідомлень, однак, вимагатиме незначних змін. Розробникам необхідно оновити код, який відповідає за відстеження стану програми.

Хардкорна конфа за С++. Ми запрошуємо лише профі.