首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> J2ME

J2ME 平台 Cookie 機制完整實現:從原理到 RMS 持久化

     編輯:J2ME

背景:為什麼 J2ME 需要手動實現 Cookie?

標準 Java SE java.net.CookieManager 在 J2ME 完全缺失。若需維持會話狀態(購物車、登錄態),只能採用 URL 重寫——繁瑣且不安全。在 MIDP 上手寫 Cookie 引擎,可復用既有 Web 後端邏輯,大幅降低協議適配成本。

Cookie 工作原理速覽

  1. 伺服器回應含 Set-Cookie: name=value; Path=/; Domain=.example.com; Expires=...
  2. 客戶端解析並存儲(按 Domain + Path 索引)
  3. 後續請求自動附加 Cookie: name=value(符合 Domain/Path/未過期)

三步走實現架構


+----------------+     +----------------+     +----------------+
| 1. 獲取 Cookie  | --> | 2. 解析/存儲    | --> | 3. 發送 Cookie  |
| HttpConnection |     | Cookie Bean    |     | HttpConnection |
| getHeaderField |     | + RMS 持久化   |     | setRequestProp |
+----------------+     +----------------+     +----------------+

步驟 1:獲取響應中的 Cookie


private static final String SET_COOKIE = "Set-Cookie";
private static final String SESSION_ID = "SessionID";  // 部分伺服器用此頭

public void extractCookies(HttpConnection conn, String url) {
    for (int i = 0; ; i++) {
        String key = conn.getHeaderFieldKey(i);
        if (key == null) break;
        if (SET_COOKIE.equalsIgnoreCase(key) || SESSION_ID.equalsIgnoreCase(key)) {
            String cookieStr = conn.getHeaderField(i);
            saveCookie(cookieStr, url);
        }
    }
}

步驟 2:解析與持久化

2.1 Cookie JavaBean


public class Cookie implements Serializable {
    private String name, value, path, domain;
    private long expire;  // 0 = session cookie

    // 序列化至 RMS
    public void serialize(DataOutputStream dos) throws IOException {
        dos.writeUTF(name);
        dos.writeUTF(value);
        dos.writeUTF(path);
        dos.writeUTF(domain);
        dos.writeLong(expire);
    }

    // 從 RMS 反序列化
    public static Cookie deserialize(DataInputStream dis) throws IOException {
        Cookie c = new Cookie();
        c.name = dis.readUTF();
        c.value = dis.readUTF();
        c.path = dis.readUTF();
        c.domain = dis.readUTF();
        c.expire = dis.readLong();
        return c;
    }

    // 解析 Set-Cookie 字串
    public static Cookie parse(String header, String requestUri) {
        Cookie c = new Cookie();
        StringTokenizer st = new StringTokenizer(header, ";");
        while (st.hasMoreTokens()) {
            String token = st.nextToken().trim();
            int eq = token.indexOf('=');
            if (eq == -1) continue;  // Secure, HttpOnly 等無值屬性
            String n = token.substring(0, eq).toLowerCase();
            String v = token.substring(eq + 1);
            if ("path".equals(n)) c.path = v;
            else if ("expires".equals(n)) c.expire = parseDate(v);  // 需實現 RFC 1123 日期解析
            else if ("domain".equals(n)) c.domain = v;
            else { c.name = n; c.value = v; }  // 第一組 name=value 為實際數據
        }
        if (c.path == null || c.path.isEmpty()) c.path = extractPath(requestUri);
        return c;
    }

    public boolean isExpired() { return expire > 0 && System.currentTimeMillis() > expire; }
    public boolean matches(String reqDomain, String reqPath) { /* 域尾匹配 + 路徑前綴匹配 */ }
}

2.2 RMS 存儲策略

  • 持久 Cookie(有 Expires):寫入 RMS RecordStore,Key = domain#path#name,Value = 序列化字節陣列
  • 會話 Cookie(無 Expires):僅保存在記憶體 Hashtable,MIDlet 銷毀自動消失
  • 空間管理:RMS 總量 < 10KB,定期清理過期記錄

步驟 3:請求發送前附加 Cookie


public void attachCookies(HttpConnection conn, String url) {
    String domain = extractDomain(url);
    String path = extractPath(url);
    StringBuilder cookieHeader = new StringBuilder();

    // 1. 記憶體會話 Cookie
    for (Cookie c : sessionCookies.values()) {
        if (c.matches(domain, path)) appendCookie(cookieHeader, c);
    }
    // 2. RMS 持久 Cookie
    RecordStore rs = RecordStore.openRecordStore("cookies", true);
    for (int id : rs.getRecordIds()) {
        Cookie c = Cookie.deserialize(new DataInputStream(
            new ByteArrayInputStream(rs.getRecord(id))));
        if (!c.isExpired() && c.matches(domain, path)) {
            appendCookie(cookieHeader, c);
        }
    }
    rs.closeRecordStore();

    if (cookieHeader.length() > 0) {
        conn.setRequestProperty("Cookie", cookieHeader.toString());
    }
}

private void appendCookie(StringBuilder sb, Cookie c) {
    if (sb.length() > 0) sb.append("; ");
    sb.append(c.name).append('=').append(c.value);
}

整合進 HTTP 框架

在《Java ME 核心技術與最佳實踐》隨書 httpme 框架中,上述邏輯被封裝為 CookieManager 单例,攔截器模式自動處理:


// 使用範例
HttpRequest req = new HttpRequest("POST", "https://api.example.com/order");
req.addParam("itemId", "123");
HttpResponse rsp = HttpClient.execute(req);  // 內部自動 extract/attach Cookie

測試驗證清單

場景預期行為
登錄介面回應 Set-Cookie: JSESSIONID=abc; Path=/後續請求自動帶上 Cookie: JSESSIONID=abc
跨子域請求 (api.example.com → img.example.com)Domain=.example.com 時自動共享
Cookie 過期自動不發送,RMS 記錄下次啟動清理
Secure 標記 + HTTPS僅 HTTPS 請求附加
HttpOnly 標記不影響網絡層發送(僅限制腳本訪問,J2ME 無腳本引擎)

結語

在 J2ME 上實現完整 Cookie 引擎約需 300~500 行代碼,卻能無縫對接現有 Web 後端會話體系。關鍵在於:嚴格遵循 RFC 2965 域/路徑匹配規則、分離會話與持久存儲、RMS 空間定期回收。將其封裝為 HTTP 客戶端攔截器,業務代碼零感知,專注核心邏輯開發。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved