使用CookieHandler管理Cookie数据

阅读更多

前言 : 因为只学过J2SE部分,对JAVA网络编程也不甚了解,所以学习在JAVA操作HTTP协议时碰到很多问题.翻译这篇文章只是为了加深理解,如有不当,还望指出.

原文地址: http://java.sun.com/developer/JDCTechTips/2005/tt0913.html

 

     在JAVA平台,访问URL资源是通过一系列协议处理器(protocol handler)来实现的.URL的起始部分指定了URL使用的协议.比如某个URL是以file:开头的,这表明这个URL资源是保存在本地文件系统的.J2SE5.0定义了几个必须实现的协议:http,https,file,jar.
     作为http协议处理器实现的一部分,J2SE5.0增加了一个CookieHandler.这个类提供了一些用于管理cookies的接口.Cookie是保存在浏览器缓存中的一小块数据.当你访问一个网站然后再次访问的时候,这个cookie数据用于鉴别你的身份.Cookies能够用于保存信息,譬如一个在线商店用于保存以购商品信息.Cookie可以是短期的,为一个单独的web事务保存数据,直到关闭浏览器;也可以是长期的,保存数据一个星期或一年.
在J2SE5中并没有设置默认的CookieHandler.不过你可以注册一个Handler以便程序能够保存cookies并且在http连接的时候得到这些cookies.
     回到CookieHandler这个类,这是个具有两组相关联方法的抽象类.第一组方法让你能得到当前已经设置的Handler或设置你自己的Handler:
        * getDefault()
        * setDefault(CookieHandler)

    对于安装了安全管理器的应用来说,得到或设置handler需要特别的权限.通过设置handler为null可以清除当前设置的handler.正如之前提到的,没有设置默认的handler.
第二组方法允许你从一个你维持的cookie缓存得到cookies,或将cookies保存到这个cookie缓存.
        * get(URI uri,Map<String,List<String>>requestHeaders)
        * put(URI uri,Map<String,List<String>>responseHeaders)

    get()方法从cookie缓存中的到之前保存的cookie并保存到requestHeaders中.put()方法从response headers 中提取cookies并保存到cookie缓存.
    这看起来很简单,事实创建一个handler确实如此.但定义cookie缓存需要做更多的事情.作为示范,我们写一个自己的CookieHandler,cookie缓存以及一个测试程序.这里是测试程序的雏形:[注 1]
  

java 代码
  1.    import  java.io.*;  
  2.    import  java.net.*;  
  3.    import  java.util.*;  
  4.   
  5.    public   class  Fetch {  
  6.      public   static   void  main(String args[])  throws  Exception {  
  7.        if  (args.length ==  0 ) {  
  8.          System.err.println("URL missing" );  
  9.          System.exit(-1 );  
  10.        }  
  11.        String urlString = args[0 ];  
  12.        CookieHandler.setDefault(new  ListCookieHandler());  
  13.        URL url = new  URL(urlString);  
  14.        URLConnection connection = url.openConnection();  
  15.        Object obj = connection.getContent();  
  16.        url = new  URL(urlString);  
  17.        connection = url.openConnection();  
  18.        obj = connection.getContent();  
  19.      }  
  20.    }  


    这个程序首先建立并安装了一个ListCookieHandler类(这个类的定义将在后面给出).然后打开了一个到URL(由命令行参数给出)的连接,并且读取其内容.再次打开另一个到URL的连接,并读取相同的内容.当第一次读取连接内容时,响应中包含的cookies将会被保存,第二次连接请求中就会包含这些保存的cookies.
下面我们来看这些是如何通过URLConnection类实现的.在建立了一个表示网络资源的URL后,我们可以用URLConnection来得到与该网站通信的输入流与输出流.
   String urlString = ...;
   URL url = new URL(urlString);
   URLConnection connection = url.openConnection();
   InputStream is = connection.getInputStream();
   // .. read content from stream


    这个连接中的信息可能有一部分是属于报文头,这与所用的协议有关.我们可以通过URLConnection来得到这些报文头消息,这个类提供了一些能提取报文头信息的方法,包括:
     *  getHeaderFields() - Gets a Map of available fields.
     *  getHeaderField(String name) - Gets header fields by name.
     *  getHeaderFieldDate(String name, long default) - Gets the header field as a date.
     *  getHeaderFieldInt(String name, int default) - Gets the header field as a number.
     *  getHeaderFieldKey(int n) or getHeaderField(int n) - Gets the header field by position.


    作为一个示例,下面的程序将指定URL的所有报文头消息列出:
  
java 代码
  1.    import  java.net.*;  
  2.    import  java.util.*;  
  3.   
  4.    public   class  ListHeaders {  
  5.      public   static   void  main(String args[])  throws  Exception {  
  6.        if  (args.length ==  0 ) {  
  7.          System.err.println("URL missing" );  
  8.        }  
  9.        String urlString = args[0 ];  
  10.        URL url = new  URL(urlString);  
  11.        URLConnection connection = url.openConnection();  
  12.        Map<String,List<String>> headerFields =  
  13.          connection.getHeaderFields();  
  14.        Set<String> set = headerFields.keySet();  
  15.        Iterator itor = set.iterator();  
  16.        while  (itor.hasNext()) {  
  17.          String key = itor.next();  
  18.          System.out.println("Key: "  + key +  " / "  +  
  19.            headerFields.get(key));  
  20.        }  
  21.      }  
  22.    }  


    这个程序用一个URL作为参数(比如:http://java.sun.com),然后将从该网站返回的所有报文头消息列出,每一个报头用如下格式显示:
     Key: <key> / [<value>]
    如果你输入:
     >> java ListHeaders http://java.sun.com
    你将会看到与下面类似的输出:
   Key: Set-Cookie / [SUN_ID=192.168.0.1:269421125489956; EXPIRES=Wednesday, 31- Dec-2025 23:59:59 GMT; DOMAIN=.sun.com; PATH=/]
   Key: Set-cookie / [JSESSIONID=688047FA45065E07D8792CF650B8F0EA;Path=/]
   Key: null / [HTTP/1.1 200 OK]
   Key: Transfer-encoding / [chunked]
   Key: Date / [Wed, 31 Aug 2005 12:05:56 GMT]
   Key: Server / [Sun-ONE-Web-Server/6.1]
   Key: Content-type / [text/html;charset=ISO-8859-1]


      这些输出只包含URL的报头,并没有包括这个URL指向的HTML页面.你可能注意到这些输出信息里面包含了这个URL站点所用的web服务器以及其日期时间.同意可以看到里面包含了两行Set-Cookie,这就是报头里面携带的cookies.这些cookie能够保存下来,然后在下一次请求的时候被发送.
    下面我们来建立一个CookieHandler,我们得实现CookieHandler的两个抽象方法:get()与put():
         *  public void put( URI uri,  Map<String, List<String>> responseHeaders) throws IOException
    *  public Map<String, List<String>> get(URI uri, Map<String, List<String>> requestHeaders) throws IOException


其中put()方法将所有报头中的cookies保存到一个缓存中.为了实现put()方法,首先要从responseHeaders中得到"Set-Cookie"对应的List.
     List<String> setCookieList =
        responseHeaders.get("Set-Cookie");

当你得到cookies对应的List,将List中所有的值保存下来.如果这个cookie已经存在,就将已保存的替换掉:
java 代码
  1.     if  (setCookieList !=  null ) {  
  2.       for  (String item : setCookieList) {  
  3.         Cookie cookie = new  Cookie(uri, item);  
  4.         // Remove cookie if it already exists in cache   
  5.         // New one will replace it   
  6.         for  (Cookie existingCookie : cache) {  
  7.           ...  
  8.         }  
  9.         System.out.println("Adding to cache: "  + cookie);  
  10.         cache.add(cookie);  
  11.       }  
  12.     }  

    这里的"cache"可以是一个数据库或者是一个Collections Framework中的List.其中的Cookie类将在下面定义.从本质上说,这些就是put()方法所要做的事:对于响应报头中每一个cookie,这个方法将cookie保存到缓存中.
    而get()方法做的是相反的事情:将缓存中所有与URI匹配cookie添加到请求报头中,如果存在多个cookie,则建立一个用','分隔的列表.方法get()返回一个Map,而且用一个包含已有报文头的map作为参数,你应该将cookie缓存与之相匹配的cookie添加这个map里面去,但是这个Map是只读的,所以你应该首先新建另一个map,并将参数map中的内容复制过去,然后再将cookie添加进去,最后返回一个只读的map. [注 2]
    为了实现get()方法,首先要从cookie缓存中查找与URI相匹配的cookie,然后删除那些已经过期的cookie:
java 代码
  1.     // Retrieve all the cookies for matching URI   
  2.     // Put in comma-separated list   
  3.     StringBuilder cookies = new  StringBuilder();  
  4.     for  (Cookie cookie : cache) {  
  5.       // Remove cookies that have expired   
  6.       if  (cookie.hasExpired()) {  
  7.         cache.remove(cookie);  
  8.       } else   if  (cookie.matches(uri)) {  
  9.         if  (cookies.length() > 0 ) {  
  10.           cookies.append(", " );  
  11.         }  
  12.         cookies.append(cookie.toString());  
  13.       }  
  14.     }  

    
    这里简单说明一下Cookie类,上面代码中用到了Coookie类的两个方法:hasExpired()和matches().hasExpired()方法用于表明这个cookie是否已经过期;而matches()方法用于检验这个cookie与某个URI是否匹配.
    get()方法余下部分将上面的StringBuilder中的文本添加到一个Map中,与之对应的key为"Cookie"
java 代码
  1. // Map to return   
  2. Map<String, List<String>> cookieMap =  
  3.   new  HashMap<String, List<String>>(requestHeaders);  
  4.   
  5. // Convert StringBuilder to List, store in map   
  6. if  (cookies.length() >  0 ) {  
  7.   List<String> list =  
  8.     Collections.singletonList(cookies.toString());  
  9.   cookieMap.put("Cookie" , list);  
  10. }  
  11. return  Collections.unmodifiableMap(cookieMap);  

    
    下面是CookieHandler的完整实现,里面添加了一些输出语句用于观察运行时刻的信息:
java 代码
  1.   import  java.io.*;  
  2.   import  java.net.*;  
  3.   import  java.util.*;  
  4.   
  5.   public   class  ListCookieHandler  extends  CookieHandler {  
  6.   
  7.     // "Long" term storage for cookies, not serialized so only   
  8.     // for current JVM instance   
  9.     private  List<Cookie> cache =  new  LinkedList<Cookie>();  
  10.   
  11.     /**  
  12.      * Saves all applicable cookies present in the response  
  13.      * headers into cache.  
  14.      * @param uri URI source of cookies  
  15.      * @param responseHeaders Immutable map from field names to  
  16.      * lists of field  
  17.      *   values representing the response header fields returned  
  18.      */   
  19.   
  20.     public   void  put(  
  21.         URI uri,  
  22.         Map<String, List<String>> responseHeaders)  
  23.           throws  IOException {  
  24.   
  25.       System.out.println("Cache: "  + cache);  
  26.       List<String> setCookieList =  
  27.         responseHeaders.get("Set-Cookie" );  
  28.       if  (setCookieList !=  null ) {  
  29.         for  (String item : setCookieList) {  
  30.           Cookie cookie = new  Cookie(uri, item);  
  31.           // Remove cookie if it already exists   
  32.           // New one will replace   
  33.           for  (Cookie existingCookie : cache) {  
  34.             if ((cookie.getURI().equals(  
  35.               existingCookie.getURI())) &&  
  36.                (cookie.getName().equals(  
  37.                  existingCookie.getName()))) {  
  38.              cache.remove(existingCookie);  
  39.              break ;  
  40.            }  
  41.          }  
  42.          System.out.println("Adding to cache: "  + cookie);  
  43.          cache.add(cookie);  
  44.        }  
  45.      }  
  46.    }  
  47.   
  48.    /**  
  49.     * Gets all the applicable cookies from a cookie cache for  
  50.     * the specified uri in the request header.  
  51.     *  
  52.     * @param uri URI to send cookies to in a request  
  53.     * @param requestHeaders Map from request header field names  
  54.     * to lists of field values representing the current request  
  55.     * headers  
  56.     * @return Immutable map, with field name "Cookie" to a list  
  57.     * of cookies  
  58.     */   
  59.   
  60.    public  Map<String, List<String>> get(  
  61.        URI uri,  
  62.        Map<String, List<String>> requestHeaders)  
  63.          throws  IOException {  
  64.   
  65.      // Retrieve all the cookies for matching URI   
  66.      // Put in comma-separated list   
  67.      StringBuilder cookies = new  StringBuilder();  
  68.      for  (Cookie cookie : cache) {  
  69.        // Remove cookies that have expired   
  70.        if  (cookie.hasExpired()) {  
  71.          cache.remove(cookie);  
  72.        } else   if  (cookie.matches(uri)) {  
  73.          if  (cookies.length() >  0 ) {  
  74.            cookies.append(", " );  
  75.          }  
  76.          cookies.append(cookie.toString());  
  77.        }  
  78.      }  
  79.   
  80.      // Map to return   
  81.      Map<String, List<String>> cookieMap =  
  82.        new  HashMap<String, List<String>>(requestHeaders);  
  83.   
  84.      // Convert StringBuilder to List, store in map   
  85.      if  (cookies.length() >  0 ) {  
  86.        List<String> list =  
  87.          Collections.singletonList(cookies.toString());  
  88.        cookieMap.put("Cookie" , list);  
  89.      }  
  90.        System.out.println("Cookies: "  + cookieMap);  
  91.    return  Collections.unmodifiableMap(cookieMap);  
  92.    }  
  93.  }  

 
      到这里,我们的工作只剩下Cookie类的实现了.这个工作的重头戏在其构造函数部分,你需要从URI以及报文头里面解析出所需要的信息.其中的cookie有效日期信息的格式是确定的,但其它信息对不同的网站有不同的格式.不过这也没有什么困难的,只需要把cookie路径,有效日期,域名这些信息保存下来就是了.
java 代码
  1.  public  Cookie(URI uri, String header) {  
  2.   String attributes[] = header.split(";" );  
  3.   String nameValue = attributes[0 ].trim();  
  4.   this .uri = uri;  
  5.   this .name = nameValue.substring( 0 , nameValue.indexOf('='));  
  6.   this .value = nameValue.substring(nameValue.indexOf('=')+ 1 );  
  7.   this .path =  "/" ;  
  8.   this .domain = uri.getHost();  
  9.   
  10.   for  ( int  i= 1 ; i < attributes.length; i++) {  
  11.     nameValue = attributes[i].trim();  
  12.     int  equals = nameValue.indexOf('=');  
  13.     if  (equals == - 1 ) {  
  14.       continue ;  
  15.     }  
  16.     String name = nameValue.substring(0 , equals);  
  17.     String value = nameValue.substring(equals+1 );  
  18.     if  (name.equalsIgnoreCase( "domain" )) {  
  19.       String uriDomain = uri.getHost();  
  20.       if  (uriDomain.equals(value)) {  
  21.         this .domain = value;  
  22.       } else  {  
  23.         if  (!value.startsWith( "." )) {  
  24.           value = "."  + value;  
  25.         }  
  26.         uriDomain =  
  27.           uriDomain.substring(uriDomain.indexOf('.' ));  
  28.         if  (!uriDomain.equals(value)) {  
  29.           throw   new  IllegalArgumentException(  
  30.             "Trying to set foreign cookie" );  
  31.         }  
  32.         this .domain = value;  
  33.       }  
  34.     } else   if  (name.equalsIgnoreCase( "path" )) {  
  35.       this .path = value;  
  36.     } else   if  (name.equalsIgnoreCase( "expires" )) {  
  37.       try  {  
  38.         this .expires = expiresFormat1.parse(value);  
  39.       } catch  (ParseException e) {  
  40.         try  {  
  41.           this .expires = expiresFormat2.parse(value);  
  42.         } catch  (ParseException e2) {  
  43.           throw   new  IllegalArgumentException(  
  44.             "Bad date format in header: "  + value);  
  45.         }  
  46.       }  
  47.     }  
  48.   }  

 
  Cookie类的其它方法只需要返回这些信息,或者检查有效日期就OK了:
java 代码
  1. public   boolean  hasExpired() {  
  2.   if  (expires ==  null ) {  
  3.     return   false ;  
  4.   }  
  5.   Date now = new  Date();  
  6.   return  now.after(expires);  
  7. }  
  8.   
  9. public  String toString() {  
  10.   StringBuilder result = new  StringBuilder(name);  
  11.   result.append("=" );  
  12.   result.append(value);  
  13.   return  result.toString();  
  14. }  

   对于一个已经过期的cookie,其matchs方法总是返回false:

    public boolean matches(URI uri) {

     if (hasExpired()) {
       return false;
     }

     String path = uri.getPath();
     if (path == null) {
       path = "/";
     }

     return path.startsWith(this.path);
   }

   注意:Cookie规范中要求同时检查域名以及路径,为了简单起见,我们这里只检查了路径.
   这里是Cookie的完整定义:

java 代码
 
  1.    import  java.net.*;  
  2.    import  java.text.*;  
  3.    import  java.util.*;  
  4.   
  5.    public   class  Cookie {  
  6.   
  7.      String name;  
  8.      String value;  
  9.      URI uri;  
  10.      String domain;  
  11.      Date expires;  
  12.      String path;  
  13.   
  14.      private   static  DateFormat expiresFormat1  
  15.         = new  SimpleDateFormat( "E, dd MMM yyyy k:m:s 'GMT'" , Locale.US);  
  16.   
  17.      private   static  DateFormat expiresFormat2  
  18.         = new  SimpleDateFormat( "E, dd-MMM-yyyy k:m:s 'GMT'" , Locale.US);  
  19.           
  20.   
  21.      /**  
  22.       * Construct a cookie from the URI and header fields  
  23.       *  
  24.       * @param uri URI for cookie  
  25.       * @param header Set of attributes in header  
  26.       */   
  27.      public  Cookie(URI uri, String header) {  
  28.        String attributes[] = header.split(";" );  
  29.        String nameValue = attributes[0 ].trim();  
  30.        this .uri = uri;  
  31.        this .name =   
  32.          nameValue.substring(0 , nameValue.indexOf('='));  
  33.        this .value =   
  34.          nameValue.substring(nameValue.indexOf('=')+1 );  
  35.        this .path =  "/" ;  
  36.        this .domain = uri.getHost();  
  37.   
  38.        for  ( int  i= 1 ; i < attributes.length; i++) {  
  39.          nameValue = attributes[i].trim();  
  40.          int  equals = nameValue.indexOf('=');  
  41.          if  (equals == - 1 ) {  
  42.            continue ;  
  43.          }  
  44.          String name = nameValue.substring(0 , equals);  
  45.          String value = nameValue.substring(equals+1 );  
  46.          if  (name.equalsIgnoreCase( "domain" )) {  
  47.            String uriDomain = uri.getHost();  
  48.            if  (uriDomain.equals(value)) {  
  49.              this .domain = value;  
  50.            } else  {  
  51.              if  (!value.startsWith( "." )) {  
  52.                value = "."  + value;  
  53.              }  
  54.              uriDomain = uriDomain.substring(  
  55.                uriDomain.indexOf('.' ));  
  56.              if  (!uriDomain.equals(value)) {  
  57.                throw   new  IllegalArgumentException(  
  58.                  "Trying to set foreign cookie" );  
  59.              }  

你可能感兴趣的:(Cache,网络协议,SUN,网络应用,浏览器)