Лекція 2.1. Використання RESTful
4. Явне використання HTTP-методів
Однією з ключових характеристик Web-сервісу RESTful є явне використання HTTP-методів згідно з протоколом, означеним в RFC 2616. Наприклад, HTTP GET означений як метод генерування даних, використовуваний клієнтським додатком для вилучення ресурсу, отримання даних з Web-сервера або виконання запиту в надії на те, що Web-сервер знайде і поверне набір відповідних ресурсів.
REST пропонує розробникам використовувати HTTP-методи явно відповідно до означення протоколу. Цей основний принцип проектування REST встановлює однозначну відповідність між операціями create, read, update і delete (CRUD) і HTTP-методами. Згідно з цим відповідності:
• Для створення ресурсу на сервері використовується POST.
• Для отримання ресурсу використовується GET.
• Для зміни стану ресурсу або його поновлення використовується PUT.
• Для видалення ресурсу використовується DELETE.
Недоліком проектування багатьох Web API є використання HTTP-методів не за прямим призначенням. Наприклад, URI запиту в HTTP GET зазвичай означує один конкретний ресурс, або ж рядок запиту в URI запиту містить ряд параметрів, що означують критерії пошуку сервером набору відповідних ресурсів. Принаймні саме так описаний метод GET в HTTP/1.1 RFC. Однак часто зустрічаються непривабливі Web API, що використовують HTTP GET для виконання різного роду транзакцій на сервері (наприклад, для додавання записів в базу даних). У таких випадках URI запиту GET використовується некоректно або, принаймні, не використовується в REST-стилі (RESTfully). Якщо Web API використовує GET для запуску віддалених процедур, запит може виглядати приблизно так:
GET /adduser?name=Robert HTTP/1.1
Це невдалий дизайн, оскільки вищезгаданий Web-метод за допомогою HTTP-запиту GET підтримує операцію, що змінює стан. Інакше кажучи, HTTP-запит GET має побічні ефекти. У разі успішного виконання запиту в сховище даних буде додано новий користувач (в нашому прикладі - Robert). Проблема тут в основному семантична. Web-сервери призначені для відповідей на HTTP-запити GET шляхом вилучення ресурсів відповідно до URI запиту (або критерію запиту) і повернення їх або їхні уявлення у відповіді, а не для додавання запису в базу даних. З точки зору передбачуваного використання і з точки зору HTTP / 1.1-сумісних Web-серверів таке використання GET є неналежним.
Крім семантики ще однією проблемою є те, що для видалення, зміни або додавання запису в базу даних або для зміни будь-яким чином стану на стороні сервера GET привертає різні засоби Web-кешування (роботи) і пошукові механізми, які можуть виконувати ненавмисні зміни на сервері шляхом простого обходу посилання. Найпростішим способом вирішення цієї загальної проблеми є вставлення імен та значень параметрів URI запиту в XML-теги. Ці теги (XML-представлення створюваного об'єкта) можна відправити в тілі HTTP-запиту POST, URI якого є батьком об'єкта (див. Листинги 1 і 2).
Лістинг 1. До
1
|
GET /adduser?name=Robert HTTP/1.1 |
Лістинг 2. Після
1
2
3
4
5
6
7
|
POST /users HTTP/1.1 Host: myserver Content-Type: application/xml <? xml version = "1.0" ?> < user > < name >Robert</ name > </ user > |
Це зразок RESTful-запиту: HTTP-запит POST використовується коректно, а тіло запиту містить корисне навантаження. На приймаючій стороні в запит може бути доданий ресурс, що міститься в тілі, підлеглий ресурсові, визначеному в URI запиті; в даному випадку новий ресурс повинен додаватися як нащадок /users. Таке ставлення включення (containment) між новим логічним об'єктом і його батьком, вказане в запиті POST, аналогічно відношенню підпорядкування між файлом і батьківським каталогом. Клієнтська програма встановлює відношення між логічним об'єктом і його батьком і означує URI нового об'єкта в запиті POST.
Потім клієнтська програма може отримати уявлення ресурсу, використовуючи новий URI, який вказує, що принаймні логічно ресурс розташований в /users (див. Лістинг 3).
Лістинг 3. HTTP-запрос GET
1
2
3
|
GET /users/Robert HTTP/1.1 Host: myserver Accept: application/xml |
Це правильне застосування запиту GET, оскільки він служить тільки для отримання даних. GET - це операція, яка повинна бути вільною від побічних ефектів. Дана властивість відома також під назвою ідемпотентність.
Аналогічний рефакторинг Web-методу необхідно виконати в тих ситуаціях, коли HTTP-запит GET підтримує операцію update (див. Лістинг 4).
Лістинг 4. Операція update в HTTP-запиті GET
1
|
GET /updateuser?name=Robert&newname=Bob HTTP/1.1 |
Це запит змінює атрибут (або властивість) name ресурсу. Хоча для такої операції можна використовувати рядок запиту і в лістингу 4 наведена найпростіша з них, модель "рядок запиту як сигнатура методу" не працює для більш складних операцій. Оскільки нашою метою є явне використання HTTP-методів, із зазначених вище причин більш RESTful-сумісним є підхід, при якому для поновлення ресурсу застосовується HTTP-запит PUT замість HTTP-запиту GET (див. Лістинг 5).
Листинг 5. HTTP-запит PUT
1
2
3
4
5
6
7
|
PUT /users/Robert HTTP/1.1 Host: myserver Content-Type: application/xml <? xml version = "1.0" ?> < user > < name >Bob</ name > </ user > |
Використання запиту PUT для заміни вихідного ресурсу забезпечує набагато більш прозорий інтерфейс, сумісний з принципами REST і з означенням HTTP-методів. Запит PUT в лістингу 5 є явним в тому сенсі, що він вказує на оновлюваний ресурс, означуючи його в URI запиту, і передає нове уявлення ресурсу від клієнта на сервер в тілі запиту PUT, замість того щоб передавати атрибути ресурсу у вигляді слабо пов'язаного набору імен і значень параметрів в URI запиту. Запит в лістингу 5 перейменовує ресурс з Robert на Bob і змінює його URI на /users/Bob. У Web-сервісі REST використання старого URI в наступних запитах ресурсу призведе до виникнення стандартної помилки 404 Not Found.
Загальноприйнятим підходом, відповідних рекомендацій REST по явному застосуванню HTTP-методів, є використання в URI іменників замість дієслів. У Web-сервісі RESTful дієслова POST, GET, PUT і DELETE вже означені протоколом. В ідеалі для реалізації узагальненого інтерфейсу і явного виклику операцій клієнтськими додатками Web-сервіс не повинен означувати додаткові команди або віддалені процедури, наприклад /adduser або /updateuser. Цей загальний принцип можна застосувати також до тіла HTTP-запиту, який призначений для передачі стану ресурсу, а не імені віддаленого методу або віддаленої процедури, що викликається.