專案採用 Hessian 作為 Web Service 實作,二進制協議效能逼近 Socket。但業務需求需獲取遠端呼叫端 IP、主機名等資訊。官方文檔、論壇、郵件列表均無解,決定深入源碼尋找突破口。
`web.xml` 配置 `HessianServlet`(程式碼區塊 1),初始化參數指定服務介面與實作類。
`init(ServletConfig)` 讀取參數、載入類別、建立 `HessianSkeleton`(程式碼區塊 2 節錄)。關鍵行:
ServiceContext.begin(req, serviceId, objectId);
使用 `ThreadLocal` 保存當前請求的 `ServletRequest`、`serviceName`、`objectId` 等(程式碼區塊 3)。提供靜態方法:
這意味著**服務端代碼可隨時透過 `ServiceContext.getContextRequest()` 獲取完整請求物件**,包含客戶端 IP、Header 等所有資訊。
解析 Hessian 輸入流、讀取 Header、方法名、參數,反射呼叫服務方法,寫入回覆(程式碼區塊 4)。關鍵發現:
String ip = context.getContextRequest().getRemoteAddr();
此處已可取得客戶端 IP!但 Hessian 預設不將其傳遞給業務方法。
for (int i = 0; i < args.length; i++) {
if (i == args.length - 1)
values[i] = in.readObject(args[i], ip); // 多載方法
else
values[i] = in.readObject(args[i]);
}
客戶端呼叫 `serviceMethod(param1, param2, String clientIp)`,最後一個參數雖由客戶端傳送,但服務端反序列化時被攔截替換為真實 IP。**服務端業務代碼零修改**,自然獲得客戶端資訊。
Spring 的 `HessianServiceExporter` / `HessianProxyFactoryBean` 同樣委派 `HessianServlet`/`HessianSkeleton`,Hack 原理通用。後續將分享 Spring 整合細節與替代方案(如 Filter/Interceptor 注入 ThreadLocal、自定義序列化策略等)。
Hessian 設計簡潔,`ServiceContext` 線程上下文機制為 Hack 提供天然切入點。雖以「暴力」方式解決資訊傳遞,但揭示了框架內部運作細節。生產環境建議評估:是否可改用標準 Header 傳遞?是否接受維護 Fork 版本? 歡迎社群提出更優雅的解法。