Hiểu về HTTP Responses và Streams — từ cơ bản đến nâng cao

1. Mở đầu — câu chuyện nhỏ
Hãy tưởng tượng bạn vào quán cà phê và gọi một cốc cà phê. Barista bắt đầu chuẩn bị, vừa pha vừa mời bạn ngồi. Khi cốc cà phê đầu tiên sẵn sàng, họ đưa cho bạn một ngụm — bạn có thể thưởng thức ngay, không cần chờ khi toàn bộ đơn hàng (ví dụ: cà phê + bánh) xong. Trong thế giới web, Request là bạn đặt hàng; Response là barista trả hàng; và streaming là việc barista đưa từng ngụm (chunk) cho bạn khi nó sẵn sàng — giúp bạn bắt đầu xử lý ngay thay vì chờ toàn bộ.
Vì sao quan trọng?
- Giảm độ trễ (latency): người dùng thấy nội dung sớm hơn.
- Xử lý dữ liệu lớn / thời gian thực: logs, video, chat, kết quả xử lý dài.
- Tránh tốn bộ nhớ: không cần tạo toàn bộ payload trong RAM trước khi gửi.
(Chủ đề này được bàn luận chi tiết trong bài gốc trên Tiger's Place). (Tigerabrodi)
2. Khái niệm cơ bản
2.1. Response object — cái gì nằm trong một “Response”?
Response là đối tượng đại diện cho phản hồi HTTP mà server gửi về (status code, headers, body). Trong JavaScript (Fetch API), Response.body là ReadableStream — nghĩa là bạn có thể đọc dữ liệu theo từng đoạn (chunk) thay vì lấy toàn bộ cùng lúc. (MDN Web Docs)
Ví dụ thực tế (2):
- Dùng trình duyệt mở trang HTML: server trả
200 OK+ HTML (thường nhỏ) — toàn bộ tải xong thì trình duyệt render. - Gọi API trả kết quả JSON lớn (ví dụ export báo cáo 100MB): nếu stream thì client có thể hiển thị tiến độ từng phần, thay vì chờ 100MB hoàn thành.
2.2. Streaming / Chunked transfer — cơ chế gửi từng mẩu
Chunked transfer (HTTP/1.1) cho phép server gửi response theo “mảnh” (chunk) mà không cần biết Content-Length từ trước. Khi kết thúc, server gửi chunk có độ dài 0 để báo hoàn tất. Đây là một cơ chế cơ bản để streaming trên HTTP/1.1. (Wikipedia)
Ví dụ thực tế (2):
- Export CSV lớn: server sinh dòng CSV từng phần và
res.write()gửi dần cho client. - Live logs dashboard: server push log line mới khi có — client hiển thị ngay, không cần refresh.
2.3. Streams API (ở client/browser)
Trình duyệt hiện đại cung cấp Streams API: bạn có thể lấy response.body như một ReadableStream và đọc từng chunk, xử lý dần (ví dụ decode, parse JSON từng phần). Đây là lý do các giao diện “streaming” (như chat hiển thị từng token) có thể hiện thực được. (MDN Web Docs)
Ví dụ thực tế (2):
- Chat AI streaming (một câu trả lời được gửi từng đoạn) — UI hiện kết quả dần.
- Progressive image / video loading — hiển thị phần đang tải để người dùng thấy nội dung sớm.
3. Từ cơ bản đến nâng cao — từng bước giải thích (mỗi khái niệm có ít nhất 2 ví dụ)
3.1. Status codes & headers (cơ bản)
200 OK— thành công. (vd: trang chính, api trả dữ liệu).301/302— redirect (vd: đổi domain, redirect http->https).404 Not Found— đường dẫn không tồn tại.- Header như
Content-Type,Content-Length,Transfer-Encodingchỉ cách trình duyệt/khách hàng xử lý body.
Ví dụ thực tế (2):
- Khi đăng nhập thành công API trả
200+Set-Cookie. - Truy cập link cũ -> server trả
301để trình duyệt tự chuyển sang URL mới.
3.2. Thực hành: server stream (Node.js/Express)
Server gửi chunk từng giây — mô phỏng "barista đưa từng ngụm".
// server-stream.js (Node.js + Express)
// Chạy: node server-stream.js
const express = require('express');
const app = express();
app.get('/numbers', (req, res) => {
// Gửi header (Transfer-Encoding chunked tự bật khi không có Content-Length)
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
let i = 1;
const interval = setInterval(() => {
if (i > 10) {
clearInterval(interval);
res.end(); // kết thúc stream
return;
}
res.write(`Số: ${i}\n`); // gửi 1 chunk
i++;
}, 500); // gửi 1 chunk mỗi 500ms
});
app.listen(3000, () => console.log('Server chạy trên http://localhost:3000'));
Ghi chú: res.write() gửi từng chunk; res.end() đóng kết nối. Đây rất hữu ích khi kết quả cần thời gian sinh ra (ví dụ tải dữ liệu lớn).
Ví dụ thực tế (2):
- Stream số liệu telemetry (IoT) lên dashboard — server gửi từng bản tin.
- Tải file lớn từ server: server gửi chunk thay vì buffer toàn bộ file.
3.3. Client đọc stream (Fetch + Streams API)
Client dùng fetch() rồi đọc response.body bằng getReader():
// client-stream.js — chạy trong trình duyệt console
fetch('/numbers').then(async res => {
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log('Chunk nhận:', decoder.decode(value));
// ở đây có thể append vào DOM, update progress, v.v.
}
});
Ví dụ thực tế (2):
- Chat streaming hiển thị từng token khi server gửi.
- Hiển thị tiến trình export CSV: mỗi chunk là 1 phần CSV, append vào table.
3.4. Streaming JSON “an toàn” — newline-delimited JSON (NDJSON)
JSON chuẩn là 1 object lớn — khó stream từng phần. Thực tế thường dùng NDJSON (mỗi dòng một JSON) để stream dữ liệu dễ parse dần.
Ví dụ:
Server gửi:
{"id":1,"msg":"hello"}\n {"id":2,"msg":"world"}\n- Client đọc từng dòng,
JSON.parse()từng dòng.
Ví dụ thực tế (2):
- API trả danh sách sự kiện thời gian thực (events) theo NDJSON cho dashboard.
- Export dữ liệu dạng NDJSON để client xử lý từng record ngay khi nhận.
3.5. HTTP/2 streams & multiplexing (nâng cao)
HTTP/2 dùng streams bên trong một kết nối TCP để đa luồng (multiplexing): nhiều request/response có thể chạy song song trên cùng 1 connection, giúp giảm latencies và tránh head-of-line blocking của HTTP/1.1. (IETF HTTP Working Group)
Ví dụ thực tế (2):
- Tải một trang web: HTML, CSS, JS, hình ảnh có thể tải song song trên cùng 1 connection nhanh hơn.
- Server push (ít dùng nhưng tồn tại): server có thể
PUSH_PROMISEtrước CSS/JS dự đoán client cần.
3.6. Khi nào dùng SSE / WebSocket / HTTP streaming?
- SSE (Server-Sent Events): 1 chiều, dễ dùng cho updates (live notifications).
- WebSocket: 2 chiều, phù hợp chat real-time cần gửi/nhận.
- HTTP streaming (chunked/streams): tốt cho responses dài / progressive output (ví dụ trả từng phần HTML/JSON).
Ví dụ thực tế (2) cho mỗi cái:
- SSE: live score trận bóng (server -> client), stock ticker.
- WebSocket: chat room, game multiplayer (2 chiều).
- HTTP streaming: trả kết quả tính toán dần (long-running job), streaming logs.
4. Các mẹo & pitfalls (những lỗi thường gặp)
- Không clone response khi cần đọc body nhiều lần.
Responselà “one-shot”. Dùngres.clone()nếu cần hai cách xử lý (ví dụ.json()và.text()). (MDN Web Docs) - Không mix
Content-LengthvàTransfer-Encoding: chunked. Thường không được dùng cùng lúc. (Wikipedia) - Proxy / load balancer có thể làm buffering, phá tính streaming (ví dụ Nginx có buffer). Kiểm tra cấu hình nếu streaming không tới client ngay.
- Browser support: Streams API và streaming request/response có sự khác biệt giữa engine; kiểm tra compatibility khi triển khai. (MDN Web Docs, Chrome for Developers)
5. ASCII diagrams (nhìn cho dễ hình dung)
HTTP 1.1 chunked (đơn giản):
Client -> GET /stream
Server -> (200 OK headers, Transfer-Encoding: chunked)
Server -> [chunk1]\r\n
Server -> [chunk2]\r\n
Server -> 0\r\n // kết thúc
HTTP/2 multiplexing (ý tưởng):
TCP connection
├─ Stream 1: GET /page -> HTML
├─ Stream 3: GET /style.css -> CSS
└─ Stream 5: GET /image.jpg -> DATA
(All go in parallel within same connection)
6. Bài tập/Nhiệm vụ nhỏ (Light interaction)
Thử thách (dành cho bạn):
- Viết một server Node/Express (như ví dụ trên) stream số từ 1 tới 20, mỗi 200ms. Tạo web page fetch
/numbersvà hiển thị mỗi chunk lên màn hình ngay khi nhận. - Mở rộng: server trả NDJSON (một JSON trên mỗi dòng), client parse từng dòng và render thành list.
- Bonus: cấu hình Nginx (hoặc dùng
curl --no-buffer) để kiểm tra xem response có thật sự stream tới client hay bị buffering.
Nếu bạn muốn, gửi code bạn viết—tôi sẽ review và gợi ý tối ưu tiếp!
7. Kết luận — tóm tắt & lời khuyên thực tế
Responsechứastatus,headers,body. Ở nhiều môi trườngbodylà stream — có thể đọc dần. (MDN Web Docs)- Chunked transfer là cơ chế HTTP/1.1 để gửi dữ liệu từng phần, hữu ích khi không biết
Content-Lengthtrước. (Wikipedia) - Streams API cho phép client xử lý dữ liệu ngay khi đến — rất quan trọng cho UI thời gian thực (chat, logs, progressive load). (MDN Web Docs)
- HTTP/2 mang lại multiplexing/flow-control, cải thiện hiệu năng so với HTTP/1.1. (IETF HTTP Working Group)
Lời khuyên: bắt đầu bằng ví dụ nhỏ (Node/Express + fetch), quan sát network tab trong DevTools để thấy chunk/stream thật sự hoạt động, rồi thử NDJSON và SSE. Thực hành giúp hiểu khác biệt giữa “đợi xong toàn bộ” và “xử lý từng phần”.
8. Tài liệu tham khảo (chọn lọc)
- Bài gốc — Understanding HTTP Responses and Streams (Tiger's Place). (Tigerabrodi)
- MDN — Response (Fetch API) (mô tả
Response.bodylà ReadableStream). (MDN Web Docs) - MDN — Streams API & Using readable streams (hướng dẫn đọc stream ở client). (MDN Web Docs)
- Wikipedia — Chunked transfer encoding (HTTP/1.1 chunked details). (Wikipedia)
- RFC 7540 — HTTP/2 (streams, multiplexing). (IETF HTTP Working Group)





