session了解及超时处理

Session了解

Session是什么
引言
    在web开发中,session是个很是重要的概念。在许多动态网站的开发者看来,session就是一个变量,并且其表现像个黑洞,他只须要将东西在合适的时机放进这个洞里,等须要的时候再把东西取出来。这是开发者对session最直观的感觉,可是黑洞里的景象或者说session内部究竟是怎么工做的呢?当笔者向身边的一些同事或朋友问及相关的更进一步的细节时,不少人每每要么含糊其辞要么主观臆断,所谓知其然而不知其因此然。javascript

笔者由此想到不少开发者,包括我本身,往往都是纠缠于框架甚至二次开发平台之上,而对于其下的核心和基础知之甚少,或者有心无力甚至绝不关心,少了逐本溯源的精神,每忆及此,无不惭愧。曾经实现过一个简单的HttpServer,但当时因为知识储备和时间的问题,没有考虑到session这块,不过近期在工做之余翻看了一些资料,并进行了相关实践,小有所得,本着分享的精神,我将在本文中尽量全面地将我的对于session的理解展示给读者,同时尽我所能地论及一些相关的知识,以期读者在对session有所了解的同时也能另有所悟,正所谓授人以渔。php

Session是什么
    Session通常译做会话,牛津词典对其的解释是进行某活动连续的一段时间。从不一样的层面看待session,它有着相似但不全然相同的含义。好比,在web应用的用户看来,他打开浏览器访问一个电子商务网站,登陆、并完成购物直到关闭浏览器,这是一个会话。而在web应用的开发者开来,用户登陆时我须要建立一个数据结构以存储用户的登陆信息,这个结构也叫作session。所以在谈论session的时候要注意上下文环境。而本文谈论的是一种基于HTTP协议的用以加强web应用能力的机制或者说一种方案,它不是单指某种特定的动态页面技术,而这种能力就是保持状态,也能够称做保持会话。css

为何须要session
    谈及session通常是在web应用的背景之下,咱们知道web应用是基于HTTP协议的,而HTTP协议偏偏是一种无状态协议。也就是说,用户从A页面跳转到B页面会从新发送一次HTTP请求,而服务端在返回响应的时候是没法获知该用户在请求B页面以前作了什么的。html

    对于HTTP的无状态性的缘由,相关RFC里并无解释,但联系到HTTP的历史以及应用场景,咱们能够推测出一些理由:java

1.  设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。那个时候没有动态页面技术,只有纯粹的静态HTML页面,所以根本不须要协议能保持状态;jquery

2.  用户在收到响应时,每每要花一些时间来阅读页面,所以若是保持客户端和服务端之间的链接,那么这个链接在大多数的时间里都将是空闲的,这是一种资源的无故浪费。因此HTTP原始的设计是默认短链接,即客户端和服务端完成一次请求和响应以后就断开TCP链接,服务器所以没法预知客户端的下一个动做,它甚至都不知道这个用户会不会再次访问,所以让HTTP协议来维护用户的访问状态也全然没有必要;程序员

3.  将一部分复杂性转嫁到以HTTP协议为基础的技术之上可使得HTTP在协议这个层面上显得相对简单,而这种简单也赋予了HTTP更强的扩展能力。事实上,session技术从本质上来说也是对HTTP协议的一种扩展。web

总而言之,HTTP的无状态是由其历史使命而决定的。但随着网络技术的蓬勃发展,人们不再知足于死板乏味的静态HTML,他们但愿web应用能动起来,因而客户端出现了脚本和DOM技术,HTML里增长了表单,而服务端出现了CGI等等动态技术。ajax

而正是这种web动态化的需求,给HTTP协议提出了一个难题:一个无状态的协议怎样才能关联两次连续的请求呢?也就是说无状态的协议怎样才能知足有状态的需求呢?算法

此时有状态是必然趋势而协议的无状态性也是木已成舟,所以咱们须要一些方案来解决这个矛盾,来保持HTTP链接状态,因而出现了cookie和session。

对于此部份内容,读者或许会有一些疑问,笔者在此先谈两点:

1.  无状态性和长链接

可能有人会问,如今被普遍使用的HTTP1.1默认使用长链接,它仍是无状态的吗?

链接方式和有无状态是彻底没有关系的两回事。由于状态从某种意义上来说就是数据,而链接方式只是决定了数据的传输方式,而不能决定数据。长链接是随着计算机性能的提升和网络环境的改善所采起的一种合理的性能上的优化,通常状况下,web服务器会对长链接的数量进行限制,以避免资源的过分消耗。

2.  无状态性和session

        Session是有状态的,而HTTP协议是无状态的,两者是否矛盾呢?

    Session和HTTP协议属于不一样层面的事物,后者属于ISO七层模型的最高层应用层,前者不属于后者,前者是具体的动态页面技术来实现的,但同时它又是基于后者的。在下文中笔者会分析Servlet/Jsp技术中的session机制,这会使你对此有更深入的理解。

Cookie和Session
    上面提到解决HTTP协议自身无状态的方式有cookie和session。两者都能记录状态,前者是将状态数据保存在客户端,后者则保存在服务端。

    首先看一下cookie的工做原理,这须要有基本的HTTP协议基础。

cookie是在RFC2109(已废弃,被RFC2965取代)里初次被描述的,每一个客户端最多保持三百个cookie,每一个域名下最多20个Cookie(实际上通常浏览器如今都比这个多,如Firefox是50个),而每一个cookie的大小为最多4K,不过不一样的浏览器都有各自的实现。对于cookie的使用,最重要的就是要控制cookie的大小,不要放入无用的信息,也不要放入过多信息。

    不管使用何种服务端技术,只要发送回的HTTP响应中包含以下形式的头,则视为服务器要求设置一个cookie:

Set-cookie:name=name;expires=date;path=path;domain=domain

    支持cookie的浏览器都会对此做出反应,即建立cookie文件并保存(也多是内存cookie),用户之后在每次发出请求时,浏览器都要判断当前全部的cookie中有没有没失效(根据expires属性判断)而且匹配了path属性的cookie信息,若是有的话,会如下面的形式加入到请求头中发回服务端:

    Cookie: name="zj"; Path="/linkage"

    服务端的动态脚本会对其进行分析,并作出相应的处理,固然也能够选择直接忽略。

   这里牵扯到一个规范(或协议)与实现的问题,简单来说就是规范规定了作成什么样子,那么实现就必须依据规范来作,这样才能互相兼容,可是各个实现所使用的方式却不受约束,也能够在实现了规范的基础上超出规范,这就称之为扩展了。不管哪一种浏览器,只要想提供cookie的功能,那就必须依照相应的RFC规范来实现。因此这里服务器只管发Set-cookie头域,这也是HTTP协议无状态性的一种体现。

须要注意的是,出于安全性的考虑,cookie能够被浏览器禁用。

    再看一下session的原理:

    笔者没有找到相关的RFC,由于session本就不是协议层面的事物。它的基本原理是服务端为每个session维护一份会话信息数据,而客户端和服务端依靠一个全局惟一的标识来访问会话信息数据。用户访问web应用时,服务端程序决定什么时候建立session,建立session能够归纳为三个步骤:

1.  生成全局惟一标识符(sessionid);

2.  开辟数据存储空间。通常会在内存中建立相应的数据结构,但这种状况下,系统一旦掉电,全部的会话数据就会丢失,若是是电子商务网站,这种事故会形成严重的后果。不过也能够写到文件里甚至存储在数据库中,这样虽然会增长I/O开销,但session能够实现某种程度的持久化,并且更有利于session的共享;

3.  将session的全局惟一标示符发送给客户端。

问题的关键就在服务端如何发送这个session的惟一标识上。联系到HTTP协议,数据无非能够放到请求行、头域或Body里,基于此,通常来讲会有两种经常使用的方式:cookie和URL重写。

1.  Cookie

读者应该想到了,对,服务端只要设置Set-cookie头就能够将session的标识符传送到客户端,而客户端此后的每一次请求都会带上这个标识符,因为cookie能够设置失效时间,因此通常包含session信息的cookie会设置失效时间为0,即浏览器进程有效时间。至于浏览器怎么处理这个0,每一个浏览器都有本身的方案,但差异都不会太大(通常体如今新建浏览器窗口的时候);

2.  URL重写

所谓URL重写,顾名思义就是重写URL。试想,在返回用户请求的页面以前,将页面内全部的URL后面所有以get参数的方式加上session标识符(或者加在path info部分等等),这样用户在收到响应以后,不管点击哪一个连接或提交表单,都会在再带上session的标识符,从而就实现了会话的保持。读者可能会以为这种作法比较麻烦,确实是这样,可是,若是客户端禁用了cookie的话,URL重写将会是首选。

    到这里,读者应该明白我前面为何说session也算做是对HTTP的一种扩展了吧。以下两幅图是笔者在Firefox的Firebug插件中的截图,能够看到,当我第一次访问index.jsp时,响应头里包含了Set-cookie头,而请求头中没有。当我再次刷新页面时,图二显示在响应中不在有Set-cookie头,而在请求头中却有了Cookie头。注意一下Cookie的名字:jsessionid,顾名思义,就是session的标识符,另外能够看到两幅图中的jsessionid的值是相同的,缘由笔者就再也不多解释了。另外读者可能在一些网站上见过在最后附加了一段形如jsessionid=xxx的URL,这就是采用URL重写来实现的session。

(图一,首次请求index.jsp)

(图二,再次请求index.jsp)


Cookie和session因为实现手段不一样,所以也各有优缺点和各自的应用场景:

1.  应用场景

Cookie的典型应用场景是Remember Me服务,即用户的帐户信息经过cookie的形式保存在客户端,当用户再次请求匹配的URL的时候,帐户信息会被传送到服务端,交由相应的程序完成自动登陆等功能。固然也能够保存一些客户端信息,好比页面布局以及搜索历史等等。

Session的典型应用场景是用户登陆某网站以后,将其登陆信息放入session,在之后的每次请求中查询相应的登陆信息以确保该用户合法。固然仍是有购物车等等经典场景;

2.  安全性

cookie将信息保存在客户端,若是不进行加密的话,无疑会暴露一些隐私信息,安全性不好,通常状况下敏感信息是通过加密后存储在cookie中,但很容易就会被窃取。而session只会将信息存储在服务端,若是存储在文件或数据库中,也有被窃取的可能,只是可能性比cookie小了太多。

Session安全性方面比较突出的是存在会话劫持的问题,这是一种安全威胁,这在下文会进行更详细的说明。整体来说,session的安全性要高于cookie;

3.  性能

Cookie存储在客户端,消耗的是客户端的I/O和内存,而session存储在服务端,消耗的是服务端的资源。可是session对服务器形成的压力比较集中,而cookie很好地分散了资源消耗,就这点来讲,cookie是要优于session的;

4.  时效性

Cookie能够经过设置有效期使其较长时间内存在于客户端,而session通常只有比较短的有效期(用户主动销毁session或关闭浏览器后引起超时);

5.  其余

Cookie的处理在开发中没有session方便。并且cookie在客户端是有数量和大小的限制的,而session的大小却只以硬件为限制,能存储的数据无疑大了太多。

Servlet/JSP中的Session
    经过上述的讲解,读者应该对session有了一个大致的认识,可是具体到某种动态页面技术,又是怎么实现session的呢?下面笔者将结合session的生命周期(lifecycle),从源代码的层次来具体分析一下在servlet/jsp技术中,session是怎么实现的。代码部分以tomcat6.0.20做为参考。

建立
在我问过的一些从事java web开发的人中,对于session的建立时机大都这么回答:当我请求某个页面的时候,session就被建立了。这句话其实很含糊,由于要建立session请求的发送是必不可少的,可是不管何种请求都会建立session吗?错。咱们来看一个例子。

众所周知,jsp技术是servlet技术的反转,在开发阶段,咱们看到的是jsp页面,但真正到运行时阶段,jsp页面是会被“翻译”为servlet类来执行的,例如咱们有以下jsp页面:

<%@ page language="java" pageEncoding="ISO-8859-1" session="true"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
        <title>index.jsp</title>
    </head>
    <body>
        This is index.jsp page.
        <br>
    </body>
</html>

在咱们初次请求该页面后,在对应的work目录能够找到该页面对应的java类,考虑到篇幅的缘由,在此只摘录比较重要的一部分,有兴趣的读者能够亲自试一下:
......

response.setContentType("text/html;charset=ISO-8859-1");
pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("\r\n");
out.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n");
out.write("<html>\r\n");
......

能够看到有一句显式建立session的语句,它是怎么来的呢?咱们再看一下对应的jsp页面,在jsp的page指令中加入了session="true",意思是在该页面启用session,其实做为动态技术,这个参数是默认为true的,这很合理,在此显示写出来只是作一下强调。很显然两者有着必然的联系。笔者在jsp/servlet的翻译器(org.apache.jasper.compiler)的源码中找到了相关证据:
......

if (pageInfo.isSession())

    out.printil("session = pageContext.getSession();");

out.printil("out = pageContext.getOut();");

out.printil("_jspx_out = out;");

......


 上面的代码片断的意思是若是页面中定义了session="true",就在生成的servlet源码中加入session的获取语句。这只可以说明session建立的条件,显然还不能说明session是如何建立的,本着逐本溯源的精神,咱们继续往下探索。

    有过servlet开发经验的应该记得咱们是经过HttpServletRequest的getSession方法来获取当前的session对象的:

public HttpSession getSession(boolean create);
public HttpSession getSession();

两者的区别只是无参的getSession将create默认设置为true而已。即:

public HttpSession getSession() {
    return (getSession(true));
}
 
那么这个参数到底意味着什么呢?经过层层跟踪,笔者终于理清了其中的脉络,因为函数之间的关系比较复杂,若是想更详细地了解内部机制,建议去独立阅读tomcat相关部分的源代码。这里我将其中的大体流程叙述一下:

1.   用户请求某jsp页面,该页面设置了session="true";

2.   Servlet/jsp容器将其翻译为servlet,并加载、执行该servlet;

3.   Servlet/jsp容器在封装HttpServletRequest对象时根据cookie或者url中是否存在jsessionid来决定是绑定当前的session到HttpRequest仍是建立新的session对象(在请求解析阶段发现并记录jsessionid,在Request对象建立阶段将session绑定);

4.   程序按需操做session,存取数据;

5.   若是是新建立的session,在结果响应时,容器会加入Set-cookie头,以提醒浏览器要保持该会话(或者采用URL重写方式将新的连接呈现给用户)。

经过上面的叙述读者应该了解了session是什么时候建立的,这里再从servlet这个层面总结一下:当用户请求的servlet调用了getSession方法时,都会获取session,至因而否建立新的session取决于当前request是否已绑定session。当客户端在请求中加入了jsessionid标识而servlet容器根据此标识查找到了对应的session对象时,会将此session绑定到这次请求的request对象,客户端请求中不带jsessionid或者此jsessionid对应的session已过时失效时,session的绑定没法完成,此时必须建立新的session。同时发送Set-cookie头通知客户端开始保持新的会话。

保持
    理解了session的建立,就很好理解会话是如何在客户端和服务端之间保持的了。当首次建立了session后,客户端会在后续的请求中将session的标识符带到服务端,服务端程序只要在须要session的时候调用getSession,服务端就能够将对应的session绑定到当前请求,从而实现状态的保持。固然这须要客户端的支持,若是禁用了cookie而又不采用url重写的话,session是没法保持的。

    若是几回请求之间有一个servlet未调用getSession(或者干脆请求一个静态页面)会不会使得会话中断呢?这个不会发生的,由于客户端只会将合法的cookie值传送给服务端,至于服务端拿cookie作什么事它是不会关心的,固然也没法关心。Session创建以后,客户端会一直将session的标识符传送到服务器,不管请求的页面是动态的、静态的,甚至是一副图片。

销毁
    此处谈到的销毁是指会话的废弃,至于存储会话信息的数据结构是回收被重用仍是直接释放内存咱们并不关心。Session的销毁有两种状况:超时和手动销毁。

    因为HTTP协议的无状态性,服务端没法得知一个session对象什么时候将再次被使用,可能用户开启了一个session以后再也没有后续的访问,并且session的保持是须要消耗必定的服务端开销的,所以不可能一味地建立session而不去回收无用的session。这里就引入了一个超时机制。Tomcat中的超时在web.xml里作以下配置:

<session-config>

<session-timeout>30</session-timeout>

</session-config>
 

    上述配置是指session在30分钟没有被再次使用就将其销毁。Tomcat是怎么计算这个30分钟的呢?原来在getSession以后,都要调用它的access方法,修改lastAccessedTime,在销毁session的时候就是判断当前时间和这个lastAccessedTime的差值。

    手动销毁是指直接调用其invalidate方法,此方法其实是调用expire方法来手动将其设置为超时。

    当用户手动请求了session的销毁时,客户端是没法知道服务端的session已经被销毁的,它依然会发送先前的session标识符到服务端。而此时若是再次请求了某个调用了getSession的servlet,服务端是没法根据先前的session标识符找到相应的session对象的,这是又要从新建立新的session,分配新的标识符,并告知服务端更新session标识符开始保持新的会话。

Session的数据结构
    在servlet/jsp中,容器是用何种数据结构来存储session相关的变量的呢?咱们猜想一下,首先它必须被同步操做,由于在多线程环境下session是线程间共享的,而web服务器通常状况下都是多线程的(为了提升性能还会用到池技术);其次,这个数据结构必须容易操做,最好是传统的键值对的存取方式。

    那么咱们先具体到单个session对象,它除了存储自身的相关信息,好比id以外,tomcat的session还提供给程序员一个用以存储其余信息的接口(在类org.apache.catalina.session. StandardSession里):

public void setAttribute(String name, Object value, boolean notify)
 

    在这里能够追踪到它到底使用了何种数据:

protected Map attributes = new ConcurrentHashMap();
 

    这就很明确了,原来tomcat使用了一个ConcurrentHashMap对象存储数据,这是java的concurrent包里的一个类。它恰好知足了咱们所猜想的两点需求:同步与易操做性。

    那么tomcat又是用什么数据结构来存储全部的session对象呢?果真仍是ConcurrentHashMap(在管理session的org.apache.catalina.session. ManagerBase类里):

protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
 

    具体缘由就没必要多说了。至于其余web服务器的具体实现也应该考虑到这两点。

Session Hijack
    Session hijack即会话劫持是一种比较严重的安全威胁,也是一种普遍存在的威胁,在session技术中,客户端和服务端经过传送session的标识符来维护会话,但这个标识符很容易就能被嗅探到,从而被其余人利用,这属于一种中间人攻击。

本部分经过一个实例来讲明何为会话劫持,经过这个实例,读者其实更能理解session的本质。

首先,我编写了以下页面:

<%@ page language="java" pageEncoding="ISO-8859-1" session="true"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
    <head>
       <title>index.jsp</title>
    </head>
    <body>
       This is index.jsp page.
       <br>
       <%
           Object o = session.getAttribute("counter");
           if (o == null) {
              session.setAttribute("counter", 1);
           } else {
              Integer i = Integer.parseInt(o.toString());
              session.setAttribute("counter", i + 1);
           }
           out.println(session.getAttribute("counter"));
       %>
       <a href="<%=response.encodeRedirectURL("index.jsp")%>">index</a>
    </body>
</html>
 

    页面的功能是在session中放置一个计数器,第一次访问该页面,这个计数器的值初始化为1,之后每一次访问这个页面计数器都加1。计数器的值会被打印到页面。另外,为了比较简单地模拟,笔者禁用了客户端(采用firefox3.0)的cookie,转而改用URL重写方式,由于直接复制连接要比伪造cookie方便多了。

    下面,打开firefox访问该页面,咱们看到了计数器的值为1:

(图三)

 

而后点击index连接来刷新计数器,注意不要刷新当前页,由于咱们没用采用cookie的方式,只能在url后面带上jsessionid,而此时地址栏里的url是没法带上jsessionid的。如图四,我把计数器刷新到了20。

(图四)

 

下面是最关键的,复制firefox地址栏里的地址(笔者看到的是http://localhost:8080/sessio

n/index.jsp;jsessionid=1380D9F60BCE9C30C3A7CBF59454D0A5),而后打开另外一个浏览器,此处没必要将其cookie禁用。这里我打开了苹果的safari3浏览器,而后将地址粘贴到其地址栏里,回车后以下图:

(图五)

 

很奇怪吧,计数器直接到了21。这个例子笔者是在同一台计算机上作的,不过即便换用两台来作,其结果也是同样的。此时若是交替点击两个浏览器里的index连接你会发现他们其实操纵的是同一个计数器。其实没必要惊讶,此处safari盗用了firefox和tomcat之间的维持会话的钥匙,即jsessionid,这属于session hijack的一种。在tomcat看来,safari交给了它一个jsessionid,因为HTTP协议的无状态性,它没法得知这个jsessionid是从firefox那里“劫持”来的,它依然会去查找对应的session,并执行相关计算。而此时firefox也没法得知本身的保持会话已经被“劫持”。

结语
    到这里,读者应该对session有了更多的更深层次的了解,不过因为笔者的水平以及视野有限,文中也不乏表述欠妥之处,通篇更多地描述了在servlet/jsp中的session机制,但其余开发平台的机制也都万变不离其宗。只要认真思考,你会发现其实这里林林总总之间,总有一些因果关系存在。在软件规模日益增大的背景下,咱们更多的时候接触到的是框架、组件,程序员的双眼被蒙蔽了,在这些框架、组件不断产生以及版本的不断更新中,其实有不少相对不变的东西,那就是规范、协议、模式、算法等等,真正令一我的获得提升的仍是那些底层的支撑技术。平时多多思考的话,你就能把相似的探索转化为印证。作技术犹如解牛,知筋知骨方能游刃有余。


 

 

session超时,处理ajax请求

首先建了个拦截器,来判断session超时。用户登陆后会保存用户信息在一个session里,在session的监听里,session超时会销毁保存在session里的用户信息,而拦截器就经过session里是否有用户信息来判断session超时。(我总以为这种方法不怎么好。不知还有什么更好的办法。)

   拦截器是spring-mvc的拦截器,在拦截器里判断是否是ajax请求:

 

public boolean preHandle(HttpServletRequest request,
             HttpServletResponse response, Object handler) throws Exception
     {
         if (request.getSession().getAttribute("user") == null)//判断session里是否有用户信息
            {
             if (request.getHeader("x-requested-with") != null
                     && request.getHeader("x-requested-with")
                             .equalsIgnoreCase("XMLHttpRequest"))//若是是ajax请求响应头会有,x-requested-with;
             {
                 response.setHeader("sessionstatus", "timeout");//在响应头设置session状态
                 return false;
             }
            
         }
         return true;
     }

 

这样,若是session超时,并且是ajax请求,就会在响应头里,sessionstatus有一个timeout;


再用一个全局的方法来处理,session超时要跳转的页面。

jquery 能够用$.ajaxSetup 方法,ext也有相似的方法

//全局的ajax访问,处理ajax清求时sesion超时
         $.ajaxSetup({
             contentType:"application/x-www-form-urlencoded;charset=utf-8",
             complete:function(XMLHttpRequest,textStatus){
                     var sessionstatus=XMLHttpRequest.getResponseHeader("sessionstatus"); //经过XMLHttpRequest取得响应头,sessionstatus,
                     if(sessionstatus=="timeout"){
                                 //若是超时就处理 ,指定要跳转的页面
                                         window.location.replace("${path}/common/login.do");

                                         //下面这种方式能够记录以前的请求URI:

                                        window.location.replace("${path}/common/login.do?rediret=" + enccodeURIComponent(window.location));
                                 }
                      }
             }
           });

 

jquery ajax方法调用在session超时之后如何跳转到登陆页面

解决两种状况下的用户访问超时。
a)普通http请求的session超时。
b)异步http请求的session超时,使用ext后大部分的界面刷新都是异步的ajax请求。

无论是那种类型的http请求老是能够由一个过滤器来捕捉。
分类:普通http请求的header参数中没有x-requested-with:XMLHttpRequest头信息,而异步的有。
其实对于常见的ajax框架,header中还有标示本身身份的header信息。

对于普通的http请求,发现session超时后直接重定向到一个超时页面,显示访问超时。
对于异步http请求,发现session超时后则向请求的response中写入特定的超时头信息,客户端ajax对象检测
头信息,发现有超时状态标志后调用显示超时信息的javascript方法,提示用户访问超时。

服务器端session超时后在过滤器中为response添加新的头信息,标记该请求超时:

if(r.getHeader("x-requested-with")!=null
&& r.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")){
response.setHeader("sessionstatus","timeout");
}
使用Ext.Ajaxt对象完成异步请求的交互,Ext.Ajax是单实例对象(很是重要,全局单一Ext.Ajax实例!)。
注册Ext.Ajax的requestcomplete事件,每一个ajax请求成功后首先响应该事件。在该事件的回调函数里面判断
访问请求是否超时。使用Ext.Ajax对象的好处是,只须要引入一个包含了几行超时处理代码的js文件,就能够
为当前应用增长超时处理功能,原有代码不须要作任何修改。


使用Ext.Ajaxt对象完成异步请求交互,假如checkUserSessionStatus是你的回调方法,每一个页面引用:

Ext.Ajax.on('requestcomplete',checkUserSessionStatus, this);
function checkUserSessionStatus(conn,response,options){
//Ext从新封装了response对象
if(typeof response.getResponseHeader.sessionstatus != 'undefined'){
//发现请求超时,退出处理代码...
}
}
能够利用的几个特性:a)全部的ajax请求均带有x-requested-with:XMLHttpRequest头信息b)Ext.Ajax是单实例对象(很是重要,全局单一Ext.Ajax实例!)c)注册Ext.Ajax的requestcomplete事件,每一个ajax请求成功后首先响应该事件(概念相似spring的aop拦截)。
jquery提供了几个全局事件能够用来处理session过时请求,如当ajax请求开始时会触发ajaxStart()方法的回调函数;当ajax请求结束时,会触发ajaxStop()方法的回调函数。这些方法都是全局的方法,所以不管建立它们的代码位于何处,只要有ajax请求发生时,都会触发它们。相似的事件还有:ajaxComplete(),ajaxError(),ajaxSend(),ajaxSuccess()等。
若是使某个ajax请求不受全局方法的影响,那么能够在使用$.ajax()方法时,将参数中的global设置为false,jquery代码以下:$.ajax({ url:"test.html", global:false//不触发全局ajax事件})
对于其余的ajax框架,解决用户访问请求超时这个问题的思路是相似的。

java代码:

当用户session过时时:

if(request.getHeader("x-requested-with")!=null
&&request.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")){
PrintWriter printWriter = response.getWriter();
printWriter.print("{sessionState:0}");
printWriter.flush();
printWriter.close();
}

js部分的代码:

/**
设置jquery的ajax全局请求参数
*/
$.ajaxSetup({
contentType:"application/x-www-form-urlencoded;charset=utf-8",
timeout:pageTimeout,
cache:false
,
complete:function(XHR,TS){
var resText=XHR.responseText;
if(resText=="{sessionState:0}"){
var nav=judgeNavigator();
if(nav.indexOf("IE:6")>-1){
window.opener=null;
window.close();
window.open(jsContextPath+'/login.jsp','');
}else{
window.open(jsContextPath+'/login.jsp','_top');
}
}
}
});

 

Ext Js 2.2x-requested-with

js部分

根目录下创建/js/ext/目录,存放全部和ext相关的js文件。/js/ext/目录下可创建ext相关子目录 

/js/ext/adapter/ — 存放适配器jquery,prototype,yui。。。 

/js/ext/experimental/ — 存放ext一些未正式推出的组件,可参考ext开发包examples例子部分。 

/js/ext/plugins/ — 存放ext扩展组件,例如ext的patch文件,ext主题,扩展组建等等。 

/js/ext/resources/ — 不用说了,ext开发包中的resources目录直接拷贝。 

/js/ — 目录下能够放一些最经常使用 的js文件。 

/js/ext/ — 目录下放置ext-all.js,ext-base.js,ext-lang-zh_CN.js,ext核心文件; 

解决两种状况下的用户访问超时。 
a)普通http请求的session超时。 
b)异步http请求的session超时,使用ext后大部分的界面刷新都是异步的ajax请求。 

无论是那种类型的http请求老是能够由一个过滤器来捕捉。 
分类:普通http请求的header参数中没有x-requested-with:XMLHttpRequest头信息,而异步的有。 
其实对于常见的ajax框架,header中还有标示本身身份的header信息。 

对于普通的http请求,发现session超时后直接重定向到一个超时页面,显示访问超时。 
对于异步http请求,发现session超时后则向请求的response中写入特定的超时头信息,客户端ajax对象检测 
头信息,发现有超时状态标志后调用显示超时信息的javascript方法,提示用户访问超时。 



服务器端session超时后在过滤器中为response添加新的头信息,标记该请求超时: 

Js代码 
if(r.getHeader("x-requested-with")!=null      
    && r.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")){     
    response.setHeader("sessionstatus","timeout");     
}  

if(r.getHeader("x-requested-with")!=null   
&& r.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")){  
response.setHeader("sessionstatus","timeout");  


使用Ext.Ajaxt对象完成异步请求的交互,Ext.Ajax是单实例对象(很是重要,全局单一Ext.Ajax实例!)。 
注册Ext.Ajax的requestcomplete事件,每一个ajax请求成功后首先响应该事件。在该事件的回调函数里面判断 
访问请求是否超时。使用Ext.Ajax对象的好处是,只须要引入一个包含了几行超时处理代码的js文件,就能够 
为当前应用增长超时处理功能,原有代码不须要作任何修改。 



使用Ext.Ajaxt对象完成异步请求交互,假如checkUserSessionStatus是你的回调方法,每一个页面引用: 

Js代码 
Ext.Ajax.on('requestcomplete',checkUserSessionStatus, this);   
function checkUserSessionStatus(conn,response,options){   
    //Ext从新封装了response对象   
    if(typeof response.getResponseHeader.sessionstatus != 'undefined'){   
        //发现请求超时,退出处理代码...   
    }   
}  

Ext.Ajax.on('requestcomplete',checkUserSessionStatus, this); 
function checkUserSessionStatus(conn,response,options){ 
//Ext从新封装了response对象 
if(typeof response.getResponseHeader.sessionstatus != 'undefined'){ 
//发现请求超时,退出处理代码... 

}  
能够利用的几个特性: 
a)全部的ajax请求均带有x-requested-with:XMLHttpRequest头信息 
b)Ext.Ajax是单实例对象(很是重要,全局单一Ext.Ajax实例!) 
c)注册Ext.Ajax的requestcomplete事件,每一个ajax请求成功后首先响应该事件(概念相似spring的aop拦截)。 



对于其余的ajax框架,解决用户访问请求超时这个问题的思路是相似的。 



在这里推荐一个很实用的Js方法: 

Js代码 
function getRootWin(){   
    var win = window;   
    while (win != win.parent){   
        win = win.parent;   
    }   
    return win;   
}   

function getRootWin(){ 
var win = window; 
while (win != win.parent){ 
win = win.parent; 

return win; 



经过该方法,能够在一个任意深度的iframe中调用父iframe中的方法。具体到这里就是不管哪个iframe中的用户访 

问请求超时,均可以经过该方法调用最外层iframe中的退出方法,这样便为用户提供了一个统一的访问超时退出的UI 

呈现。 



4)系统异常处理 

将实际业务代码中的各类异常封装成IOException, ServletException异常,指定过滤器捕获。其他处理思路同 
用户访问超时处理。 



5)添加jquery支持 

使用jquery顺手的且但愿在Ext项目中同时使用某些jquery插件的时候,添加jquery支持。 

页面head中直接添加: 

Js代码 
<link rel="stylesheet" type="text/css" href="/js/ext/resources/css/ext-all.css" />   
  
<script type="text/javascript" src="/js/ext/adapter/jquery/jquery.js"></script>   
<script type="text/javascript" src="/js/jquery.cookie.js"></script>   
<script type="text/javascript" src="/js/ext/adapter/jquery/ext-jquery-adapter.js"></script>   
  
<script type="text/javascript" src="/js/ext/ext-base.js"></script>   
<script type="text/javascript" src="/js/ext/ext-all.js"></script>   
<script type="text/javascript" src="/js/ext/ext-lang-zh_CN.js"></script>  

<link rel="stylesheet" type="text/css" href="/js/ext/resources/css/ext-all.css" /> 

<script type="text/javascript" src="/js/ext/adapter/jquery/jquery.js"></script> 
<script type="text/javascript" src="/js/jquery.cookie.js"></script> 
<script type="text/javascript" src="/js/ext/adapter/jquery/ext-jquery-adapter.js"></script> 

<script type="text/javascript" src="/js/ext/ext-base.js"></script> 
<script type="text/javascript" src="/js/ext/ext-all.js"></script> 
<script type="text/javascript" src="/js/ext/ext-lang-zh_CN.js"></script> 

6)修改布局 

常见的布局通常是:header,center,footer,以及一个位于页面左侧的tree menu。其实对于Ext的UI实现来讲, 

去掉header,footer也不错,由于Ext的UI原本就作得挺好看再加上去掉header及footer后能够为center增长不 

少可视区面积,一个页面还能够显示更多的内容。 

应该能够支持这两种布局方式的切换,交给用户选择。 
试了几回,在border布局初始化完毕以后再想去掉header,footer区域好像比较麻烦,ext的官方论坛上也说设 

计border布局的本意就是应付静态呈现。 
可是好像已经有javaeye上的同志实现了动态的border布局呵呵。能够参考一下 EXT2的动态BorderLayout组件 。 



7)更换主题 

去ext的官网上下载各类主题皮肤 Themes for Ext 2.0 

主题皮肤文件拷贝至本地/js/ext/plugins/theme/css/,/js/ext/plugins/theme/images/ 目录 

最好将用户选择的主题配置保存在cookie中,这样用户每次登录均可以使用相同的界面主题。 

Ext主题切换: 

Js代码 
if($.cookie('ext.theme') != null && $.cookie('ext.theme') != 'default'){   
    Ext.util.CSS.swapStyleSheet("theme","/js/ext/plugins/theme/css/"+$.cookie('ext.theme'));   
}  

if($.cookie('ext.theme') != null && $.cookie('ext.theme') != 'default'){ 
Ext.util.CSS.swapStyleSheet("theme","/js/ext/plugins/theme/css/"+$.cookie('ext.theme')); 


8)添加自定义的toolbar图标 

直接参考javaeye上的这边文章 共享一些Ext的图标 便可,做者提供的图标很好看,使用也很是简单。 



9)生成Excel文档 
最早参考的资料是extjs论坛上面的这篇文章:GridPanel directly to Excel. 
做者思路不错,就是利用javascript直接读取GridPanel的store数据,而后生成一个描述excel文档的xml数据,最后
再经过一个包含了该xml数据的"data" URL下载该excel。 
该方法的好处是通用性比较强,生成的excel文档也不难看,而且是不须要服务器端参与处理的一种纯客户端解决方案。 
可是最大的缺点是目前IE7不支持(This needs a browser that supports data URLs. FF, Opera and IE8 will support this.)。 
然后发现dojochina网站上的一个用户整理和修改了这个生成excel文档的实现方法。 

引用 
如下的几个问题我都已经整理和修改: 
一、没有考虑到含有序号和选择框的grid, 
二、utf8转换bug. 
三、宽度的bug 
四、不支持ie六、ie7和Safari 


原文地址:官方Grid导出到Excel修正版 (做者给出的代码有些小问题,须要略微进行些调整) 

若是是IE浏览器,客户端将以multipart/form-data方式向服务器端提交该xml数据。 
原文给出了后台由php实现时的exportexcel.php代码。 



若是后台由java实现,exportexcel.jsp 

Java代码 
<%@page import="java.util.Date"%>   
<%@page import="org.apache.commons.lang.time.DateFormatUtils"%>   
<%@page import="com.oreilly.servlet.multipart.*"%>   
<%   
    response.setContentType("application/vnd.ms-excel");   
    response.setHeader("Content-disposition","attachment;filename="+   
                       (DateFormatUtils.format(new Date(),"yyyyMMddHHmmss"))+".xls" );    
  
    MultipartParser parse = new MultipartParser(request,1000000000);   
    Part part = null;   
    int maxcount = 0;   
    ParamPart param = null;   
       
    while(true){   
        part = parse.readNextPart();   
        if(part == null || maxcount>1000)   
            break;   
        if(part.isParam() && part.getName().equalsIgnoreCase("exportContent")){   
            param = (ParamPart)part;   
            break;   
        }   
        maxcount++;   
    }   
       
    if(param!=null){   
        response.getWriter().println(param.getStringValue());   
    }else{   
        ;   
    }   
%>  

<%@page import="java.util.Date"%> 
<%@page import="org.apache.commons.lang.time.DateFormatUtils"%> 
<%@page import="com.oreilly.servlet.multipart.*"%> 
<% 
response.setContentType("application/vnd.ms-excel"); 
response.setHeader("Content-disposition","attachment;filename="+ 
                       (DateFormatUtils.format(new Date(),"yyyyMMddHHmmss"))+".xls" ); 

MultipartParser parse = new MultipartParser(request,1000000000); 
Part part = null; 
int maxcount = 0; 
ParamPart param = null; 

while(true){ 
part = parse.readNextPart(); 
if(part == null || maxcount>1000) 
break; 
if(part.isParam() && part.getName().equalsIgnoreCase("exportContent")){ 
param = (ParamPart)part; 
break; 

maxcount++; 


if(param!=null){ 
response.getWriter().println(param.getStringValue()); 
}else{ 


%> 

这里使用 com.oreilly.servlet 解析multipart/form-data类型数据。com.oreilly.servlet 很适合文件,表单混合提 
交、多文件上传的数据解析。 



10)js文件管理 

凡是这种基于javascript的富客户端解决方案一大问题就是js文件太多。每一个页面不只要导入Ext的css,js文件, 
还要导入每一个页面应用须要的一些js文件,这样管理起来很麻烦。 
原来的状况,至少要导入: 

Html代码 
<link rel="stylesheet" type="text/css" href="/js/ext/resources/css/ext-all.css" />  
  
<script type="text/javascript" src="/js/ext/adapter/jquery/jquery.js"></script>  
<script type="text/javascript" src="/js/jquery.cookie.js"></script>  
<script type="text/javascript" src="/js/ext/adapter/jquery/ext-jquery-adapter.js"></script>  
  
<script type="text/javascript" src="/js/ext/ext-base.js"></script>  
<script type="text/javascript" src="/js/ext/ext-all.js"></script>  
<script type="text/javascript" src="/js/ext/ext-lang-zh_CN.js"></script>  
<script type="text/javascript" src="/js/extajax.js"></script>  
<script type="text/javascript" src="/js/exttheme.js"></script>  

<link rel="stylesheet" type="text/css" href="/js/ext/resources/css/ext-all.css" /> 

<script type="text/javascript" src="/js/ext/adapter/jquery/jquery.js"></script> 
<script type="text/javascript" src="/js/jquery.cookie.js"></script> 
<script type="text/javascript" src="/js/ext/adapter/jquery/ext-jquery-adapter.js"></script> 

<script type="text/javascript" src="/js/ext/ext-base.js"></script> 
<script type="text/javascript" src="/js/ext/ext-all.js"></script> 
<script type="text/javascript" src="/js/ext/ext-lang-zh_CN.js"></script> 
<script type="text/javascript" src="/js/extajax.js"></script> 
<script type="text/javascript" src="/js/exttheme.js"></script> 

推荐使用 JSLoader 管理众多的js,css文件 
1,编写一个js文件统一管理支持全部公用css,js文件的动态导入 

Js代码 
//添加jquery支持   
JSLoader.loadJavaScript("/js/ext/adapter/jquery/jquery.js");   
JSLoader.loadJavaScript("/js/jquery.cookie.js");   
JSLoader.loadJavaScript("/js/ext/adapter/jquery/ext-jquery-adapter.js");   
//Ext支持   
JSLoader.loadStyleSheet("/js/ext/resources/css/ext-all.css");   
JSLoader.loadJavaScript("/js/ext/ext-base.js");   
JSLoader.loadJavaScript("/js/ext/ext-all.js");   
JSLoader.loadJavaScript("/js/ext/ext-lang-zh_CN.js");   
//加载自定义toolbar图标css样式   
JSLoader.loadStyleSheet("/js/ext/plugins/icon/css/ext-extend.css");   
//加载用户超时,异常处理   
JSLoader.loadJavaScript("/js/extajax.js");   
//主题管理   
JSLoader.loadJavaScript("/js/exttheme.js");   
//Excel导出支持   
JSLoader.loadJavaScript("/js/ext.excel.js");  

//添加jquery支持 
JSLoader.loadJavaScript("/js/ext/adapter/jquery/jquery.js"); 
JSLoader.loadJavaScript("/js/jquery.cookie.js"); 
JSLoader.loadJavaScript("/js/ext/adapter/jquery/ext-jquery-adapter.js"); 
//Ext支持 
JSLoader.loadStyleSheet("/js/ext/resources/css/ext-all.css"); 
JSLoader.loadJavaScript("/js/ext/ext-base.js"); 
JSLoader.loadJavaScript("/js/ext/ext-all.js"); 
JSLoader.loadJavaScript("/js/ext/ext-lang-zh_CN.js"); 
//加载自定义toolbar图标css样式 
JSLoader.loadStyleSheet("/js/ext/plugins/icon/css/ext-extend.css"); 
//加载用户超时,异常处理 
JSLoader.loadJavaScript("/js/extajax.js"); 
//主题管理 
JSLoader.loadJavaScript("/js/exttheme.js"); 
//Excel导出支持 
JSLoader.loadJavaScript("/js/ext.excel.js"); 
2,每一个页面只须要引入: 

Html代码 
<script type="text/javascript" src="/js/jsloader.js"></script>  
<script type="text/javascript" src="/js/assets.js"></script>   

<script type="text/javascript" src="/js/jsloader.js"></script> 
<script type="text/javascript" src="/js/assets.js"></script>  

相关文章
相关标签/搜索