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()