Mục lục
- Mục Tiêu Bài Học
- 9 HTTP Method Toàn Cảnh
- Safe Method — Read-Only Không Side Effect
- Idempotent Method — Gọi N Lần = Gọi 1 Lần
- GET, POST, PUT, PATCH, DELETE — Chi Tiết
- HEAD, OPTIONS, TRACE, CONNECT — Less Common
- Quy Ước REST: Method ↔ CRUD ↔ URL
- Method Override Pattern
- Tổng Kết
- Bài Tập Củng Cố
- Bài Tiếp Theo
Mục Tiêu Bài Học
Sau bài học, bạn sẽ:
- Liệt kê được 9 HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, TRACE, CONNECT) và mục đích của từng method.
- Phân biệt được safe và unsafe method, hiểu vì sao server không được phép thay đổi state khi xử lý safe method.
- Phân biệt được idempotent và non-idempotent, biết method nào an toàn để retry khi mạng lỗi.
- Nắm quy ước REST mapping method với thao tác CRUD: POST=create, GET=read, PUT=replace, PATCH=update, DELETE=remove.
- Biết khi nào dùng PUT, khi nào dùng PATCH, và cách hai method này thể hiện trong Shop API.
- Hiểu method override pattern qua header
X-HTTP-Method-Overridevà form field_methoddành cho legacy client.
9 HTTP Method Toàn Cảnh
RFC 7231 (Semantics and Content của HTTP/1.1, năm 2014) định nghĩa 8 method cốt lõi: GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE. RFC 5789 (năm 2010) bổ sung PATCH như một extension cho partial update. Tổng cộng 9 method được dùng phổ biến trong REST API hiện đại.
Bảng overview ba thuộc tính quan trọng — safe (không đổi state), idempotent (gọi N lần giống 1 lần), và cacheable (response cache được):
Method Safe Idempotent Cacheable Mục đích
GET Yes Yes Yes Đọc resource
HEAD Yes Yes Yes Đọc metadata, không body
POST No No Conditional Tạo resource, trigger action
PUT No Yes No Thay thế toàn bộ resource
PATCH No No Conditional Sửa một phần resource
DELETE No Yes No Xoá resource
OPTIONS Yes Yes No Liệt kê method allowed
TRACE Yes Yes No Echo request (debug)
CONNECT No No No Tunneling cho HTTPS proxy
POST và PATCH được đánh dấu conditional cacheable: response chỉ cache được nếu server gửi rõ Cache-Control hoặc Expires cho phép, mặc định không cache. Bốn method GET, POST, PUT, DELETE chiếm gần như toàn bộ traffic REST thực tế; PATCH bổ sung cho update bộ phận. Năm method còn lại phục vụ vai trò chuyên biệt, sẽ trình bày ở mục 6.
Safe Method — Read-Only Không Side Effect
Một method được gọi là safe khi nó không thay đổi state của server. Cụ thể: gọi method đó một lần, mười lần, hay một triệu lần thì dữ liệu trên server vẫn nguyên vẹn. Theo RFC 7231 mục 4.2.1, bốn method safe là GET, HEAD, OPTIONS, TRACE.
Cụm từ "không thay đổi state" cần hiểu chính xác. Server hoàn toàn được phép ghi log access, tăng metric counter, hay cập nhật cache nội bộ khi nhận GET — đó là side effect quan sát chứ không phải side effect do client yêu cầu. Quy tắc thật sự: client không được phép kỳ vọng resource bị sửa đổi khi gọi safe method, và server không được phép phụ thuộc vào side effect đó như một phần contract.
Đặc tính safe có ý nghĩa thực tiễn rất lớn. Trình duyệt được phép prefetch link GET ngầm trước khi user click. Search engine crawler được phép GET mọi URL công khai mà không lo phá dữ liệu. Reverse proxy được phép cache response GET và phục vụ lại nhiều client. Toàn bộ hạ tầng web — CDN, browser cache, web crawler — vận hành được vì có ranh giới rõ ràng giữa safe và unsafe method.
Trong tư duy SQL, safe method tương đương SELECT — chỉ đọc, không bao giờ INSERT/UPDATE/DELETE. Một anti-pattern thường gặp là dùng GET cho endpoint kích hoạt action: GET /users/42/delete hay GET /orders/99/cancel. Endpoint kiểu này phá vỡ contract safe, khiến crawler vô tình xoá dữ liệu khi index site. Action phải dùng POST hoặc DELETE.
Idempotent Method — Gọi N Lần = Gọi 1 Lần
Một method được gọi là idempotent khi gọi nó N lần liên tiếp dẫn tới cùng một state cuối trên server như khi gọi 1 lần. Theo RFC 7231 mục 4.2.2, sáu method idempotent là GET, HEAD, OPTIONS, TRACE, PUT, DELETE.
Lưu ý: idempotent không có nghĩa response giống nhau. Gọi DELETE /products/42 lần đầu trả 204 No Content (xoá thành công); lần hai trả 404 Not Found (đã xoá rồi). Response khác nhau, nhưng state server sau lần một và sau lần hai đều giống nhau — product 42 không tồn tại. Đó vẫn là idempotent.
POST không idempotent: mỗi POST đến cùng endpoint tạo một resource mới. Gọi POST /api/v1/products hai lần sinh ra hai product khác nhau với hai ID khác nhau. Đây là khác biệt cốt lõi với PUT.
PATCH thường không idempotent: vì payload PATCH có thể là delta tương đối (ví dụ {"stock_increment": 1}). Tuy nhiên PATCH với payload tuyệt đối ({"stock": 100}) thì idempotent. RFC 5789 nói rõ: PATCH có thể idempotent hoặc không, tuỳ ngữ nghĩa server định nghĩa.
Ứng dụng quan trọng nhất của idempotency là retry-on-failure. Khi client gửi PUT mà nhận timeout, client không biết server đã nhận và xử lý hay chưa. Vì PUT idempotent, client retry an toàn — kết quả cuối vẫn đúng. Với POST, retry mù sinh duplicate; phải dùng kỹ thuật idempotency key (sẽ học ở bài payment) để chống duplicate. Đây là một trong những lý do tại sao Stripe, GitHub, AWS đều yêu cầu idempotency key cho POST.
GET, POST, PUT, PATCH, DELETE — Chi Tiết
Năm method dưới đây chiếm hơn 99% traffic REST API thực tế.
GET — Đọc Resource
Lấy biểu diễn của resource. Tham số đi qua query string: ?page=1&size=20&sort=price:asc. Theo RFC 7231 mục 8.1.3, GET không nên có body — nhiều proxy và CDN sẽ strip body GET. Response cacheable theo header Cache-Control và ETag. Mã trả về thường gặp: 200 OK, 304 Not Modified (cache hit), 404 Not Found.
GET /api/v1/products/42 HTTP/1.1
Host: shop.example.com
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "a1b2c3d4"
Cache-Control: public, max-age=300
{"id": 42, "name": "Laptop X", "price": 1299.00}
POST — Tạo Resource Hoặc Trigger Action
Tạo resource mới hoặc kích hoạt một action server-side (ví dụ POST /api/v1/checkout). Body chứa dữ liệu, content-type thường là application/json. Khi tạo resource thành công, server trả 201 Created kèm header Location chỉ tới URL resource vừa tạo. POST không idempotent.
POST /api/v1/products HTTP/1.1
Host: shop.example.com
Content-Type: application/json
{"name": "Laptop Y", "price": 1499.00, "stock": 10}
HTTP/1.1 201 Created
Location: /api/v1/products/43
Content-Type: application/json
{"id": 43, "name": "Laptop Y", "price": 1499.00, "stock": 10}
PUT — Thay Thế Toàn Bộ Resource
Body PUT là biểu diễn đầy đủ của resource sau khi update. Server thay toàn bộ resource bằng body; field nào không có trong body coi như bị reset về default. PUT idempotent: gọi N lần cùng body cho kết quả giống nhau. Mã trả về: 200 OK (kèm resource mới) hoặc 204 No Content (không body).
PUT /api/v1/products/43 HTTP/1.1
Host: shop.example.com
Content-Type: application/json
{"name": "Laptop Y Pro", "price": 1799.00, "stock": 5}
HTTP/1.1 200 OK
PATCH — Update Một Phần
Body PATCH chứa chỉ thay đổi cần áp dụng, không phải toàn bộ resource. Hai chuẩn payload phổ biến: JSON Patch (RFC 6902) dạng array operation [{"op": "replace", "path": "/price", "value": 1599}], và JSON Merge Patch (RFC 7396) đơn giản hơn dạng object {"price": 1599}. Phần lớn API public chọn Merge Patch vì gọn.
PATCH /api/v1/products/43 HTTP/1.1
Host: shop.example.com
Content-Type: application/merge-patch+json
{"price": 1599.00}
HTTP/1.1 200 OK
DELETE — Xoá Resource
Xoá resource khỏi server. DELETE idempotent: lần đầu xoá trả 204 No Content, các lần sau trả 404 Not Found nhưng state server sau mỗi lần đều giống nhau (resource không tồn tại). Mã trả về thường gặp: 200 OK (kèm resource vừa xoá), 204 No Content, 404 Not Found.
DELETE /api/v1/products/43 HTTP/1.1
Host: shop.example.com
Authorization: Bearer eyJhbGc...
HTTP/1.1 204 No Content
HEAD, OPTIONS, TRACE, CONNECT — Less Common
Bốn method còn lại ít gặp trong handler ứng dụng nhưng vẫn xuất hiện hằng ngày ở tầng hạ tầng.
HEAD — Như GET Nhưng Không Body
HEAD trả về header giống hệt GET nhưng không kèm body. Dùng để check nhanh metadata: kích thước qua Content-Length, thời điểm sửa đổi qua Last-Modified, version qua ETag. Trình quản lý download dùng HEAD để xác định kích thước file trước khi tải; cache layer dùng HEAD để invalidate khi ETag đổi.
OPTIONS — Liệt Kê Method Allowed
Trả về header Allow liệt kê method được phép trên một resource. Ứng dụng quan trọng nhất là CORS preflight: trước khi gửi request cross-origin có header tuỳ chỉnh hoặc method khác GET/POST/HEAD, browser tự động gửi một OPTIONS request hỏi server có cho phép hay không.
OPTIONS /api/v1/products/43 HTTP/1.1
Host: shop.example.com
Origin: https://app.example.com
Access-Control-Request-Method: PATCH
HTTP/1.1 204 No Content
Allow: GET, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, PUT, PATCH, DELETE
TRACE — Debug Echo
Server echo lại request mà client vừa gửi để client thấy chính xác phiên bản đã đi qua mọi proxy trung gian. Hữu ích cho debug, nhưng tiềm ẩn rủi ro lộ header nhạy cảm (Cookie, Authorization) — gọi là Cross-Site Tracing. Thực tế phần lớn server production disable TRACE bằng cách trả 405 Method Not Allowed.
CONNECT — Tunneling Cho HTTPS Proxy
Client yêu cầu proxy thiết lập một TCP tunnel tới host đích, sau đó dữ liệu TLS truyền raw qua tunnel. Đây là cách HTTPS đi qua proxy HTTP truyền thống. Application backend gần như không bao giờ tự xử lý CONNECT — đó là việc của proxy/gateway.
Quy Ước REST: Method ↔ CRUD ↔ URL
Quy ước mapping HTTP method với thao tác CRUD và URL pattern là viên gạch chính của uniform interface đã giới thiệu ở bài 1. Bảng dưới áp dụng trực tiếp cho Shop API, dùng resource products làm ví dụ:
CRUD HTTP URL Pattern Response thành công
Create POST /api/v1/products 201 Created + Location header
Read all GET /api/v1/products 200 OK + danh sách (paginated)
Read one GET /api/v1/products/:id 200 OK + entity / 404 Not Found
Update PUT /api/v1/products/:id 200 OK / 204 No Content
Partial PATCH /api/v1/products/:id 200 OK + entity sau update
Delete DELETE /api/v1/products/:id 204 No Content / 404 Not Found
Quy ước này lặp lại cho mọi resource trong Shop API — orders, cart/items, categories, reviews, ... tất cả đều theo cùng pattern /api/v1/<resource> và cùng mapping method. Lợi ích: developer mới nhìn vào endpoint đoán được hành vi không cần đọc doc. Đó chính là tinh thần uniform mà Fielding đề xuất.
Vài lưu ý áp dụng vào Shop:
- List có filter:
GET /api/v1/products?category=laptop&min_price=500. Tham số đi qua query string, không phải body. - Sub-resource:
GET /api/v1/products/:slug/reviewsđọc reviews thuộc product cụ thể. - Action không map gọn CRUD: dùng POST với verb-noun rõ ràng, ví dụ
POST /api/v1/orders/:id/cancel,POST /api/v1/checkout. Không lạm dụng RPC-style nhưng cũng không cố gò mọi thứ vào CRUD. - Admin scope: tách prefix
/api/v1/admin/<resource>để phân quyền tầng route, ví dụPOST /api/v1/admin/products.
Method Override Pattern
Một số môi trường client cũ chỉ hỗ trợ GET và POST: HTML form thuần (chỉ method="get" và method="post"), một số firewall hoặc proxy cũ chặn PUT/PATCH/DELETE, một vài SDK enterprise legacy. Khi cần PUT/PATCH/DELETE qua những client này, dùng method override pattern.
Hai cách phổ biến:
- Header override: client gửi POST kèm header
X-HTTP-Method-Override: PUT. Server phát hiện header và xử lý request như PUT thật. - Form field override: client gửi POST với form field
_method=DELETE. Server đọc field và xử lý như DELETE.
POST /api/v1/products/43 HTTP/1.1
Host: shop.example.com
X-HTTP-Method-Override: PATCH
Content-Type: application/json
{"price": 1599.00}
Server phải explicitly enable tính năng này. Trong axum, không có middleware override sẵn — bạn cần tự viết một tower::Layer nhỏ inspect header trước khi route matching, hoặc dùng pattern tương đương từ tower-http. Để mặc định không bật là quyết định bảo mật hợp lý: nếu mọi POST đều có thể trở thành DELETE, CSRF mitigation và rate limit dựa trên method sẽ vô hiệu.
Trong API hiện đại, method override gần như không cần thiết. Mọi browser hỗ trợ fetch() với mọi method; mọi mobile SDK (Retrofit, Alamofire, ...) đều dùng PUT/PATCH/DELETE trực tiếp; HTTP client phía Rust như reqwest không có giới hạn nào. Shop API sẽ không bật override — chỉ giữ kiến thức này để hiểu code base legacy nếu gặp khi maintain.
Tổng Kết
- 9 HTTP method dùng phổ biến: 8 method từ RFC 7231 (GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE) cộng PATCH từ RFC 5789.
- Safe = read-only, server không đổi state do client yêu cầu: GET, HEAD, OPTIONS, TRACE. Cho phép prefetch, crawler index, cache vô tư.
- Idempotent = gọi N lần dẫn tới state cuối giống 1 lần: GET, HEAD, OPTIONS, TRACE, PUT, DELETE. Cho phép retry an toàn khi mạng lỗi.
- POST không idempotent — mỗi POST sinh resource mới. PATCH thường không idempotent vì có thể chứa delta tương đối.
- REST mapping: POST=create, GET=read, PUT=replace toàn bộ, PATCH=update một phần, DELETE=remove. Apply nhất quán cho mọi resource Shop API qua pattern
/api/v1/<resource>. - PUT yêu cầu body đầy đủ; PATCH chỉ chứa thay đổi. Shop API sẽ dùng cả hai ở B65/B66.
- HEAD cho check metadata mà không tải body. OPTIONS phục vụ CORS preflight và liệt kê method allowed.
- TRACE và CONNECT hiếm gặp ở handler ứng dụng — TRACE thường disable vì rủi ro security, CONNECT là việc của proxy.
- Method override (
X-HTTP-Method-Override,_method) là workaround cho legacy client; API modern không cần và Shop API sẽ không bật.
Bài Tập Củng Cố
Tự trả lời, đáp án ở cuối:
- Liệt kê các method được coi là safe theo RFC 7231. Vì sao GET được liệt kê safe trong khi server vẫn ghi access log mỗi lần GET?
- PUT có idempotent không? POST có idempotent không? Giải thích tại sao có khác biệt, và hệ quả của khác biệt đó khi retry request thất bại.
- Khi tạo product mới trong Shop API, nên dùng HTTP method nào? Endpoint URL? Status code nào server trả về khi tạo thành công, và header nào cần đính kèm?
- Phân biệt PUT và PATCH ở ba khía cạnh: nội dung body, tính idempotent, và content-type thường dùng. Khi nào nên chọn cái nào?
- OPTIONS method có vai trò gì trong CORS? Trình duyệt gửi OPTIONS preflight trong tình huống cụ thể nào?
Đáp án
- Safe method theo RFC 7231 mục 4.2.1: GET, HEAD, OPTIONS, TRACE. GET vẫn safe dù server ghi log vì "safe" có nghĩa client không kỳ vọng resource bị sửa đổi và server không phụ thuộc vào side effect đó như một phần contract. Access log, metric, internal cache update là side effect quan sát chứ không phải state thay đổi do client yêu cầu. Quy tắc là crawler/prefetcher gọi GET bất kỳ lúc nào cũng không phá dữ liệu.
- PUT idempotent, POST không idempotent. PUT vì body chứa biểu diễn đầy đủ resource — gọi PUT cùng body N lần cho state cuối giống nhau. POST tạo resource mới mỗi lần — N POST sinh N resource khác nhau. Hệ quả khi retry: PUT timeout retry được an toàn vì kết quả cuối vẫn đúng. POST timeout retry mù sinh duplicate; phải dùng idempotency key (client gửi UUID kèm request, server lưu key cùng response, request thứ hai cùng key trả response cũ không xử lý lại).
- Method POST, endpoint
POST /api/v1/products(hoặcPOST /api/v1/admin/productsnếu chỉ admin được tạo). Status code 201 Created, headerLocation: /api/v1/products/<id_mới>trỏ tới URL của product vừa tạo. Body response thường kèm representation đầy đủ của product (id, name, price, stock, timestamp). - PUT body chứa biểu diễn đầy đủ của resource (toàn bộ field); PATCH body chỉ chứa thay đổi cần áp dụng (subset field). PUT idempotent; PATCH thường không idempotent. Content-type PUT thường
application/json; PATCH thườngapplication/merge-patch+json(RFC 7396) hoặcapplication/json-patch+json(RFC 6902). Chọn PUT khi client có sẵn full state và muốn thay toàn bộ; chọn PATCH khi chỉ update vài field, tiết kiệm băng thông và tránh ghi đè field không liên quan từ client khác. - OPTIONS phục vụ CORS preflight: trước khi gửi request cross-origin có method khác GET/POST/HEAD hoặc có header tuỳ chỉnh (vd
Authorization,Content-Type: application/jsonvới body không phải form), browser tự động gửi OPTIONS hỏi server có cho phép request thực sắp tới hay không. Response chứa headerAccess-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Headers. Nếu OPTIONS pass, browser mới gửi request thực; nếu fail, browser block và không gọi handler.
Bài Tiếp Theo
Bài 3: HTTP Status Codes: 2xx/3xx/4xx/5xx — giới thiệu 5 class status code, top 20 code thường gặp (200, 201, 204, 301, 304, 400, 401, 403, 404, 409, 422, 429, 500, 502, 503, 504), cách chọn đúng code cho mỗi tình huống, anti-pattern 200-with-error-body.
