🌐 Розділ 6 · Питання #3

Які основні HTTP методи використовуються в REST

В архітектурному стилі REST методи зіставляються з CRUD-операціями над даними:


🟢 Junior Level

HTTP-методи (HTTP Verbs / дієслова HTTP) — це стандартні команди протоколу HTTP (специфікація RFC 9110), які повідомляють серверу, яку дію клієнт хоче виконати над зазначеним ресурсом (URI).

В архітектурному стилі REST методи зіставляються з CRUD-операціями над даними:

HTTP Метод CRUD-операція Призначення Приклад URI Успішний статус-код
GET Read Отримання представлення ресурсу (читання) GET /api/v1/orders/42 200 OK
POST Create Створення нового дочірнього ресурсу або виклик дії POST /api/v1/orders 201 Created
PUT Update (Replace) Повна заміна ресурсу за вказаною адресою PUT /api/v1/orders/42 200 OK / 204 No Content
PATCH Update (Partial) Часткове оновлення окремих полів ресурсу PATCH /api/v1/orders/42 200 OK / 204 No Content
DELETE Delete Видалення зазначеного ресурсу DELETE /api/v1/orders/42 204 No Content

Приклад реалізації REST-контролера на Spring Boot 3

package com.example.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;
import java.util.List;

@RestController
@RequestMapping("/api/v1/products")
public class ProductController {

    private final ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    // 1. GET: читання списку або одного елемента
    @GetMapping("/{id}")
    public ResponseEntity<ProductDto> getProduct(@PathVariable Long id) {
        return ResponseEntity.ok(productService.findById(id));
    }

    // 2. POST: створення ресурсу із заголовком Location
    @PostMapping
    public ResponseEntity<ProductDto> createProduct(@RequestBody CreateProductRequest req) {
        ProductDto created = productService.create(req);
        URI location = ServletUriComponentsBuilder.fromCurrentRequest()
                .path("/{id}")
                .buildAndExpand(created.id())
                .toUri();
        return ResponseEntity.created(location).body(created); // 201 Created + Location
    }

    // 3. PUT: повна заміна ресурсу
    @PutMapping("/{id}")
    public ResponseEntity<ProductDto> replaceProduct(@PathVariable Long id, 
                                                    @RequestBody ReplaceProductRequest req) {
        return ResponseEntity.ok(productService.replace(id, req));
    }

    // 4. PATCH: часткове оновлення
    @PatchMapping("/{id}")
    public ResponseEntity<ProductDto> patchProduct(@PathVariable Long id, 
                                                  @RequestBody PatchProductRequest req) {
        return ResponseEntity.ok(productService.patch(id, req));
    }

    // 5. DELETE: видалення ресурсу
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
        productService.delete(id);
        return ResponseEntity.noContent().build(); // 204 No Content
    }
}

🟡 Middle Level

Класифікація методів: Безпечність (Safe) та Ідемпотентність (Idempotent)

За специфікацією RFC 9110 (HTTP Semantics) усі методи поділяються за двома фундаментальними характеристиками:

                          ┌─── Safe (Безпечний: не змінює стан на сервері): GET, HEAD, OPTIONS, TRACE
             ┌─── Властивості ┤
             │             └─── Idempotent (Ідемпотентний: повтор N разів дає той самий результат): GET, HEAD, PUT, DELETE, OPTIONS
HTTP Методи ┤
             │             ┌─── PUT: Повна заміна (усі поля обов'язкові)
             └─── Мутації  ┼─── PATCH: Часткове оновлення (RFC 6902 / RFC 7386)
                           └─── POST: Створення / Запуск бізнес-операції (НЕ ідемпотентний!)

Зведена матриця властивостей методів

Метод Наявність тіла запиту Безпечний (Safe)? Ідемпотентний (Idempotent)? Кешований (Cacheable)?
GET ❌ Ні ✅ Так ✅ Так ✅ Так (за замовчуванням)
HEAD ❌ Ні ✅ Так ✅ Так ✅ Так
OPTIONS ❌ Ні ✅ Так ✅ Так ❌ Ні
POST ✅ Так ❌ Ні ❌ Ні ⚠️ Лише з явним Cache-Control
PUT ✅ Так ❌ Ні ✅ Так ❌ Ні
PATCH ✅ Так ❌ Ні ⚠️ Залежить від операції ❌ Ні
DELETE ⚠️ Не рекомендовано ❌ Ні ✅ Так ❌ Ні

Допоміжні методи: HEAD та OPTIONS

  • HEAD: виконує абсолютно такий самий запит, як GET, але сервер повертає тільки HTTP-заголовки без тіла відповіді. Застосування: перевірка розміру великого файлу (Content-Length) або валідності кешу (ETag, Last-Modified) до початку реального завантаження.
  • OPTIONS: опитування комунікаційних можливостей сервера. Застосування: механізм CORS Preflight у браузерах. Перед відправкою нестандартного запиту браузер надсилає OPTIONS, а сервер повертає дозволені методи (Access-Control-Allow-Methods: GET, POST, PUT, DELETE) і заголовки.

🔴 Senior Level

Тонкощі семантики PUT vs PATCH

  1. Суворий контракт PUT (Повна заміна):
    • Якщо надіслано PUT /users/42 з тілом {"name": "Ivan"}, а у користувача в БД було заповнене поле email: "ivan@test.com", бекенд зобов’язаний затерти email в null, оскільки PUT заміщує ресурс повністю. Якщо поле потрібно зберегти, клієнт зобов’язаний надіслати його заново.
  2. Специфікації PATCH:
    • JSON Merge Patch (RFC 7386): клієнт надсилає тільки змінювані поля: {"email": "new@mail.com"}. Проблема: як відрізнити «поле не передано (не змінювати)» від «поле передано як null (стерти значення)»? У Spring Boot це вирішується через модуль org.openapitools.jackson.nullable.JsonNullable<T>.
    • JSON Patch (RFC 6902): надсилається декларативний масив атомарних операцій (add, remove, replace, copy, move, test):
      [
        { "op": "replace", "path": "/email", "value": "new@mail.com" },
        { "op": "remove", "path": "/phoneNumber" }
      ]
      

Автоматичні повтори (Retries) у Service Mesh (Envoy / Istio)

У розподілених мікросервісних системах знання ідемпотентності методів є критичним при налаштуванні мережевих політик:

  • Безпечні повтори (Safe Retries): При отриманні мережевої помилки (503 Service Unavailable, Connection Reset) проксі Envoy / Sidecar може автоматично повторити запити GET, PUT, DELETE без ризику зіпсувати дані.
  • Небезпека повторів для POST: Автоматичний повтор запиту POST категорично заборонений (ризик подвійного списання грошей з рахунку або дублювання замовлення). Для безпечного повтору POST в архітектуру впроваджують заголовок Idempotency-Key зі збереженням унікальних ключів у Redis.

Тунелювання методів (X-HTTP-Method-Override)

Деякі застарілі корпоративні проксі-сервери, фаєрволи (WAF) та клієнти блокують методи PUT, PATCH або DELETE.

  • Для обходу обмежень використовують тунелювання через POST із заголовком:
    POST /api/v1/orders/42 HTTP/1.1
    X-HTTP-Method-Override: DELETE
    
  • У Spring Boot підтримка тунелювання вмикається через бін HiddenHttpMethodFilter.

4 Tricky Questions

1. У чому принципова відмінність між властивостями Safe (Безпечний) та Idempotent (Ідемпотентний)?

Відповідь: Метод є Safe, якщо він взагалі не змінює стан даних на сервері (тільки читання: GET, HEAD, OPTIONS). Метод є Idempotent, якщо його повторні ідентичні виклики призводять до того ж кінцевого стану даних на сервері, що й перший виклик. Наприклад, DELETE не є безпечним (він модифікує БД), але він ідемпотентний (після 1-го або 100-го виклику сутність у БД видалена).


2. Якщо другий виклик DELETE /users/1 повертає 404 Not Found, чи порушується ідемпотентність?

Відповідь: Ні, не порушується. Ідемпотентність за RFC 9110 визначається кінцевим станом даних на сервері (Resource State), а не статус-кодом HTTP-відповіді. Після першого і після другого запиту користувача з ID 1 в системі немає — отже, стан сервера не змінився, і метод ідемпотентний.


3. Навіщо потрібен метод HEAD і коли його використовують на практиці?

Відповідь: Метод HEAD повертає абсолютно ідентичні заголовки, що й GET, але з гарантовано порожнім тілом відповіді. Він незамінний для:

  • Перевірки доступності ресурсу (200 vs 404) без передачі зайвого трафіку.
  • Перевірки розміру файлу перед завантаженням через заголовок Content-Length.
  • Перевірки валідності клієнтського кешу через перевірку заголовків ETag або Last-Modified.

4. Що станеться, якщо в запиті PUT не передати одне з існуючих полів сутності?

Відповідь: За контрактом PUT виконує повну заміну ресурсу. Сервер зобов’язаний затерти непередане поле в null (або встановити дефолтне значення). Якщо клієнт хоче оновити тільки частину полів, не чіпаючи інші, він зобов’язаний використовувати метод PATCH.


🎯 Interview Cheat Sheet

30-секундна відповідь

У REST використовуються 5 базових методів HTTP (RFC 9110):

  • GET — читання ресурсу (Safe, Idempotent).
  • POST — створення підпорядкованого ресурсу або тригер дії (Неідемпотентний).
  • PUT — повна заміна ресурсу за вказаним URI (Idempotent).
  • PATCH — часткове оновлення полів ресурсу (зазвичай неідемпотентний).
  • DELETE — видалення ресурсу (Idempotent).

Допоміжні методи: HEAD (заголовки без тіла для перевірки кешу та розміру) та OPTIONS (опитування можливостей сервера та CORS Preflight).

Червоні прапорці (чого категорично не можна говорити)

  • ❌ «PUT і PATCH роблять одне й те саме, це синоніми» — PUT повністю заміщує ресурс, PATCH оновлює тільки вказані поля.
  • ❌ «DELETE неідемпотентний, тому що другий запит повертає помилку 404» — статус відповіді може змінюватися, важливий стан сервера.
  • ❌ «GET можна використовувати для видалення або зміни записів у базі» — грубе порушення специфікації Safe Methods.
  • ❌ «Метод POST ідемпотентний з коробки» — POST принципово неідемпотентний і без Idempotency-Key дублює дії при повторах.

Пов’язані теми