Bản chất của khung web
Mọi ứng dụng Web về cơ bản đều dựa trên giao tiếp socket: máy chủ khởi tạo một socket để lắng nghe kết nối, còn trình duyệt người dùng đóng vai trò như một client socket gửi yêu cầu. Trong Python, các khung web có thể được chia thành hai loại chính:
- Tự viết lớp socket: Kiểm soát hoàn toàn việc xử lý kết nối và yêu cầu (ví dụ: Tornado).
- Dựa trên WSGI (Web Server Gateway Interface): Sử dụng chuẩn giao tiếp giữa máy chủ và ứng dụng để giảm sự phụ thuộc (ví dụ: Django, Flask).
Dưới đây là ví dụ minh họa cách một khung web hoạt động ở cấp độ thấp nhất bằng việc tự triển khai socket:
import socket
def xu_ly_yeu_cau(client):
du_lieu = client.recv(1024)
client.send(b"HTTP/1.1 200 OK\r\n\r\n")
client.send(b"Xin chao, ban da ket noi thanh cong!")
def khoi_tao_server():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 8000))
server.listen(5)
while True:
ket_noi, dia_chi = server.accept()
xu_ly_yeu_cau(ket_noi)
ket_noi.close()
if __name__ == '__main__':
khoi_tao_server()
Đoạn mã này cho thấy rõ ràng rằng toàn bộ nền tảng web đều bắt nguồn từ socket. Tuy nhiên, trong thực tế phát triển, việc tách biệt phần xử lý giao thức (máy chủ) và phần xử lý nghiệp vụ (ứng dụng) là rất quan trọng để tăng tính tái sử dụng và linh hoạt.
Giải pháp chuẩn hóa: WSGI
WSGI ra đời nhằm mục đích thống nhất cách giao tiếp giữa máy chủ và ứng dụng Python. Khi cả hai bên tuân theo chuẩn này, bất kỳ máy chủ WSGI nào cũng có thể chạy bất kỳ ứng dụng WSGI nào.
Ví dụ sử dụng wsgiref – thư viện chuẩn của Python hỗ trợ WSGI:
from wsgiref.simple_server import make_server
def ung_dung(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'<h1>Chao mung den voi WSGI!</h1>']
server = make_server('', 8000, ung_dung)
print("Server dang lang nghe tai cong 8000...")
server.serve_forever()
Trong đó:
environ: Từ điển chứa tất cả thông tin yêu cầu (method, path, header...).start_response: Hàm dùng để thiết lập trạng thái và header phản hồi.
Xây dựng khung web đơn giản
Dựa trên WSGI, ta có thể xây dựng một khung web nhỏ với định tuyến URL cơ bản:
from wsgiref.simple_server import make_server
# Xử lý view
def trang_chu():
return b'<h1>Trang chu</h1>'
def trang_dang_nhap():
return b'<form>Ten dang nhap: <input/></form>'
# Định nghĩa định tuyến
dinh_tuyen = [
('/', trang_chu),
('/dang-nhap/', trang_dang_nhap)
]
# Ứng dụng chính
def khoi_tao_ung_dung(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
duong_dan = environ.get('PATH_INFO')
for tuyen, ham_xu_ly in dinh_tuyen:
if duong_dan == tuyen:
return [ham_xu_ly()]
return [b'<h1>404 - Khong tim thay trang</h1>']
# Chạy server
if __name__ == '__main__':
server = make_server('', 8000, khoi_tao_ung_dung)
print("Khung web dang chay tren cong 8000...")
server.serve_forever()
Hỗ trợ mẫu động với Jinja2
Để trả về nội dung động, ta cần một công cụ render mẫu. Jinja2 là lựa chọn phổ biến nhờ cú pháp mạnh mẽ:
Ví dụ file mẫu trang_ca_nhan.html:
<!DOCTYPE html>
<html>
<head><title>Ca nhan</title></head>
<body>
<h1>Xin chao {{ ten }}</h1>
<p>Tuoi: {{ tuoi }}</p>
<ul>
{% for san_pham in danh_sach %}
<li>{{ san_pham }}</li>
{% endfor %}
</ul>
</body>
</html>
Render mẫu trong Python:
from jinja2 import Template
import time
def trang_ca_nhan():
noi_dung = open('trang_ca_nhan.html', 'r', encoding='utf-8').read()
mau = Template(noi_dung)
ket_qua = mau.render(
ten='Nguyen Van A',
tuoi=25,
danh_sach=['San pham 1', 'San pham 2'],
thoi_gian_hien_tai=time.strftime("%Y-%m-%d %H:%M:%S")
)
return ket_qua.encode('utf-8')
Mô hình kiến trúc: MVT và MVC
Các khung web hiện đại thường tổ chức mã theo mô hình phân tầng để dễ bảo trì:
- MVC:
- Model: Thao tác dữ liệu, CSDL.
- View: Giao diện người dùng (HTML).
- Controller: Điều phối logic, xử lý yêu cầu.
- MVT (Django-style):
- Model: Quản lý dữ liệu.
- View: Xử lý logic nghiệp vụ (tương đương Controller).
- Template: Mẫu giao diện (tương đương View).
Sự khác biệt nằm ở cách đặt tên, nhưng tư tưởng cốt lõi vẫn là tách biệt trách nhiệm nhằm tăng khả năng kiểm thử và mở rộng hệ thống.