Bài 11 · Nâng cao · 24 phút· Cập nhật 11/06/2026
Internet & HTTP
Biên soạn bởi Nguyễn Anh Tuấn
Gọi và dựng API trong Python: HTTP request/response, REST & JSON, mã trạng thái, thư viện requests/httpx - tới FastAPI đầu tiên của bạn.
Giao tiếp web theo mô hình request → response: bạn gửi yêu cầu (method + path + headers + body), server trả về mã trạng thái + dữ liệu. Gửi thử vài request:
Gửi thử một request tới API tasks (giả lập):
GET /tasks
200 OK · 2xx · Thành công
[ { "id": 1, "tieu_de": "Hoc HTTP" } ]| 2xx | Thành công (200 OK, 201 Created, 204 No Content) |
| 3xx | Chuyển hướng (301 Moved, 304 Not Modified) |
| 4xx | Lỗi phía CLIENT (400 Bad Request, 401/403, 404 Not Found) |
| 5xx | Lỗi phía SERVER (500 Internal Error, 503 Unavailable) |
- ▸Request: METHOD + PATH + HEADERS (vd Authorization) + đôi khi BODY.
- ▸Response: MÃ TRẠNG THÁI (200/404…) + headers + body (thường JSON).
- ▸Mỗi request độc lập (stateless): đăng nhập thường gửi kèm token ở header mỗi lần.
requests là thư viện gọi HTTP phổ biến nhất. Khi gọi NHIỀU lần tới một API, dùng Session để tái dùng kết nối & đặt header mặc định:
goi_api.py
# Cai: uv add requests
import requests
s = requests.Session() # tai dung ket noi + header mac dinh
s.headers["Authorization"] = "Bearer <token>"
# jsonplaceholder: API gia-lap MIEN PHI, chay that - /todos = danh sach "viec can lam"
r = s.get("https://jsonplaceholder.typicode.com/todos", timeout=10)
r.raise_for_status() # 4xx/5xx -> nem ngoai le
data = r.json() # body JSON -> list/dict Python
print(r.status_code, len(data)) # -> 200 200 - ▸requests.get(url, timeout=...) → response; r.json() đổi body JSON thành dict/list.
- ▸Session: tái dùng kết nối TCP (nhanh hơn) + header/auth mặc định cho mọi request.
- ▸LUÔN đặt timeout (requests không có mặc định!) và gọi raise_for_status().
API để tập gọi thật
requests hay httpx?
status.py
r = s.get(url, timeout=10)
if r.status_code == 200:
dung(r.json())
elif r.status_code == 404:
print("Khong tim thay")
elif r.status_code >= 500:
print("Loi phia server - co the retry + backoff")
else:
r.raise_for_status() - ▸2xx thành công · 3xx chuyển hướng · 4xx lỗi CLIENT · 5xx lỗi SERVER.
- ▸4xx = "lỗi của bạn" (sai dữ liệu/chưa đăng nhập); 5xx = "lỗi của họ" (nên retry có backoff).
- ▸raise_for_status() để không lỡ xử lý dữ liệu lỗi như thành công.
post.py
r = s.post(
"https://jsonplaceholder.typicode.com/todos",
json={"title": "Hoc HTTP"}, # tu dat Content-Type: application/json
headers={"Idempotency-Key": "abc-123"},# vd: chong tao trung khi retry
timeout=10,
)
r.raise_for_status()
print(r.status_code, r.json()) # vi du: 201 {'title': 'Hoc HTTP', 'id': 201} - ▸json=... gửi body JSON & tự đặt header Content-Type đúng.
- ▸POST tạo mới (201); PUT/PATCH cập nhật; DELETE xoá (204).
- ▸POST không idempotent → cân nhắc Idempotency-Key khi có thể retry (nhắc bài chuẩn nghề).
Mạng sẽ lỗi và dữ liệu thường chia trang. Hai mẫu thực chiến: retry có backoff cho lỗi tạm thời, và generator duyệt hết các trang:
resilience.py - retry + pagination
import time, requests
def get_retry(s, url, lan=3):
loi = None
for i in range(lan):
try:
r = s.get(url, timeout=10)
if r.status_code < 500: # 5xx -> thu lai; con lai tra ve
return r
loi = None # nhan duoc phan hoi (5xx)
except requests.RequestException as e:
loi = e # loi mang -> ghi nho de bao lai
time.sleep(2 ** i) # backoff: 1s, 2s, 4s...
if loi is not None:
raise loi # mang dut ca 3 lan
r.raise_for_status() # het luot voi 5xx -> nem loi HTTP
def duyet_tat_ca(s, base): # base = ".../todos"
page = 1
while True: # phan trang: xin tung trang
# ten tham so tuy API; jsonplaceholder dung _page & _limit
items = s.get(f"{base}?_page={page}&_limit=20", timeout=10).json()
if not items: # trang rong -> het
return
yield from items # tra tung item ra ngoai (luoi)
page += 1 - ▸Retry CHỈ cho lỗi tạm thời (5xx/timeout/mất mạng), backoff giãn dần, có giới hạn.
- ▸Thao tác retry nên idempotent (GET an toàn; POST cần Idempotency-Key).
- ▸Phân trang gói thành generator (yield từng item) → duyệt lười, tự dừng khi hết, ít RAM.
Không chỉ GỌI - bạn cũng dựng API được. FastAPI rất gọn:
app.py - chạy: uv run --with fastapi --with uvicorn uvicorn app:app
from fastapi import FastAPI
app = FastAPI()
@app.get("/tasks") # GET /tasks -> 200
def liet_ke():
return [{"id": 1, "tieu_de": "Hoc HTTP"}]
@app.post("/tasks", status_code=201) # POST /tasks -> 201
def tao(task: dict):
return {"id": 2, **task} - ▸Mỗi route = method + path + hàm trả dữ liệu (FastAPI tự đổi sang JSON).
- ▸FastAPI dùng type hint (bài type-hints!) để kiểm dữ liệu vào & sinh tài liệu API tự động.
- ▸REST: map tài nguyên (tasks) với method (GET/POST/PUT/DELETE) + status hợp lý.
Hết phần “thế giới thật”
Câu hỏi thường gặp
Theo mô hình REQUEST → RESPONSE: client gửi request gồm METHOD (GET/POST/…), PATH (/tasks/1), HEADERS (metadata như Authorization, Content-Type) và đôi khi BODY; server trả RESPONSE gồm MÃ TRẠNG THÁI (200, 404…), headers, và body (thường JSON). Mỗi request độc lập (stateless).
Cả hai là thư viện gọi HTTP với API gần giống nhau (requests.get(...), r.json()). httpx mới hơn, hỗ trợ HTTP/2 và quan trọng là CÓ async (await client.get(...)) - hợp khi gọi nhiều API đồng thời (bài async). Người mới dùng requests là đủ.
requests.Session() TÁI DÙNG kết nối TCP (connection pooling) → nhanh hơn rõ khi gọi nhiều request tới cùng host; và cho đặt headers/auth MẶC ĐỊNH một lần cho mọi request. Gọi lẻ requests.get mỗi lần mở kết nối mới - phí. Dùng Session khi gọi cùng một API nhiều lần.
timeout= ngăn chương trình TREO vô hạn khi mạng/đối tác chậm (requests KHÔNG có timeout mặc định!). r.raise_for_status() biến status 4xx/5xx thành ngoại lệ để bạn không vô tình xử lý dữ liệu lỗi như thành công. Đây là hai thói quen "chịu lỗi" tối thiểu khi gọi mạng.
Chỉ retry lỗi TẠM THỜI (timeout, mất mạng, 5xx, 429) - KHÔNG retry 4xx do dữ liệu sai. Dùng exponential backoff + jitter (giãn dần + ngẫu nhiên) để khỏi "bão retry"; giới hạn số lần. Thao tác được retry nên IDEMPOTENT. (Thư viện như tenacity, hoặc urllib3 Retry gắn vào Session, lo việc này cho bạn.)
Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.
Trả lời vài câu để chắc rằng em đã nắm bài.
Một trao đổi HTTP theo mô hình nào?
- 1
Đọc một response
Dùng widget Bước 1: gửi "Tạo (chưa đăng nhập)" và "Tạo (đã đăng nhập)". Hai mã trạng thái là gì, họ nào, nghĩa gì?
Hoàn thành khi: 401 Unauthorized (4xx - chưa đăng nhập) và 201 Created (2xx - tạo thành công).
- 2
Gọi API + Session
Cài
requests(uv add requests). Tạo mộtSessionđặt header mặc định; gọiGET https://jsonplaceholder.typicode.com/todos/1; instatus_codevà một trườngr.json().Hoàn thành khi: Dùng
Session(tái dùng kết nối + header); lấy được dữ liệu JSON; đọcstatus_code. - 3
Chịu lỗi
Thêm
timeout=vàr.raise_for_status(); viết một vòng retry có backoff cho lỗi tạm thời (5xx/timeout).Hoàn thành khi: Lời gọi có
timeout; status lỗi ném ngoại lệ; retry chỉ cho lỗi tạm thời, giãn dần, có giới hạn. - 4
Phân trang bằng generator
Viết generator
duyet_tat_ca()lặp xin từng trang (vd/todos?_page=1,2,…&_limit=20) vàyieldtừng item, dừng khi trang rỗng.Hoàn thành khi: Bên ngoài chỉ
for item in duyet_tat_ca(); không nạp hết vào RAM; tự dừng đúng lúc. - 5
Phác một API
Thiết kế 3 route cho "tasks": liệt kê, tạo, xoá - với method & mã trạng thái phù hợp.
Hoàn thành khi:
GET /tasks→ 200;POST /tasks→ 201;DELETE /tasks/{id}→ 204. Hợp REST.