1. Hạn chế của giao thức HTTP không trạng thái
Khi phát triển ứng dụng web, HTTP là giao thức không lưu trữ trạng thái — nghĩa là mỗi yêu cầu từ trình duyệt đến máy chủ đều độc lập, không liên quan đến nhau. Máy chủ không thể tự nhận biết hai yêu cầu liên tiếp có đến từ cùng một người dùng hay không.
2. Vấn đề thực tế và giải pháp
Trong nhiều tình huống, ta cần máy chủ xác định được danh tính người dùng qua các yêu cầu khác nhau. Ví dụ: khi bạn thêm sản phẩm vào giỏ hàng trên trang thương mại điện tử, hệ thống phải đảm bảo rằng tất cả sản phẩm bạn chọn đều được lưu vào đúng giỏ hàng của bạn — không bị lẫn sang tài khoản người khác. Để làm được điều này, cần một cơ chế giúp máy chủ "ghi nhớ" thông tin phiên làm việc của từng khách hàng.
3. Quản lý phiên và trạng thái
Cơ chế này gọi là quản lý phiên (session management). Một phiên làm việc bao gồm chuỗi các yêu cầu và phản hồi giữa trình duyệt và máy chủ. Trạng thái phiên chính là dữ liệu được sinh ra trong quá trình đó, giúp máy chủ liên kết các yêu cầu thuộc cùng một phiên.
Để phân biệt các phiên, mỗi yêu cầu từ client cần mang theo một định danh duy nhất — thường là SessionID.
4. Hai phương pháp theo dõi phiên phổ biến
- Cookie: lưu trữ dữ liệu tại phía client (trình duyệt).
- Session: lưu trữ dữ liệu tại phía server.
5. Hoạt động của Cookie
Khi trình duyệt lần đầu gửi yêu cầu đến server, request không chứa Cookie. Server sẽ phản hồi kèm theo một đoạn dữ liệu nhỏ (Cookie) trong header. Trình duyệt lưu lại và đính kèm Cookie này vào mọi request sau đó đến cùng domain. Server nhờ đó có thể nhận diện client qua các lần truy cập.
6. Minh họa bằng mã Java Spring Boot
Dự án A (port 8083) — Gửi Cookie:
@RestController
@RequestMapping("/api")
public class CookieSender {
@GetMapping("/set")
public void setCookie(HttpServletResponse res) {
Cookie visitor = new Cookie("visitor", "Người Dùng A");
res.addCookie(visitor);
}
}
Dự án B (port 8084) — Đọc Cookie:
@RestController
@RequestMapping("/api")
public class CookieReader {
@GetMapping("/read")
public void readCookie(HttpServletRequest req) {
Cookie[] list = req.getCookies();
if (list != null) {
for (Cookie c : list) {
System.out.println("Tên: " + c.getName() + " | Giá trị: " + c.getValue());
}
} else {
System.out.println("Không tìm thấy Cookie.");
}
}
}
Khi truy cập /set trước, sau đó gọi /read, bạn sẽ thấy Cookie đã được truyền thành công.
7. Thời gian sống của Cookie
- Mặc định: Cookie tồn tại đến khi đóng trình duyệt (gọi là session cookie).
- Thiết lập thời gian sống:
cookie.setMaxAge(3600); // sống 1 giờ setMaxAge(0): xóa ngay lập tức.setMaxAge(-1): chỉ sống trong phiên hiện tại.
8. Chia sẻ Cookie giữa các ứng dụng trên cùng Tomcat
Mặc định, các ứng dụng khác nhau không chia sẻ Cookie. Để cho phép chia sẻ, sử dụng:
cookie.setPath("/"); // áp dụng cho toàn bộ domain
9. Chia sẻ Cookie giữa các server khác nhau
Chỉ khả thi nếu các ứng dụng có cùng tên miền cấp cao nhất. Ví dụ:
cookie.setDomain(".example.com");
Áp dụng cho shop.example.com và blog.example.com.
10. Đặc điểm của Cookie
- Lưu ở client → dễ bị can thiệp, kém an toàn hơn Session.
- Kích thước giới hạn: tối đa 4KB/cookie, tối đa 20 cookie/domain.
11. Ứng dụng thực tế: Ghi nhớ lần truy cập cuối
@RestController
@RequestMapping("/visit")
public class VisitTracker {
@GetMapping
public void trackVisit(HttpServletRequest req, HttpServletResponse res) throws IOException {
res.setContentType("text/html; charset=utf-8");
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String now = fmt.format(new Date());
Cookie[] cookies = req.getCookies();
boolean found = false;
if (cookies != null) {
for (Cookie c : cookies) {
if ("lastVisit".equals(c.getName())) {
res.getWriter().write("Chào mừng trở lại! Lần trước bạn ghé: " + c.getValue());
c.setValue(now);
c.setMaxAge(3600);
res.addCookie(c);
found = true;
break;
}
}
}
if (!found) {
res.getWriter().write("Xin chào! Đây là lần đầu bạn ghé thăm.");
Cookie newCookie = new Cookie("lastVisit", now);
newCookie.setMaxAge(3600);
res.addCookie(newCookie);
}
}
}
Truy cập endpoint này lần đầu sẽ hiển thị lời chào mới. Các lần sau sẽ hiển thị thời gian truy cập trước đó.