cookie和session在java web开发中扮演了十分重要的做用,本篇文章对其中的重要知识点作一些探究和总结。(转发自https://www.cnblogs.com/roy-blog/p/8250519.html)html
随意打开一个网址,用火狐的调试工具,随意选取一个连接,查看其请求头。你就会看到cookie的信息。以下图所示。java
如上图所示,咱们访问了新浪网,经过火狐浏览器的调试窗口能够看到cookie存在于请求头也就是httprequest中,而且是以键值对(数组)的形式存在。ios
只要有请求,就会在请求头携带一个cookie的数组(键值对)。cookie是浏览器层面的东西。web
事实上,在java的servlet体系里,咱们能够经过以下方式获取cookie.redis
HttpServletRequest req=ServletActionContext.getRequest(); Cookie[] cookies=req.getCookies(); for(int i=0;i<cookies.length;i++){ Cookie cookie=cookies[i]; System.out.println("name:"+cookie.getName()+",domain"+cookie.getDomain()+",value:"+cookie.getValue()+",maxage:"+cookie.getMaxAge()); }
能够看到,在servlet体系中,把cookie做为一个属性放到了HttpRequest对象里面。经过getCookies()方法获得一个cookie数组。数据库
咱们在一个action中加入上述代码,而且访问这个action,则能够看到控制台打印出以下信息。小程序
servlet对cookie进行了封装,cookie对象有几个属性,如name,domain,value,maxage等,具体的意义能够参考servlet的api文档。微信小程序
以上的请求的cookie是我首次访问某一个网站的连接时候产生的。能够看到cookie数组中只有一个元素。这边先注意一下,后续会有更进一步的说明。api
说了获取cookie数组和cookie,咱们必定也想知道如何把咱们本身的一些信息放进cookie,其实很简单。http的一次请求老是伴随着一次响应,咱们就将cookie信息放入到响应中,传递给浏览器。在java下代码是这样写的。数组
HttpServletResponse res=ServletActionContext.getResponse(); Cookie cookie=new Cookie("xdx", "i'm xdx"); res.addCookie(cookie);
能够看到当咱们发起这个请求时,在响应头有下列信息。
也就是经过此次请求,咱们把xdx=i'm xdx 这个cookie经过response放进了浏览器。
当咱们再次访问该网站上的其余页面的时候,在请求头都将带有这个cookie。以下图所示。
而假如咱们清除历史记录,包括cookie。
再次访问该网站的某一个地址。刚才咱们加进去的cookie就不存在了。
总结来讲就是:servlet经过response将cookie放入到cookie数组中,这样浏览器端就会拥有这一个cookie信息。浏览器会在之后的请求过程当中把这个cookie信息放在请求头。
session咱们通常指的是HTTPSession,为了理解它,咱们直接打开HttpSession的源码来一看究竟。
/** * * Provides a way to identify a user across more than one page * request or visit to a Web site and to store information about that user. * * <p>The servlet container uses this interface to create a session * between an HTTP client and an HTTP server. The session persists * for a specified time period, across more than one connection or * page request from the user. A session usually corresponds to one * user, who may visit a site many times. The server can maintain a * session in many ways such as using cookies or rewriting URLs. * * <p>This interface allows servlets to * <ul> * <li>View and manipulate information about a session, such as * the session identifier, creation time, and last accessed time * <li>Bind objects to sessions, allowing user information to persist * across multiple user connections * </ul> * * <p>When an application stores an object in or removes an object from a * session, the session checks whether the object implements * {@link HttpSessionBindingListener}. If it does, * the servlet notifies the object that it has been bound to or unbound * from the session. Notifications are sent after the binding methods complete. * For session that are invalidated or expire, notifications are sent after * the session has been invalidated or expired. * * <p> When container migrates a session between VMs in a distributed container * setting, all session attributes implementing the {@link HttpSessionActivationListener} * interface are notified. * * <p>A servlet should be able to handle cases in which * the client does not choose to join a session, such as when cookies are * intentionally turned off. Until the client joins the session, * <code>isNew</code> returns <code>true</code>. If the client chooses * not to join * the session, <code>getSession</code> will return a different session * on each request, and <code>isNew</code> will always return * <code>true</code>. * * <p>Session information is scoped only to the current web application * (<code>ServletContext</code>), so information stored in one context * will not be directly visible in another. * * @author Various * * @see HttpSessionBindingListener * @see HttpSessionContext
简单的翻译一下:
--提供一个在多页面请求切换的状况下用于验证、存储用户信息的手段。 --servlet容器使用Httpsession来建立链接客户端和服务端的一个会话(session)。这个会话能持续一段指定的时间(也就是咱们常说的session过时时间),该会话能在多个请求之间共享。 --这个会话通常跟用户信息关联,因为这个用户可能屡次访问网站,因此咱们把他们存储在这个会话(也就是httpsession)里。 --服务端一般是经过cookies或者rewriting URLs来保持一个session。
个人理解,session是一种持久的会话,它的存在主要是为了克服http无状态的特色,关于http无状态,或者说没有记忆,这里很少阐述,涉及到计算机网络的知识,简单来讲就是http一次请求对应一次响应,在这个过程当中会携带一些信息,但这些信息也仅仅在这个过程当中有效。当一个请求结束,咱们进入下一个请求的时候,上一个请求里面的信息对当前的请求就没什么意义了,由于当前的请求根本不会知道上一个请求里面所包含的信息。
那么当咱们须要一些在各个请求都能公用的信息的时候,该怎么办呢?有不少办法,能够把信息存在数据库,而后每次从数据库去取出来,固然io存取会浪费不少时间,它仅仅针对大数据量。还有一种就是将这些信息存在内存当中。没错session其实就是这样一种对象,他把项目当中一些经常使用的信息存在内存当中,这些经常使用的信息一般是跟用户相关的,好比用户名,用户昵称,用户角色等。由于他们须要常常用到,因此把这些信息存在session中进行管理再好不过了。
在servlet体系里,咱们能够用以下代码来获取session。
HttpServletRequest request = ServletActionContext.getRequest(); HttpSession httpSession=request.getSession(); System.out.println(httpSession);
咱们来查阅HttpServletRequest的源码,看看其getSession()方法。
/** * * Returns the current session associated with this request, * or if the request does not have a session, creates one. * * @return the <code>HttpSession</code> associated * with this request * * @see #getSession(boolean) * */ public HttpSession getSession();
它的解释是返回当前与request关联的session,若是这个请求不存在session,就新建一个。
咱们在两个请求中加入上述代码并运行,获得以下结果。
能够看到在整个项目内,这个session被共享着调用。
简单点说,每个session对象都有一个sessionId,而在cookie数组中,又一个元素叫作JSESSIONID,服务器将session对象的sessionId,以名称叫作JSESSIONID,值为sessionId的形式存入cookie数组中,这样cookie和session就发生了关联。
上面的描述能够用下图来表示。
上述的过程能够用相似以下的代码来实现。
HttpServletRequest req=ServletActionContext.getRequest(); HttpServletResponse res=ServletActionContext.getResponse(); Cookie cookie=new Cookie("JSESSIONID", req.getSession().getId());
res.addCookie(cookie);
只不过咱们并不须要写这个代码,servlet自动帮咱们完成了如上的操做。
具体的过程是这样的:
(1)当咱们首次在某个请求中经过调用request.getSession去获取session的时候(这个调用不必定是显式的,不少框架把session封装成map等其余的类型,名称也不必定是session,可是本质都是在调用session),首先servlet经过getCookie的到本次请求的cookie信息,而后去寻找cookie数组中是否有否有一个名为JSESSIONID的cookie,没有的话就建立一个session,而且把sessionId作为JSESSIONID这个cookie的值,而后调用addCookie()方法把该cookie放入cookie数组。
(2)若是上一步中从cookie数组中取到的cookie数组已经包含了JSESSIONID这个cookie,这时候我咱们取出JSESSIONID的值,而后去内存中的session(内存中有不少session)去寻找对应的sessionId为JSESSIONID的值的session,若是找获得的话,就使用这个session,找不到的话,就新建一个session,而且一样的调用addCookie()方法覆盖掉原来的JSESSIONID这个cookie的值。
上述的过程能够用相似以下的代码来表示。
HttpServletRequest req=ServletActionContext.getRequest();//具体获取request的状况可能有所不一样 HttpSession session; Cookie JSESSIONID=null; Cookie[] cookies=req.getCookies(); for(int i=0;i<cookies.length;i++){ Cookie cookie=cookies[i]; if(cookie.getName().equals("JSESSIONID")){ JSESSIONID=cookie; } } if(JSESSIONID==null){ session= createSession();//建立一个session }else{ session=findSessionBySessionId(JSESSIONID.getValue());//经过sessionId获取session if(session==null){ session= createSession();//建立一个session } HttpServletRequest req=ServletActionContext.getResponse();//具体状况可能有所不一样 Cookie cookie=new Cookie("JSESSIONID", session.getId()); res.addCookie(cookie);
咱们将浏览器缓存清除,这样cookie中就没有JSESSIONID了,而后咱们访问一个action。以下。
这个问题包含着一些陷阱。由于不少时候当咱们清空浏览器之后,确实须要从新登陆系统才能够操做,因此不少人天然而然认为清空浏览器缓存(包含cookie)之后。session就会消失。
其实这种结论是错误的。要知道,session是存在于服务器的,你清除浏览器缓存,只是清除了cookie,跟session一点关系都没有。那么为何咱们却不能访问网站,而须要从新登陆了呢?
通常的web项目会经过session来判断用户是否有登陆,经常使用的判断语句是if(session.get("userId")==null。若是为空,则表示须要从新登陆。这时候其实隐式地调用了getSession()方法,这就回到了上一步咱们所讲的session获取的具体步骤了。
由于清空了浏览器缓存,这时候cookie数组中一定不会有JSESSIONID这个cookie,因此必须得新建一个session,用新的sessionId来给JSESSIONID这个cookie赋值。因为是新建的session,session中一定没有userId这样的属性值,因此判断结果天然为空,因此须要从新登陆。此次赋值之后,下一次再请求该网站的时候,因为cookie数组中已经有了JSESSIONID这个cookie,而且能经过该JSESSIONID的值找到相应的session,因此就不须要再从新登陆了。
第一种:当项目正常运行,咱们清空浏览器,cookie和session会发生什么变化。
由于清空了浏览器,因此不会存在JSESSIONID这个cookie,servlet没法找到对应的session,因此他会新建一个session,而后在本次请求的响应中将sessionId传入cookie中。
下一次请求,cookie数组中就带有JSESSIONID这个cookie了,servlet就能够找到对应的session,沿用便可。
第二种:浏览器正常,项目重启(可中止tomcat来模拟这种状况),cookie和session会发生什么变化。
由于项目重启,内存中的一切session都消失了,虽然访问一个action,请求头中有JSESSIONID这个cookie,可是经过它的值(sessionId)并不能找到session(由于根本没有任何session),因此仍是得从新建立一个session,而且这个session的sessionId跟当前cookie数组中的JSESSIONID的值不同,因此它会将新的sessionId覆盖掉cookie数组中原来的JSESSIONID,而且因为此时的session是崭新的,因此他不可能有userId这样的属性值,因此在拦截的时候依然会被截获,所以也是须要从新登陆的。
有兴趣的同窗能够去试验一下。
能够经过setMaxInactiveInterval()方法来设置session的时限,好比能够设为半个小时。这个时间指的是session不活跃开始计算的时间。超过这个时间,session就失效了。此时若再getSession(),则会建立一个新的session,而且其sessionId为此时浏览器中JSESSIONID的值。
有三种方式来设置session的时限:
--经过在web容器中设置(以tomcat为例),在tomcat-7.0\conf\web.xml中设置,如下是tomcat7.0中默认配置:
<session-config> <session-timeout>30</session-timeout> </session-config>
--经过web项目的web.xml文件来设置。设置15分钟失效。
<session-config> <session-timeout>15</session-timeout> </session-config>
--直接在代码中设置
session.setMaxInactiveInterval(30*60);//以秒为单位,即在没有活动30分钟后,session将失效
上述三种方法的优先级:1<2<3.
咱们来作个试验,在web.xml中设置session的过时时间为1分钟,而后观察session和cookie的变化。
第一次访问:
过了一分钟后,再次访问
发现仍是没变,但立刻再次访问。
结合咱们以前所说的session和cookie的做用过程来解释一下:第二次访问的时候,由于过了一分钟,超过了session的过时时间,因此此时虽然cookie中仍是原来的sessionID(这也是为何第二次与第一次的请求头中SessionID相同的缘由),可是经过此SessionID是没法再找到那个已经失效的session,因此服务端必须从新建立一个session,而且把新的sessionID放到cookie中,覆盖掉原来旧的。因此当咱们立刻再次访问的时候,这一次就是新的cookie了。
ps:其实这个过程跟服务端暂停服务的效果是同样的,只不过服务端暂停服务影响的是内存中的全部session,而session过时只是影响当前过时的这个session。
pss:cookie过时与session过时相似,只不过是发生在客户端,你们能够依照前面讲的做用过程试着推导cookie过时会发生什么事情。
psss:若是用了redis等内存数据库来管理session,那么设置过时时间将不起做用。
cookie依赖于浏览器,session依赖于服务器。若是项目不在浏览器上面运行,那么cookie也就无用武之地,可是咱们仍是想要使用session。不巧的是,session依赖cookie进行管理,这时候要怎么办呢?
举个很常见的场景,咱们想要在安卓,或者ios、或者微信小程序等非浏览器的项目中使用session。这时候这些客户端不会自动帮咱们作管理cookie的工做。那么这时候咱们须要本身来作。
前面讲到过,servlet底层帮咱们自动使用cookie来管理session,大致过程是:从cookie中找寻sessionID,根据sessionID去找session,找到合适的就用,找不到的话就新建一个,而且用新的sessionID覆盖掉cookie中旧的。
而如今须要明白两点:
(1)咱们没有浏览器了,因此不会在http的请求头中携带cookie信息,servlet后台收不到cookie信息,天然找不到JESSIONID,因此他会在每次请求都建立一个新的session,这对于服务端来讲,是一笔严重的性能开销。
(2)由于没有浏览器了,servlet在更新完sessionID之后,不会自动执行set-cookie操做将新的sessionID去覆盖旧的cookie中的JESSIONID的值。
根据以上两点,我咱们须要作以下几件事情。
(1):模拟http请求的时候在请求头中带上cookie,在cookie中塞入从JESSIONID。这样服务端就能够取到JESSIONID,从而获取sessionID,进行比对来获取session;
(2),在响应给客户端的时候,手动调用set-cookie方法将本次的sessionID放入JESSIONID中,这样客户端就能够获得最新的JESSIONID。
(3):而后客户端须要维护一个cookie的静态变量(或者用其余方法,总之就是维护一个cookie的内存变量)。将服务端响应回来的JESSIONID的值存在这个变量里面。做为下次请求的时候放入请求头。
(4):咱们一样能够在服务端写一个拦截器,客户端的每一个请求都必须先通过拦截器,这样就能够经过session来判断用户是否处于登陆状态了。这个过程与在浏览器平台毫无二致。
下面咱们来实现上面的功能。
因为本人不会写安卓和iOS的代码,因此以一个客户端程序来模拟安卓端。
首先,定义一个静态变量用于存储JESSIONID.初始值为空。
public static String J_SESSIONID="";
接下来咱们编写模拟http请求的类,须要在这个类中的请求头中加入cookie,而且在响应的时候获得响应头中的cookie.具体代码以下。
public static String http4Cookie(String url, String param,String jsessionId) { String result = ""; BufferedReader in = null; try { String urlNameString = url + "?" + param; URL realUrl = new URL(urlNameString); // 打开和URL之间的链接 HttpURLConnection connection = (HttpURLConnection) realUrl .openConnection(); // 设置通用的请求属性 connection.setRequestProperty("accept", "*/*"); connection.setRequestProperty("connection", "Keep-Alive"); connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); connection.setRequestProperty("Cookie", "JSESSIONID="+jsessionId);//请求头中加入JessionID // 创建实际的链接 connection.connect(); // 获取全部响应头字段 Map<String, List<String>> map = connection.getHeaderFields(); // 遍历全部的响应头字段,获取响应头中的JESSIONID信息 for (String key : map.keySet()) { // System.out.println(key + "--->" + map.get(key)); if("Set-Cookie".equals(key)){ String setCookie=map.get(key).toString(); String newJessionId=setCookie.substring(12,setCookie.lastIndexOf("]")); J_SESSIONID=newJessionId;// 维护新的J_SESSIONID System.out.println(setCookie.substring(12,setCookie.lastIndexOf("]"))); } } // 定义 BufferedReader输入流来读取URL的响应 in = new BufferedReader(new InputStreamReader( connection.getInputStream(), "utf-8")); String line; while ((line = in.readLine()) != null) { result += line; } } catch (Exception e) { System.out.println("发送GET请求出现异常!" + e); e.printStackTrace(); } // 使用finally块来关闭输入流 finally { try { if (in != null) { in.close(); } } catch (Exception e2) { e2.printStackTrace(); } } return result; }
而后咱们在服务端写一个Action让客户端来模拟请求。
@ResponseBody @RequestMapping("httpCookieTest") public String httpTest(HttpServletRequest req,HttpServletResponse res) { Cookie[]cookies=req.getCookies(); for(Cookie cookie:cookies){ if(cookie.getName().equals("JSESSIONID")); System.out.println(cookie.getValue()); } OutPutMsg.outPutMsg(res, req, "httpCookieTest"); return null; }
特别注意这里的OutPutMsg,它的代码以下。
1 public static void outPutMsg(HttpServletResponse response,HttpServletRequest request,String msg) { 2 response.setCharacterEncoding("utf-8"); 3 String sessionId = request.getSession().getId(); 4 response.setContentType("text/html; charset=UTF-8"); 5 response.setHeader("Set-Cookie", "JSESSIONID=" + sessionId); 6 PrintWriter writer = null; 7 try { 8 writer = response.getWriter(); 9 writer.print(msg); 10 writer.flush(); 11 } catch (IOException e) { 12 e.printStackTrace(); 13 } finally { 14 if (writer != null) 15 writer.close(); 16 } 17 }
注意到第4和第5行代码,这两行代码往响应头中加入了此时的sessionId,做为JSESSIONID的值,放入到cookie中。这样客户端才能从响应头中获取最新的JSESSIONID,以更新静态变量J_SESSIONID的值,对应http4Cookie中的代码。
而后咱们来看看客户端的主函数。
public static void main(String args[]){ HttpUtil.http4Cookie("http://192.168.1.185:8080/warrior/httpCookieTest","1=1",J_SESSIONID); }
这样之后,其实这个过程已经跟浏览器的cookie运做机制并没有二致了。
最后,咱们在写一个拦截器,对全部客户端请求进行拦截。客户端每次请求之间都率先访问这个拦截器。
@ResponseBody @RequestMapping("otherPlatformIntercept") public String otherPlatformIntercept(HttpServletRequest req){ HttpSession httpSession=req.getSession(); if(httpSession.getAttribute("userId")!=null){ return "valid"; } return "Invalid"; }