Java中的cookie管理方案——完整易用的客户端cookie操作库
客户端HTTP状态管理对于创建需要与象基于网络浏览器的email或在线银行服务网络程序交互作用的Java应用程序是十分重要的。本文介绍了在Java中一个强大易用的客户端HTTP状态管理cookie库,这个库在固有的java.net工具箱中很少见。其中存在几种客户端HTTP状态管理APIs,它们提供了难于学习并没必要重新开发设计的函数方法。这篇文章中Cookie管理库尽量使用核心Java API类。 |
当在开发一个针对所有主要的internet邮件服务器(基于Web或其他类型)提供单点访问的通用邮件客户端时,我发现我的应用程序经常不得不作为一个小的网络浏览器与提供邮件服务的网站交互。 当开发XML网络服务以便于机器更容易访问网站时我总在需要网站交互时遇到困难。这些网站经常使用cookies进行状态管理及维护用户会话数据,在这两种情况,我意识到多数网站交互都涉及cookie操作。我也注意到虽然两种情况下的应用程序都执行cookie操作,但其逻辑处理较困难及不具有互换性。针对此限制,我从开发一个小型普通用途库出发致力于cookie操作。在这篇文章中我将与你分享这个库。 |
为了在运行中图解说明库,我建议使用基于Hotmail邮件检测器的控制台。此外,我从在J2ME平台上使用MIDP的移动设备观点探究了客户端状态管理。 |
要回答第一个问题,我们必须更精密地检测一下HTTP。HTTP是无国界协议,因为从网络服务器观点看所有HTTP请求都独立于先前请求。就是说每一个HTTP响应完全依赖于相应请求中包含的信息。当这种行为使网络服务执行更简单有效时,用它作为复杂网络应用的基础将更为合适。 |
状态管理机制克服了HTTP的一些限制并允许网络客户端及服务器端维护请求间的关系。在这种关系维持的期间叫做会话(session)。多数要求你登录的网络应用程序使用了会话及状态管理。购物推车应用程序使用状态管理控制所有标记为已购买项目的列表。状态管理能够使个别用户参数的入口及搜索引擎个性化定制。网络应用程序甚至能使用状态管理根据用户爱好兴趣定制网站内容。 |
Cookies影响着状态管理。Cookies是服务器在本地机器上存储的小段文本并随每一个请求发送至同一个服务器。 是通用cookie规范。网络服务器用HTTP头向客户端发送cookies,在客户终端,浏览器解析这些cookies并将它们保存为一个本地文件,它会自动将到同一服务器的任何请求缚上这些cookies。在这篇文章后面,我同义性地使用了cookie操作和状态管理术语。 |
如果你要找出你访问的哪个网站使用了cookies,可以试试这个简单的试验: |
注意: 只有当你觉得改变你的浏览器设置没什么问题并知道方法时才执行这个练习。 |
● 打开你常用的浏览器,我假设你使用的是Internet Explorer (IE) 5+或Netscape Navigator 4+。 |
- 在IE浏览器的“工具”菜单中选择“Internet选项”,再选择“安全”标签,单击“自定义级别”然后向下拉动滚条直到你看见“允许使用存储在你计算机上的cookies”并选中“提示”选项,同时也选中“允许使用每个对话cookies(未存储)”的“提示”选项,单击“确定”按钮回到主窗口。
|
- 在Netscape Navigator的“编辑”菜单中选择“参数选择”中的“高级”,选中“接收cookie时警告”,单击“确定”按钮回到主窗口。
|
● 现在浏览你“收藏”中的站点,特别是当你检查你的网络邮件或进入在线电子商店时,要求你允许接收cookies的对话框会不断地向你轰来。 |
将上面的步骤恢复到你以前的初始设置,你也能看见哪些cookies被保存到了你的本地机器上(在警告应用之前): |
● 对于IE:使用“Windows资源管理器”或“我的电脑”浏览C:\Windows\Cookies文件夹,在这个文件夹中的所有文本文件都包含cookies。 |
- 在Windows系统中,使用“Windows资源管理器”或“我的电脑”浏览C:\Program Files\Netscape\Users文件夹,找到一个名叫“cookies.txt”的文件或“cookies”子目录。
|
- 在Unix类似系统中,在“.netscape”目录中找到一个名叫“cookies”的文件。
|
注意: 根据你安装的系统不同,使自动cookie操作无效及查看保存的cookies的步骤也可能不同。 |
现在你已经知道了一些基本知识,接下来我将阐述怎样将这些与Java联系起来。 |
Java应用程序在以下几种情况要求cookie操作: |
● 网站交互:为了与网站交互,基于Internet的客户端应用程序经常扮演小型网络浏览器的角色。这些站点使用cookies进行状态管理以维护用户的会话数据。 |
● 网络服务实现:网络服务承诺使网络成为电脑机器的友好地方。一个都希望的允许机器-网站进行交互的方法就是在网站前面有一个网络服务。因此,网络服务将目标网站的视窗十分友好地呈现在机器面前。这种网络服务的实现将需要cookie操作以达到真正的网站交互。 |
● 网络浏览:基于网络浏览的Java将需要cookie操作模块以支持状态管理。 |
为了执行客户端cookie操作,先看下面几个步骤: |
2. 分别解析cookies的组成部分(名称,值,路径等等)。 |
2. 对于多个cookies,判定必须发送的cookies的顺序。 |
3. 与外发的HTTP头一起格式并发送cookies。 |
一个客户端Java应用程序须遵循上面的所有步骤,但是用RFC2965列出的规范执行上述步骤将消耗大量的时间并分散开发者在核心程序上的注意力。结果,开发者经常选择向规范妥协而用很容易就被破坏的随意编写的cookie操作代码结束。 |
例如,假设你想要写一个与网络商店应用程序的servlet交互的Java客户应用程序,在服务器端,当servlet第一次通过调用request.getSession()为一个会话询问servlet容器时,容器创建一个新的会话并且服务器用一个会话ID在并发请求时检索会话对象,服务器自动将这个会话ID作为一个HTTP cookie发送到客户端。在并发请求时,客户端与请求一起回送同一个会话ID。服务器用ID区别正确的会话对象以便servlet处理请求。典型的客户端代码如下: |
HttpURLConnection huc= (HttpURLConnection) url.openConnection(); |
InputStream is = huc.getInputStream(); |
String cookieVal = hc.getHeaderField("Set-Cookie"); |
sessionId = cookieVal.substring(0, cookieVal.indexOf(";")); |
HttpURLConnection huc= (HttpURLConnection) url.openConnection(); |
huc.setRequestProperty("Cookie", sessionId); |
InputStream is = huc.getInputStream(); |
cookie规范RFC2965为cookies版本1定义了一个新报头,Set-Cookie2。假如我们用新报头升级服务器,上面的代码将不能履行。上述代码也不能处理多重cookies。另外,版本1的cookie值可以是一个加引号的字符串,假如会话cookie的值是一个包含分号的加引号字符串,这也将引起上述代码不能履行。简而言之,上面的代码片断不是与cookie的版本使用孤立开来的。 |
上述代码对于只和一个特别的主机及路径影射交互的简单程序是适合的,但对于一个更庞大的应用程序,当涉及多重主机及路径时cookie管理将变得更复杂。开发者实现cookie规范中的所有算法、安全检查及平衡将证明是痛苦和徒然的。 |
为了减轻这种情形,我开发了一个普通用途cookie库,命名为jCookie,用来实现cookie规范。这个库使客户端cookie操作所必需的额外代码和努力最小化并让开发者的精力集中在核心应用程序上。其他APIs库也有(例如,Apache的HTTPClient),但是他们使用了从内建本地的java.net APIs移出的结构,因此需要一个新的学习过程。我的API是一个调用已存在的java.net对象的简单方法。 |
你也能使用现在发展的jCookie延伸版本,叫jCookieMicro,在J2ME移动设备上创建一套令人激动的能与网络服务应用程序交互的客户系统。 |
现在我介绍jCookie API的主要行为,先从两个主要数据结构开始: |
1. Cookie类:此类的一个实例表明一个独立的cookie。它封装了RFC 2965定义的所有cookie属性并提供用getters和setters访问这些属性。 |
2. CookieJar类:此类的一个实例被作为一个Cookie对象集的容器。它符合集合结构并提供操作cookie集合的方法。 |
API提供两个视野以同时满足开发者对于cookie透明操作的要求及开发者对于高级特性的要求。下面的图形说明了这些视野或层。 |
|
jCookie库的分层视图 | |
那些开发者多数都想进行透明cookie操作,这通常是使用层1的情形。在这个级别,你用Client类操作cookies。它有两个主要的方法: |
· public CookieJar getCookies(URLConnection urlConn): 这个方法从给出的URLConnection中析取cookies,将它们解析到Cookie对象,并作为一个CookieJar返回。 |
· public CookieJar setCookies(URLConnection urlConn, CookieJar cj): 这个方法从CookieJar中提取合适的Cookie对象并设置URLConnection的报头。 |
这些开发者没有在使用层0的代码中深入就无法呼吸(包括我)。在这里,你可以通过使用cookie操作代码改变解析逻辑和安全规则。要这样做,首先实现CookieParser接口,它有以下四个方法: |
· public Header getCookieHeaders(CookieJar cj): 在CookieJar中转换Cookies为一报头以适合与一个HTTP请求一起发送。 |
· public boolean allowedCookie(Cookie c, URL url): 检查是否一个给出URL的请求能返回指定的Cookie。 |
· public CookieJar parseCookies(Header h, URL url): 在一个HTTP响应中将报头转换到一个Cookie对象的CookieJar中。 |
· public boolean sendCookieWithURL(Cookie c, URL url, boolean bRespectExpires): 检查是否给出的Cookie能被与给出URL的一个请求一起发送。 |
你能使用Client类的setCookieParser(CookieParser cp)方法去设置CookieParser实现。被库缺省使用的CookieParser是一个RFC 2965 cookie规范中的实现。 |
在层1,jCookie作为一个库;在层0,它成为一个API的基础。 |
Client类在两个层都调用cookie操作逻辑。它提供了应用程序开发者的库架构。要使用jCookie库,按照下面这些步骤: |
- 创建一个URLConnection对象并初始化。
- 连接URLConnection。
- 创建一个Client对象并设定一个定制的CookieParser。
- 通过调用Client实例的getCookies()方法得到一个Cookies的CookieJar,作为在URLConnection中的一个参数。
- 与HTTP响应一起作一些事情。
|
· 和一个请求(假定一个CookieJar已被检索)一起发送cookies: |
- 创建一个URLConnection对象并初始化。
- 创建一个Client对象并设定一个定制的CookieParser。
- 通过调用Client实例的setCookies()方法设置cookie报头,作为URLConnection and CookieJar 中的参数。
- 连接URLConnection。
- 与HTTP响应一起作一些事情。
|
下面的摘录显示了普通jCookie的用法。这个jCookie代码十分突出: |
import com.sonalb.net.http.cookie.*; |
HttpURLConnection huc = (HttpURLConnection) url.openConnection(); |
//在这里初始化HttpURLConnection. |
InputStream is = huc.getInputStream(); |
Client client = new Client(); |
CookieJar cj = client.getCookies(huc); |
huc = (HttpURLConnection) url.openConnection(); |
client.setCookies(huc, cj); |
上面的代码描述了jCookie API的两个方面: |
· 本地java.net对象的使用(HttpURLConnection)。 |
· 轻易地回收和发送cookies(单个方法调用)。 |
在实践中,上述代码已经能成功地维护两个请求间的会话。现在我们转换层的基本结构,让我们将jCookie与一些真实代码连接。 |
为了阐明jCookie库的使用方便,我将在一个显示一个Hotmail账号新消息的发件人、主题及日期字段的应用程序中使用它。为了简单起见,应用程序在控制台显示这些信息。为了在Hotmail收件箱接收新消息,应用程序需要完成以下步骤: |
· 在登录表单中执行一个HTTP POST操作登录Hotmail。 |
多数站点要求用户第一次通过一个表单执行一个HTTP POST 操作以完成登录过程。为了成功鉴定身份,POST的响应通常是一个带一些cookie报头的HTTP重定向。当重定向页被请求时cookies返回给服务器。 |
jCookie库包括一个很有用的类叫HTTPRedirectHandler,它管理当完成客户端cookie操作时操作重定向的普通任务。要使用这个类,首先要在一个未连接的HttpURLConnection中创建一个HTTPRedirectHandler实例,然后调用HTTPRedirectHandler实例的connect()方法去操作重定向及cookie。句柄从HTTP响应代码中确定是否运行成功。一旦进程完成,调用的类就检索表明最后一次请求的HttpURLConnection对象。CookieJar包含所有在能被检索的重定向过程中接收的cookies。Cookie操作逻辑存在于HTTPRedirectHandler的connect()方法中。让我们来看一看这个方法的代码。Cookie操作部份进行了注释: |
package com.sonalb.net.http; |
import com.sonalb.net.http.cookie.*; |
public class HTTPRedirectHandler |
public HTTPRedirectHandler(HttpURLConnection huc) |
public void connect() throws IOException |
throw new IllegalStateException("No can do. Already connected."); |
huc.setFollowRedirects(false); |
client.setCookies(huc,cj); |
is = huc.getInputStream(); |
// 从HttpURLConnection中提取Cookies并加到CookieJar中去 |
cj.addAll(Client.getCookies(huc)); |
while((code = huc.getResponseCode()) != successCode && maxRedirects > 0) |
throw new IOException("Can't deal with this code (" + code + ")."); |
url = new URL(huc.getHeaderField("location")); |
huc = (HttpURLConnection) url.openConnection(); |
Client.setCookies(huc, cj); |
huc.setFollowRedirects(false); |
is = huc.getInputStream(); |
cj.addAll(Client.getCookies(huc)); |
if(maxRedirects <= 0 && code != successCode) |
throw new IOException("Max redirects exhausted."); |
public void handleCookies(boolean b) |
public void setSuccessCode(int i) |
public void setCookieJar(CookieJar cj) |
public void addCookies(CookieJar cj) |
public CookieJar getCookieJar() |
public HttpURLConnection getConnection() |
public void setMaxRedirects(int i) |
HotmailChecker应用程序使用HTTPRedirectHandler进行登录操作。应用程序从使用带有并发请求的HTTPRedirectHandler中检索CookieJar。HotmailChecker的相关部份显示如下。Hotmail细节和jCookie关联注释被突出显示: |
public boolean doLogin() throws Exception |
System.getProperties().put("java.protocol.handler.pkgs","com.sun.net.ssl.internal."); |
java.security.Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider()); |
//创建HttpURLConnection并初始化 |
HttpURLConnection huc = (HttpURLConnection) url.openConnection(); |
huc.setRequestMethod("POST"); |
huc.setRequestProperty("User-Agent","Mozilla/4.7 [en] (Win98; I)"); |
StringBuffer sb = new StringBuffer(); |
sb.append("login="); sb.append(URLEncoder.encode(user)); |
OutputStream os = huc.getOutputStream(); |
os.write(sb.toString().getBytes("US-ASCII")); |
HTTPRedirectHandler hrh = new HTTPRedirectHandler(huc); |
huc = hrh.getConnection(); |
//Microsoft有一个中间过渡页使用了一个刷新元标签以便于在HTTPS和HTTP间转换,这将防止安全 |
//我们需要通过读取响应和解析URL手动取出URL |
BufferedReader br = new BufferedReader(new InputStreamReader(huc.getInputStream())); |
//一旦我们有了主页的URL,我们就又使用HTTPRedirectHandler重定向并处理响应以校验正确的注 |
huc = (HttpURLConnection) url.openConnection(); |
huc.setRequestProperty("User-Agent","Mozilla/4.7 [en] (Win98; I)"); |
hrh = new HTTPRedirectHandler(huc); |
cj.addAll(hrh.getCookieJar()); |
现在我们已经登录到Hotmail,我们请求收件箱页,在登录过程中已检索的Cookies中通过。一旦我们拥有了收件箱页,我们必须因为与新消息有关的信息而解析这个HTML。代替使用暴力的StringTokenizer检索这个信息,我们将用一个稍微文雅(既复杂的)方法调控XML。这种方法包括: |
· 将成形不好的HTML转换为well-formed HTML。 |
· 用DOM(文档对象模型)通过well-formed HTML去得到新消息的信息。 |
假如DOM、XML和well-formed 对你来说一窍不通,只要说我们把收件箱HTML转换成一个树状结构的对象并得到想要的信息就足够了。 |
要将成形不好的HTML转换成well-formed HTML,我们用一个可自由下载的组件JTidy工具和一个通用的处理器。ConvertBadHTMLToGood帮助类将成形不好的Hotmail HTML转换成well-formed HTML。相关代码显示如下: |
public class ConvertBadHTMLToGood |
public ConvertBadHTMLToGood(Reader r) |
throw new IllegalArgumentException(); |
public Reader doConvert() throws IOException |
tidy.setErrout(new PrintWriter(new StringWriter())); |
//JTidy解析器要求一个InputStream,对于我的知识来说这里没有直接的办法将一个Reader转换 |
//成一个InputStream。这个工作区代码没有字符编码安全,但还可以混过。 |
BufferedReader br = new BufferedReader(inReader); |
StringBuffer sb = new StringBuffer(); |
while((line = br.readLine()) != null) |
ByteArrayInputStream bais = new ByteArrayInputStream(sb.toString().getBytes("US-ASCII")); |
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
//作一个将HTML转换well-formed HTML 的预备。 |
//整理一些遗漏的JTidy得到能被“true-blue”XML解析器解析的输出。 |
FixEntities fe = new FixEntities(baos.toString()); |
return(fe.getFixedReader()); |
一旦我们拥有了well-formed HTML,我们就用XML解析的Java API(JAXP)去转换well-formed HTML 成一个DOM树并通过树得到新消息的表单、主题及日期字段。我将忽略一些代码而向你展示如何使用HotmailChecker: |
import com.sonalb.net.http.cookie.*; |
public class HotmailChecker |
public static void main(String args[]) throws Exception |
HotmailChecker hmc = new HotmailChecker(uname,pass); |
System.out.println("Could not login to Hotmail."); |
Vector newMessages = hmc.getNewMessages(); |
System.out.println("No NEW Messages."); |
System.out.println("You have " + newMessages.size() + " NEW Messages"); |
System.out.println("---------------------------------------------"); |
Iterator iter = newMessages.iterator(); |
//HMMessage封装了一个Hotmail消息 |
hm = (HMMessage) iter.next(); |
System.out.println(" From: " + hm.getFrom()); |
System.out.println(" Subject: " + hm.getSubject()); |
System.out.println("Sent Date: " + hm.getSentDate()); |
System.out.println("---------------------------------------------"); |
System.out.println("\nUsage: java HotmailChecker "); |
public HotmailChecker(String username, String password) |
public boolean doLogin() throws Exception |
public Vector getNewMessages() throws Exception |
你可以从下载完全功能的HotmailChecker及相关类。 |
前面,我曾提起用jCookieMicro库在J2ME平台的移动设备上建立与网络应用程序交互的客户系统的可能性。我仍然在开发jCookieMicro库。它的结构及用法将与jCookie库类似,除了URLConnection,jCookieMicro库将用MIDP HttpConnection对象。这部分描述了在移动应用程序(在我们的案例MIDlets中)使用成熟的cookie操作的好处。 |
让我们先练习在J2ME应用程序中状态管理常用的方法。诺基亚论坛一篇命名为“”(2002年三月)的论文描述了一个方法。论文提出作为一个URL重写机制的变异工作的机制:一个在网络服务器上的servlet站点操作所有的商务逻辑和使用通常的HTTP报头传送状态信息要胜于cookies。MIDlet简单地作为一个用户界面,传递用户输入到servlet并显示结果。(更多的关于在J2ME应用程序上的状态管理的URL重写及其他方法,请读“”,Michael Juntao Yuan和Ju Long著(JavaWorld,2002年四月).) |
使用上述方法,这有与此讨论相关的应用程序的解决方法,一是象一个小型网络浏览器一样与网络服务器或应用程序交互: |
· MIDlet从用户那里收集相关输入(比如,一个Hotmail用户名和密码) |
· Servlet用输入与网络服务器或应用程序交互(比如,Hotmail网站) |
· Servlet传送结果到 MIDlet(比如,一个新消息列表) |
在上述解决方法中,通用HTTP报头维护一个MIDlet和servlet间的会话。因此,servlet和MIDlet 都包含执行会话管理的逻辑。这证明前面讲过的不受欢迎的同一原因:通用代码很容易被破坏,甚至成为常规管理变化所带来的必然结果,比如服务器升级。这种方法的另一个缺点:它要求有一个在目标网络应用程序(如Hotmail)和移动应用程序之间的中间件。 |
对于上述方法你可以用两种办法替代jCookieMicro: |
1. 将商务逻辑转移到移动应用程序上并完全消除中间servlet。在移动客户系统上用jCookieMicro进行会话管理。 |
2. 将商务逻辑保持在中间servlet上,但除去通用报头,并用jCookieMicro进行透明坚固的会话管理。 |
修改已存在的应用程序第二种方法证明更适合。第一种方法导致成本的降低和移动应用程序开发更轻松,因为它除去了服务器端资源的开销。下面的应用程序使用了第一种方法: |
· MIDlet直接连接到目标网络服务器并与之交互 |
第一种方法同时也消除了将商务逻辑保持在servlet的如下一些缺点: |
· 一个servlet容器故障会导致整个应用程序离线,即使目标网络服务仍在运行。 |
在移动应用程序中进行状态管理的另一个方法在Sun无线Java开发者的一篇不依赖风俗权威的文章“”(2002年一月)中有描述,但包括在移动应用程序中写操作cookies的通用代码。前面关于写通用代码的缺点的讨论及使用jCookie(Micro)的优势请看上述命为“在Java中的状态管理”部分)。 |
自从PJAE提供连同一些Java2类的完整JDK 1.1.8平台,即使在工作中的jCookieMicro,今天你也能在PersonalJava Application Environment (PJAE)下的应用程序中使用jCookie库。。 |
· 当cookie解析逻辑及安全标准插入到已存在的API(用CookieParser)时,没有一个对于核心数据结构和Cookie类有用的机制。 |
你可以从得到jCookie最近的版本以及项目源代码。有一些项目是为未来版本计划的包括: |
· 雅加达项目log4J logging API的使用提供了用户可配置的记录和调试。 |
· 用一个用户定义的控制器可简单修改jCookie行为,这可以决定解析一单个cookie是否失败将导致致命错误。 |
这些及其他部分的执行大量依赖于你的反馈。请在的jCookie项目站点上使用邮件列表、bug追踪、特征请求等等。 |
这里提到的jCookie库能帮助减轻客户端应用程序状态开发的难度。作为前面曾提到的,其他库的执行类似于函数,但这些结构已和存在的本地java.net API远无关系。另外,没有API/库单独从事cookie操作。其他的库将cookie操作合并作为一个完整的Java 网络客户结构的一部份,结果,使用这些库涉及了整个新的学习体系。 |
jCookie库接近于存在的java.net对象。在普遍的URLConnection或HttpConnection两个方法调用中状态管理十分简单。你可以通过一个有用的HTTPRedirectHandler类使得状态管理更简单。在处理客户端应用程序开发者时jCookie努力把浏览器作为cookie管理器。这能成功走多远只能由你的反响决定。我将感激任何你所分享的提示或建议。 |
阅读(2835) | 评论(0) | 转发(0) |