Java Servlet和JSP教程 8-9-10-

来源:互联网 发布:法国 衰落知乎 编辑:程序博客网 时间:2024/06/06 11:48

8.1 HTTP应答头概述

   Web服务器的HTTP应答一般由以下几项构成:一个状态行,一个或多个应答头,一个空行,内容文档。设置HTTP应答头往往和设置状态行中的状态代码结合起来。例如,有好几个表示“文档位置已经改变”的状态代码都伴随着一个Location头,而401(Unauthorized)状态代码则必须伴随一个WWW-Authenticate头。

   然而,即使在没有设置特殊含义的状态代码时,指定应答头也是很有用的。应答头可以用来完成:设置Cookie,指定修改日期,指示浏览器按照指定的间隔刷新页面,声明文档的长度以便利用持久HTTP连接,……等等许多其他任务。

   设置应答头最常用的方法是HttpServletResponse的setHeader,该方法有两个参数,分别表示应答头的名字和值。和设置状态代码相似,设置应答头应该在发送任何文档内容之前进行。

   setDateHeader方法和setIntHeadr方法专门用来设置包含日期和整数值的应答头,前者避免了把Java时间转换为GMT时间字符串的麻烦,后者则避免了把整数转换为字符串的麻烦。

   HttpServletResponse还提供了许多设置常见应答头的简便方法,如下所示:

setContentType:设置Content-Type头。大多数Servlet都要用到这个方法。
setContentLength:设置Content-Length头。对于支持持久HTTP连接的浏览器来说,这个函数是很有用的。
addCookie:设置一个Cookie(Servlet API中没有setCookie方法,因为应答往往包含多个Set-Cookie头)。
另外,如上节介绍,sendRedirect方法设置状态代码302时也会设置Location头。
   8.2 常见应答头及其含义

   有关HTTP头详细和完整的说明,请参见http://www.w3.org/Protocols/规范。

应答头  说明  
Allow  服务器支持哪些请求方法(如GET、POST等)。  
Content-Encoding  文档的编码(Encode)方法。只有在解码之后才可以得到Content-Type头指定的内容类型。利用gzip压缩文档能够显著地减少HTML文档的下载时间。Java的GZIPOutputStream可以很方便地进行gzip压缩,但只有Unix上的Netscape和Windows上的IE 4、IE 5才支持它。因此,Servlet应该通过查看Accept-Encoding头(即request.getHeader("Accept-Encoding"))检查浏览器是否支持gzip,为支持gzip的浏览器返回经gzip压缩的HTML页面,为其他浏览器返回普通页面。  
Content-Length  表示内容长度。只有当浏览器使用持久HTTP连接时才需要这个数据。如果你想要利用持久连接的优势,可以把输出文档写入ByteArrayOutputStram,完成后查看其大小,然后把该值放入Content-Length头,最后通过byteArrayStream.writeTo(response.getOutputStream()发送内容。  
Content-Type  表示后面的文档属于什么MIME类型。Servlet默认为text/plain,但通常需要显式地指定为text/html。由于经常要设置Content-Type,因此HttpServletResponse提供了一个专用的方法setContentTyep。  
Date  当前的GMT时间。你可以用setDateHeader来设置这个头以避免转换时间格式的麻烦。  
Expires  应该在什么时候认为文档已经过期,从而不再缓存它?  
Last-Modified  文档的最后改动时间。客户可以通过If-Modified-Since请求头提供一个日期,该请求将被视为一个条件GET,只有改动时间迟于指定时间的文档才会返回,否则返回一个304(Not Modified)状态。Last-Modified也可用setDateHeader方法来设置。  
Location  表示客户应当到哪里去提取文档。Location通常不是直接设置的,而是通过HttpServletResponse的sendRedirect方法,该方法同时设置状态代码为302。  
Refresh  表示浏览器应该在多少时间之后刷新文档,以秒计。除了刷新当前文档之外,你还可以通过setHeader("Refresh", "5; URL=http://host/path")让浏览器读取指定的页面。
注意这种功能通常是通过设置HTML页面HEAD区的<META HTTP-EQUIV="Refresh" CONTENT="5;URL=http://host/path">实现,这是因为,自动刷新或重定向对于那些不能使用CGI或Servlet的HTML编写者十分重要。但是,对于Servlet来说,直接设置Refresh头更加方便。

注意Refresh的意义是“N秒之后刷新本页面或访问指定页面”,而不是“每隔N秒刷新本页面或访问指定页面”。因此,连续刷新要求每次都发送一个Refresh头,而发送204状态代码则可以阻止浏览器继续刷新,不管是使用Refresh头还是<META HTTP-EQUIV="Refresh" ...>。

注意Refresh头不属于HTTP 1.1正式规范的一部分,而是一个扩展,但Netscape和IE都支持它。

Server  服务器名字。Servlet一般不设置这个值,而是由Web服务器自己设置。  
Set-Cookie  设置和页面关联的Cookie。Servlet不应使用response.setHeader("Set-Cookie", ...),而是应使用HttpServletResponse提供的专用方法addCookie。参见下文有关Cookie设置的讨论。  
WWW-Authenticate  客户应该在Authorization头中提供什么类型的授权信息?在包含401(Unauthorized)状态行的应答中这个头是必需的。例如,response.setHeader("WWW-Authenticate", "BASIC realm=\"executives\"")。
注意Servlet一般不进行这方面的处理,而是让Web服务器的专门机制来控制受密码保护页面的访问(例如.htaccess)。



   8.3 实例:内容改变时自动刷新页面

   下面这个Servlet用来计算大素数。因为计算非常大的数字(例如500位)可能要花不少时间,所以Servlet将立即返回已经找到的结果,同时在后台继续计算。后台计算使用一个优先级较低的线程以避免过多地影响Web服务器的性能。如果计算还没有完成,Servlet通过发送Refresh头指示浏览器在几秒之后继续请求新的内容。

   注意,本例除了说明HTTP应答头的用处之外,还显示了Servlet的另外两个很有价值的功能。首先,它表明Servlet能够处理多个并发的连接,每个都有自己的线程。Servlet维护了一份已有素数计算请求的Vector表,通过查找素数个数(素数列表的长度)和数字个数(每个素数的长度)将当前请求和已有请求相匹配,把所有这些请求同步到这个列表上。第二,本例证明,在Servlet中维持请求之间的状态信息是非常容易的。维持状态信息在传统的CGI编程中是一件很麻烦的事情。由于维持了状态信息,浏览器能够在刷新页面时访问到正在进行的计算过程,同时也使得Servlet能够保存一个有关最近请求结果的列表,当一个新的请求指定了和最近请求相同的参数时可以立即返回结果。

   PrimeNumbers.java

   注意,该Servlet要用到前面给出的ServletUtilities.java。另外还要用到:PrimeList.java,用于在后台线程中创建一个素数的Vector;Primes.java,用于随机生成BigInteger类型的大数字,检查它们是否是素数。(此处略去PrimeList.java和Primes.java的代码。)
package hall;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;

public class PrimeNumbers extends HttpServlet {
  private static Vector primeListVector = new Vector();
  private static int maxPrimeLists = 30;
  
  public void doGet(HttpServletRequest request,
                    HttpServletResponse response)
      throws ServletException, IOException {
    int numPrimes = ServletUtilities.getIntParameter(request, "numPrimes", 50);
    int numDigits = ServletUtilities.getIntParameter(request, "numDigits", 120);
    PrimeList primeList = findPrimeList(primeListVector, numPrimes, numDigits);
    if (primeList == null) {
      primeList = new PrimeList(numPrimes, numDigits, true);
      synchronized(primeListVector) {
        if (primeListVector.size() >= maxPrimeLists)
          primeListVector.removeElementAt(0);
        primeListVector.addElement(primeList);
      }
    }
    Vector currentPrimes = primeList.getPrimes();
    int numCurrentPrimes = currentPrimes.size();
    int numPrimesRemaining = (numPrimes - numCurrentPrimes);
    boolean isLastResult = (numPrimesRemaining == 0);
    if (!isLastResult) {
      response.setHeader("Refresh", "5");
    }
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    String title = "Some " + numDigits + "-Digit Prime Numbers";
    out.println(ServletUtilities.headWithTitle(title) +
                "<BODY BGCOLOR=\"#FDF5E6\">\n" +
                "<H2 ALIGN=CENTER>" + title + "</H2>\n" +
                "<H3>Primes found with " + numDigits +
                " or more digits: " + numCurrentPrimes + ".</H3>");
    if (isLastResult)
      out.println("<B>Done searching.</B>");
    else
      out.println("<B>Still looking for " + numPrimesRemaining +
                  " more<BLINK>...</BLINK></B>");
    out.println("<OL>");
    for(int i=0; i<numCurrentPrimes; i++) {
      out.println("  <LI>" + currentPrimes.elementAt(i));
    }
    out.println("</OL>");
    out.println("</BODY></HTML>");
  }

  public void doPost(HttpServletRequest request,
                     HttpServletResponse response)
      throws ServletException, IOException {
    doGet(request, response);
  }

  // 检查是否存在同类型请求(已经完成,或者正在计算)。
  // 如存在,则返回现有结果而不是启动新的后台线程。
  private PrimeList findPrimeList(Vector primeListVector,
                             int numPrimes,
                             int numDigits) {
    synchronized(primeListVector) {
      for(int i=0; i<primeListVector.size(); i++) {
        PrimeList primes = (PrimeList)primeListVector.elementAt(i);
        if ((numPrimes == primes.numPrimes()) &&
            (numDigits == primes.numDigits()))
          return(primes);
      }
      return(null);
    }
  }
}




   PrimeNumbers.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
  <TITLE>大素数计算</TITLE>
</HEAD>
<CENTER>
<BODY BGCOLOR="#FDF5E6">
<FORM ACTION="/servlet/hall.PrimeNumbers">
  <B>要计算几个素数:</B>
  <INPUT TYPE="TEXT" NAME="numPrimes" VALUE=25 SIZE=4><BR>
  <B>每个素数的位数:</B>
  <INPUT TYPE="TEXT" NAME="numDigits" VALUE=150 SIZE=3><BR>
  <INPUT TYPE="SUBMIT" VALUE="开始计算">
</FORM>
</CENTER>
</BODY>
</HTML>

9.1 Cookie概述

   Cookie是服务器发送给浏览器的体积很小的纯文本信息,用户以后访问同一个Web服务器时浏览器会把它们原样发送给服务器。通过让服务器读取它原先保存到客户端的信息,网站能够为浏览者提供一系列的方便,例如在线交易过程中标识用户身份、安全要求不高的场合避免用户重复输入名字和密码、门户网站的主页定制、有针对性地投放广告,等等。

   Cookie的目的就是为用户带来方便,为网站带来增值。虽然有着许多误传,事实上Cookie并不会造成严重的安全威胁。Cookie永远不会以任何方式执行,因此也不会带来病毒或攻击你的系统。另外,由于浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4 KB,因此Cookie不会塞满你的硬盘,更不会被用作“拒绝服务”攻击手段。

   9.2 Servlet的Cookie API

   要把Cookie发送到客户端,Servlet先要调用new Cookie(name,value)用合适的名字和值创建一个或多个Cookie(2.1节),通过cookie.setXXX设置各种属性(2.2节),通过response.addCookie(cookie)把cookie加入应答头(2.3节)。

   要从客户端读入Cookie,Servlet应该调用request.getCookies(),getCookies()方法返回一个Cookie对象的数组。在大多数情况下,你只需要用循环访问该数组的各个元素寻找指定名字的Cookie,然后对该Cookie调用getValue方法取得与指定名字关联的值,这部分内容将在2.4节讨论。

   9.2.1 创建Cookie

   调用Cookie对象的构造函数可以创建Cookie。Cookie对象的构造函数有两个字符串参数:Cookie名字和Cookie值。名字和值都不能包含空白字符以及下列字符:
  [ ] ( ) = , " / ? @ : ;




   9.2.2 读取和设置Cookie属性

   把Cookie加入待发送的应答头之前,你可以查看或设置Cookie的各种属性。下面摘要介绍这些方法:

getComment/setComment
获取/设置Cookie的注释。
getDomain/setDomain
获取/设置Cookie适用的域。一般地,Cookie只返回给与发送它的服务器名字完全相同的服务器。使用这里的方法可以指示浏览器把Cookie返回给同一域内的其他服务器。注意域必须以点开始(例如.sitename.com),非国家类的域(如.com,.edu,.gov)必须包含两个点,国家类的域(如.com.cn,.edu.uk)必须包含三个点。
getMaxAge/setMaxAge
获取/设置Cookie过期之前的时间,以秒计。如果不设置该值,则Cookie只在当前会话内有效,即在用户关闭浏览器之前有效,而且这些Cookie不会保存到磁盘上。参见下面有关LongLivedCookie的说明。
getName/setName
获取/设置Cookie的名字。本质上,名字和值是我们始终关心的两个部分。由于HttpServletRequest的getCookies方法返回的是一个Cookie对象的数组,因此通常要用循环来访问这个数组查找特定名字,然后用getValue检查它的值。
getPath/setPath
获取/设置Cookie适用的路径。如果不指定路径,Cookie将返回给当前页面所在目录及其子目录下的所有页面。这里的方法可以用来设定一些更一般的条件。例如,someCookie.setPath("/"),此时服务器上的所有页面都可以接收到该Cookie。
getSecure/setSecure
获取/设置一个boolean值,该值表示是否Cookie只能通过加密的连接(即SSL)发送。
getValue/setValue
获取/设置Cookie的值。如前所述,名字和值实际上是我们始终关心的两个方面。不过也有一些例外情况,比如把名字作为逻辑标记(也就是说,如果名字存在,则表示true)。
getVersion/setVersion
获取/设置Cookie所遵从的协议版本。默认版本0(遵从原先的Netscape规范);版本1遵从RFC 2109 , 但尚未得到广泛的支持。
   9.2.3 在应答头中设置Cookie

   Cookie可以通过HttpServletResponse的addCookie方法加入到Set-Cookie应答头。下面是一个例子:
  Cookie userCookie = new Cookie("user", "uid1234");
  response.addCookie(userCookie);




   9.2.4 读取保存到客户端的Cookie

   要把Cookie发送到客户端,先要创建Cookie,然后用addCookie发送一个Set-Cookie HTTP应答头。这些内容已经在上面的2.1节介绍。从客户端读取Cookie时调用的是HttpServletRequest的getCookies方法。该方法返回一个与HTTP请求头中的内容对应的Cookie对象数组。得到这个数组之后,一般是用循环访问其中的各个元素,调用getName检查各个Cookie的名字,直至找到目标Cookie。然后对这个目标Cookie调用getValue,根据获得的结果进行其他处理。

   上述处理过程经常会遇到,为方便计下面我们提供一个getCookieValue方法。只要给出Cookie对象数组、Cookie名字和默认值,getCookieValue方法就会返回匹配指定名字的Cookie值,如果找不到指定Cookie,则返回默认值。

   9.3 几个Cookie工具函数

   下面是几个工具函数。这些函数虽然简单,但是,在和Cookie打交道的时候很有用。

   9.3.1 获取指定名字的Cookie值

   该函数是ServletUtilities.java的一部分。getCookieValue通过循环依次访问Cookie对象数组的各个元素,寻找是否有指定名字的Cookie,如找到,则返回该Cookie的值;否则,返回参数中给出的默认值。getCookieValue能够在一定程度上简化Cookie值的提取。
  public static String getCookieValue(Cookie[] cookies,
               String cookieName,
               String defaultValue) {
    for(int i=0; i<cookies.length; i++) {
      Cookie cookie = cookies[i];
      if (cookieName.equals(cookie.getName()))
        return(cookie.getValue());
    }
    return(defaultValue);
  }




   9.3.2自动保存的Cookie

   下面是LongLivedCookie类的代码。如果你希望Cookie能够在浏览器退出的时候自动保存下来,则可以用这个LongLivedCookie类来取代标准的Cookie类。
package hall;

import javax.servlet.http.*;

public class LongLivedCookie extends Cookie {
  public static final int SECONDS_PER_YEAR = 60*60*24*365;
  public LongLivedCookie(String name, String value) {
    super(name, value);
    setMaxAge(SECONDS_PER_YEAR);
  }
}




   9.4.实例:定制的搜索引擎界面

   下面也是一个搜索引擎界面的例子,通过修改前面HTTP状态代码的例子得到。在这个Servlet中,用户界面是动态生成而不是由静态HTML文件提供的。Servlet除了负责读取表单数据并把它们发送给搜索引擎之外,还要把包含表单数据的Cookie发送给客户端。以后客户再次访问同一表单时,这些Cookie的值将用来预先填充表单,使表单自动显示最近使用过的数据。

   SearchEnginesFrontEnd.java

   该Servlet构造一个主要由表单构成的用户界面。第一次显示的时候,它和前面用静态HTML页面提供的界面差不多。然而,用户选择的值将被保存到Cookie(本页面将数据发送到CustomizedSearchEngines Servlet,由后者设置Cookie)。用户以后再访问同一页面时,即使浏览器是退出之后再启动,表单中也会自动填好上一次搜索所填写的内容。

   注意该Servlet用到了ServletUtilities.java,其中getCookieValue前面已经介绍过,headWithTitle用于生成HTML页面的一部分。另外,这里也用到了前面已经说明的LongLiveCookie类,我们用它来创建作废期限很长的Cookie。
package hall;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.*;

public class SearchEnginesFrontEnd extends HttpServlet {
  public void doGet(HttpServletRequest request,
                 HttpServletResponse response)
      throws ServletException, IOException {
    Cookie[] cookies = request.getCookies();
    String searchString =
      ServletUtilities.getCookieValue(cookies,
             "searchString",
             "Java Programming");
    String numResults =
      ServletUtilities.getCookieValue(cookies,
             "numResults",
             "10");
    String searchEngine =
      ServletUtilities.getCookieValue(cookies,
              "searchEngine",
              "google");
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    String title = "Searching the Web";
    out.println(ServletUtilities.headWithTitle(title) +
                "<BODY BGCOLOR=\"#FDF5E6\">\n" +
                "<H1 ALIGN=\"CENTER\">Searching the Web</H1>\n" +
                "\n" +
                "<FORM ACTION=\"/servlet/hall.CustomizedSearchEngines\">\n" +
                "<CENTER>\n" +
                "Search String:\n" +
                "<INPUT TYPE=\"TEXT\" NAME=\"searchString\"\n" +
                "       VALUE=\"" + searchString + "\"><BR>\n" +
                "Results to Show Per Page:\n" +
                "<INPUT TYPE=\"TEXT\" NAME=\"numResults\"\n" +
                "       VALUE=" + numResults + " SIZE=3><BR>\n" +
                "<INPUT TYPE=\"RADIO\" NAME=\"searchEngine\"\n" +
                "       VALUE=\"google\"" +
                checked("google", searchEngine) + ">\n" +
                "Google |\n" +
                "<INPUT TYPE=\"RADIO\" NAME=\"searchEngine\"\n" +
                "       VALUE=\"infoseek\"" +
                checked("infoseek", searchEngine) + ">\n" +
                "Infoseek |\n" +
                "<INPUT TYPE=\"RADIO\" NAME=\"searchEngine\"\n" +
                "       VALUE=\"lycos\"" +
                checked("lycos", searchEngine) + ">\n" +
                "Lycos |\n" +
                "<INPUT TYPE=\"RADIO\" NAME=\"searchEngine\"\n" +
                "       VALUE=\"hotbot\"" +
                checked("hotbot", searchEngine) + ">\n" +
                "HotBot\n" +
                "<BR>\n" +
                "<INPUT TYPE=\"SUBMIT\" VALUE=\"Search\">\n" +
                "</CENTER>\n" +
                "</FORM>\n" +
                "\n" +
                "</BODY>\n" +
                "</HTML>\n");
  }

  private String checked(String name1, String name2) {
    if (name1.equals(name2))
      return(" CHECKED");
    else
      return("");
  }
}




   CustomizedSearchEngines.java

   前面的SearchEnginesFrontEnd Servlet把数据发送到CustomizedSearchEngines Servlet。本例在许多方面与前面介绍HTTP状态代码时的例子相似,区别在于,本例除了要构造一个针对搜索引擎的URL并向用户发送一个重定向应答之外,还要发送保存用户数据的Cookies。
package hall;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.*;

public class CustomizedSearchEngines extends HttpServlet {
  public void doGet(HttpServletRequest request,
                    HttpServletResponse response)
      throws ServletException, IOException {
    
    String searchString = request.getParameter("searchString");
    Cookie searchStringCookie =
      new LongLivedCookie("searchString", searchString);
    response.addCookie(searchStringCookie);
    searchString = URLEncoder.encode(searchString);
    String numResults = request.getParameter("numResults");
    Cookie numResultsCookie =
      new LongLivedCookie("numResults", numResults);
    response.addCookie(numResultsCookie);
    String searchEngine = request.getParameter("searchEngine");
    Cookie searchEngineCookie =
      new LongLivedCookie("searchEngine", searchEngine);
    response.addCookie(searchEngineCookie);
    SearchSpec[] commonSpecs = SearchSpec.getCommonSpecs();
    for(int i=0; i<commonSpecs.length; i++) {
      SearchSpec searchSpec = commonSpecs[i];
      if (searchSpec.getName().equals(searchEngine)) {
        String url =
          searchSpec.makeURL(searchString, numResults);
        response.sendRedirect(url);
        return;
      }
    }
    response.sendError(response.SC_NOT_FOUND,
                       "No recognized search engine specified.");
  }

  public void doPost(HttpServletRequest request,
                     HttpServletResponse response)
      throws ServletException, IOException {
    doGet(request, response);
  }
}

10.1 会话状态概述

   HTTP协议的“无状态”(Stateless)特点带来了一系列的问题。特别是通过在线商店购物时,服务器不能顺利地记住以前的事务就成了严重的问题。它使得“购物篮”之类的应用很难实现:当我们把商品加入购物篮时,服务器如何才能知道篮子里原先有些什么?即使服务器保存了上下文信息,我们仍旧会在电子商务应用中遇到问题。例如,当用户从选择商品的页面(由普通的服务器提供)转到输入信用卡号和送达地址的页面(由支持SSL的安全服务器提供),服务器如何才能记住用户买了些什么?

   这个问题一般有三种解决方法:

Cookie。利用HTTP Cookie来存储有关购物会话的信息,后继的各个连接可以查看当前会话,然后从服务器的某些地方提取有关该会话的完整信息。这是一种优秀的,也是应用最广泛的方法。然而,即使Servlet提供了一个高级的、使用方便的Cookie接口,仍旧有一些繁琐的细节问题需要处理:
从其他Cookie中分别出保存会话标识的Cookie。
为Cookie设置合适的作废时间(例如,中断时间超过24小时的会话一般应重置)。
把会话标识和服务器端相应的信息关联起来。(实际保存的信息可能要远远超过保存到Cookie的信息,而且象信用卡号等敏感信息永远不应该用Cookie来保存。)
改写URL。你可以把一些标识会话的数据附加到每个URL的后面,服务器能够把该会话标识和它所保存的会话数据关联起来。这也是一个很好的方法,而且还有当浏览器不支持Cookie或用户已经禁用Cookie的情况下也有效这一优点。然而,大部分使用Cookie时所面临的问题同样存在,即服务器端的程序要进行许多简单但单调冗长的处理。另外,还必须十分小心地保证每个URL后面都附加了必要的信息(包括非直接的,如通过Location给出的重定向URL)。如果用户结束会话之后又通过书签返回,则会话信息会丢失。
隐藏表单域。HTML表单中可以包含下面这样的输入域:<INPUT TYPE="HIDDEN" NAME="session" VALUE="...">。这意味着,当表单被提交时,隐藏域的名字和数据也被包含到GET或POST数据里,我们可以利用这一机制来维持会话信息。然而,这种方法有一个很大的缺点,它要求所有页面都是动态生成的,因为整个问题的核心就是每个会话都要有一个唯一标识符。
   Servlet为我们提供了一种与众不同的方案:HttpSession API。HttpSession API是一个基于Cookie或者URL改写机制的高级会话状态跟踪接口:如果浏览器支持Cookie,则使用Cookie;如果浏览器不支持Cookie或者Cookie功能被关闭,则自动使用URL改写方法。Servlet开发者无需关心细节问题,也无需直接处理Cookie或附加到URL后面的信息,API自动为Servlet开发者提供一个可以方便地存储会话信息的地方。

   10.2 会话状态跟踪API

   在Servlet中使用会话信息是相当简单的,主要的操作包括:查看和当前请求关联的会话对象,必要的时候创建新的会话对象,查看与某个会话相关的信息,在会话对象中保存信息,以及会话完成或中止时释放会话对象。

   10.2.1 查看当前请求的会话对象

   查看当前请求的会话对象通过调用HttpServletRequest的getSession方法实现。如果getSession方法返回null,你可以创建一个新的会话对象。但更经常地,我们通过指定参数使得不存在现成的会话时自动创建一个会话对象,即指定getSession的参数为true。因此,访问当前请求会话对象的第一个步骤通常如下所示:
  HttpSession session = request.getSession(true);



   10.2.2 查看和会话有关的信息

   HttpSession对象生存在服务器上,通过Cookie或者URL这类后台机制自动关联到请求的发送者。会话对象提供一个内建的数据结构,在这个结构中可以保存任意数量的键-值对。在2.1或者更早版本的Servlet API中,查看以前保存的数据使用的是getValue("key")方法。getValue返回Object,因此你必须把它转换成更加具体的数据类型。如果参数中指定的键不存在,getValue返回null。

   API 2.2版推荐用getAttribute来代替getValue,这不仅是因为getAttribute和setAttribute的名字更加匹配(和getValue匹配的是putValue,而不是setValue),同时也因为setAttribute允许使用一个附属的HttpSessionBindingListener 来监视数值,而putValue则不能。

   但是,由于目前还只有很少的商业Servlet引擎支持2.2,下面的例子中我们仍旧使用getValue。这是一个很典型的例子,假定ShoppingCart是一个保存已购买商品信息的类:
  HttpSession session = request.getSession(true);
  ShoppingCart previousItems =
    (ShoppingCart)session.getValue("previousItems");
  if (previousItems != null) {
    doSomethingWith(previousItems);
  } else {
    previousItems = new ShoppingCart(...);
    doSomethingElseWith(previousItems);
  }



   大多数时候我们都是根据特定的名字寻找与它关联的值,但也可以调用getValueNames得到所有属性的名字。getValuesNames返回的是一个String数组。API 2.2版推荐使用getAttributeNames,这不仅是因为其名字更好,而且因为它返回的是一个Enumeration,和其他方法(比如HttpServletRequest的getHeaders和getParameterNames)更加一致。

   虽然开发者最为关心的往往是保存到会话对象的数据,但还有其他一些信息有时也很有用。

getID:该方法返回会话的唯一标识。有时该标识被作为键-值对中的键使用,比如会话中只保存一个值时,或保存上一次会话信息时。
isNew:如果客户(浏览器)还没有绑定到会话则返回true,通常意味着该会话刚刚创建,而不是引用自客户端的请求。对于早就存在的会话,返回值为false。
getCreationTime:该方法返回建立会话的以毫秒计的时间,从1970.01.01(GMT)算起。要得到用于打印输出的时间值,可以把该值传递给Date构造函数,或者GregorianCalendar的setTimeInMillis方法。
getLastAccessedTime:该方法返回客户最后一次发送请求的以毫秒计的时间,从1970.01.01(GMT)算起。
getMaxInactiveInterval:返回以秒计的最大时间间隔,如果客户请求之间的间隔不超过该值,Servlet引擎将保持会话有效。负数表示会话永远不会超时。
   10.2.3 在会话对象中保存数据

   如上节所述,读取保存在会话中的信息使用的是getValue方法(或,对于2.2版的Servlet规范,使用getAttribute)。保存数据使用putValue(或setAttribute)方法,并指定键和相应的值。注意putValue将替换任何已有的值。有时候这正是我们所需要的(如下例中的referringPage),但有时我们却需要提取原来的值并扩充它(如下例previousItems)。示例代码如下:
  HttpSession session = request.getSession(true);
  session.putValue("referringPage", request.getHeader("Referer"));
  ShoppingCart previousItems =
    (ShoppingCart)session.getValue("previousItems");
  if (previousItems == null) {
    previousItems = new ShoppingCart(...);
  }
  String itemID = request.getParameter("itemID");
  previousItems.addEntry(Catalog.getEntry(itemID));

  session.putValue("previousItems", previousItems);



   10.3 实例:显示会话信息

   下面这个例子生成一个Web页面,并在该页面中显示有关当前会话的信息。
package hall;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.*;
import java.util.*;

public class ShowSession extends HttpServlet {
  public void doGet(HttpServletRequest request,
                 HttpServletResponse response)
      throws ServletException, IOException {
    HttpSession session = request.getSession(true);
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    String title = "Searching the Web";
    String heading;
    Integer accessCount = new Integer(0);;
    if (session.isNew()) {
      heading = "Welcome, Newcomer";
    } else {
      heading = "Welcome Back";
      Integer oldAccessCount =
        // 在Servlet API 2.2中使用getAttribute而不是getValue
        (Integer)session.getValue("accessCount");
      if (oldAccessCount != null) {
        accessCount =
          new Integer(oldAccessCount.intValue() + 1);
      }
    }
    // 在Servlet API 2.2中使用putAttribute
    session.putValue("accessCount", accessCount);
      
    out.println(ServletUtilities.headWithTitle(title) +
                "<BODY BGCOLOR=\"#FDF5E6\">\n" +
                "<H1 ALIGN=\"CENTER\">" + heading + "</H1>\n" +
                "<H2>Information on Your Session:</H2>\n" +
                "<TABLE BORDER=1 ALIGN=CENTER>\n" +
                "<TR BGCOLOR=\"#FFAD00\">\n" +
                "  <TH>Info Type<TH>Value\n" +
                "<TR>\n" +
                "  <TD>ID\n" +
                "  <TD>" + session.getId() + "\n" +
                "<TR>\n" +
                "  <TD>Creation Time\n" +
                "  <TD>" + new Date(session.getCreationTime()) + "\n" +
                "<TR>\n" +
                "  <TD>Time of Last Access\n" +
                "  <TD>" + new Date(session.getLastAccessedTime()) + "\n" +
                "<TR>\n" +
                "  <TD>Number of Previous Accesses\n" +
                "  <TD>" + accessCount + "\n" +
                "</TABLE>\n" +
                "</BODY></HTML>");
  }
  public void doPost(HttpServletRequest request,
                 HttpServletResponse response)
      throws ServletException, IOException {
    doGet(request, response);
  }
}

原创粉丝点击