Giao thức HTTP và máy chủ web tĩnh xử lý đồng thời

Giao thức HTTP là nền tảng cho việc truyền tải dữ liệu giữa trình duyệt và máy chủ web. Khi người dùng truy cập một trang, trình duyệt gửi yêu cầu HTTP đến máy chủ, nhận về nội dung HTML và các tài nguyên liên quan để hiển thị trang đầy đủ.

Phân tích giao thức HTTP qua công cụ phát triển

Sử dụng Chrome DevTools, mục Network cho phép theo dõi toàn bộ quá trình giao tiếp giữa client và server. Mỗi yêu cầu gồm:

  • Request Headers: Chứa phương thức (GET/POST), đường dẫn, host, và các thông tin bổ sung.
  • Response Headers: Trả về mã trạng thái (200, 404, 500...), kiểu nội dung (Content-Type), và có thể kèm theo phần thân (Body).

Cấu trúc cơ bản của HTTP

Mỗi yêu cầu hoặc phản hồi HTTP đều tuân theo định dạng văn bản đơn giản:

GET /path HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0

Phản hồi:

HTTP/1.1 200 OK
Content-Type: text/html

<html><body>Nội dung trang</body></html>

Phần thân (Body) bắt đầu sau hai ký tự xuống dòng \r\n\r\n. Nếu có nén dữ liệu (gzip), client cần giải nén trước khi xử lý.

Xây dựng máy chủ web tĩnh đơn giản

Dưới đây là phiên bản máy chủ trả về nội dung cố định:

import socket

def xu_ly_ket_noi(conn):
    yeu_cau = conn.recv(1024).decode('utf-8')
    headers = "HTTP/1.1 200 OK\r\n\r\n"
    noi_dung = "Xin chào từ máy chủ!"
    conn.send((headers + noi_dung).encode('utf-8'))
    conn.close()

def khoi_dong():
    sock = socket.socket()
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('', 7788))
    sock.listen(5)
    while True:
        client, addr = sock.accept()
        xu_ly_ket_noi(client)

if __name__ == '__main__':
    khoi_dong()

Máy chủ hỗ trợ nhiều tệp

Phiên bản nâng cao phân tích đường dẫn yêu cầu và trả về tệp tương ứng:

import socket
import re

THU_MUC_GOC = "./html"

def xu_ly_yeu_cau(conn):
    du_lieu = conn.recv(1024).decode('utf-8', errors='ignore')
    dong_dau = du_lieu.splitlines()[0]
    duong_dan = re.match(r"[^/]+(/[^ ]*)", dong_dau).group(1)
    
    if duong_dan == "/":
        duong_dan = THU_MUC_GOC + "/index.html"
    else:
        duong_dan = THU_MUC_GOC + duong_dan

    try:
        with open(duong_dan, 'rb') as f:
            body = f.read()
            header = "HTTP/1.1 200 OK\r\n\r\n"
    except FileNotFoundError:
        header = "HTTP/1.1 404 Not Found\r\n\r\n"
        body = b"File khong ton tai"

    conn.send(header.encode('utf-8'))
    conn.send(body)
    conn.close()

def chay_server():
    server = socket.socket()
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(('', 8080))
    server.listen(10)
    while True:
        client, _ = server.accept()
        xu_ly_yeu_cau(client)

if __name__ == '__main__':
    chay_server()

Xử lý đồng thời với đa tiến trình

Sử dụng multiprocessing để phục vụ nhiều client cùng lúc:

import socket
import re
import multiprocessing

class HttpServer:
    def __init__(self, dia_chi):
        self.sock = socket.socket()
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind(dia_chi)
        self.sock.listen(10)

    def chay(self):
        while True:
            client, _ = self.sock.accept()
            p = multiprocessing.Process(target=self.xu_ly, args=(client,))
            p.start()
            client.close()  # đóng ở cha, con vẫn dùng được

    def xu_ly(self, conn):
        du_lieu = conn.recv(1024).decode('utf-8')
        path = re.match(r"[^/]+(/[^ ]*)", du_lieu.splitlines()[0]).group(1)
        file_path = "./html" + ("/index.html" if path == "/" else path)

        try:
            with open(file_path, 'rb') as f:
                noi_dung = f.read()
                phan_dau = "HTTP/1.1 200 OK\r\n\r\n"
        except:
            phan_dau = "HTTP/1.1 404 Not Found\r\n\r\n"
            noi_dung = b"Khong tim thay trang"

        conn.send(phan_dau.encode('utf-8'))
        conn.send(noi_dung)
        conn.close()

if __name__ == '__main__':
    server = HttpServer(('', 8888))
    server.chay()

Xử lý đồng thời với đa luồng

Thay thế tiến trình bằng luồng để tiết kiệm tài nguyên:

import socket
import re
import threading

class HttpServer:
    def __init__(self, dia_chi):
        self.sock = socket.socket()
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind(dia_chi)
        self.sock.listen(10)

    def chay(self):
        while True:
            client, _ = self.sock.accept()
            t = threading.Thread(target=self.xu_ly, args=(client,))
            t.start()
            # Không đóng socket ở đây vì các luồng chia sẻ cùng kết nối

    def xu_ly(self, conn):
        du_lieu = conn.recv(1024).decode('utf-8')
        path = re.match(r"[^/]+(/[^ ]*)", du_lieu.splitlines()[0]).group(1)
        file_path = "./html" + ("/index.html" if path == "/" else path)

        try:
            with open(file_path, 'rb') as f:
                noi_dung = f.read()
                phan_dau = "HTTP/1.1 200 OK\r\n\r\n"
        except:
            phan_dau = "HTTP/1.1 404 Not Found\r\n\r\n"
            noi_dung = b"Khong tim thay trang"

        conn.send(phan_dau.encode('utf-8'))
        conn.send(noi_dung)
        conn.close()

if __name__ == '__main__':
    server = HttpServer(('', 8888))
    server.chay()

Thẻ: http socket multiprocessing threading web-server

Đăng vào ngày 19 tháng 5 lúc 05:36