Java Web高级编程(二)

使用会话维持状态

一、会话

为了实现关联同一个用户端的多个请求和这些请求之间数据的共享,需要用到会话,会话用于维持请求和请求之间的状态。从服务器的角度,当用户的Web浏览器打开第一个链接到服务器的套接字时请求就开始了,直到服务器返回最后一个数据包并关闭链接是,该请求将结束。此时用户浏览器和服务器之间不再有任何的联系,当下一个链接开始时,无法将新的请求和之前的请求关联起来。

维持状态

最经典的例子就是在线购物网站需要用购物车来保证用户和商品都能够被保持。

记住用户

这样的例子是用户论坛网站,在多个操作中,用户只需要登录一次。

启动任务程序工作流

用户在使用Web应用程序完成某个任务时,需要某种形式的工作流,比如新闻的发布。

二、使用会话cookie和URL重写。

会话是由服务器或Web应用程序管理的某些文件、内存片段、对象或者容器,它包含了分配给它的各种不同数据。

通常会话被赋予一个随机生成的字符串,称为会话ID。第一次创建会话时,创建的会话ID会作为响应的一部分返回到用户的浏览器中。接下来从该用户浏览器发出的请求都将通过某种方式包含这个会话ID。当应用程序收到含有会话ID的请求时,它可以通过该ID将现有会话和当前请求关联起来。

实现会话ID从服务器返回到浏览器中的方法包括会话cookie和URL重写。

会话cookie

这种技术也叫HTTP cookie。cookie是一种必要的通信机制,可以通过Set-Cookie响应头在服务器和浏览器中传递任意数据,并存储在用户计算机中,然后再通过请求头Cookie从浏览器返回到服务器中。cookie包含了域名、路径、过期日期或最大生命周期,安全标志或只含有HTTP标志。会话cookie的名字默认为JSESSIONID。

Domain将告诉浏览器应该将cookie发送到哪个域名中,

Path则进一步将cookie限制在相对于域的某个特定的URL中,

Expries定义了cookie的绝对过期日期,

如果存在Secure特性,浏览器只会通过HTTPS发送cookie,进行加密传输,

HttpOnly将cookie限制在浏览器,避免JavaScript和flash。

URL中的会话ID

另一种传输会话ID的方式是通过URL,Web服务器直到如何查找URL中包含会话ID的特定模式,不同的技术对如何在URL中内嵌和定位会话ID使用不同的策略。在Java EE中,会话ID被添加到URL的最后一个路径段的举证参数中,通过这种方式会分离开会话ID与查询字符串的参数。例如:

http://www.example.com/supprot;JSESSIONID=NRxclGg2vG7kI4MdlLn?foo=bar

必须将会话ID内嵌在应用程序返回的所有URL中,包括页面的链接、表单操作以及重定向。

HttpServletResponse接口定义了两个重写URL的方法:encodeURL和encodeRedirectURL,它们将在必要的时候把会话ID内嵌在URL中。

漏洞

复制粘贴错误,会话固定,跨站脚本和会话劫持,不安全的cookie

三、在会话中存储数据

在部署描述符中配置会话:

<session-config>
        <session-timeout>30</session-timeout>
        <cookie-config>
            <http-only>true</http-only>
        </cookie-config>
        <tracking-mode>COOKIE</tracking-mode>
    </session-config>

所有的<session-config>和<cookie-config>标签都是可选的,至于标签的作用可以另行查看。使用上述配置,会话超时时间为30min,只接受cookie用于会话追踪,。

存储,删除和获取数据:

在Servlet中创建map,可以使用该Map来进行数据的相关操作。

private final Map<Integer, String> products = new Hashtable<>();

    public StoreServlet()
    {
        this.products.put(1, "Sandpaper");
        this.products.put(2, "Nails");
        this.products.put(3, "Glue");
        this.products.put(4, "Paint");
        this.products.put(5, "Tape");
    }

1.doGet方法中的使用:

@Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {
        String action = request.getParameter("action");
        if(action == null)
            action = "browse";

        switch(action)
        {
            case "addToCart":
                this.addToCart(request, response);
                break;

            case "emptyCart":
                this.emptyCart(request, response);
                break;

            case "viewCart":
                this.viewCart(request, response);
                break;

            case "browse":
            default:
                this.browse(request, response);
                break;
        }
    }

    private void addToCart(HttpServletRequest request,
                           HttpServletResponse response)
            throws ServletException, IOException
    {
        int productId;
        try
        {
            productId = Integer.parseInt(request.getParameter("productId"));
        }
        catch(Exception e)
        {
            response.sendRedirect("shop");
            return;
        }

        HttpSession session = request.getSession();
        if(session.getAttribute("cart") == null)
            session.setAttribute("cart", new Hashtable<Integer, Integer>());

        @SuppressWarnings("unchecked")
        Map<Integer, Integer> cart =
                (Map<Integer, Integer>)session.getAttribute("cart");
        if(!cart.containsKey(productId))
            cart.put(productId, 0);
        cart.put(productId, cart.get(productId) + 1);

        response.sendRedirect("shop?action=viewCart");
    }

    private void emptyCart(HttpServletRequest request,
                           HttpServletResponse response)
            throws ServletException, IOException
    {
        request.getSession().removeAttribute("cart");
        response.sendRedirect("shop?action=viewCart");
    }

    private void viewCart(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {
        request.setAttribute("products", this.products);
        request.getRequestDispatcher("/WEB-INF/jsp/view/viewCart.jsp")
                .forward(request, response);
    }

    private void browse(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {
        request.setAttribute("products", this.products);
        request.getRequestDispatcher("/WEB-INF/jsp/view/browse.jsp")
               .forward(request, response);
    }

四、将使用会话的用户群集化

集群为应用程序增加了冗余和可扩展性,经过正确配置的群集应用程序即使在遇到某些服务器终止时也能够正常运行,是指在执行日常维护工作时也可以正常处理用户请求。管理员甚至可以升级应用程序,并保证应用程序不会终止对请求的处理。

Advanced Message Queuing Protocol(AMQP)、Java Message Service(JMS)、Microsoft Message Queuing(MSMQ)。

问题:会话一对象的方式存在于内存中,并且只存在于Web容器的单个实例中,来自同一个客户端的两个连续请求将会访问不同的Web容器,而第一个容器分配的ID,第二个容器无法识别。

解决:使用粘滞会话:使负载均衡机制能够感知到会话,并且总是将来自于同一会话的请求发送到相同的服务器。(取决于负载均衡技术,比如负载均衡器在响应中添加他们自己的会话cookie,并在后学的请求中识别这些cookie)。

相关推荐