再次系统学习一下cookie和session
之前写得一篇cookie与session区别
一、会话技术
会话:一次会话中包含多次请求和响应。
一次会话:浏览器第一次给服务器资源发送请求,会话建立,直到有一方断开为止。
功能:在一次会话的范围内的多次请求间,共享数据
方式
- 客户端会话技术:cookie
- 服务器端会话技术:session
有关http协议的知识,移步到这里
二、Cookie
概念:客户端会话技术,将数据保存到客户端
2.1 快速入门
快速入门
- 创建cookie对象,绑定数据
- Cookie(String name, String value)
- 发送cookie
- response.addCookie(Cookie cookie)
- 获取cookie,拿到数据
- Cookie[] request.getCookies()
| |
上面这串代码,在访问该网址时,服务器返回一串cookie,"msg=hello"。

| |
上面这些代码,是将浏览器端请求服务器时携带的cookie打印到控制台。

如果想要在浏览器查看cookie的话

2.2 实现原理
浏览器初次请求服务器,服务器响应放回set-cookie,浏览器将接收到的cookie保存到本地。
浏览器二次请求服务器,请求头中携带cookie的值。
就相当于买东西
Cookie 就像是在超级市场买东西拿到的小票,由超市(Server)发给消费者(Browser),超市方面不用记住每一个消费者的脸,但是他们认识消费者手里的小票(Cookie),可以通过小票知道消费者之前的一些消费信息(在服务端产生的数据)。
2.3 Cookie细节
一次可以发送多个Cookie
- 可以创建多个对象,使用response调用多次addCookie方法发送cookie即可
Cookie在浏览器中保存时间
- 默认情况下,当浏览器关闭后,Cookie数据被销毁
- 设置Cookie生命周期:持久化存储
- setMaxAge(int seconds)
- 正数:将Cookie数据写到硬盘的文件中,持久化存储。正数代表cookie的存活时间。比如30,在30s之后,这个Cookie文件将被删除掉
- 负数:默认值,存储在内存中,浏览器关闭,就销毁
- 零:删除Cookie信息
- setMaxAge(int seconds)
Cookie存储中文
- Tomcat8之前,Cookie中不能直接存储中文数据。需要将中文数据转码,即url编码(%E3,即%跟两个十六进制的数字表示一个字节,有多少个字节,就会有多少%)
- Tomcat8之后,Cookie支持中文数据,但是特殊字符还是不支持,比方说空格。需要用url编码
Cookie共享问题
- 默认同一服务器不同项目,是不会共享Cookie的
- setPath(String path):设置cookie的获取范围。默认情况下,会去设置当前的虚拟目录
- 不同服务器实现cookie共享
- setDomain(String path):设置一级域名相同,那么多个服务器之间cookie可以共享
- 默认同一服务器不同项目,是不会共享Cookie的
| |

2.4 Cookie的特点和作用
特点
- Cookie存储数据在客户端浏览器
- 浏览器对于单个Cookie的大小有限制(4KB左右,不同浏览器不同),以及同一个域名下的总Cookie数量也有限制(20个,不同浏览器不同)
作用
- Cookie一般用于存储少量的、不敏感的数据
- 在不登录的情况下,完成服务器对客户端的身份识别(比方说百度在不登录的情况下,存储的一些个性化设置,当然这个也是可以通过本地存储来实现)
2.5 案例-记录访问
需求
记住上一次访问时间
如果是初次访问,提示你好,欢迎首次访问
如果是二次访问,提示你好,欢迎回来,上次访问时间:xxx
分析:判断是否有lastTime该cookie,如果没有,则是初次访问。访问时,服务端将访问时间存储到lastTime的cookie中
实现
| |

注意cookie存储中特殊字符的编解码即可
三、JSP
3.1 概念
JSP:Java Server Pages,java服务端页面
本质:一个特殊页面,既可以定义html标签,又可以定义java代码。本质就是一个Servlet
作用:简化Servlet书写
3.2 原理
- 客户端请求**.jsp**,服务端判断有无**.jsp**。无则404
- 找到**.jsp**,会将**.jsp转换为.java**文件
- 编译**.java文件,生成.class**字节码文件
- 由字节码文件提供访问
我们在CATALINA_BASE的路径下,能够找到集成到idea上面Tomcat的路径,在conf下面,能够找到服务器访问的源文件。像servlet生成的源码放到了WEB-INF的classes下面

.jsp文件经过编译之后,存储在CATALINA_BASE路径下的work中

可以看出,编译后**.java文件继承了Tomcat中的HttpJspBase.java**,我们进入Tomcat找到该类

HttpJspBase继承了HttpServlet,故jsp的本质就是一个Servlet
3.3 jsp脚本
jsp脚本:jsp定义Java代码的方式
- <% 代码 %>:定义在jsp转换后的java类(Servlet)的service方法中。service方法中可以定义什么,该脚本中就可以定义什么。
- <%! 代码 %>:定义在jsp转换后的java类(Servlet)的成员位置。(不过尽量不要再Servlet中定义成员变量,会引发线程安全问题)
- <%= 代码 %>:定义在jsp转换后的java类(Servlet)的service方法中。会输出在网页页面上,输出语句能输出啥,该脚本中就能定义啥
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<% System.out.println("Hello JSP"); %>
<%!
int i = 3;
%>
<%= i %>
<%= "我打你妈的" %>
<h1>Hello JSP</h1>
</body>
</html>以上面这个例子为例,编译生成的index_jsp.java如下
| |
3.4 jsp内置对象
内置对象概念:在jsp页面中不需要获取和创建,可以直接使用的对象。
因为jsp编译后就是一个servlet类,其中某些代码直接放到了service方法中,所以,像request这种的,是在Servlet中已经定义好的,就可以直接使用。叫做内置对象。
具体可以参照上面jsp生成的java代码
jsp一共有9个内置对象(本次介绍三个,详细看下篇博客)
- request
- response
- out:字符输出流对象,可以将数据输出到页面上。类似于response.getWriter()
- out.write():按代码顺序输出
- response.getWriter().write():直接先输出。在jsp中尽量不要用这个。
在Tomcat服务器做出响应之前,会先找response.getWriter()缓冲区的内容,再找out缓冲区中的内容,故response.getWriter().write()数据输出永远在out.write()之前

通过jsp,我们可以优化Cookie的案例
<%@ page import="java.text.SimpleDateFormat" %>
<%@ page import="java.util.Date" %>
<%@ page import="java.net.URLEncoder" %>
<%@ page import="java.nio.charset.StandardCharsets" %>
<%@ page import="java.net.URLDecoder" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
//设置有没有lastTime这个cookie标记
boolean flag = false;
//获取当前时间
String time = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss").format(new Date());
//直接存储time,会出错,因为有个空格。当然删掉是可以的,不过最好的办法是url编码
String time_en = URLEncoder.encode(time, StandardCharsets.UTF_8);
//获取cookie
Cookie[] cks = request.getCookies();
//二次访问
if (cks != null && cks.length > 0) {
for (Cookie ck : cks) {
String name = ck.getName();
if ("lastTime".equals(name)) {
flag = true;
//响应数据
String value = URLDecoder.decode(ck.getValue(), "utf-8");
%>
<h1>欢迎回来,您上次访问时间<%=value%>
</h1>
<%
//重新设置cookie值
ck.setValue(time_en);
ck.setMaxAge(60 * 60 * 24 * 30);//cookie存储一个月
response.addCookie(ck);
break;
}
}
}
//初次访问
if (cks == null || cks.length == 0 || !flag) {
%>
<h1>您好,这是你的首次访问</h1>
<%
Cookie lt = new Cookie("lastTime", time_en);
lt.setMaxAge(60 * 60 * 24 * 30);
response.addCookie(lt);
}
%>
</body>
</html>优点:简单、方便,不用重启服务器即可运行。
缺点:代码没有进行分离。后期很难维护。当然实际开发中,还是不会用这种模式的。
四、Session
概念:服务器端会话技术。在**一次会话**的多次请求间共享数据,将数据保存在服务器端的对象中(有一个对象就是HttpSession)
4.1 快速入门
获取HttpSession
- request.getSession()
HttpSession对象
- Object getAttribute(String name)
- void setAttribute(String name, Object value)
- void removeAttribute(String name)
| |
访问上一个页面的时候,获取到session,并存储了数据
| |
访问demo02Session,即可获取到session中存储的数据。
如果客户端关闭之后,则再次获取数据,会获取到null
4.2 实现原理
Session是依赖于Cookie的!
初次访问服务器,获取Session,没有Cookie。会在内存中创建一个新的Session对象,该Session对象有唯一的id(jsp的话就是JSESSIONID;如果是php,会是PHPSESSID),然后将该id以set-cookie的形式返回给客户端。

二次访问服务器,请求头cookie中就会携带该Session对象的id,然后服务器根据id来判断获取的是哪个Session对象

4.3 Session细节
- 客户端关闭后,服务器不关闭,两次获取到的Session默认不是同一个
- 如果需要相同,则可以创建Cookie,键为JSESSIONID,并且设置最大存活时间,让Cookie持久保存
- 客户端不关闭,服务器关闭后,两次获取到的Session不是同一个
- 那么如何在服务器关闭后保持数据不丢失?
- Session钝化
- 在服务器正常关闭之前,将session对象存储到硬盘上
- Session活化
- 在服务器启动后,将session文件转化为内容中的session对象即可
- Session钝化
- 那么如何在服务器关闭后保持数据不丢失?
- Session的销毁
- 默认情况下,服务器关闭
- session对象调用invalidate()
- session默认失效时间为30分钟
- 可以在web.xml进行选择性修改
Session的钝化和活化,在本地Tomcat中是可以自动完成的,在idea的集成环境下,是不可以的。在服务器正常关闭的时候,Tomcat会自动在work目录下生成SESSIONS.ser,当服务器再次启动时,会将该文件读取到内存并删除。javaweb之session序列化与反序列化

4.4 Session的特点
特点
- session用于存储一次会话的多次请求的数据,存在服务器端
- session可以存储任意类型,任意大小
4.5 Session与Cookie区别
区别
- Session存储在服务器端,Cookie存储在客户端
- Session没有数据大小限制,Cookie有大小限制
- 相对来说,Session比较安全,Cookie不太安全
4.6 案例-验证码
需求
- 访问带有验证码的登录页面login.jsp
- 用户输入用户名、密码以及验证码
- 若用户名和密码输入有误,跳转登录页面,提示用户名或密码错误
- 如果验证码输入有误,跳转登录页面,提示验证码错误
- 如果全部正确,跳转主页success.jsp,提示用户名,欢迎你
分析
- 设置request编码
- 获取参数Map集合
- 获取验证码
- 将用户信息封装到User对象
- 判断程序生成的验证码和用户输入的验证码是否一致(从Session中获取程序生成的随机验证码)
- 不一致
- 给用户提示信息
- 跳转登录页面:转发
- 一致
- 判断用户名和密码
- 正确
- 不正确
- 判断用户名和密码
- 不一致
实现
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
<style>
* {
margin: 0;
padding: 0;
}
.box {
background-color: #74C3B5;
color: #fff;
width:100vw;
height:100vh;
position:fixed;
}
.container {
width: 600px;
margin: 80px auto;
text-align: center;
}
.logo {
width: 250px;
height: 70px;
margin: auto;
border-radius: 5px;
margin-bottom: 40px;
font-size:44px;
}
input {
color: #fff;
width: 250px;
height: 40px;
/*opacity:.5;*/
transition: all ease .4s;
background-color: rgba(255, 255, 255, .2);
text-align: center;
font-size: 20px;
border: 2px solid rgba(255, 255, 255, .4);
outline: none;
border-radius: 5px;
}
img {
outline: 0;
border: 1px solid rgba(255, 255, 255, 0.4);
background-color: rgba(255, 255, 255, 0.2);
width: 250px;
height: 70px;
border-radius: 3px;
padding: 5px 8px;
margin: 0 auto 5px auto;
display: block;
text-align: center;
color: white;
opacity: .9;
box-sizing: border-box;
}
input:focus {
color: #75DFB7;
width: 300px;
background-color: #fff;
}
button {
width: 250px;
height: 40px;
border: none;
background-color: rgba(255, 255, 255, .9);
font-size: 20px;
color: #75DFB7;
cursor: pointer;
border-radius: 5px;
}
ul {
list-style: none;
}
li {
line-height: 50px;
}
form a {
text-decoration: none;
color: #fff;
}
input::placeholder {
color:#fff;
}
.bg-bubbles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
pointer-events: none;
}
.bg-bubbles li {
position: absolute;
list-style: none;
display: block;
width: 40px;
height: 40px;
background-color: rgba(255, 255, 255, 0.15);
bottom: -160px;
-webkit-animation: square 25s infinite;
animation: square 25s infinite;
-webkit-transition-timing-function: linear;
transition-timing-function: linear;
}
.bg-bubbles li:nth-child(1) {
left: 10%;
}
.bg-bubbles li:nth-child(2) {
left: 20%;
width: 80px;
height: 80px;
-webkit-animation-delay: 2s;
animation-delay: 2s;
-webkit-animation-duration: 17s;
animation-duration: 17s;
}
.bg-bubbles li:nth-child(3) {
left: 25%;
-webkit-animation-delay: 4s;
animation-delay: 4s;
}
.bg-bubbles li:nth-child(4) {
left: 40%;
width: 60px;
height: 60px;
-webkit-animation-duration: 22s;
animation-duration: 22s;
background-color: rgba(255, 255, 255, 0.25);
}
.bg-bubbles li:nth-child(5) {
left: 70%;
}
.bg-bubbles li:nth-child(6) {
left: 80%;
width: 120px;
height: 120px;
-webkit-animation-delay: 3s;
animation-delay: 3s;
background-color: rgba(255, 255, 255, 0.2);
}
.bg-bubbles li:nth-child(7) {
left: 32%;
width: 160px;
height: 160px;
-webkit-animation-delay: 7s;
animation-delay: 7s;
}
.bg-bubbles li:nth-child(8) {
left: 55%;
width: 20px;
height: 20px;
-webkit-animation-delay: 15s;
animation-delay: 15s;
-webkit-animation-duration: 40s;
animation-duration: 40s;
}
.bg-bubbles li:nth-child(9) {
left: 25%;
width: 10px;
height: 10px;
-webkit-animation-delay: 2s;
animation-delay: 2s;
-webkit-animation-duration: 40s;
animation-duration: 40s;
background-color: rgba(255, 255, 255, 0.3);
}
.bg-bubbles li:nth-child(10) {
left: 90%;
width: 160px;
height: 160px;
-webkit-animation-delay: 11s;
animation-delay: 11s;
}
@-webkit-keyframes square {
0% {
-webkit-transform: translateY(0);
transform: translateY(0);
}
100% {
-webkit-transform: translateY(-700px) rotate(600deg);
transform: translateY(-700px) rotate(600deg);
}
}
@keyframes square {
0% {
-webkit-transform: translateY(0);
transform: translateY(0);
}
100% {
-webkit-transform: translateY(-700px) rotate(600deg);
transform: translateY(-700px) rotate(600deg);
}
}
error {
color:deeppink;
}
</style>
</head>
<body>
<%
String login_error = (String) request.getAttribute("login_error");
String cc_error = (String) request.getAttribute("cc_error");
%>
<div class="box">
<div class="container">
<div class="logo">
神话Q传
</div>
<%
if(login_error!=null){
out.write("<error>"+login_error+"</error>");
}
if(cc_error!=null){
out.write("<error>"+cc_error+"</error>");
}
%>
<form action="/session/loginServlet" method="post">
<ul>
<li><input type="text" name="username" placeholder="用户名" autocomplete="off"></li>
<li><input type="text" name="password" placeholder="密码" autocomplete="off"></li>
<li><input type="text" name="checkcode" placeholder="不区分大小写"></li>
<li><img id="checkcode" src="/session/checkCodeServlet" alt=""></li>
<li>
<button>登录</button>
</li>
</ul>
</form>
</div>
<ul class="bg-bubbles">
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
<script>
document.querySelector("#checkcode").onclick=function (){
let url = "/session/checkCodeServlet";
let date=new Date().getTime();
this.setAttribute("src", url+"?"+date)
}
</script>
</body>
</html>CheckCodeServlet.java
| |
LoginServlet.java
| |
success.jsp
| |

这个页面是我原来模仿一个网页游戏写的一个页面,今天无意间发现了那么久远的代码,就直接拿来用了。
问题
验证码重复使用的问题已经解决了。
还有一个问题,就是如果用户直接访问LoginServlet会报错,这个是直接将LoginServlet摆在了明面上,这样做是不太好的,实际开发中,可以异步请求LoginServlet,而LoginServlet只需返回结果即可。这样应该会相对更安全易用
