HTTP 無狀態特性要求額外機制維持會話。Servlet 提供 Cookie (客戶端) 與 HttpSession (伺服器端) 兩大支柱。本文完整實作購物車範例,展示兩者協作細節、生命週期控制與安全考量。
特性:Key-Value 配對、附屬 Domain/Path、可設定 MaxAge (秒)、Secure (僅 HTTPS)、HttpOnly (防 XSS)。
// 建立 Cookie
Cookie cookie = new Cookie("userId", "12345");
cookie.setMaxAge(60 * 60 * 24 * 7); // 7 天
cookie.setPath("/app"); // 僅 /app 路徑可見
cookie.setSecure(true); // 僅 HTTPS 傳輸
cookie.setHttpOnly(true); // JavaScript 不可存取
resp.addCookie(cookie); // 寫入回應 Header
// 讀取 Cookie
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie c : cookies) {
if ("userId".equals(c.getName())) {
String userId = c.getValue();
}
}
}
@WebServlet("/show-cookies")
public class ShowCookiesServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
Cookie[] cookies = req.getCookies();
out.println("請求中的 Cookie
");
if (cookies == null || cookies.length == 0) {
out.println("無 Cookie
");
} else {
out.println("名稱 值 註解 MaxAge Domain Path Secure HttpOnly ");
for (Cookie c : cookies) {
out.printf("%s %s %s %d %s %s %b %b ",
c.getName(), c.getValue(), c.getComment(), c.getMaxAge(),
c.getDomain(), c.getPath(), c.getSecure(), c.isHttpOnly());
}
out.println("
");
}
}
}
核心差異:資料儲存在伺服器記憶體/資料庫/Redis,客戶端僅持有 JSESSIONID (Cookie 或 URL 重寫)。安全性高、容量大、支援任意物件 (需實作 Serializable 以支援叢集/持久化)。
HttpSession session = req.getSession(true); // 取得或建立 (預設)
HttpSession session = req.getSession(false); // 僅取得現有,無則回傳 null
// 設定閒置逾時 (秒),web.xml 全域設定:30
session.setMaxInactiveInterval(1800); // 30 分鐘
// 銷毀 Session (登出)
session.invalidate();
商品頁面:shop.html
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>百貨商場</title></head><body>
<h1 align="center">百貨商場</h1><hr>
<form action="shop-cart" method="POST">
<p><input type="checkbox" name="item" value="0"> 糖果 ($50)</p>
<p><input type="checkbox" name="item" value="1"> 收音機 ($1200)</p>
<p><input type="checkbox" name="item" value="2"> 練習簿 ($30)</p>
<hr>
<input type="submit" value="加入購物袋">
</form>
</body></html>
購物車 Servlet:ShopCartServlet.java
package com.example;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import java.io.*;
import java.util.*;
@WebServlet("/shop-cart")
public class ShopCartServlet extends HttpServlet {
private static final String[] ITEMS = {"糖果", "收音機", "練習簿"};
private static final int[] PRICES = {50, 1200, 30};
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
HttpSession session = req.getSession(true);
// 從 Session 取得購物車 (List 存商品索引)
@SuppressWarnings("unchecked")
List cart = (List) session.getAttribute("cart");
if (cart == null) {
cart = new ArrayList<>();
session.setAttribute("cart", cart);
}
// 處理新增商品
String[] selected = req.getParameterValues("item");
if (selected != null) {
for (String idxStr : selected) {
try {
int idx = Integer.parseInt(idxStr);
if (idx >= 0 && idx < ITEMS.length) {
cart.add(idx);
}
} catch (NumberFormatException ignored) {}
}
}
// 導向購物車檢視頁 (避免重複提交)
resp.sendRedirect(req.getContextPath() + "/view-cart");
}
}
檢視購物車:ViewCartServlet.java
@WebServlet("/view-cart")
public class ViewCartServlet extends HttpServlet {
private static final String[] ITEMS = {"糖果", "收音機", "練習簿"};
private static final int[] PRICES = {50, 1200, 30};
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
HttpSession session = req.getSession(false);
List cart = (session != null) ? (List) session.getAttribute("cart") : null;
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("購物袋 ");
out.println("你的購物袋
");
if (cart == null || cart.isEmpty()) {
out.println("購物袋是空的 繼續購物
");
} else {
out.println("商品 單價 ");
int total = 0;
for (int idx : cart) {
out.printf("%s $%,d ", ITEMS[idx], PRICES[idx]);
total += PRICES[idx];
}
out.printf("總計 $%,d ", total);
out.println("
");
out.println("");
}
out.println("");
}
}
清空購物車:ClearCartServlet.java
@WebServlet("/clear-cart")
public class ClearCartServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
HttpSession session = req.getSession(false);
if (session != null) {
session.removeAttribute("cart");
}
resp.sendRedirect(req.getContextPath() + "/view-cart");
}
}
| 維度 | Cookie | HttpSession |
|---|---|---|
| 儲存位置 | 客戶端瀏覽器 | 伺服器記憶體/外部存儲 |
| 容量限制 | 單 Cookie 4KB,每域 ~20-50 個 | 受限於伺服器記憶體/快取容量 |
| 安全性 | 可被竄改、攔截、XSS 盜取 | 資料不暴露客戶端,需防 Session Fixation/Hijacking |
| 生命週期控制 | MaxAge 精確控制 | 閒置逾時 + 手動 invalidate() |
| 跨瀏覽器/裝置 | 自動攜帶 | 需共享 Session 儲存 (Redis/DB) 實現叢集 |
| 適用場景 | 偏好設定、記住我、追蹤 ID、CSRF Token | 登入狀態、購物車、權限、多步驟表單、敏感資料 |
Secure (HTTPS)、HttpOnly、定期輪換 (登入後 session.invalidate() → req.getSession(true))。掌握 Cookie 與 Session 協作模式,是構建有狀態 Web 應用的基石。實務上多採「Session 存業務資料、Cookie 存識別 Token/偏好」的混合策略,兼顧安全、效能與使用者體驗。