提出问题
如何实现有状态的会话
Cookie机制
采用的是在客户端保持HTTP状态信息的方案。
Cookie是在浏览器访问WEB服务器的某个资源时,由WEB服务器在HTTP响应消息头中附带传送给浏览器一个小文本文件。
一旦WEB浏览器保存了某个Cookie,那么它在以后每次访问该WEB服务器时,都会在HTTP请求头中将这个Cookie回传给WEB服务器。
底层的实现原理:WEB服务器通过在HTTP响应消息中增加Set-Cookie响应头字段将Cookie消息发送给浏览器,浏览器则通过在HTTP请求消息中增加Cookie请求头字段将Cookie回传给WEB服务器。
一个Cookie只能标识一种信息,它至少含有一个标识该信息的名称(NAME)和设置值(VALUE)。
一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个WEB浏览器也可以存储多个WEB站点提供的Cookie。
浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
Cookie的传送过程示意图
在Servl程序中使用Cookie
Servlet API中提供了一个javax.servlet.htp.Cookie类来封装Cookie信息,它包含有生成Cookie信息和提取Cookie信息的各个属性的方法。
Cookie类的方法:
HttpServletResponse接口中定义了一个addCookie方法,它用于在发送给浏览器的HTTP响应消息中增加了一个Set-Cookie响应头字段。
HttpServletRequest接口定义了一个getCookies方法,它用于从HTTP请求信息的Cookie请求头字段中读取所有的Cookie项
package org.lanqiao.cookie.demo;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/cookieServlet")
public class CookieServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
//1 创建cookie
Cookie cookie = new Cookie("name","lanqiao");
//2 设置cookie的最大有效期
cookie.setMaxAge(60*60*24);
//3 将cookie回传给客户端
response.addCookie(cookie);
//4 获取cookile 通过request的getCookies() 获取到的是当前请求中的所有的cookie 因此 这里得到的是一个数组
Cookie[] cookies = request.getCookies();
// 5 对获取的cookie数组进行遍历
for(Cookie cook: cookies){
//6 获取每个cookie的名称
// String cookName = cook.getName();
//7 判断获取到的cookie的名称是否是我们所关心的cookie i如果是 则 获取当前cookie的值
//if(cookName.equals("name")){
out.print(cook.getName() +"-------"+cook.getValue());
// }
}
}
}
Cookie的发送
1、创建Cookie对象
2、设置最大时效
3、将Cookie放入到HTTP响应报头
如果创建了一个cookie,并将他发送到浏览器,默认情况下它是一个会话级别的cookie;存储在浏览器的内存中,用户退出浏览器之后被删除。若希望浏览器将该cookie存储在磁盘上,则需要使用maxAge,并给出一个以秒为单位的时间。将最大时效设为0则是命令浏览器删除该cookie。
发送cookie需要使用HttpServletResponse的addCookie方法,将cookie插入到一个Set-Cookie HTTP响应报头中。由于这个方法并不修改任何之前指定的Set-Cookie报头,而是创建新的报头,因此将这个方法称为是addCookie,而非setCookie。
Cookie的读取
1、调用request.getCookies。要获取浏览器发送来的cookie,需要调用HttpServletRequest的getCookies方法,这个调用返回Cookie对象的数组,对应由HTTP请求中Cookie报头输入的值。
2、对数组进行循环,调用每个cookie的getName方法,直到找到感兴趣的cookie为止。
会话Cookie和持久Cookie的区别
如果不设置过期时间,则表示这个Cookie生命周期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不保存在硬盘而是保存在内存里。
如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie依然有效直到超过设定的过期时间。
存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存的cookie,不同的浏览器有不同的处理方式。
使用Cookie进行简易自动登录
新建一个页面
<%--
Created by IntelliJ IDEA.
User: huwei
Date: 2018/7/15
Time: 10:56
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" session="false" language="java" %>
$Title$
<%--cookie--%>
<%=request.getSession().isNew()%>
再建一个LoginServlet
package org.lanqiao.cookie.demo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1 从客户端的请求中获取用户名和密码
//2 验证用户名和密码
//3 如果用户名和密码正确, 将用户的登陆信息保存到cookie中 则直接跳转到success.jsp
// 4 如果不正确 重新跳转到登陆页面
//5 如果客户端没有获取到输入的用户名和密码 则需要从cookie去检索用户名和密码 如果正确 则直接跳转到success.jsp
/*
在cookie保存对象的实现方式:
可以采用JSON 将该对象转换为一个JSON字符串 需要对转化之后的json字符串实行编码 保存在cookie中
当需要该信息的时候 可以从cookie中获取这个json字符串(需要解码) 然后 把他重新转换为我们的对象 这样就可已使用了
*/
String username = request.getParameter("username");
String password = request.getParameter("pwd");
User user = null;
if(username != null && !username.trim().equals("")){
if(username.equals("lanqiao") && password.equals("123456")){
user = new User(username,password);
//将一个对象转换成json字符串的时候 我们需要对该对象实行编码
String userStr = JSON.toJSONString( user);
Cookie cookie = new Cookie("user",URLEncoder.encode(userStr,"utf-8"));
/*
在cookie保存中文时 也同样需要采用URLEncoder对其进行编码 在获取的时候 同样采用URLDecode进行解码
*/
Cookie cookie1 = new Cookie("name","张三");
cookie.setMaxAge(60*60*24);
response.addCookie(cookie);
response.addCookie(cookie1);
}
}else{
Cookie[] cookies = request.getCookies();
for (Cookie cook : cookies){
String cookName = cook.getName();
if(cookName.equals("user")){
//再次获取到该对象之后 需要进行解码
JSONObject jObj = JSON.parseObject(URLDecoder.decode(cook.getValue(),"utf-8"));
String u = jObj.getString("username");
String p = jObj.getString("password");
user = new User(u,p);
}
if(cookName.equals("name")){
System.out.println("name = " + cook.getValue());
}
}
}
if(user !=null){
request.setAttribute("user",user);
request.getRequestDispatcher("/success.jsp").forward(request,response);
}else{
request.getRequestDispatcher("/index.jsp").forward(request,response);
}
}
}
再来个User对象,进行封装
package org.lanqiao.cookie.demo;
import java.util.Objects;
public class User {
private String username;
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(username, user.username) &&
Objects.equals(password, user.password);
}
@Override
public int hashCode() {
return Objects.hash(username, password);
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
再来个登录成功页面
<%@ page import="org.lanqiao.cookie.demo.User" %><%--
Created by IntelliJ IDEA.
User: huwei
Date: 2018/7/15
Time: 11:40
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
<%
User user = (User)request.getAttribute("user");
%>
你好:<%=user.getUsername()%>
Session
中文翻译为会话,其本来含义是指有始有终的一系列动作/消息,比如打电话是从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个session。
session在Web开发环境下的语义又有了新的扩展,它的含义是指一类用来在客户端与服务器端之间保持状态的解决方案,有时候session也用来指解决方案的存储结构。
在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象,注意:一个浏览器独占一个Session对象(默认情况下)。因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户数据写到用户浏览器独占的session中,当用户使用浏览器访问其他程序时,其他程序可以从用户的session中去除该用户的数据,为用户服务。
Session和Cookie区别:
保存session id的几种方式
保存session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照贵族把这个标识发送给服务器。
服务器创建session出来后,会把session的id号,以cookie的形式回写给客户机,这样,只要客户机的浏览器不关,再去访问服务时,都会带着session的id号去,服务器发现客户机浏览器带session id过来了,就会使用内存中与之对应的session为之服务。
由于cookie可以被人为的禁用,必须有其它的机制以便在cookie被禁用时仍然能够把session id传递回服务器,经常采用的一种技术叫做URL重写,就是把session id,既必须在每个客户端可能请求的路径后面都包含这附加在URL后面。网络在整个交互过程中始终保持状态session id。
Session Cookie
session通过Session ID来区分不同的客户,session是以cookie或URL重写为基础的,默认使用cookie来实现,系统会创造一个名为JSESSIONID的输出cookie,这称之为session cookie,以区别persistent cookies(也就是我们通常说的cookie),session cookie是存储与浏览器内存中的,并不是写到硬盘上的,通常看不到JSESSIONID,但是当把浏览器的cookie禁止后,web服务器会采用URL重写的方式传递Sessionid,这时地址栏可以看到
session cookie针对某一次会话而言,会话结束session cookie也就随着消失了,而presistent cookie知识存在于客户端硬盘上的一段文本。
关闭浏览器,只会是浏览器端内存里的session cookie消失,但不会使保存在服务器端的session对象消失,同样也不会使已经保存到硬盘上的持久化cookie消失。
Session的持久化
<%
Cookie cookie = new Cookie("JSESSIONID" , session.get());
cookie.setMaxAge(20);
response.addCookie(cookie);
%>
HttpSession的常用方法
Date creationTime = new Date(session.getCreationTime());
Date accessedTime = new Date(session.getLastAccessedTime());
先建一个页面
<%@ page import="java.util.Date" %><%--
Created by IntelliJ IDEA.
User: huwei
Date: 2018/7/15
Time: 15:00
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
session
SESSIONID: <%=session.getId()%>
Session的有效期:<%=session.getMaxInactiveInterval()%>
Session的创建时间:<%=new Date(session.getCreationTime())%>
Session的访问时间:<%=new Date(session.getLastAccessedTime())%>
Session是否新建:<%=session.isNew()%>
再建一个SessionServlet.Java
package org.lanqiao.session.demo;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/sessionServlet")
public class SessionServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
username = new String(username.getBytes("ISO-8859-1"),"utf-8");
System.out.println("username = " + username);
// 1 通过request获取session
HttpSession session = request.getSession();
//2 将获取的用户数据保存在session中
session.setAttribute("username",username);
request.getRequestDispatcher("/loginSuccess.jsp").forward(request,response);
}
}
再建一个登录成功跳转页面
<%--
Created by IntelliJ IDEA.
User: huwei
Date: 2018/7/15
Time: 15:11
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
success
欢迎您:<%=session.getAttribute("username")%>
安全退出
Session的生命周期
创建:
1、什么时候创建HttpSession对象?对于JSP:是否浏览器访问服务端的任何一个JSP,服务器都会立即创建一个HttpSession对象呢?
不一定。若当前JSP是客户端访问的当前WEB应用的第一个资源,且JSP的page指定的session属性值为false,则服务器就不会为JSP创建一个HttpSession对象;若当前JSP不是客户端访问的当前WEB应用的第一个资源,且其他页面以及创建一个HttpSession对象,则服务器也不会为当前JSP页面创建一个HttpSession对象,而会把和当前会话关联的那个HttpSession对象返回给当前的JSP页面
2、对于Servlet:若Servlet是客户端访问的第一个WEB应用的资源,则只有调用了request.getSession()或request.getSession(true)才会创建HttpSession对象
3、page指令的session = “false”到底表示什么意思?
当前JSP页面禁用session隐含变量,但可以使用其他的显式的HttpSession对象
4、在Servlet中如何获取HttpSession对象?
create 为 false, 若没有和当前 JSP 页面关联的 HttpSession 对象, 则返回 null; 若有, 则返回 true
create 为 true, 一定返回一个 HttpSession 对象. 若没有和当前 JSP 页面关联的 HttpSession 对象, 则服务器创建一个新的
HttpSession 对象返回, 若有, 直接返回关联的.
5、什么时候销毁 HttpSession 对象?
获取HttpSession的过期时间:session.getMaxInactiveInterval(); 设置 HttpSession 的过期时间: session.setMaxInactiveInterval(5); 单位为秒
利用URL重写实现Session跟踪
当cookie被禁用时,如何跟踪Session?
Servlet规范中引入一种补充的会话管理机制,它允许不支持Cookie的浏览器也可以与WEB服务器保持连续的会话。这种补充机制要求在响应消息的实体内容中必须包含下一次请求的超链接,并将会话标识号作为超链接的URL地址的一个特殊参数。
将会话标识号以参数形式附加在超链接URL地址后面的技术称为URL重写。如果在浏览器不支持的Cookie或者关闭了Cookie功能的情况下,WEB服务器还要能够与浏览器实现有状态的会话,就必须对所有可能被客户端访问的请求路径(包括超链接、form表单的action属性设置和重定向的URL)进行URL重写。
HttpServletRespouse接口中定义了两个用于完成URL重写方法:
encodeURL()及encodeRedirectURL()方法首先判断cookies是否被浏览器支持;如果支持,则参数URL被原样返回,session ID将通过cookies来维持
在实际开发中,我们不免要遇到从页面到页面,从页面经servlet到页面之间的跳转时,我们应该使用绝对路径还是相对路径呢?
在实际开发中,建议使用绝对路径,绝对路径是肯定不会出现问题的,相对路径可能会存在问题。
在由Servlet跳转到jsp页面时,此时浏览器地址栏上显示的是Servlet的路径,而若jsp页面的超链接还是相对于该jsp页面的地址,则可能会出现路径混乱的问题。
编写绝对路径来避免上面的问题
在javaweb中什么叫“绝对路径”?
相对于contextPath的路径,即任何的路径都必须带上contextPath
比如:http://localhost:8080/contextPath(当前WEB应用的上下文路径)/a.jsp
如何解决?
当前WEB应用的根路径:http://localhost:8080/contextPath/: 若/交给servlet容器来处理
----请求转发时:request.getRequestDispacher("/path/b.jsp").forward(request,response);
----Web.xml文件中映射servlet访问路径
WEB站点的根路径:http://localhost:8080/: 若/交给浏览器来处理
---超链接:去BBB界面
---表达中的action:
---做请求重定向的时候:response.sendRedirect("/a.jsp")
如果 / 代表的是站点的根目录,在其前面加上contextPath,这个contextPath可以由request或application的getContextPath()方法来获取
这样上面的错误就解决了。