1 简介
webApp: 网站、完整的项目。【平台】
servlet: 实现WebApp的工具,准确来说是一个框架,通过框架可以更方便的开发项目(后端API)。【工具】
Tomcat: 部署Servlet程序的。【商家】
它们之间的关系就是:平台给商家提高工具。
Servlet的难点是:配置比较多,且配置过程中的问题特别多。
配置过程中的问题特别多的原因是:
- 电脑的问题,杀毒软件或防火墙导致下载问题。
- JDK配置环境有问题。
- 其他问题。
概念: Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
定位: Java Servlet用Java编写的服务器端程序(web application)。
作用: 其主要功能在于交互式地浏览和修改数据,生成动态Web内容。
理解: 狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,我们将Servlet理解为后者。
2 创建Servlet项目并成功发布运行
创建Servlet项目并成功发布运行的步骤如下所示:
- 创建一个webapp的maven项目,除了第一步的Create from archetype模板选择和前面创建maven项目时所选的不一样,其余步骤均一样。我们此时应该选择如下图所示的Create from archetype模板。
创建成功后出现的目录如下图所示:
- 手动创建Java源代码根路径,在main路径下面创建一个例如名叫src的目录(这个目录必须是浅蓝色的)。
- 手动添加Servlet引用(也就是maven项目添加外部引用的操作步骤)。
- 修改配置文件web.xml,把此文件修改成Servlet3.1的配置文件即可。下面就是Servlet3.1的配置文件:
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1"
metadata-***plete="true">
<!-- 这块是固定不变的-->
<!-- 用来注册servlet接口的实现类-->
<servlet>
<servlet-name>Hello</servlet-name>
<servlet-class>HelloServlet</servlet-class>
<!-- 此时在上面创建的src文件夹下必须创建一个HelloServlet类,也就是要有一个HelloServlet.java文件 -->
</servlet>
<!-- 用来注册servlet的接口-->
<servlet-mapping>
<servlet-name>Hello</servlet-name>
<url-pattern>/hello-servlet</url-pattern>
</servlet-mapping>
</web-app>
这里必须说一下访问流程,就是当我们前端去访问一个url地址的时候,它先是进去servlet项目去找web.xml文件中的servlet-mapping和我的url进行对比,看有没有一个叫/hello的,如果有就说明我们此时访问的地址是对的。
接下来根据url拿到servlet-mapping中的servlet-name,然后根据这个servlet-name去servlet中找和前面这个servlet-name一样的servlet-name,然后就可以得到servlet-class的名字,这个servlet-class对应的就是我们后端代码的地址的类名。
servlet项目具体访问流程如下图所示:
5. 创建普通类并且将它升级为Servlet后端类。
//普通类
//public class HelloServlet{
//}
//将它升级为Servlet后端类
//public class HelloServlet extends HttpServlet{
//}
//具体写的一个实现例子
public class HelloServlet extends HttpServlet{
//方法类型为GET的请求
//重写doGet方法,把里面的内容删空,替换成你自己要实现的内容
//实现内容:让服务器给客户端相应一个HelloServlet
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
//1.获得写入流
PrintWriter printWriter = response.getWriter();
printWriter.println("<h1>HelloServlet</h1>");
}
}
- 运行Servlet方式1(使用tomcat运行):
(1)maven打包项目:
点击右侧的Maven中的Lifecycle,双击里面的package就可以将项目成功打包成war/jar包,这个war/jar包就是最终运行的文件。
(2)发布servlet项目到tomcat:
先把前面打的war/jar包复制粘贴到tomcat的webapps根目录下面即可。
(3)启动tomcat:bin/startup.bat.
(4)打开浏览器输入:https://localhost:8080/(所打war包的名称)/(前面在web.xml配置的路由,例如hello)即可成功访问到你所实现的页面。
此运行方式一般只有在程序开发完成上线阶段使用。 - 运行Servlet方式2(Idea+Tomcat):
(1)添加配置,点击idea中的Add Configuration…按钮:
再向下拉会有下图这个东西,如果你觉得自己war包的名字太长了想修改成如下路径,你就可以进行修改这里,这个东西可改可不改。
(2)点击idea中运行代码的那个绿的三角形即可运行成功。
(3)页面有时候会直接弹出来,但有时间不会,不会的时候我们去浏览器和运行Servlet方式1中的第四步一样即可成功访问到你所实现的页面。
3 新加Servlet步骤
- 先创建一个Servlet后端类。
- 配置web.xml中的路由。
注意:可能会出现页面乱码问题,如何解决呢?我们在写代码时需要设置页面编码和设置返回的数据类型即可解决此问题。
4 Servlet项目练习
写一个前后端交互的计算器(这里目前仅实现加法运算)。
- 在servlet项目中的webapp目录(webapp目录下存放的是静态资源文件)下写一个html文件。
calc.html
<html>
<head>
<meta charset="utf-8">
<title>我的计算器</title>
</head>
<body>
<!-- 此时需要写一个form表单,因为我们就是用这个form表单来提交数据的。
这个form表单里有几个属性是需要设置一下的:
method:我提交的方法类型是什么?get/post都可以,下面是以post进行举例的。
action:我要提交到哪?例如这里起了一个后端接口叫calc。如果这个html和写的后端接口放在同一级底下,直接写calc即可,不用写成/calc等格式。
-->
<form method="post" action="calc">
<div style="text-align: center;"> <!-- 设置一个公共样式,让所有信息居中 -->
<h1>计算器<h1>
数字1:<input id="n1" name="number1" type="number"><p></p>
数字2:<input id="n2" name="number2" type="number"><p>
<input type="submit" value="提交">
</p>
</div>
</form>
</body>
</html>
- 写servlet后端接口。
Calc.java
public class Calc extends HttpServlet{
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
//业务逻辑
//1.获得前端提交的参数
Integer num1 = Integer.parseInt(response.getParameter("number1"));
Integer num2 = Integer.parseInt(response.getParameter("number2"));
Integer total = num1 + num2;
PrintWriter printWriter = response.getWriter();
printWriter.println("<h1>计算的结果:"+total+"</h1>");
}
}
- 配置web.xml中的路由(注意url一定全是小写的字母)。
<servlet>
<servlet-name>calc</servlet-name>
<servlet-class>Calc</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>calc</servlet-name>
<url-pattern>/calc</url-pattern>
</servlet-mapping>
- 此时运行起来你会发现,页面出现405问题。因为上面步骤一中你用的是post方法,而步骤二中却用的get方法,所以页面出现405问题啦。
- 此时你可以在步骤2中再重写一个post方法和doget方法里面的内容一模一样,或者直接使用下面的语句代替一模一样的内容。
public class Calc extends HttpServlet{
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
//业务逻辑
//1.获得前端提交的参数
Integer num1 = Integer.parseInt(response.getParameter("number1"));
Integer num2 = Integer.parseInt(response.getParameter("number2"));
Integer total = num1 + num2;
PrintWriter printWriter = response.getWriter();
printWriter.println("<h1>计算的结果:"+total+"</h1>");
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
this.doGet(request,response);
}
}
5 Servlet运行原理
Servlet运行原理图如下所示:
当第四步完成之后就要去执行执行Servlet后端类了,Servlet后端类执行的过程有三个方法:
- Servlet框架的init方法。
也就是当你点击运行的那一刻,启动Servlet的时候就会执行这个方法,这个方法用来加载最基础的Servlet框架的。并且这个init方法只执行一次。执行完成之后,就证明servlet已经启动成功。 - service方法。
接着它会执行你后端类里面的service方法,service方法也就是doget和dopost方法的另一种叫法。这个service方法是每请求一次就会执行一次,也就是每一次输入url后端地址去访问的时候都会执行一次。 - destroy方法。
当你执行结束,点击stop停止tomcat的时候就会执行destroy方法,这个方法就会回收一下垃圾内存,释放一些资源等。它和init方法一样也只执行一次。
总结:
也就是Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程: - Servlet 通过调用 init () 方法进行初始化。
- Servlet 调用 service() 方法来处理客户端的请求。
- Servlet 通过调用 destroy() 方法终止(结束)。
- 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
6 操作 HTTP Request头的方法(部分方法示例)
Servlet 程序中读取 HTTP Request头内容的基本方法使用示例如下所示:
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
public class HelloServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse
response)throws ServletException,IOException{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
//获取请求方法类型
String method = request.getMethod();//返回请求的 HTTP 方法的名称
String encoding = request.getCharacterEncoding();//返回请求主体中使用的字符编码的名称
String url = request.getContextPath();//返回指示请求上下文的请求 URI 部分
String contentType = request.getContentType();//返回请求主体的 MIME 类型,如果不知道类型则返回 null
//输出获取参数信息
String title = "HTTP Function Test!";
String docType = "<!doctype html>\n";
out.println(docType + "<html>\n" +"<head><title>" +title+ "</title></head>\n" +"<body>"+"<h3> Method: " +method+ "</h3>" +
"<h3> encoding: " +encoding+ "</h3>" +"<h3> contentType: "+contentType+ "</h3>" +"</body>"+"</html>");
}
@Override
public void doPost(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
doGet(request,response);
}
}
运行结果:
7 操作 HTTP Response头的方法(部分方法示例)
Servlet 程序中读取 HTTP Response头内容的基本方法使用示例如下所示:
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
import java.util.GregorianCalendar;
// 扩展 HttpServlet 类
public class HelloServlet extends HttpServlet {
// 处理 GET 方法请求的方法
public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
// 设置刷新自动加载时间为 1 秒
response.setIntHeader("Refresh", 1); //设置一个带有给定的名称和整数值的响应报头。
// 设置响应内容类型
response.setContentType("text/html");
// Get current time
Calendar calendar = new GregorianCalendar();//获取当前系统时间
String am_pm;
int hour = calendar.get(Calendar.HOUR);//获得时
int minute = calendar.get(Calendar.MINUTE);//获得分
int second = calendar.get(Calendar.SECOND);//获得秒
if(calendar.get(Calendar.AM_PM) == 0) //判定是否是上午和下午
am_pm = "AM";
else
am_pm = "PM";
String CT = hour+":"+ minute +":"+ second +" "+ am_pm;
PrintWriter out = response.getWriter();
String title = "auto refresh Header set";
String docType = "<!doctype html>\n";
out.println(docType +"<html>\n" +"<head><title>" + title + "</title></head>\n"+"<body>\n" +"<h1 align=\"center\">" + title + "</h1>\n" +"<p>current time: " + CT + "</p>\n");}
// 处理 POST 方法请求的方法
public void doPost(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException {
doGet(request, response);
}
}
运行结果:
或页面每秒进行一下刷新代码示例:
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
this.doPost(request, response);}
@Override
public void doPost(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException {
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
//response.setHeader("Refresh","1");
response.setIntHeader("Refresh",1);
PrintWriter printWriter = response.getWriter();
printWriter.println(String.format("<h1>Data:%s"</h1>,new Date()));
}
}
8 两种重定向(页面跳转)方法
方法一:
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
this.doPost(request, response);}
@Override
public void doPost(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException {
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
String name = request.getParameter("name");
String password = request.getParameter("pwd");
if(name!= null && pwd!= null && name.equals("root")&& pwd.equals("root")){
//用户名和密码输入正确,跳转到百度
response.setStatus(301);
response.setHeader("location","https://www.baidu.***/");
}else{
PrintWriter printWriter = response.getWriter();
printWriter.println("<h1>输入的用户名和密码错误!</h1>");
}
}
}
方法二:
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
this.doPost(request, response);}
@Override
public void doPost(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException {
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
String name = request.getParameter("name");
String password = request.getParameter("pwd");
if(name!= null && pwd!= null && name.equals("root")&& pwd.equals("root")){
response.sendRedirect("https://www.baidu.***");
}else{
PrintWriter printWriter = response.getWriter();
printWriter.println("<h1>输入的用户名和密码错误!</h1>");
}}
}
9 cookie
9.1 Cookie工作原理
前言: http是一个无状态(短链接)的协议,所以客户端和服务器端在交互的时候不知道对方是谁。那么在某些需要记录状态的场景下我们就需要想一种机制来保存会话信息。比如说我们在做登录功能的时候是要保存会话信息的,我们登录一次之后,以后的页面(接口)在访问的时候是不需要登陆的,这叫会话(身份信息),这个时候靠谁来保存呢?
短链接: 客户端把请求发送给服务器端,服务器端响应给客户端,然后响应完成之后整个链接就没有了。
那么http在设计的时候为什么不设置成有状态(长链接)的协议呢?因为服务器端只有一份,而客户端可能会有多份。如果设置成长链接,当服务在处理你的请求的时候就不能处理其他人的请求了,那么服务器端链接的同时人数使用限制的,最大是65535。比如我们现在服务器上有65535条链接,当其他人进来的时候服务器就不能提供服务了,这是不允许的,所以当人数超过这个数的时候就不能用了。但如果设置成短链接,一秒钟响应给你之后,那么下一秒钟我们就可以服务另一个人了,所以http一定要设置成无状态(短链接)的协议。
但此时我们做登录功能的时候是要保存会话信息的,所以只能我们自己想办法来保存这个会话信息了。这个时候怎么来保存呢?才开始我们用的是Cookie进行保存会话信息的,那么cookie是怎么保存会话信息的呢?
- 首先浏览器端输入用户名和密码,然后发送请求给服务器端(登录操作)。
- 服务器访问后端数据库验证用户名和密码是否正确,如果是错误的直接响应给客户端,告诉你的用户名和密码是错误的;如果是正确的则认证成功。
- 认证成功之后服务器端就会给客户端发送一个set-cookie(发送一个设置存储cookie的信息)。
- 客户端拿到服务器端这个响应指令之后,它会在自己的浏览器端去存储一个会话信息(存储一个cookie信息)。
- 存储下来之后,每次浏览器端(当登录成功之后)请求服务器端的时候都会带上本地的这个cookie信息。
- 然后服务器端每次拿到这个接口之后先看一下http这个请求头里有没有cookie,如果有的话,解析一下cookie,判断这个cookie是登陆的cookie信息之后,就可以正常的和客户端进行一个交互了。
这就是cooike的一个运行机制(运行原理),先认证,然后告诉。
也就是说cookie本质是在客户端存储一个会话信息,并且在存储之后,每次请求服务器端,会把这个会话信息(标识)带上请求给服务器端。
Cookie: 用来保存服务器端和客户端交互的会话信息(身份信息)。本质上是客户端实现会话保存的一种机制。
总结: cookie工作原理:
- 客户端向服务区发起登录请求。
- 服务器脚本向浏览器发送一组 Cookies。例如:姓名、年龄或识别号码等。
- 浏览器将这些信息存储在本地计算机上,以备将来使用。
- 当下一次浏览器向 Web 服务器发送任何请求时,浏览器会把这些 Cookies 信息发送到服务器,服务器将使用这些信息来识别用户。
9.2 cookie构成
Cookies 通常设置在 HTTP 头信息中。设置 Cookie 的http请求,会向 Servlet 会发送如下的头信息:
HTTP/1.1 200 OK
Date: Fri, 04 Feb 2000 21:03:38 GMT
Server: Apache/1.3.9
Set-Cookie: name=xyz; expires=Friday, 04-Feb-07 22:03:38 GMT;
path=/; domain=bit.***
Connection: close
Content-Type: text/html
- Set-Cookie 头包含了一个名称值对、一个 GMT 日期、一个路径和一个域。名称和值会被 URL 编码。
- expires 字段是一个指令,告诉浏览器在给定的时间和日期之后过期(“忘记”)该 Cookie。
- 如果浏览器被配置为存储 Cookies,它将会保留此信息直到到期日期。
如果用户的浏览器指向任何匹配该 Cookie 的路径和域的页面,它会重新发送Cookie 到服务器。浏览器的头信息可能如下所示:
GET / HTTP/1.0
Connection: Keep-Alive
User-Agent: Mozilla/4.6 (X11; I; Linux 2.2.6-15apmac ppc)
Host: zink.demon.co.uk:1126
A***ept: image/gif, /
A***ept-Encoding: gzip
A***ept-Language: en
A***ept-Charset: iso-8859-1,*,utf-8
Cookie: name=xyz
Servlet 就能够通过请求方法 request.getCookies() 访问 Cookie,该方法将返回一个 Cookie 对象的数组。
9.3 Servlet 操作cookie方法
Servlet 中操作 Cookies 时有可使用的有用的方法列表。
这里以"提交表单,设置cookie"项目为例进行介绍。
具体操作:
- 打开idea,创建一个servlet项目。
- index.html内容:
<!DOCTYPE html>
<html lang="en">
<head>
<!--设置编码格式-->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
<form action="hello-servlet" method="GET">
名字:<input type="text" name="first_name"><br />
姓氏:<input type="text" name="last_name" />
<input type="submit" value="提交" />
</form>
</body>
</html>
- Servlet 代码:
// 导入必需的 java 库
import javax.servlet.ServletException;
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;
// 扩展 HttpServlet 类
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
// 为名字和姓氏创建 Cookies
Cookie firstName = new Cookie("first_name",request.getParameter("first_name"));
Cookie lastName = new Cookie("last_name",request.getParameter("last_name"));
// 为两个 Cookies 设置过期日期为 24 小时后
firstName.setMaxAge(60*60*24);
lastName.setMaxAge(60*60*24);
// 在响应头中添加两个 Cookies
response.addCookie( firstName );
response.addCookie( lastName );
// 设置响应内容类型
response.setContentType("text/html");
// 设置响应的编码格式
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
String title = "设置 Cookies 实例";
String docType ="<!doctype html public>\n";
out.println(docType +"<html>\n" +"<head><title>" + title + "</title></head>\n" +"<body bgcolor=\"#f0f0f0\">\n" +"<h1 align=\"center\">" + title + "</h1>\n" +"<ul>\n" +" <li><b>名字</b>:"
+ request.getParameter("first_name") + "\n" +" <li><b>姓氏</b>:"
+ request.getParameter("last_name") + "\n" +"</ul>\n" +"</body></html>");
}
public void doPost(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
doGet(request,response);
}
}
- 发布运行项目:
5.补充:获取写入的cookie
// 导入必需的 java 库
import javax.servlet.ServletException;
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;
// 扩展 HttpServlet 类
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
Cookie cookie = null;
Cookie[] cookies = null;
// 获取与该域相关的 Cookies 的数组
cookies = request.getCookies();
// 设置响应内容类型
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
String title = "Reading Cookies Example";
String docType ="<!doctype html>\n";
out.println(docType +"<html>\n" +"<head><title>" + title + "</title></head>\n" +"<body>\n" );
if( cookies != null ){
out.println("<h2>查找 Cookies 名称和值</h2>");
for (int i = 0; i < cookies.length; i++){
cookie = cookies[i];
out.print("名称:" + cookie.getName( ) + ",");
out.print("值:" + cookie.getValue( )+" <br/>");
}
}
out.println("</body>");
out.println("</html>");
}
public void doPost(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
doGet(request,response);
}
}
- 补充:删除cookie
删除 Cookies 是非常简单的。如果想删除一个 cookie,那么需要按照以下三个步骤进行:
(1)读取一个现有的 cookie,并把它存储在 Cookie 对象中。
(2)使用 setMaxAge() 方法设置 cookie 的年龄为零,来删除现有的 cookie。
(3)把这个 cookie 添加到响应头。
Cookie cookie = null;
Cookie[] cookies = null;
// 获取与该域相关的 Cookies 的数组
cookies = request.getCookies();
// 设置响应内容类型
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
String title = "Delete Cookies Example";
String docType ="<!doctype html>\n";
out.println(docType +"<html>\n" +"<head><title>" + title + "</title></head>\n" +"<body>\n" );
if( cookies != null ){
out.println("<h2>Cookies 名称和值</h2>");
for (int i = 0; i < cookies.length; i++){
cookie = cookies[i];
if((cookie.getName( )).***pareTo("first_name") == 0 ) {
cookie.setMaxAge(0); //将过期时间设置为0,删除指定cookie
response.addCookie(cookie);
out.print("已删除的 cookie:" +
cookie.getName( ) + "<br/>");
}
out.print("名称:" + cookie.getName( ) + ",");
out.print("值:" + cookie.getValue( )+" <br/>");
}
}
out.println("</body>");
out.println("</html>");
9.4 总结
- Http 是一个无状态协议, 就是说这一次请求和上一次请求是没有任何关系的,互不认识的,没有关联的。这种无状态的的好处是快速。坏处是需要进行用户状态保持的场景时[比如,登陆状态下进行页面跳转,或者用户信息多页面共享等场景],必须使用一些方式或者手段比如: session 和 cookie。
- Http 是一个无状态的协议,但是访问有些资源的时候往往需要经过认证的账户才能访问,而且要一直保持在线状态,所以,cookie是一种在浏览器端解决的方案,将登陆认证之后的用户信息保存在本地浏览器中,后面每次发起http请求,都自动携带上该信息,就能达到认证用户,保持用户在线的作用。
- 设置cookie的方法在 Http 的 Response 报头中可以携带 Set-Cookie 字段来完成。
Cookie的缺点: 因为Cookie是存储在客户端的,所以它有被篡改的风险,所以现在我们只用cookie来存储一些不重要的信息。
10 Session
10.1 Session工作原理
为了解决上面的篡改问题,我们引入了session,session字面翻译过来就是会话的意思。
Session的作用和特点就是保存在服务器端,对于服务器我们是没有权限去修改的(除非程序员用代码去操作服务器),所以很好的解决了篡改问题。
10.2 Servlet 操作Session方法
HttpSession 对象:
- Servlet 还提供了 HttpSession 接口,该接口提供了一种跨多个页面请求或访问网站时识别用户以及存储有关用户信息的方式。
- Servlet 容器使用这个接口来创建一个 HTTP 客户端和 HTTP 服务器之间的 session 会话。会话持续一个指定的时间段,跨多个连接或页面请求。
- 我们可以通过调用 HttpServletRequest 的公共方法 getSession() 来获取 HttpSession 对象,如下所示:
HttpSession session = request.getSession();
。 - 需要在向客户端发送任何文档内容之前调用 request.getSession()。
Servlet 中操作 Session时 HttpSession 对象中有可用的方法列表。
这里以本例说明了如何使用 HttpSession 对象获取 session 会话创建时间和最后访问时间。如果不存在session 会话,我们将通过请求创建一个新的 session 会话。
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
public class HelloServlet extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException{
// 如果不存在 session 会话,则创建一个 session 对象
HttpSession session = request.getSession(true);
// 获取 session 创建时间
Date createTime = new Date(session.getCreationTime());
// 获取该网页的最后一次访问时间
Date lastA***essTime = new Date(session.getLastA***essedTime());
String title = "欢迎回来";
String visitCountKey = new String("visitCount");
Integer visitCount = new Integer(0);
String userIDKey = new String("userID");
String userID = new String("abcd");
// 检查网页上是否是新的访问者
if(session.isNew()){
title = "欢迎来到我的网站";
session.setAttribute(userIDKey, userID);
}else{
visitCount = (Integer) session.getAttribute(visitCountKey);
visitCount++;
userID = (String)session.getAttribute(userIDKey);
}
session.setAttribute(visitCountKey,visitCount);
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
String docType = "<!doctype html>\n";
out.println(docType + "<html>\n" +"<head><title>" +title+ "</title></head>\n" +"<body>\n" +"<h1>" + title + "</h1>\n"+
"<h2> Session 信息 </h2>" +"<p>" +"ID: " + session.getId() + "<hr/><br/>"+"Create Time: " + createTime + "<hr/><br/>"+"Time of Last A***ess: " + lastA***essTime + "<hr/><br/>"+"User ID: " + userID + "<hr/><br/>"+"Number of visits: " + visitCount + "<hr/><br/>"+"</p>"+ "</body>" +"</html>");
}
}
Step 1: 发起请求,查看结果
Step 2: 分析请求结果
Step 3: 多次刷新
10.3 session持久化
一般情况下,session是直接存储在指定服务器的内存中的,一般使用够了,但是在集群场景当中,可能需要通过session共享,来保证用户在不同的服务器上都可以得到认证。所以我们一般可以将用户的session信息统一保存在后端的数据库服务器中[比如,mysql/redis/memcache等],从而达到不同服务器间session的共享。
我们可以简单理解:将session保存在服务器数据库或者文件中的行为称之session持久化。
10.4 总结
将用户敏感信息放到本地浏览器中,能解决一定的问题,但是又引进了新的安全问题,一旦cookie丢失,用户信息泄露,也很容易造成跨站攻击,所以有了另一种解决方法,将用户敏感信息保存至服务器,而服务器本身采用md5算法或相关算法生成唯一值(session id),将该值保存值客户端浏览器,随后,客户端的后续请求,浏览器都会自动携带该id,进而再在服务器端认证,进而达到状态保持的效果。
两者有什么区别呢:
- Cookie以文本文件格式存储在浏览器中,而session存储在服务端。
- 因为每次发起 Http 请求,都要携带有效Cookie信息,所以Cookie一般都有大小限制,以防止增加网络压力,一般不超过4k。
- 可以轻松访问cookie值但是我们无法轻松访问会话值,因此session方案更安全。
11 Servlet文件上传
HttpServletRequest 对文件上传的支持:
此前,Servlet 本身没有对文件上传提供直接的支持,一般需要使用第三方框架来实现,实现起来很麻烦。不过,Servlet 3.0 之后提供了这个功能,而且使用也非常简单。为此,HttpServletRequest 提供了两个方法用于从请求中解析出上传的文件:
Part getPart(String name) //获取请求中给定 name 的文件
Collection<Part> getParts() //获取所有的文件
其中,每一个文件用一个 javax.servlet.http.Part 对象来表示。该接口提供了处理文件的简易方法,比如 write()、delete() 等。至此,结合 HttpServletRequest 和 Part 来保存上传的文件变得非常简单。
Part img = request.getPart("img");
img.write("E:\\apache-tomcat-8.5.47\\webapps\\file_dir\\img.jpg");
另外,可以配合前面提到的 @MultipartConfig 注解来对上传操作进行一些自定义的配置,比如限制上传文件的大小,以及保存文件的路径等。其用法非常简单,故不在此赘述了。
需要注意的是,如果请求的 MIME 类型不是 multipart/form-data,则不能使用上面的两个方法,否则将抛异常。
例子:
前端页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>文件上传表单</title>
</head>
<body>
<h3>文件上传:</h3>
请选择要上传的文件:<br />
<form action="upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" size="50" />
<br />
<input type="submit" value="上传文件" />
</form>
</body>
</html>
UploadServlet:
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.***.http.HttpRequest;
import java.***.http.HttpResponse;
@MultipartConfig
public class UploadServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException,IOException{
//具体上传上来的文件放在什么地方,由自己决定
File path = new File("E:\\apache-tomcat-8.5.47\\webapps\\file_dir");
//获取文件,文件在html中的name是“file”
Part img = request.getPart("file");
//制作文件全路径
String filePath = path.getPath()+File.separator +
img.getSubmittedFileName();
//获取成功之后,写入指定路径
img.write(filePath);
//显示到标准输出
System.out.println("file Upload: " + filePath);
//同样的信息,显示给用户浏览器
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("file Upload: " + filePath);
}
public void doGet(HttpRequest request, HttpResponse response)
throws ServletException,IOException {
//上传文件,不能用GET方法
System.out.println("上传文件只能用POST方法!");
}
}
运行结果:
总结: