Cuộc hội thoại giữa trình duyệt và server, đi sâu: HTTP thực sự là gì, một request biến thành response ra sao, Flask biến hàm Python thành trang web thế nào — và cách kiểm thử tất cả bằng curl.
← / → để chuyển slide · chuyển EN/VI bên dưới
HTTP (Hypertext Transfer Protocol) điều hành mọi thứ. Đổi lượt nghiêm ngặt: client nói trước, server trả lời một lần, và lượt chơi kết thúc.
Trình duyệt, app điện thoại, curl… tức là bạn. Phía duy nhất được quyền mở cuộc hội thoại.
Một chương trình lắng nghe trên một cổng (80/443). Nó không bao giờ chủ động — chỉ trả lời.
một request → một response → xong · mỗi cú click, mỗi lần refresh, mỗi API call đều đúng là thế này
Trước HTTP, mỗi cặp client–server tự phát minh phương ngữ riêng. HTTP là phương ngữ duy nhất mà tất cả cùng thống nhất.
Hàng tỷ client và server — do nhiều người xây, bằng nhiều ngôn ngữ code — thống nhất một bộ quy tắc duy nhất. HTTP là thỏa thuận đó.
Client hỏi, server trả lời. Vai trò không bao giờ hoán đổi — chỉ một quy tắc đó khiến web có thể dự đoán ở quy mô toàn cầu.
Mỗi trao đổi là một cuộc hội thoại mới; server quên bạn ngay khi response được gửi đi. Cookie và session là chiêu trò giả lập bộ nhớ.
Request và response chỉ là text có định dạng. Bạn đọc được bằng chính mắt mình (curl -i) — chính vì thế HTTP dễ học.
Mọi ứng dụng web bạn sẽ ship đều là hai vai trò này nói chuyện. Khóa học này giúp bạn trở thành người xây đúng phía cần xây.
Trình duyệt, app điện thoại, hay curl của bạn. Nó mở đầu mọi cuộc hội thoại, hiển thị kết quả, và không giữ gì giữa các lần truy cập.
Client theo thiết kế là 'không kiên nhẫn': hỏi một lần, chờ, rồi tiếp tục.
Một chương trình trên máy, chờ ở một cổng. Nó làm việc thật: đọc cơ sở dữ liệu, chạy logic, dựng câu trả lời.
Server theo thiết kế là 'kiên nhẫn': chờ mãi, trả lời hàng nghìn lần mỗi giây.
Bốn nhịp, nửa giây. Mọi thứ trong khóa học này đều là phóng to một trong những nhịp này.
Trình duyệt quyết định 'tôi cần địa chỉ đó' và dựng một request: URL, method (GET), headers — một tin nhắn text nhỏ.
Request đi qua mạng đến đúng máy, đúng cổng. (TLS mã hóa chuyến đi — chủ đề sau này.)
Server chuyển request sang ứng dụng của bạn. Ứng dụng làm việc: đọc dữ liệu, chạy logic, dựng trang hay JSON.
Response đi về. Trình duyệt phân tích HTML, áp dụng CSS, chạy JS, và vẽ. Xong — cho đến cú click tiếp theo.
Bộ quy tắc cho việc client hỏi và server trả lời: giải phẫu request và response, methods, status codes, headers, redirect.
Tất cả những gì client có thể nói, gói trong một tin nhắn. Không hơn, không kém.
Địa chỉ được gọi: host (server nào) + path (điều gì trên đó) + query tùy chọn (?sort=new). Là dòng 'gửi ai' của tin nhắn.
Động từ: GET, POST, PUT, PATCH, DELETE. Tuyên bố ý định để server biết phải làm gì trước khi đọc bất cứ thứ gì khác.
Các dòng metadata, mỗi dòng một dữ kiện: ai đang hỏi (User-Agent), body ở định dạng gì (Content-Type), bạn khai là ai (Authorization).
Nội dung thật đang gửi: form, JSON, file. Tùy chọn — GET không có; POST và PUT thường mang theo.
Đây chính là thứ di chuyển giữa client và server. Trái: request (method + URL + headers + body). Phải: response (status + headers + body).
GET /users/42?sort=new HTTP/1.1
Host: api.shop.com
Content-Type: application/json
{"name": "Ada"}
tin nhắn của client — method, URL, ai đang hỏi, và dữ liệu gửi kèm
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 46
{"id": 42, "name": "Ada", "posts": 3}
câu trả lời của server — chuyện gì xảy ra (200), định dạng gì, và dữ liệu
Mỗi URL là năm công việc trong một dòng. Trình duyệt và server tách nó ra theo cùng một cách mỗi lần.
https://api.shop.com:8443/users/42?sort=new&page=2
scheme https how to talk
host api.shop.com which machine answers
port :8443 which door (80 and 443 are the defaults)
path /users/42 which resource
query ?sort=new... options, as key=value pairs
Đa số thời gian cổng bị giấu — :80 cho http, :443 cho https được ngầm hiểu.
Ảnh phản chiếu của request — nhưng nó bắt đầu bằng một phán quyết: mã trạng thái.
Dòng đầu: HTTP/1.1 200 OK. Mã ba chữ số là tiêu đề — thành công, đổi hướng hay lỗi — trước cả một byte nội dung.
Metadata về nội dung: định dạng (Content-Type), kích thước (Content-Length), gợi ý cache. Mỗi dòng dạng 'Tên: giá trị', như headers của request.
Nội dung thật: trang HTML, JSON, ảnh — hoặc không có gì. Mọi thứ sau dòng trống ngăn cách với headers.
Method là lời cam kết về ý định. Bạn và server đều có thể dựa vào nó: GET không bao giờ thay đổi gì, POST tạo mới, DELETE xóa.
| Method | Nghĩa là | Ví dụ |
|---|---|---|
| GET | Đọc — không bao giờ thay đổi gì | GET /users/42 |
| POST | Tạo mới, hoặc gửi thứ gì đó | POST /users |
| PUT | Thay thế toàn bộ tài nguyên | PUT /users/42 |
| PATCH | Chỉnh một phần tài nguyên | PATCH /users/42 |
| DELETE | Xóa một tài nguyên | DELETE /users/42 |
Chọn theo ý định, không theo thói quen. Method nói cho cache, trình duyệt và app khác biết endpoint của bạn an toàn để đụng đến ra sao.
GET nghĩa là 'cho tôi xem', không bao giờ 'thay đổi gì'. Trình duyệt, cache và công cụ tìm kiếm dựa vào nó — nên GET phải an toàn để lặp lại.
POST để gửi: form, bản ghi mới, tải file lên. Có body, và lặp lại có thể tạo bản ghi trùng — điều đó được chấp nhận.
PUT thay thế toàn bộ tài nguyên; PATCH chỉnh một phần. Thực tế: đa số app chỉ dùng POST cho 'ghi' và giữ sự đơn giản.
DELETE xóa một tài nguyên. Response là mã trạng thái (200 hay 204), không phải trang — client kiểm tra mã, không phải pixel.
Mọi response bắt đầu bằng một số ba chữ số. Chữ số đầu kể trọn câu chuyện trong một cái nhìn.
| 2xx | Nghĩa là | Mã chính |
|---|---|---|
| 2xx | Thành công — xong việc | 200 OK · 201 Created |
| 3xx | Đổi hướng — sang nơi khác mà xem | 301 moved · 302 see other |
| 4xx | Lỗi phía client — lỗi của bạn (nói nhẹ nhàng) | 400 bad · 401 not authed · 403 denied · 404 not found |
| 5xx | Lỗi phía server — lỗi do phía máy chủ | 500 internal error |
Ghi nhớ khuôn mẫu, không phải danh sách: 2xx xong, 3xx đi nơi khác, 4xx lỗi của bạn, 5xx lỗi của chúng tôi.
| Code | Nghĩa là | Bạn làm gì |
|---|---|---|
| 200 OK | Mọi thứ ổn; đây là dữ liệu | Không có gì — xong việc |
| 201 Created | Một tài nguyên mới ra đời (POST của bạn thành công) | Giữ lại header Location — địa chỉ của thứ mới |
| 301 Moved Permanently | Địa chỉ này đã đổi vĩnh viễn | Trình duyệt tự cập nhật đường dẫn; bạn nên sửa link cũ |
| 302 Found | Sang URL kia mà xem — chỉ lần này | Trình duyệt tự theo; bạn hầu như không bao giờ thấy nó |
| 400 Bad Request | Request của bạn sai cấu trúc — tôi không parse nổi | Kiểm tra URL, body, headers của bạn |
| 401 Unauthorized | Bạn là ai? Tôi cần thông tin xác thực | Đăng nhập / thêm header Authorization |
| 403 Forbidden | Tôi biết bạn là ai — nhưng không | Vấn đề quyền; hỏi quản trị viên |
| 404 Not Found | Không có thứ gì ở địa chỉ đó | Kiểm tra URL — gõ sai, hoặc đã bị xóa |
| 500 Internal Error | Phía tôi sập — không phải lỗi của bạn | Xem log server; trang lỗi thường chỉ đúng dòng code |
Cặp hay nhầm nhất trong HTTP. 401 là vấn đề danh tính; 403 là vấn đề quyền.
Bạn chưa trình thông tin xác thực, hoặc thông tin sai. Server đang yêu cầu bạn tự nhận diện.
Cách sửa: đăng nhập, thêm token. Sau đó, cánh cửa có thể mở.
Thông tin xác thực đúng — nhưng tài khoản đó không có quyền với tài nguyên này.
Cách sửa: không ai code sai; đó là vấn đề quyền.
Có tổng cộng ~100 headers. Bốn cái này trả lời những câu hỏi xuất hiện nhiều nhất.
Server nào mà bạn đang gọi — bắt buộc, vì virtual host đặt nhiều site trên một IP.
Định dạng của body: text/html, application/json… Dấu hiệu đầu tiên của server về cách đọc body của bạn.
Danh thiếp: trình duyệt, app hay curl nào đang hỏi. Dùng cho phân tích và tương thích.
Thông tin xác thực của bạn, thường là Bearer token. Đây là cách API cho bạn vào mà không cần trang đăng nhập.
Redirect không phải lỗi — nó là tấm biển chỉ đường. Bạn không viết nó, bạn chỉ xem nó diễn ra.
GET /old-page. Server biết nó đã dời — nhưng không gửi trang, nó gửi một tấm biển chỉ đường.
301 + header Location: /new-page. Một response nhỏ xíu, không nội dung — chỉ là 'sang đó mà'.
Nó gọi lại URL mới, nhận 200, và hiển thị trang. Từ chỗ ngồi của bạn, nó chỉ như một cú click.
Bỏ qua phần quảng cáo: một web server là chương trình lặp lại bốn bước, hàng nghìn lần mỗi giây.
Nó chờ trên một cổng (80 cho HTTP, 443 cho HTTPS) các kết nối đến — như để điện thoại luôn sẵn sàng nghe gọi.
Nó giải mã các byte thành method, URL, headers, body — bốn phần đã nói ở trên.
Nó chuyển request sang ứng dụng của bạn (Flask trong khóa này), nơi quyết định làm gì — thường là: đọc dữ liệu, dựng trang.
Nó gói kết quả thành status + headers + body và gửi lại. Rồi quay về bước 1.
Một máy chạy nhiều dịch vụ; cổng chọn cánh cửa. Production trả lời ở 80/443; dev server của Flask thường mở 5000.
Web server nói bằng byte; bạn nghĩ bằng Python. Flask là lớp mỏng ở giữa — một route là lời hứa: 'khi URL + method này đến, chạy hàm này'.
Framework là bộ khung bạn không phải tự xây. Flask là bộ khung nhẹ nhất trong các framework Python web phổ biến.
Cố tình nhỏ: routing, parse request, response. Cơ sở dữ liệu, admin, xác thực — bạn gắn thêm chỉ những gì cần.
Bạn viết hàm Python thuần; Flask lo phần lộn xộn — byte, headers, mạng. Lớp mỏng, không phải nền tảng.
Mỗi @app.route đăng ký một lời hứa: 'khi method này chạm URL này, chạy hàm này'. Phần còn lại là thủ tục sổ sách.
flask run cho bạn một server chạy được để phát triển. Nó không đạt chuẩn production — đó là việc của nginx (chủ đề sau).
Đây là một ứng dụng web hoàn chỉnh, chạy được. Hai route: một cho người (HTML), một cho máy (JSON).
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/")
def home():
return "<h1>Hello, shipper!</h1>"
@app.route("/api/hello")
def api_hello():
return jsonify(name="shipper", status="alive")
Chạy flask run, mở http://127.0.0.1:5000 — đó là toàn bộ phía server của một ứng dụng web.
Đó là toàn bộ vòng lặp: Python trên máy bạn trả lời HTTP trên một cổng. Bạn test được trước khi người nào thấy nó.
Khi đã biết lời hứa, route chỉ là biểu mẫu đăng ký. Bốn chi tiết đáng biết.
@app.route("/about") đăng ký hàm bên dưới cho GET /about. Tên hàm có thể là gì cũng được — Flask không quan tâm.
Route mặc định là GET (và HEAD). Endpoint ghi phải khai: @app.route("/users", methods=["POST"]). Method sai → 405.
Trả về chuỗi → trang HTML. Trả về jsonify(...) → JSON. Trả về (body, 404) → bạn tự kiểm soát mã trạng thái.
/users/<int:uid> gắn 42 trong /users/42 với tham số uid — có kiểu, ngay trong địa chỉ.
Cùng app, ba kiểu trả về. Giá trị trả về quyết định client nhận gì — và ở mã trạng thái nào.
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/")
def page():
return "<h1>Hello, shipper!</h1>" # -> text/html
@app.route("/api/hello")
def api():
return jsonify(name="shipper") # -> application/json
@app.route("/missing")
def gone():
return "not here", 404 # -> you pick the status
Một quy tắc cần nhớ: hàm trả về gì, Flask tự gói thành status + headers + body.
Một app thường làm cả hai: route HTML cho người, route /api cho máy.
Trình duyệt phân tích, trang trí, vẽ nó. Con người là người tiêu dùng cuối; trang là sản phẩm.
Test bằng một cú click trình duyệt. Pixel đúng là chạy.
Máy đọc nó: app khác, điện thoại, framework frontend. Key và giá trị, không trang trí.
Test bằng curl -i. Mã 200 và JSON parse được là chạy.
Client gửi dữ liệu theo nhiều hình dạng; Flask mở mỗi hình dạng ở một chỗ riêng. Biết nhìn đúng chỗ, cú pháp tự theo sau.
from flask import request
@app.route("/users/<int:uid>") # 1. URL parameter
def show_user(uid):
return jsonify(uid=uid)
@app.route("/search")
def search():
q = request.args.get("q") # 2. query string: /search?q=shoes
name = request.form.get("name") # 3. form field (login, checkout...)
body = request.json # 4. JSON body (from an API client)
Một ứng dụng, bốn lối vào. <uid> đến từ URL; phần còn lại nằm trên đối tượng request.
Ghép kênh với việc — đó là toàn bộ quyết định thiết kế phía sau mọi API.
| Kênh | Đáp trong Flask | Ví dụ | Dùng cho |
|---|---|---|---|
| Tham số URL | <int:uid> in the route | /users/42 | Tài nguyên nào — một phần của địa chỉ |
| Query string | request.args | /search?q=shoes | Bộ lọc, tùy chọn, phân trang |
| Form body | request.form | <form> posts | Đăng nhập, thanh toán — form trình duyệt kinh điển |
| JSON body | request.json | curl -d '{...}' | API nói chuyện với API — mặc định thời nay |
Giá trị query đến dưới dạng chuỗi — kể cả '42'. Đặt giá trị mặc định, và tự chuyển kiểu.
@app.route("/search")
def search():
q = request.args.get("q", "") # ?q=shoes -> "shoes", else ""
page = request.args.get("page", 1, type=int) # ?page=2 -> 2 (an int)
if not q:
return "nothing to search", 400
return jsonify(q=q, page=page)
get(key, default, type=...) là giải pháp 90% cho query string.
Bốn trạm giữa 'byte đến' và 'byte đi'. Hiểu route giúp gỡ lỗi nhanh gấp 10 lần.
Web server (hay dev server của Flask) nhận byte HTTP thô và chuyển cho Flask.
Nó đi qua các lời hứa đã đăng ký: method + URL. Không khớp? 404 ngay lập tức — code của bạn không chạy.
Hàm view đọc input (args, form, json), làm việc — thường là: lấy dữ liệu — rồi trả về một giá trị.
Giá trị trả về thành status + headers + body, và đi về phía client. Vòng đi-về khép lại.
Dev server của Flask hiển thị traceback đầy đủ khi 500 — dòng cuối thường chỉ đúng lỗi. Sửa, lưu, curl lại.
Một app đúng chuẩn sẽ lỗi lịch sự: mã đúng nói cho client biết chính xác nên thử gì tiếp.
return 'not found', 404 — giá trị thứ hai đặt mã trạng thái. Đừng trả 200 cho mọi thứ.
400: bản thân request sai cấu trúc. 404: request đúng, nhưng thứ đó không tồn tại.
Client API rẽ nhánh theo mã trạng thái. 4xx và 5xx của bạn là chỉ dẫn, không phải trang trí.
API trả JSON thì trình duyệt không nhìn thấy gì. curl là client HTTP của terminal: thực thi một request và in ra response thô.
curl là người anh em thô mộc và trung thực của trình duyệt: nó cho thấy phần ống dẫn mà trình duyệt giấu.
Trình duyệt cho bạn xem kết quả đã vẽ và nuốt chửng mã trạng thái, headers, redirect. curl cho thấy mọi byte.
Mở endpoint JSON bằng trình duyệt là đường cụt. curl là môi trường sống tự nhiên của API.
Tự chọn method, headers, body, đúng URL. Những gì trình duyệt làm thay bạn khi click, bạn làm có chủ đích.
curl sống trong vòng lặp shell, Makefile và pipeline CI. Nếu một test gõ được, nó tự động hóa được.
Thành công, dữ liệu, dữ liệu lọc, thất bại. Đây là cảm giác test một API.
Mặc định của curl là: GET một URL và in body. Các cờ biến nó thành client API đầy đủ.
| Flag | Nó làm gì | Ví dụ |
|---|---|---|
| -X POST | Đổi method | curl -X POST http://…/users |
| -H 'K: V' | Thêm header | -H 'Content-Type: application/json' |
| -d data | Đính kèm body | -d '{"name":"Ada"}' |
| -i | Hiển thị status line + headers | curl -i http://…/ |
| -v | Verbose: toàn bộ câu chuyện kết nối | curl -v http://…/ |
| -o file | Lưu body vào file thay vì in màn hình | curl -o page.html http://…/ |
Method + header + body + URL. Một khuôn mẫu này đáp ứng phần lớn việc 'gửi dữ liệu vào API của bạn'.
# the classic "create a record" one-liner
curl -X POST \
-H 'Content-Type: application/json' \
-d '{"name": "Ada"}' \
http://127.0.0.1:5000/users
# ...and check what the server actually said
curl -i -X POST \
-H 'Content-Type: application/json' \
-d '{"name": "Ada"}' \
http://127.0.0.1:5000/users
Với -i bạn nên thấy HTTP/1.1 201 CREATED — bằng chứng bản ghi đã ra đời.
Dòng bắt đầu bằng > là của bạn; dòng bắt đầu bằng < là của server. Đây là toàn bộ giao thức, ngay trước mắt bạn.
Ba vai trò, một đường ống — và chủ đề tiếp theo (nginx) cắm vào đúng chỗ này.
Trình duyệt cho người, curl cho bạn. Chúng mở đầu mọi cuộc hội thoại.
Chủ đề sau. Xử lý HTTPS, serve file tĩnh, chuyển request thật đi tiếp.
Code của bạn: route, dữ liệu, quyết định. Dựng response và gửi ngược lại.
client nói chuyện với cánh cửa · cánh cửa nói chuyện với ứng dụng · ứng dụng nói chuyện với thế giới — tất cả bằng HTTP thuần
Mọi ứng dụng web bạn sẽ ship đều là một vòng lặp: lắng nghe, phân tích, chạy, trả lời. Flask viết câu trả lời; curl chứng minh chúng đến nơi.