上一章链接:JSP基础
在使用网站的登录界面时,点击输入栏时会发现输入栏有以前登录的账户的名称预选值,而这就是Cookie的作用。那么何为Cookie?Cookie里又有什么?Cookie又怎么用?这些问题将在下面的文章一一解答。
目录
Cookie
Cookie和Http协议
Cookie的作用
Cookie的组成
JavaWeb中使用Cookie
Cookie的其他属性
maxAge(最大生命)
path(路径)
domain(域名)
Session
Session概述
Session域
Session部分源码剖析
Session登录小案例
Session其他方法
URL重写
JSESSIONID
后话
说起Cookie,不得不说起Http协议。Cookie是Http协议制定的,并且Http协议规定:
一个Cookie最大4KB
一个服务器最多向一个浏览器保存20个Cookie
一个浏览器最多可以保存300个Cookie
那么服务器和Cookie之间又如何联系?先由服务器保存Cookie到浏览器,在下次浏览器请求服务器时,把上一次请求得到的Cookie归还给服务器。
Cookie可以保存申请的用户名并且显示出来,也可以用来保存购物车中的商品。这两个作用都是基于服务器使用Cookie来跟踪客户端的状态而体现出来的实例作用。
以Google浏览器为例,依次打开设置——隐私设置和安全性——Cookie——查看所有Cookie和网站数据可以看到各服务器保存的所有Cookie。以CSDN为例,随意打开一个CSDN的Cookie,根据不同作用会有不同的结构,最主要需要了解的结构如下所示:
Cookie:Cookie名称、内容、域名、路径、为何发送、脚本可访问、创建时间、到期时间
最原始的方式是使用response发送Set-Cookie响应头,使用request获取Cookie请求头。
而后续又增加了response.addCookie()方法和request.getCookies()方法来分别保存和获取Cookie。
这里用代码演示一下:
a.jsp
<%
Cookie cookie = new Cookie("aaa", "AAA");
response.addCookie(cookie);
%>
b.jsp
<%
Cookie[] cookies = request.getCookies();
if(cookies != null) {
for(Cookie c : cookies) {
out.print(c.getName() + "=" + c.getValue() + "---" + c.getMaxAge() + "
");
}
}
%>
分别打开b.jsp和设置查看Cookie,显示结果如下所示:
不难发现,除了自己设置的Cookie以外,还有一个JSESSIONID。这里暂时不提JSESSIONID,在后面将会详细地剖析这个JSESSIONID有什么用。
Cookie除去名称和内容,还有其他属性。如maxAge、path和domain,分别代表了Cookie的最大生命、Cookie的路径和Cookie的域名。接下来分别讲解这三个属性。
所谓的最大生命,即Cookie可保存的最大时长,特别需要注意的是,在相关的方法中参数为int,说明以秒为单位。
当maxAge>0时,浏览器会把Cookie保存到客户机硬盘上,有效时长为maxAge值决定。
当maxAge<0时,Cookie只能在浏览器内存中存在,即当浏览器进程结束时,Cookie就销毁了。
当maxAge=0时,浏览器会马上删除这个Cookie。
以下为演示代码:
// a.jsp
<%
Cookie cookie = new Cookie("aaa", "AAA");
cookie.setMaxAge(60*60);
response.addCookie(cookie);
%>
// b.jsp
<%
Cookie[] cookies = request.getCookies();
if(cookies != null) {
for(Cookie c : cookies) {
out.print(c.getName() + "=" + c.getValue() + "---" + c.getMaxAge() + "
");
c.setMaxAge(0);
response.addCookie(c);
}
}
%>
请求a.jsp时,打开浏览器设置查看相关的cookie信息。由于setMaxAge()方法中的参数值设置为了60*60(即1小时),则该aaa的到期时间只有一个小时。
请求b.jsp时,将cookie的最大存活时间设置为0,再次查看浏览器的Cookie信息,发现只剩下了JSESSIONID。
首先必须得明确的一点是:尽管Cookie是保存在客户端的硬盘中的,但是Cookie的path并不是设置这个Cookie在客户端的保存路径。至于这个路径是由服务器创建Cookie时设置的,即有默认路径。
浏览器访问服务器的路径,如果包含了某个Cookie的路径,那么就会归还这个Cookie给服务器。例如:
// 有三个不同的Cookie,并且路径也不相同
aCookie.path = /java_web/;
bCookie.path = /java_web/cookie/;
cCookie.path = /java_web/cookie/c/;
/*
* 当访问/java_web/a.jsp时,浏览器归还aCookie
* 当访问/java_web/cookie/b.jsp时,浏览器归还aCookie、bCookie
* 当访问/java_web/cookie/c/c.jsp时,浏览器归还aCoookie、bCoookie、cCookie
*/
路径这一块主要是理解到路径实际代表的是什么。
正常的cookie只能在一个应用中共享,即一个cookie只能由创建它的应用获得。而要想跨域,就得用到domain。
domain是用来指定Cookie的域名,当多个二级域中共享Cookie时才用。
举个例子:
'''
www.baidu.com、zhidao.baidu.com、tieba.baidu.com之间共享Cookie
就得设置Cookie的domain为:cookie.setDomain(".baidu.com");
或者设置path为:cookie.setPath("/");
'''
以上,就是Cookie的相关介绍,其中还有一个JSESSIONID将会放在最后的一部分讲解。
HttpSession是由JavaWeb提供的,用来会话跟踪的类。有一点要特别记住,Session是服务器端的对象,保存在服务器端。
还有一点需要记住,HttpSession底层依赖Cookie,或者是URL重写。
Session是一个会话对象,那么什么是会话呢?会话是指一个用户对服务器的多次连贯性请求。何为连贯性请求,就是同一个用户多次请求在这之间没有关闭过浏览器。那么这样也能给出会话范围的定义:某一个用户从首次访问服务器开始,到该用户关闭浏览器结束。
由这个会话范围可以看出服务器会为每个客户端创建一个Session对象。Session就好比客户在服务器端的账户,然后保存到一个Map中,这个Map被称之为Session。
JavaWeb四大域对象,已经接触了两种:ServletContext、ServletRequest、pageContext。而剩下的最后一种就是HttpSession。也就是说,Session域中也有既定的三种方法:
public abstract void setAttribute(String s, Object obj);
public abstract Object getAttribute(String s);
public abstract void removeAttribute(String s);
而在Servlet和JSP中又如何得到Session对象?
Servlet:通过 HttpSession session = request.getSession()方法获得。
JSP:session是九大内置对象之一,不需要创建即可使用。
HttpSession只是一个接口类,那么通过request.getSession()方法寻找HttpServletRequest,还是个接口类。真正实现HttpServletRequest的是catalina包下的connector的RequestFacade(这里其实在第二章的时候提及过了)。
不出意外的在RequestFacade里找到了getSession()方法。先来看看这个getSession()方法。
// org.apche.catalina.connector.RequestFacade.java
public HttpSession getSession(boolean create)
{
if (request == null)
throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
if (SecurityUtil.isPackageProtectionEnabled())
return (HttpSession)AccessController.doPrivileged(new
GetSessionPrivilegedAction(create));
else
return request.getSession(create);
}
通过第一个判断条件,可以得知Session是基于客户端请求服务器时才会生成。第二个判断条件暂时先忽略,不是主要内容。当前面两个判断条件都不满足时则调用同一个包下的Request类对象的getSession(boolean create)方法,源码如下所示:
// org.apache.catalina.connector.Request.java
public HttpSession getSession(boolean create)
{
Session session = doGetSession(create);
if (session == null)
return null;
else
return session.getSession();
}
public HttpSession getSession()
{
Session session = doGetSession(true);
if (session == null)
return null;
else
return session.getSession();
}
从源码来看,首先调用doGetSession方法获取Session,然后再调用session的getSession方法返回HttpSession。那么就得查看以下doGetSession()方法里又是怎么样获取Session的,源码中每一部分负责不同的地方,这里将源码分块讲解。
首先是第一部分:
// 这一部分是判断Session是否存在并且有效
Context context = getContext();
if (context == null)
return null;
if (session != null && !session.isValid())
session = null;
if (session != null)
return session;
Manager manager = context.getManager();
if (manager == null)
return null;
// 这一部分是指根据JSESSIONID去查找Session,如果存在并且有效,则返回找到的Session。
if (requestedSessionId != null) {
try {
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
session = null;
}
if (session != null && !session.isValid())
session = null;
if (session != null) {
session.access();
return session;
}
}
// 这一部分是在响应还未提交时下一步就创建一个新的Session
if (!create)
return null;
if (response != null &&
context.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.COOKIE) &&
response.getResponse().isCommitted()) throw new
IllegalStateException(sm.getString("coyoteRequest.sessionCreateCommitted"));
// 这里是根据SSL会话ID是否存在判断Session是否可使用
// 代码过长就不予以放置了,有要求的可以自行查看
// 这一部分是基于SessionID创建一个新的Session的Cookie,一般是第一次访问的情况
session = manager.createSession(sessionId);
if (session != null && getContext() != null &&
getContext().getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.COOKIE)) {
Cookie cookie = ApplicationSessionCookieConfig.createSessionCookie(context,
session.getIdInternal(), isSecure());
response.addSessionCookieInternal(cookie);
}
if (session == null) {
return null;
} else {
session.access();
return session;
}
// session.access()是保存该session的最后访问时间
综合源码来看,可以得知整个获取Session的步骤如下描述:
以上的步骤描述其实已经可以把获取Session的原理讲清楚了。那么为了更好的理解Session、SessionID、Cookie三者之间的关系,在这里举一个类似的例子:银行业务的例子。
进银行办理业务,业务人员问有没有银行卡?没银行卡,则创建账户并且办带有ID的银行卡。不小心银行卡丢了,但是在银行数据库有你卡上的ID,重补一张卡即可。突然有一天数据库崩了,ID没了,卡废了,说明只能重新办卡,再次循环。
在这里面,银行卡本身是个Cookie,卡里保存的是SessionID,而银行账户是Session。也就是说,通过一个Cookie保存的SessionID去找Session。这就是获取Session的原理。
三个JSP和一个Servlet:login.jsp、succ1.jsp、LoginServlet。需要完成的功能如下:
login.jsp提供登录表单,提交表单请求LoginServlet;
LoginServlet获取请求参数,校验用户是否登录成功。若登录成功,则保存用户信息到session域中,重定向到succ1.jsp页面,显示session域中的用户信息。若登录失败,则保存错误信息到session域中,转发到login.jsp显示request域中的错误信息。
succ1.jsp则是从session域中获取用户信息,若用户信息存在,则显示用户信息;如果用户信息不存在则将“你还没有登录”信息转回给login.jsp。
代码如下所示:
login.jsp
登录
<%
//这里的作用就是从Cookie获取输入过的用户名数据
String uname = "";
Cookie[] cookies = request.getCookies();
if(cookies != null) {
for(Cookie c : cookies) {
if("uname".equals(c.getName())) {
uname = c.getValue();
}
}
}
%>
<%
String msg = (String)request.getAttribute("msg");
if(msg == null)
msg = "";
%>
<%=msg %>
succ1.jsp
<%
String uname = (String)session.getAttribute("user");
if(uname == null) {
String msg = "未登录,无法访问内部";
request.setAttribute("msg", msg);
request.getRequestDispatcher("login.jsp").forward(request, response);
}
%>
欢迎<%=uname %>用户登录
LoginServlet
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取表单数据
String uName = req.getParameter("username");
String pwd = req.getParameter("password");
// 这里一般是数据库查询 这里只是案例简化了
if("test".equals(uName) && "123".equals(pwd)) {
Cookie cookie = new Cookie("uname", uName);
cookie.setMaxAge(60*60*24);
resp.addCookie(cookie);
// ↑以上设置Cookie信息,为了在输入框中显示输入过的用户名
HttpSession session = req.getSession();
session.setAttribute("user", uName);
resp.sendRedirect("session/succ1.jsp");
} else {
String msg = "登录失败,非系统内用户登录";
req.setAttribute("msg", msg);
req.getRequestDispatcher("session/login.jsp").forward(req, resp);
}
}
}
结果演示:
登录后,在原有的浏览器没有关闭的条件下,再次打开一个新的浏览器访问succ1.jsp一样能够成功访问。只要用户没有关闭浏览器,session就一直存在。那么,保存在session中的用户信息就一直存在。
验证码部分是在后续加进去的。这里附上验证码类的代码和实现的相关Servlet:
/*
* 生成验证码图片
* 组成:图片宽高、文字字体、字体样式、字体大小、字体颜色、验证码文本
*
* */
public class VerifyCode {
private int w = 70;
private int h = 35;
private Random r = new Random();
private String[] fontNames = { "微软雅黑", "宋体", "黑体", "华文楷体", "华文新魏", "华文隶书", "楷体_GB2312" };
private String codes = "23456789abcdefghjkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
private Color bgColor = new Color(255, 255, 255);
private String text;
private final int textLen = 4;
// 创建背景图片
private BufferedImage createImage() {
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
g.setColor(bgColor);
g.fillRect(0, 0, w, h);
return image;
}
// 生成随机单个随机文字
private String randomChar() {
String str = codes.charAt(r.nextInt(codes.length())) + "";
return str;
}
// 随机颜色
private Color randomColor() {
return new Color(r.nextInt(150), r.nextInt(150), r.nextInt(150));
}
// 随机字体
private Font randomFont() {
int index = r.nextInt(fontNames.length);
String fontName = fontNames[index];
int style = r.nextInt(4);
int size = r.nextInt(5) + 24;
return new Font(fontName, style, size);
}
// 绘格挡线
private void drawLine(BufferedImage image) {
int num = 3;
Graphics2D g = (Graphics2D) image.getGraphics();
for (int i = 0; i < num; i++) {
int x1 = r.nextInt(w);
int y1 = r.nextInt(h);
int x2 = r.nextInt(w);
int y2 = r.nextInt(h);
g.setStroke(new BasicStroke(1.5F));
g.setColor(Color.BLUE);
g.drawLine(x1, y1, x2, y2);
}
}
// 得到验证码图片
public BufferedImage getImage() {
BufferedImage image = createImage();
Graphics2D g = image.createGraphics();
StringBuilder sb = new StringBuilder();
// 生成随机文字
for (int i = 0; i < textLen; i++) {
String s = randomChar();
sb.append(s);
float x = i * 1.0F * w / 4;
g.setColor(randomColor());
g.setFont(randomFont());
g.drawString(s, x, h - 5);
}
drawLine(image);
this.text = sb.toString();
return image;
}
public String getText() {
return text;
}
public static void output(BufferedImage image, OutputStream out) throws IOException {
ImageIO.write(image, "JPEG", out);
}
}
public class VerifyCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//生成验证码图片,将验证码文本保存至Session
VerifyCode vc = new VerifyCode();
BufferedImage image = vc.getImage();
req.getSession().setAttribute("session_vcode", vc.getText());
VerifyCode.output(image, resp.getOutputStream());
}
}
// 获取SessionId
public abstract String getId();
// 获取Session的最大不活动时间(秒),默认值为30分钟
public abstract int getMaxInactiveInterval();
// 让Session失效,类似于银行卡中的账户被冻结
public abstract void invalidate();
// 查看session是否为新的,作用是判断getSession是创建session还是获取session
public abstract boolean isNew();
这里的方法自行测试效果,不予以过于详细地介绍。
前面说过,HttpSession底层依赖Cookie,为的就是让客户端发出请求时归还sessionId,这样才能找到对应的session。
如果客户端禁用了Cookie,那么就无法得到sessionId,那么session也就没有任何用处了。那么就得需要另一个方法:URL重写。
URL重写可以让网站的所有超链接、表单中都添加一个特殊的请求参数,即SessionId,这样服务器就可以通过获取请求参数来获取SessionId,从而获取到session对象。
那么该如何实现URL重写呢?在response对象中有一个encodeURL(String url)方法会对url进行智能的重写。当请求中没有归还sessionid这个cookie时,那么该方法会重写url。否则不重写url。
演示代码如下:
urlReWrite.jsp
点击这里1
点击这里2
点击这里3
<%
//会查看Cookie是否存在,如果不存在,在指定的url后添加JSESSIONID参数
//如果cookie存在,它就不会在url后添加任何东西
out.println(response.encodeURL("../CheckSession"));
%>
checkSession
public class CheckSession extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().print("请查看是否有jsessionid这个cookie");
}
}
结果如下显示:
首先说明JSESSIONID是一个Cookie,用来记录用户session的。创建会话的时候,即当request调用getSession()方法时,就会创建一个这样的Cookie。
按下F12查看,第一次访问服务器的时候,响应头中能看到一个Set-Cookie信息。浏览器会根据响应头中的Set-Cookie信息设置浏览器的cookie并且保存下来。由于没有设置Cookie的到期时间即有效时间,默认情况为-1,即关闭浏览器后Cookie会丢失。
在之后的多次访问服务器,浏览器都会将cookie中的JSESSIONID发送给服务器。为什么要每次都发送,为的就是判断当前访问服务器的用户对应于哪个Session,Session中的getId()方法就是获取JSESSIONID的。
查看JSESSIONID,可以发现,其是32位16进制的随机字符串。底层是用UUID来生成随机字符串,至于UUID如何生成的在这里不太多概述,并不是主要的地方,可以自行查询UUID。
JSESSIONID生成演示如下所示:
public class CommonUtils {
public static String uuid() {
return UUID.randomUUID().toString().replace("-", "").toUpperCase();
}
}
Tomcat会在session不被使用时钝化session对象,所谓的钝化session对象,即把session通过序列化的方式保存到硬盘中。而当用户再次使用session时,Tomcat会把钝化的session对象活化。所谓的活化session对象,即把硬盘中序列化的session再反序列化回内存。当session被tomcat钝化时,session中的对象也被钝化;当session被活化时,也会把session中存储的对象活化。
分别创建两个jsp页面,一个页面向session中保存数据,一个页面从session中获取数据,如下所示:
向session中保存数据
<%
session.setAttribute("key", "value");
%>
从session中获取数据
<%
session.getAttribute("key");
%>
演示一下序列化,将服务器停止。如果是将项目文件发送到tomcat中,则根据以下路径找到文件夹:tomcat根目录/work/Catalina/localhost/项目名/。在这个文件夹里会发现一个叫sessions.ser的文件,如下所示:
在实现钝化效果前,需要在Tomcat的配置文件夹中的context.xml配置好以下参数:
接下来访问获取数据页面,等待一分钟,会在配置的文件夹内出现一个sessionID.session文件,这个就是钝化后的session。
而重新访问获取数据页面,会发现该文件消失,其是被读取到了内存中,成功显示数据,如图所示:
这就是Session的钝化和活化特性。
'''
Cookie和Session其实都不是什么好的东西。Session容易劫持。这是另一部分的内容了。
在中途讲了Cookie和Session,接下来还要继续讲jsp的指令部分。
这部分的东西也挺多的,细细考虑后再写吧。
最后想了想,jsp指令部分和九大内置对象都添加到jsp那一章。
下一章的内容,JavaBean了解一下就好,其实在Java学习反射的时候就该了解到了
'''
下一章链接:JavaBean