您的位置:首页 > 理论基础 > 计算机网络

HTTP代理如何正确处理Cookie(2) - 转载(写的比较详细,并以实例讲解)

2011-04-15 16:01 531 查看
HTTP代理如何正确处理Cookie(2) - 转载(写的比较详细,并以实例讲解)
http://hi.baidu.com/bdui/blog/item/de891ad90cb4b12f11df9ba1.html
2007-12-09 19:38
解决问题的两种思路

Cookie的存在是要解决HTTP协议本身先天的缺陷-无状态性,它为用户保存了一些需要的状态信息。因此我们解决此问题的最本质的出发点,也就是找到一种途径能为用户保存Cookie所提供用户状态信息,实际上就是Name/Value对。

思路一

第一种思路就是修改目标服务器取得的Cookie,使之符合MTS站点的属性,然后作为MTS站点的Cookie存储到用户的浏览器中去。当然,这 种修改必须保留原始Cookie的所有属性值,当以后访问同一个目标服务器的时候,MTS能根据保存的属性值还原出原始Cookie,然后进行提交。

具体到属性值的保存位置,没有太多选择的余地,实际上,domain,path,secure,expires这几个属性都无法利用,只有利用 name=value这一属性对。我们的做法是创造一个新的Cookie,把原始Cookie的domain,path的值与name值进行编码,用分隔 符附加在Name值的后面,符值给新的Cookie。这样做也同时避免了不同目标服务器如果出现同名的Cookie,将会互相覆盖的情况(Cookie规 范里面也规定了,客户端以domain,path,name作为Cookie的唯一标示)。而原始Cookie的secure和expires值,直接符 给新的Cookie,新Cookie的domain和path设成缺省值,这样,新Cookie就可以被浏览器正常接受。由于浏览器接受的所有 Cookie的domain和path值都一样,因此每次用户对MTS提出请求时,浏览器都会把所有与MTS站点相关的Cookie上传,因此,MTS还 需要还原原始的Cookie,过滤掉与目标服务器不相干的Cookie,然后上传有用的Cookie。

这种思路的优点在于Cookie存储在客户端,可以做到长期存储,浏览器自己根据Cookie的expires值做出判断,省掉很多开发的麻烦。缺 点是转换的过程相对较复杂。另外还有一个缺点,也是由于Cookie规范的限制所造成的。Cookie规范对于一个浏览器同时能够存储的Cookie数量 作出了规定。

总共300 个cookie

每个Cookie 4 K 的存储容量

每一个domain 或者 server 20 个cookie。

以上是浏览器所应达到的最小存储数量,超出这个限制,浏览器应该自动按照最少最近被使用的原则删除超出得Cookie。由于用户有可能通过MTS这 一个网站翻译大量的目标服务器,因此浏览器存储在MTS的domain下的cookie数量就很有可能超过20个,这时候就会导致某些Cookie被删 除。一般这也不会造成太大问题,因为规范是要求浏览器删除最少最近被使用的Cookie,但我们在实际测试当中发现有些浏览器并不遵守这样的规范,而是删 除最新的Cookie,这就将导致用户很大的不便。

思路二

第二种思路在于把原始的Cookie组织成dataBean,存储到用户的Session当中去。这样,在用户端只需要存储一个SessionID 的Cookie,而不需要存储所有目标服务器的每一个Cookie。另外,当接收到用户的又一次翻译请求时,再从Session当中取出所有的 dataBean,逐一进行分析,找出与用户所请求的目标服务器相符的原始Cookie,进行提交。

这种思路可以克服上一种思路中Cookie超过标准数量时的缺陷,而且不需编码保存原始的Cookie属性值,减少了程序的复杂度。缺点是需要程序 员自己处理expires。而且由于是把Cookie存储在Session中,一旦Session失效,所有Cookie都将被删除,所以,无法保存那些 长期的Cookie。

总之,两种思路各有利弊,在实际应用当中要权衡考虑。下面我们针对两种思路进行技术实现,分别对应方案一和方案二。

由于MTS需要与目标服务器连接,遵循HTTP协议读取和返回Cookie,但是如果用JDK中的java.net.URLConnection处理Cookie将非常不方便,因此我们使用HTTPClient来处理与目标服务器的连接。



方案一:Cookie存储在浏览器端

用户每发起一次新的请求,浏览器在检查完本地存储Cookie的有效性后,会把所有由MTS产生的有效Cookie附加在请求头里送到MTS。 MTS接受到客户端的翻译请求后,从Request中提取出所有的Cookie,还原后根据目标服务器的domain和path进行过滤。产生所有与目标 服务器相关的Cookie。

//从request中获取所有的Cookie
javax.servlet.http.Cookie[] theCookies = request.getCookies();
ArrayList cookiesList = new ArrayList();
String url = request.getParameter("url");
String domain = URLUtil.getURLHost(url);
String path = URLUtil.getPath(url);
if (theCookies != null)
{
 for (int i = 0; i < theCookies.length; i++)
 {
  RE r = new RE();
         //用正则表达式把name项还原成domain,path,name
  REDebugCompiler compiler = new REDebugCompiler();
  r.setProgram(compiler.compile("//|//|"));
  String[] values = r.split(theCookies[i].getName());
  //"9.181.116.183||/MTModule||testCookie:value1" or " ||              
  ||testCookie:value1"
  if (values.length == 3)
  {
   if (values[0].trim().startsWith("."))
   {
    if (!domain.endsWith(values[0].trim()))
     continue;
   } else if (!domain.endsWith("://" + values[0].trim()))
     continue;
   if (!path.startsWith(values[1].trim()))
    continue;
   Cookie tempCookie = new Cookie();
   tempCookie.setDomain(
    ("".equals(values[0].trim())) ? null : values[0]);
   tempCookie.setPath(
    ("".equals(values[1].trim())) ? null : values[1]);
   tempCookie.setName(
    ("".equals(values[2].trim())) ? null : values[2]);
   tempCookie.setSecure(theCookies[i].getSecure());
   tempCookie.setValue(theCookies[i].getValue());
   tempCookie.setVersion(theCookies[i].getVersion());
   tempCookie.setComment(theCookies[i].getComment());
   cookiesList.add(tempCookie);
  }
 }
}
//transferedCookie用来存储将被传到目标服务器的Cookie
Cookie[] transferedCookie = new Cookie[cookiesList.size()];
cookiesList.toArray(transferedCookie);

接下来,需要把Cookie送到目标服务器中。我们使用HTTPClient与目标服务器连接。HTTPClient在与目标服务器连接以后,允许 服务器设置Cookie并在需要的时候自动将Cookie返回服务器,也支持手工设置 Cookie后发送到服务器端。但是,由于如何处理cookie有几个规范互相冲突:Netscape Cookie 草案、RFC2109、RFC2965,而且还有很大数量的软件商的Cookie实现不遵循任何规范。 为了处理这种状况,需要把HttpClient设置成Cookie兼容模式,这样可以最大限度的处理好各种Cookie。下面的代码把Cookie送到目 标服务器。

HttpClient client = new HttpClient();
//从request得到所有需要传输的cookie 
Cookie[] questCookie = getCookieFromRequest(request);
//设置HTTPClient为Cookie兼容模式
client.getState().setCookiePolicy(CookiePolicy.COMPATIBILITY);
if (questCookie.length > 0)
//把Cookie加到httpclient中
 client.getState().addCookies(questCookie);
HttpMethod method = new GetMethod(TagerURL);
 //向目标服务器发送请求
int statusCode = client.executeMethod(method);
method.releaseConnection();

MTS把请求和Cookie送出后,继续接收目标服务器的应答,读取返回的原始Cookie,并转换成可以存储在用户浏览器端的Cookie。下面 的代码将对原始Cookie的内容进行变换,保留expires和secure等项,把domain和path项编码到name中去。

//从HTTPClient中取得所有的Cookie
Cookie[] temp = client.getState().getCookies();
if (temp != null)
{
 javax.servlet.httpCookie theCookie = new javax.servlet.http.Cookie[temp.length];
     //逐一对Cookie进行处理
 for (int i = 0; i < temp.length; i++)
 {    StringBuffer sb = new StringBuffer();
        //编码成domain||path||name
        sb.append(
         temp[i].getDomain() == null ? " " : temp[i].getDomain());
        sb.append("||");
        sb.append(temp[i].getPath() == null ? " " : temp[i].getPath());
        sb.append("||");
        sb.append(temp[i].getName() == null ? " " : temp[i].getName());
        theCookie[i] =
         new Cookie(sb.toString(),temp[i].getValue());
        //复制其他项
        theCookie[i].setMaxAge(theCookie[i].getMaxAge();
        theCookie[i].setSecure(temp[i].getSecure());
        theCookie[i].setVersion(temp[i].getVersion());
        theCookie[i].setComment(temp[i].getComment());
 }
}

最后一步,把这些Cookie保存到response里,随HTTP应答头返回用户浏览器。并保存在浏览器中。

//把所有转换后的Cookie加入response
for (int i = 0; i < theCookie.length; i++) {
 response.addCookie(theCookie[i]);
}

至此,我们已经完成了接收用户请求,转换Cookie,发送到目标服务器,接收目标服务器的原始Cookie,并保存在客户浏览器的整个处理过程。

方案二:Cookie存储在服务器端

在此种方案中,目标服务器返回给MTS的Cookie将被组织成dataBean,存储在用户的Session中。因此,我们首先生成一个用来存储 Cookie的类CookiesBean,根据它的特性,它可以继承ArraryList类。此对象将存储用户访问目标服务器时接收到的所有 Cookie,并提供与新接收到的Cookie融合的功能,同时能够删除过期的Cookie,更新同名的Cookie。

public class CookiesBean extends ArrayList
{
 /**
  * 处理Cookies.
  * @参数 Cookies array
  */
 public CookiesBean(Cookie[] cook)
 {
  if (cook == null)
   return;
//add all cookie which isn't expired.
  for (int i = 0; i < cook.length; i++)
  {
   if (!cook[i].isExpired())
   {
    add(cook[i]);
   }
  }
 }
 /**
  * 融合参数中的bean
  * @参数 bean
  * 参考: rfc2109 4.3.3  Cookie Management
  */
 public void RefreshBean(CookiesBean bean)
 {
  if (bean == null)
   return;
  Iterator it = bean.iterator();
         //针对bean中的每一个Cookie进行处理
  while (it.hasNext())
  {
   Cookie beanCookie = (Cookie) it.next();
   if (beanCookie == null) continue;
   ArrayList drop = new ArrayList();
   Iterator thisIt = iterator();
              //取出存储的Cookie进行比较和处理
   while (thisIt.hasNext())
   {
    Cookie thisCookie = (Cookie) thisIt.next();
    if (thisCookie == null) continue;
                   //比较name,domain和path,如果一样的话,则把此Cookie移到drop中
    if (CommonMethods
     .CompString(beanCookie.getName(), thisCookie.getName())
     && CommonMethods.CompString(
      beanCookie.getDomain(),
      thisCookie.getDomain())
     && CommonMethods.CompString(
      beanCookie.getPath(),
      thisCookie.getPath()))
    {
     drop.add(thisCookie);
     continue;
    }
                  //删除过期的Cookie
    if (thisCookie.isExpired())
     drop.add(thisCookie);
   }
             //删除所有drop中的Cookie
   this.removeAll(drop);
             //如果beanCookie有效,则加入到存储区中。
   if (!beanCookie.isExpired())
    add(beanCookie);
  }
  return;
 }
}

当MTS接受到客户端的翻译请求后,会从Session中提取出所有的dataBean,并得到存储的所有Cookie。如以下代码:

CookiesBean dataBean = null;
  Cookie[] theCookies = new Cookie[0];
  ArrayList cookiesList = new ArrayList();
         //获得Session,并获得dataBean
  HttpSession session = request.getSession(false);
  if (session != null)
  {
   dataBean = (CookiesBean) session.getAttribute(SESSION_NAME);
  }
  else
  {
   return theCookies;
  }

MTS在所有的存储的Cookie中,检查Cookie的Domain、path和secure的值,筛选出符合目标服务器的Cookie。

//提取目标服务器的domain和path
  String url = context.getURL();
  String domain = URLUtil.getURLHost(url);
  String path = url.substring(domain.length());
  
  String cookiedomain = null;
  String cookiepath = null;
  //逐个比较Cookie的domain和path
  //把符合要求的Cookie纪录到cookiesList中
  for (int i = 0; i < dataBean.size(); i++)
  {
   Cookie cookie = (Cookie) dataBean.get(i);
   if (cookie == null) continue;
   cookiedomain =
    (cookie.getDomain() == null) ? "" : cookie.getDomain();
   cookiepath = (cookie.getPath() == null) ? "  " : cookie.getPath();
   if (!path.startsWith(cookiepath))
    continue;
   if (cookiedomain.startsWith("."))
   {
    if (!domain.endsWith(cookiedomain))
     continue;
   }
   else if (!domain.endsWith("://" + cookiedomain))
    continue;
   if (cookie.isExpired())
    continue;
   if (cookie.getSecure() && url.toLowerCase().startsWith("http:"))
    continue;
   cookiesList.add(cookie);
  }
  theCookies = new Cookie[cookiesList.size()];
  cookiesList.toArray(theCookies);
  return theCookies;

把Cookie送到目标服务器的代码与方案一基本一样,在此忽略。

最后一步,需要把Cookie存储到Session中。下面的代码将从目标服务器接受Cookie,融入到dataBean中,并保存到客户的Session中。

//从目标服务器得到Cookie集
     Cookie[] cookies = client.getState().getCookies();
 CookiesBean bean = new CookiesBean(cookies);
     CookiesBean dataBean = bean;
     //取得用户Session
 HttpSession session =  request.getSession(false);
 if (session != null)
 {
  if (session.getAttribute(SESSION_NAME) != null)
  {
              //读取Session中存取的dataBean
   dataBean = (CookiesBean) session.getAttribute(SESSION_NAME);
              //目标服务器端的Cookie融合到Session中的dataBean中
   dataBean.RefreshBean(bean);
  }
         //把最终的dataBean存入Session中
  session.setAttribute(SESSION_NAME, dataBean);
 }

至此,我们已经完成了在Session中保存个目标服务器所产生Cookie的整个处理过程。



关于Session的考虑

在研究完如何管理和传递Cookie之后,我们也需要研究一下Session的传递。因为目前大部分站点都在采用Session机制保存用户状态数据,如果不能解决Session的传递问题,HTTP应用代理服务器的适用范围同样会大打折扣。

首先我们了解一下Session的实现机制。Session是一种服务器端的机制,服务器使用一种类似于散列表的结构来保存信息。当程序需要为某个 客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识 - 称为session id,如果已包含一个session id则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个),session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串。

保存这个session id的方式之一就是采用Cookie。一般这个Cookie的名字都类似于 SESSIONID。比如WebSphere对于Web应用程序生成的Cookie:JSESSIONID= 0001HWF4iVD94pY8Cpbx6U4CXkf:10lro0398,它的名字就是 JSESSIONID。

保存session id的其他方式还包括URL重写和表单隐藏字段。这两种方式都不需要代理服务器作特殊处理。因此实际上,我们解决了Cookie的管理和传递的问题之后,也就解决了Session的管理和传递。

结束语

从上面的讨论中可以看出,由于Cookie本身的规范限制,HTTP应用代理所必需面对的一个问题就是如何对Cookie进行正确的处理。本文对此 提出了两种解决思路并列出了实现代码。对于MTS项目本身,我们使用的是第二种方案。开发人员在认识好Cookie本身的特性之后,参照本文的思路,根据 自己系统的特点,也会找出更适宜的解决方案。

参考资料

Netscape Cookie Specification 对Netscape Cookie 使用的特性进行了简要的介绍。

RFC2965:HTTP State Management Mechanism 介绍了HTTP 状态管理机制

RFC2109 w3c发布的第一个官方cookie规范

RFC2616:Hypertext Transfer Protocol 超文本传输协议

Ronald Tschalr开发了 HTTPClient,将其作为 URLConnection 的替代品。

Jakarta Regexp Apache 的开源项目,处理正则表达式的java包。



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: