Chuyển đến nội dung chính (Skip to content)
Mục lục bài viết
Web Development Essentials

Web Development Essentials 031.3: Khái niệm cơ bản về HTTP

Web Development Essentials 031.3: Khái niệm cơ bản về HTTP

Đọc URL, phương thức, header và trạng thái HTTP; phân biệt cookie, phiên, HTTPS và HTTP/3; thực hành ETag với chuỗi 200, 304, 200 trên tệp DEMO cục bộ.

Trang cũ, yêu cầu mới và một phản hồi không có nội dung

Một hướng dẫn Help Desk đã sửa nhưng máy người đọc vẫn hiện bản cũ. Trong công cụ mạng, kỹ thuật viên thấy mã 304 rồi tưởng server không trả được trang. Thực ra cần phân biệt bản đang lưu, yêu cầu kiểm tra lại và nội dung server hiện có. HTTP cung cấp thông tin để làm việc đó, nếu ta đọc đúng phương thức, header và trạng thái.

Bài thuộc 031.3, khung PDF 030-100-vi, trang in 32–49, tương ứng trang vật lý 40–57. Sau bài, bạn nên giải thích URL, GET/POST, status, kiểu nội dung, cookie và cache, rồi kiểm chứng một tài nguyên tĩnh trong lab. Kiến thức tiên quyết là vai trò client/server ở bài 031.2.

Nội dung được biên soạn độc lập và đối chiếu nguồn ngày 23/10/2026. Lab dùng Node.js 24.22.0 trong container Alpine, không công bố server ra mạng ngoài và không sử dụng dữ liệu thật. HTTP ở loopback phục vụ mục đích quan sát; ví dụ không thay cho cấu hình HTTPS sản xuất.

HTTP tổ chức request và response

Theo tổng quan HTTP của MDN, client gửi request và nhận response. Phương thức mô tả thao tác, header cung cấp ngữ cảnh cùng metadata, còn body chứa dữ liệu khi có. Không phải mọi phản hồi đều mang nội dung tài nguyên; 304 trong tình huống đầu là một ví dụ.

Đây là hình thức minh họa HTTP/1.1, không phải bản ghi mạng của website thật:

GET /guide.html HTTP/1.1
Host: help.example.test
Accept: text/html

Host giúp xác định đích trong trường hợp nhiều website dùng chung hạ tầng. Với các phiên bản HTTP khác, cách biểu diễn trên dây có thể thay đổi. Hãy học ý nghĩa thông điệp trước, không suy rằng mọi lưu lượng HTTP đều là những dòng văn bản giống ví dụ.

Đọc URL trước khi gọi

Với URL https://help.example.test:8443/guide.html?topic=printer#steps, scheme là https, host là help.example.test, cổng chỉ định là 8443, đường dẫn là /guide.html, query chứa topic=printer, còn fragment là steps. Fragment phục vụ phía client và không được gửi như một phần request target HTTP thông thường.

Đường dẫn URL không tự là đường dẫn ổ đĩa. Query có thể điều khiển xử lý nhưng không phải chỗ thích hợp để đặt bí mật. Một URL có thể xuất hiện trong lịch sử, log hoặc công cụ hỗ trợ; trước khi gửi bằng chứng lỗi, loại bỏ dữ liệu nhạy cảm theo quy trình của tổ chức.

Với cổng mặc định, HTTP thường dùng 80 và HTTPS dùng 443. Tuy nhiên, cần xem URL thực tế và giao thức đã thương lượng, không chẩn đoán chỉ từ số cổng. DNS, kết nối, TLS và xử lý ứng dụng là các bước có thể phát sinh lỗi khác nhau.

GET và POST được chọn theo ý nghĩa

RFC 9110 quy định ý nghĩa chung của các phương thức. GET yêu cầu biểu diễn tài nguyên và được thiết kế với ngữ nghĩa an toàn; không dùng một liên kết GET để thực hiện thay đổi như xóa yêu cầu. “An toàn” ở đây là thuật ngữ về thao tác được yêu cầu, không có nghĩa URL đáng tin hoặc trang không có mã độc.

POST gửi dữ liệu để tài nguyên đích xử lý. Body có thể dùng định dạng biểu mẫu hoặc JSON theo thỏa thuận. Không chọn POST chỉ vì số trường nhiều, cũng không cho rằng POST tự mã hóa dữ liệu. HTTPS và chính sách xử lý dữ liệu vẫn cần được xem riêng.

Content-Type mô tả định dạng body; Accept thể hiện những định dạng client có thể chấp nhận. Kích thước thông điệp và framing phụ thuộc phiên bản cùng cơ chế truyền, không phải mọi request đều bắt buộc ứng dụng tự đặt Content-Length. Thư viện HTTP nên đảm nhiệm phần này thay vì ghép thông điệp thủ công.

Đọc status để chọn bước kiểm tra

Tài liệu mã trạng thái của MDN là nguồn đối chiếu. Các mã sau cần nhận diện trong mục tiêu:

Mã Ý nghĩa ở mức nhập môn
200 Yêu cầu được xử lý thành công theo ngữ nghĩa tương ứng
301 Tài nguyên được chuyển sang URL lâu dài khác
302 Chuyển hướng tạm thời tới URL khác
401 Thiếu thông tin xác thực hợp lệ cho tài nguyên
403 Server từ chối thực hiện yêu cầu
404 Không có biểu diễn tìm thấy hoặc server không muốn tiết lộ
500 Server gặp điều kiện không mong đợi khi xử lý

Một status không phải chẩn đoán nguyên nhân đầy đủ. 403 có thể liên quan policy hoặc quyền; 404 có thể là route sai, tài nguyên không còn hoặc cách che thông tin. Nhận 5xx cũng không loại trừ sự cố hạ tầng phía sau proxy. Cần biết thành phần nào tạo phản hồi và thời điểm xảy ra.

Ghi status, URL đã lọc dữ liệu nhạy cảm và thông báo liên quan trước khi sửa. Không thử lặp một thao tác POST có tác động thật chỉ để “xem hết lỗi chưa”, vì yêu cầu có thể đã được xử lý dù phản hồi bị mất.

Đọc yêu cầu, phản hồi và lớp cache để chọn bước kiểm tra ít tác động.
Hình 1. Đọc yêu cầu, phản hồi và lớp cache để chọn bước kiểm tra ít tác động. Mở hình để xem chi tiết.

Tài nguyên tĩnh, nội dung động và document root

Tệp HTML, CSS hoặc ảnh có thể được phục vụ như tài nguyên tĩnh. Nội dung động được tạo theo dữ liệu và logic khi xử lý; nó không nhất thiết phải có chuyển động trên màn hình. Cả hai đều đi qua HTTP và có thể có chính sách cache phù hợp.

Apache mô tả ánh xạ URL qua DocumentRoot. NGINX có chỉ thị root theo cấu hình của nó. Không lấy một đường dẫn ví dụ trong sách làm vị trí chắc chắn trên máy đang hỗ trợ. Proxy, alias và routing ứng dụng có thể làm ánh xạ khác đi.

Khi đưa tệp lên máy chủ được phép, xác định virtual host và document root, giữ bản trước, chuyển đúng tệp rồi kiểm tra qua URL. Không đưa .env, private key hoặc bản backup vào thư mục được phục vụ công khai. Tệp đã có trên ổ đĩa vẫn có thể lỗi vì quyền đọc hoặc cấu hình; đừng chữa bằng quyền ghi rộng cho mọi người.

Cache: lưu được khác dùng lại được

Cache riêng thường phục vụ một người dùng; cache chia sẻ có thể phục vụ nhiều client. Chính sách cần phù hợp với dữ liệu, đặc biệt nội dung cá nhân. RFC 9111 phân biệt các chỉ thị: no-store yêu cầu không lưu, còn no-cache không có đối số yêu cầu xác nhận lại trước khi dùng để đáp ứng request khác.

Hướng dẫn cache của MDN giải thích validator. Server gửi ETag; client có bản lưu có thể gửi lại nó trong If-None-Match. Nếu biểu diễn vẫn phù hợp, server trả 304 để client dùng bản đã có. Nếu thay đổi, server có thể trả 200 cùng nội dung mới. Đây không phải quy trình bắt buộc luôn gọi HEAD trước.

Không kết luận mọi phản hồi POST đều không thể cache hoặc chỉ vài mã được lưu trong mọi hoàn cảnh. Quy tắc còn phụ thuộc phương thức, chỉ thị và điều kiện của phản hồi. Khi gặp trang cũ, xem header và lớp cache liên quan trước khi xóa toàn bộ dữ liệu trình duyệt, vì thao tác đó có thể làm mất phiên và thiết lập cần giữ.

Cookie HTTP có thể được đặt bằng Set-Cookie và gửi lại khi phù hợp phạm vi cùng chính sách. Một cookie phiên có thể tham chiếu trạng thái ở server; cookie không đồng nghĩa toàn bộ phiên hoặc danh tính người thật. Cùng địa chỉ IP cũng không đủ để ghép mọi yêu cầu thành một người.

Bảo vệ phiên cần TLS, thuộc tính cookie phù hợp, giới hạn thời gian và xử lý thu hồi theo thiết kế. HttpOnly hạn chế JavaScript đọc cookie, không ngăn mọi request sử dụng nó. Không sao chép cookie tài khoản thật vào công cụ lab. Nếu bí mật phiên bị lộ, đổi mật khẩu đơn thuần chưa bảo đảm mọi phiên đã bị thu hồi.

HTTPS bảo vệ HTTP trong kết nối TLS theo ranh giới triển khai. Đường dẫn và header được bảo vệ trên đoạn kết nối đó; điểm chấm dứt TLS vẫn thấy dữ liệu. Metadata mạng và cách phân giải tên có thể để lộ thông tin khác. Không suy từ ổ khóa rằng nội dung website đáng tin hoặc mọi người trung gian hoàn toàn không thấy gì.

HTTP/3, WebSocket và chi phí kết nối

RFC 9114 mô tả HTTP/3 trên QUIC, khác giả định mọi HTTP đều chạy trên TCP. HTTP/1.1 và HTTP/2 vẫn cần được nhận diện khi đọc công cụ mạng. Lab dưới đây chỉ kiểm tra HTTP/1.1 cục bộ, chưa thử QUIC hoặc thương lượng phiên bản.

WebSocket hỗ trợ giao tiếp hai chiều qua một kết nối. Không tự xem nó là lựa chọn tốt nhất cho mọi âm thanh hoặc video. Băng thông liên quan lượng dữ liệu có thể truyền; độ trễ liên quan thời gian chờ. Nhiều request tuần tự có thể chậm dù tệp nhỏ và đường truyền có băng thông lớn.

ETag phù hợp nhận 304; khi tệp thay đổi, server trả 200 cùng byte và ETag mới.
Hình 2. ETag phù hợp nhận 304; khi tệp thay đổi, server trả 200 cùng byte và ETag mới. Mở hình để xem chi tiết.

Lab: chuyển tệp và kiểm chứng 200 → 304 → 200

Tạo cache-demo.mjs trong thư mục riêng. Mã tạo một cây tạm, sao chép tệp DEMO vào document root mô phỏng, phục vụ đúng một URL rồi kiểm tra validator. Đây là chuyển tệp nội bộ, không phải upload lên Apache hoặc NGINX thật:

import http from 'node:http';
import assert from 'node:assert/strict';
import { mkdtemp, writeFile, copyFile, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { createHash } from 'node:crypto';
const dir = await mkdtemp(join(tmpdir(), 'lpi-cache-'));
const source = join(dir, 'source.txt');
const target = join(dir, 'guide.txt');
await writeFile(source, 'DEMO version 1\n');
await copyFile(source, target);
const server = http.createServer(async (req, res) => {
  if (req.method !== 'GET' || req.url !== '/guide.txt') {
    res.writeHead(404); return res.end();
  }
  const body = await readFile(target);
  const tag = '"' + createHash('sha256').update(body).digest('hex') + '"';
  const headers = {'ETag':tag, 'Cache-Control':'no-cache'};
  if (req.headers['if-none-match'] === tag) {
    res.writeHead(304, headers); return res.end();
  }
  res.writeHead(200, {...headers, 'Content-Type':'text/plain; charset=utf-8'});
  res.end(body);
});
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
const url = `http://127.0.0.1:${server.address().port}/guide.txt`;
try {
  const first = await fetch(url);
  assert.equal(first.status, 200);
  assert.equal(await first.text(), 'DEMO version 1\n');
  const tag = first.headers.get('etag');
  const unchanged = await fetch(url, {headers:{'If-None-Match':tag}});
  assert.equal(unchanged.status, 304);
  assert.equal(await unchanged.text(), '');
  await writeFile(source, 'DEMO version 2\n');
  await copyFile(source, target);
  const changed = await fetch(url, {headers:{'If-None-Match':tag}});
  assert.equal(changed.status, 200);
  assert.notEqual(changed.headers.get('etag'), tag);
  assert.equal(await changed.text(), 'DEMO version 2\n');
  console.log('PASS: 200 -> 304 -> 200; new bytes and ETag');
} finally {
  await new Promise(resolve => server.close(resolve));
  await rm(dir, {recursive:true});
}

Chạy node cache-demo.mjs, mong đợi dòng PASS. Client gửi validator bằng tay; phép thử không chứng minh cache tự động của trình duyệt. Fixture chỉ so một ETag đúng định dạng do chính nó tạo, chưa triển khai đầy đủ parser và mọi điều kiện HTTP.

Kiểm chứng trên Node.js 24.22.0, user node, container tắt mạng ngoài, filesystem gốc chỉ đọc và /tmp riêng đã đạt chuỗi trên cùng nội dung byte mới. Lab chưa thử TLS, cookie, virtual host, POST hay CDN. Khi hoàn tất, server đóng và cây tạm được dọn.

Tự kiểm tra và chuyển cấp

Giải thích vì sao 304 không mang lại bản HTML mới, vì sao no-cache vẫn có thể có bản lưu và tại sao POST không tự bảo vệ bí mật. Với một tình huống lỗi, ghi điều đã thấy cùng phần chưa biết; không gửi cookie thật trong ảnh chụp hoặc log chuyển cấp. Bạn đạt bài khi dự đoán được ba phản hồi trong lab và liên hệ chúng với bản tệp đang được phục vụ.

Bài liên quan trong bộ LPI

Tìm hiểu thêm