Thiết kế Web Server và Framework Mini sử dụng WSGI trong Python

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.

Thẻ: wsgi Python Web Server Web Framework Socket Programming HTTP Server

Đăng vào ngày 19 tháng 6 lúc 17:40