Hessian 與 Session
作者:終南 <[email protected]>
1. ServiceContext
ServiceContext 代表爲 Hessian 客戶端提供服務的上下文環境,用來處理與客戶端請求有關的信息。在最簡單和常用的應用中,在服務器端可以通過 ServiceContext 來獲取代表客戶端的 ServletRequest (在 HTTP 環境中爲 HttpServletRequest),因此就可以知道客戶端的相關信息,如客戶端的 IP 地址、端口、Header和用戶名等,重要的是可以獲取到代表會話的 Session 對象。
代碼舉例:
package example;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import com.caucho.services.server.ServiceContext;
public class BasicService implements Basic {
public String hello(String name) {
System.out.println("======== hello ========/n");
HttpServletRequest req = (HttpServletRequest) ServiceContext
.getContextRequest();
req.getSession().setAttribute("port", 1000);
System.out.println("getRemoteAddr() = " + req.getRemoteAddr());
System.out.println("getRemotePort() = " + req.getRemotePort());
System.out.println("getRemoteUser() = " + req.getRemoteUser());
System.out
.println("getSession().getId() = " + req.getSession().getId());
for (Enumeration<String> e = req.getHeaderNames(); e.hasMoreElements();) {
String headerName = e.nextElement();
System.out.println(headerName + " = " + req.getHeader(headerName));
}
return "Hello, " + name;
}
}
在上面的示例中,通過 ServiceContext 的靜態方法 getContextRequest() 獲取代表客戶端請求的 HttpServletRequest 對象,然後就可以像普通 Web 編程那樣使用 Request 和 Session 對象了。
2. Hessian 中 Sessian 的問題
一般來說,RPC 或 Web Service 這類的應用都是無狀態的,但是有些應用則需要有狀態的遠程調用,比如先登錄認證,然後再執行其他操作。
Hessian 本身也沒有對有狀態的應用提供直接支持。查看 HessianProxy 的代碼可以發現,每次發送請求的時候,Hessain 都會調用 HessianProxyFactory 類的 openConnection 重新打開連接,並且在遠程調用完成後關閉連接。
重新打開連接
protected URLConnection sendRequest(String methodName, Object []args)
throws IOException
{
URLConnection conn = null;
conn = _factory.openConnection(_url);
boolean isValid = false;
try {
// Used chunked mode when available, i.e. JDK 1.5.
if (_factory.isChunkedPost() && conn instanceof HttpURLConnection) {
try {
HttpURLConnection httpConn = (HttpURLConnection) conn;
httpConn.setChunkedStreamingMode(8 * 1024);
} catch (Throwable e) {
}
}
addRequestHeaders(conn);
OutputStream os = null;
try {
os = conn.getOutputStream();
} catch (Exception e) {
throw new HessianRuntimeException(e);
}
if (log.isLoggable(Level.FINEST)) {
PrintWriter dbg = new PrintWriter(new LogWriter(log));
os = new HessianDebugOutputStream(os, dbg);
}
AbstractHessianOutput out = _factory.getHessianOutput(os);
out.call(methodName, args);
out.flush();
isValid = true;
return conn;
} finally {
if (! isValid && conn instanceof HttpURLConnection)
((HttpURLConnection) conn).disconnect();
}
}
關閉連接
public Object invoke(Object proxy, Method method, Object []args)
throws Throwable
{
...
conn = sendRequest(mangleName, args);
if (conn instanceof HttpURLConnection) {
httpConn = (HttpURLConnection) conn;
int code = 500;
try {
code = httpConn.getResponseCode();
} catch (Exception e) {
}
parseResponseHeaders(conn);
...
try {
if (httpConn != null)
httpConn.disconnect();
} catch (Exception e) {
log.log(Level.FINE, e.toString(), e);
}
}
}
在登錄認證的應用中,在調用登錄認證的方法後執行其他業務操作時,需要對用戶是否登錄進行驗證,由於 Hessian 每次調用都會重新打開和關閉連接,因此每次調用都是一個新的 Session,前面是否登錄的信息就不能獲取到。
3. 擴展 Hessian 的客戶端程序實現對 Sessian 的支持
HTTP 是一個無狀態的協議,其狀態的實現是通過 Cookie 來實現的,根據 HTTP 的這個特點,我們可以在 Hessian 客戶端首次調用後的每次調用中,將首次調用得到的 Cookie 添加到 HTTP Header 中,從而模擬出有狀態的 HTTP 連接。
通過查看 HessianProxy 類的代碼發現,其中有兩個沒有實現內容方法,可以獲取服務器端返回的頭信息 和設置發送給服務器端的 HTTP 請求頭信息:
/**
* Method that allows subclasses to parse response headers such as cookies.
* Default implementation is empty.
* @param conn
*/
protected void parseResponseHeaders(URLConnection conn) {
}
/**
* Method that allows subclasses to add request headers such as cookies.
* Default implementation is empty.
*/
protected void addRequestHeaders(URLConnection conn) {
}
在使用 Hessian 進行遠程調用時,addRequestHeaders 方法在 sendRequest 時調用,parseResponseHeaders 在成功發送請求後調用。因此我們可以擴展 HessianProxy 類,重新實現這兩個方法,在首次調用時獲取 Cookie 新,在隨後的調用中 將 Cookie 添加到請求頭信息中。
除了重新實現 HessianProxy 類外,還需要重新實現 HessianProxyFactory 類的 public Object create(Class api, String urlName, ClassLoader loader) 方法,以便讓 HessianProxyFactory 使用重新實現的 HessianProxy 類。
重新實現的代碼:
package example;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import com.caucho.hessian.client.HessianProxy;
import com.caucho.hessian.client.HessianProxyFactory;
import com.caucho.hessian.io.HessianRemoteObject;
public class MyHessianProxyFactory extends HessianProxyFactory {
@Override
public Object create(Class api, String urlName, ClassLoader loader)
throws MalformedURLException {
if (api == null)
throw new NullPointerException(
"api must not be null for HessianProxyFactory.create()");
InvocationHandler handler = null;
URL url = new URL(urlName);
handler = new MyHessianProxy(url, this);
return Proxy.newProxyInstance(loader, new Class[] { api,
HessianRemoteObject.class }, handler);
}
}
class MyHessianProxy extends HessianProxy {
/** Variable for saving cookie list */
private List<String> cookies = null;
public MyHessianProxy(URL url, HessianProxyFactory factory) {
super(url, factory);
}
/** Get cookie list from the headers of response */
@Override
protected void parseResponseHeaders(URLConnection conn) {
List<String> _cookies = conn.getHeaderFields().get("Set-Cookie");
if (_cookies != null)
cookies = _cookies;
}
/** Add cookie list to request headers*/
@Override
protected void addRequestHeaders(URLConnection conn) {
if (cookies != null) {
for (String cookieString : cookies) {
conn.setRequestProperty("Cookie", cookieString);
}
}
}
}
4. 應用舉例
接口:
package example;
import java.util.List;
public interface Basic {
public boolean login(String user, String password);
public int giveMeMoney();
public void increaseMyMoney() throws Exception;
}
實現接口的服務:
package example;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import com.caucho.services.server.ServiceContext;
public class BasicService implements Basic {
public boolean login(String user, String password) {
HttpServletRequest req = (HttpServletRequest) ServiceContext
.getContextRequest();
if (user.equals("user") && password.equals("password"))
{
req.getSession().setAttribute("user", user);
return true;
}
return false;
}
public int giveMeMoney()
{
HttpServletRequest req = (HttpServletRequest) ServiceContext
.getContextRequest();
if(req.getSession().getAttribute("user") != null)
return 1000;
return 0;
}
public void increaseMyMoney() throws Exception
{
HttpServletRequest req = (HttpServletRequest) ServiceContext
.getContextRequest();
if(req.getSession().getAttribute("user") != null)
{
System.out.println(req.getSession().getAttribute("user") + "'s money increased!!!");
}
else
throw new Exception("Invalid User");
}
}
客戶端程序:
package example;
import java.net.MalformedURLException;
import com.caucho.hessian.client.HessianProxyFactory;
public class BasicClient {
public static void main(String []args) throws MalformedURLException
{
String url = "http://127.0.0.1:8080/hproj/hello";
HessianProxyFactory factory = new MyHessianProxyFactory();
factory.setUser("tomcat");
factory.setPassword("tomcat");
Basic basic = (Basic) factory.create(Basic.class, url);
try {
basic.increaseMyMoney();
} catch (Exception e) {
System.out.println("ERROR: " + e.getMessage());
}
basic.login("user", "password");
System.out.println(basic.giveMeMoney());
try {
basic.increaseMyMoney();
} catch (Exception e) {
System.out.println("ERROR: " + e.getMessage());
}
}
}
在客戶端程序中,不在使用原來的 HessianProxyFactory, 而是使用重新實現的 MyHessianProxyFactory 類。