Giới hạn kích thước header để khống chế bộ nhớ server

HTTP parser phải đệm toàn bộ một header trước khi emit ra application — vì name và value chỉ có nghĩa khi đủ một dòng kết thúc bằng \r\n. Nếu server cho phép header tuỳ ý lớn, một attacker (hoặc client hỏng) gửi header dài 1MB sẽ buộc parser cấp phát 1MB buffer cho mỗi connection. Ở quy mô cao, bộ nhớ phình theo số connection × kích thước header tối đa — đến mức không tồn tại máy nào chịu nổi. Đây là lý do mọi production HTTP server đều giới hạn header size, và lý do nên hiểu phép tính này khi thiết kế server hiệu năng cao.

Phép tính minh hoạ

Một back-of-envelope estimation từ tác giả http-parser:

Server xử lý 1.000.000 request/s, header tối đa cho phép 1MB → bộ nhớ in-flight có thể tới (1.000.000 × 1MB) ≈ 1TB.

Phép tính này dùng “kịch bản tệ nhất đồng thời” để cho thấy: cho phép header lớn ngầm chấp nhận bộ nhớ vô hạn. Production server không chấp nhận. Cách khống chế chuẩn là giới hạn buffer ở tầng parser — buffer size không vượt một ngưỡng cố định, vượt thì reject với 413/431.

Default của các production server

Default của các server phổ biến nằm trong khoảng 4-32KB, không gần 1MB:

Sự khác biệt giữa Nginx/Apache (KB) và Go/Node mặc định (MB) phản ánh phong cách thiết kế: Nginx/Apache xuất thân từ web hosting đa khách hàng nên paranoid về bảo mật và bộ nhớ; Go/Node mặc định cho hyper-developer setup nên thư thái hơn.

Heuristic chọn ngưỡng

Công thức ngược: cho memory budget mỗi worker M và số kết nối đồng thời cần phục vụ C, ngưỡng header tối đa là:

max_header_size ≤ M / (C × safety_factor)

Ví dụ một worker có 512MB ngân sách, phục vụ 10.000 connection đồng thời, safety_factor 4 (để chừa cho body, scope, task) → max_header_size ≤ 12.8KB. Khớp với default của Nginx/Apache.

Phép tính này gắn chặt với backpressure ở tầng C10K: khi parser đệm header vượt ngưỡng, server reject ngay thay vì cố hoàn thành — đây cũng là một dạng backpressure đẩy ngược về client.

Trải nghiệm cá nhân

Đây là một trong những bài học rút ra khi viết http-parser bằng C (Teko era) — parser dùng buffer cố định char token[4096] thay vì dynamic allocation, khớp với triết lý “biết trước max size để budget chính xác”. README repo cũng ghi phép tính 1TB như cảnh báo rõ ràng cho người đọc.

Cập nhật: 2026-05-29