Yêu cầu xử lý tài nguyên động từ máy chủ
Khi trình duyệt gửi yêu cầu đến một trang web động, toàn bộ quá trình bao gồm việc phân tích HTTP request, xác định loại tài nguyên cần truy cập (tĩnh hay động), sau đó chuyển tiếp đúng cách để xử lý. Để xây dựng một máy chủ web có khả năng xử lý cả nội dung tĩnh lẫn động, cần thiết lập một chuẩn giao tiếp giữa máy chủ và ứng dụng web.
Giải pháp WSGI – Cổng giao tiếp giữa Server và Ứng dụng
WSGI (Web Server Gateway Interface) là tiêu chuẩn trong Python nhằm tách biệt phần máy chủ web và phần framework ứng dụng. Nhờ WSGI, lập trình viên có thể chạy các framework như Flask hoặc Django trên nhiều máy chủ khác nhau như Gunicorn, uWSGI mà không cần thay đổi mã nguồn.
Một hàm tuân thủ WSGI phải nhận hai tham số:
- environ: Từ điển chứa tất cả thông tin về request HTTP.
- start_response: Hàm dùng để thiết lập trạng thái và header của phản hồi.
Ví dụ đơn giản về một ứng dụng WSGI:
def application(environ, start_response):
status = '200 OK'
headers = [('Content-Type', 'text/html; charset=utf-8')]
start_response(status, headers)
return [b'Hello from WSGI!']
Lưu ý rằng giá trị trả về nên ở dạng danh sách các bytes, phù hợp với đặc tả WSGI mới.
Cấu trúc dữ liệu môi trường WSGI
Khi một request được xử lý, WSGI truyền vào một đối tượng environ chứa đầy đủ thông tin như phương thức HTTP, đường dẫn, header, địa chỉ IP,... Ví dụ:
{
'REQUEST_METHOD': 'GET',
'PATH_INFO': '/index.py',
'HTTP_HOST': 'localhost:8080',
'REMOTE_ADDR': '127.0.0.1',
...
}
Dữ liệu này cho phép ứng dụng web hiểu rõ yêu cầu người dùng và đưa ra phản hồi phù hợp.
Xây dựng máy chủ Web hỗ trợ WSGI
Cấu trúc thư mục cơ bản
├── web_server.py
├── dynamic/
│ └── my_web.py
├── static/
│ └── css/, js/, images/
└── templates/
└── index.html, center.html
Triển khai máy chủ WSGI đơn giản
Máy chủ sử dụng socket và xử lý đa tiến trình để phục vụ đồng thời nhiều client:
import socket
import multiprocessing
import re
import sys
class MiniWebServer:
def __init__(self, port, static_dir, app):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind(('', port))
self.sock.listen(128)
self.static_root = static_dir
self.app = app # Ứng dụng WSGI
def serve_forever(self):
while True:
client_sock, addr = self.sock.accept()
proc = multiprocessing.Process(
target=self.handle_request,
args=(client_sock,)
)
proc.start()
client_sock.close()
def handle_request(self, sock):
try:
data = sock.recv(1024).decode('utf-8')
if not data:
sock.close()
return
except:
sock.close()
return
request_line = data.splitlines()[0]
method, path, _ = request_line.split()
if path.endswith('.py'):
self.serve_dynamic(sock, path)
else:
self.serve_static(sock, path)
def serve_static(self, sock, path):
if path == '/':
path = '/index.html'
try:
with open(self.static_root + path, 'rb') as f:
body = f.read()
header = "HTTP/1.1 200 OK\r\n"
header += f"Content-Length: {len(body)}\r\n"
header += "\r\n"
sock.send(header.encode() + body)
except FileNotFoundError:
response = "HTTP/1.1 404 Not Found\r\n"
response += "Content-Type: text/html\r\n"
response += "Content-Length: 13\r\n"
response += "\r\n"
response += "File not found"
sock.send(response.encode())
def serve_dynamic(self, sock, path):
environ = {
'PATH_INFO': path,
'REQUEST_METHOD': 'GET'
}
body_bytes = self.app(environ, self.start_response)
body_str = body_bytes.decode('utf-8') if isinstance(body_bytes, bytes) else str(body_bytes)
header = f"HTTP/1.1 {self.status}\r\n"
header += "Content-Type: text/html; charset=utf-8\r\n"
header += f"Content-Length: {len(body_str.encode('utf-8'))}\r\n"
for k, v in self.headers:
header += f"{k}: {v}\r\n"
header += "\r\n"
sock.send((header + body_str).encode('utf-8'))
def start_response(self, status, headers):
self.status = status
self.headers = headers
Framework mini xử lý logic ứng dụng
Tệp dynamic/my_web.py đóng vai trò như một framework nhỏ:
import os
import re
TEMPLATE_DIR = "./templates"
def load_template(filename):
"""Đọc file mẫu HTML"""
filepath = TEMPLATE_DIR + "/" + filename.replace(".py", ".html")
try:
with open(filepath, "r", encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
return "Template not found"
def render_template(content_placeholder):
"""Thay thế placeholder trong template bằng nội dung thực tế"""
template = load_template("/center.html")
return re.sub(r"\{\%content\%\}", content_placeholder, template)
def application(environ, start_response):
path = environ.get('PATH_INFO', '')
start_response('200 OK', [('Content-Type', 'text/html; charset=utf-8')])
if path == "/index.py":
return load_template(path)
elif path == "/center.py":
real_data = "Dữ liệu lấy từ cơ sở dữ liệu đang được cập nhật..."
return render_template(real_data)
else:
return f"Xin chào từ WSGI! Thời gian: {__import__('time').ctime()}"
Chạy ứng dụng
Bắt đầu máy chủ với lệnh:
python3 web_server.py 8080 my_web:application
Truy cập http://localhost:8080/index.py hoặc http://localhost:8080/center.py để xem kết quả.
Mở rộng và cải tiến
Giao diện WSGI cho phép mở rộng dễ dàng:
- Thêm hỗ trợ POST, cookie, session.
- Liên kết với cơ sở dữ liệu thực.
- Triển khai middleware xử lý logging, xác thực.
Hệ thống này minh họa rõ ràng cách tách biệt giữa lớp máy chủ (networking) và lớp ứng dụng (business logic), giúp phát triển linh hoạt và tái sử dụng cao.