Servlet终究是属于应用层,它是在应用层进行的一系列操作,它的底层仍然是依赖传输层,网络层,数据链路层这些通信
换句话说,Servlet是属于上层建筑,下面的输层,网络层,数据链路层属于是底层基础,上下层之间是有比较紧密的联系
通过这张图,可以很明确Servlet和Tomcat的关系
虽然Tomcat在Servlet之下,但是Tomcat其实也是一个应用程序,运行在用户态的普通进程(Tomcat其实也是一个Java进程)
用户写的代码(根据请求计算相应),通过Servlet和Tomcat进行交互,Tomcat拿到数据之后,进一步的和浏览器之间进行网络传输(封装和分用)
用户和服务器之间交互详解图:
用户和服务器之间交互主要有三大过程
1)接收请求
1、用户在浏览器输入一个 URL,此时浏览器就会构造一个 HTTP 请求
2、这个 HTTP 请求会经过网络协议栈逐层进行封装成二进制的 bit 流, 最终通过物理层的硬件设备转换成光信号/电信号传输出去
3、这些承载信息的光信号/电信号通过互联网上的一系列网络设备,最终到达目标主机(这个过程也需要网络层和数据链路层参与)
4、服务器主机收到这些光信号/电信号,又会通过网络协议栈逐层进行分用,层层解析,最终还原成HTTP 请求,并交给 Tomcat 进程进行处理(根据端口号确定进程)
5、Tomcat 通过 Socket 读取到这个请求(一个字符串),并按照 HTTP 请求的格式来解析这个请求,根据请求中的 Context Path 确定一个 webapp,再通过 Servlet Path 确定一个具体的类,再根据当前请求的方法 (GET/POST/…),决定调用这个类的 doGet 或者 doPost 等方法。此时我们的代码中的doGet / doPost 方法的第一个参数 HttpServletRequest 就包含了这个 HTTP 请求的详细信息
2)根据请求计算响应
在我们的 doGet / doPost 方法中,就执行到了我们自己的代码。我们自己的代码会根据请求中的一些信息,来给 HttpServletResponse 对象设置一些属性。例如状态码,header,body 等
3)返回响应
1、我们的 doGet / doPost 执行完毕后,Tomcat 就会自动把 HttpServletResponse 这个我们刚设置好的对象转换成一个符合 HTTP 协议的字符串,通过 Socket 把这个响应发送出去
2、此时响应数据在服务器的主机上通过网络协议栈层层封装,最终又得到一个二进制的 bit 流,通过物理层硬件设备转换成光信号/电信号传输出去
3、这些承载信息的光信号/电信号通过互联网上的一系列网络设备,最终到达浏览器所在的主机(这个过程也需要网络层和数据链路层参与)
4、浏览器主机收到这些光信号/电信号,又会通过网络协议栈逐层进行分用,层层解析,最终还原成
HTTP 响应,并交给浏览器处理
5、浏览器也通过 Socket 读到这个响应(一个字符串),按照 HTTP 响应的格式来解析这个响应,并且把body中的数据按照一定的格式显示在浏览器的界面上
class Tomcat {
// 用来存储所有的 Servlet 对象
private List<Servlet> instanceList = new ArrayList<>();
public void start() {
// 根据约定,读取 WEB-INF/web.xml 配置文件;
// 并解析被 @WebServlet 注解修饰的类
// 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类.
Class<Servlet>[] allServletClasses = ...;
// 这里要做的的是实例化出所有的 Servlet 对象出来;
for (Class<Servlet> cls : allServletClasses) {
// 这里是利用 java 中的反射特性做的
// 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的
// 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是
// 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。
Servlet ins = cls.newInstance();
instanceList.add(ins);
}
// 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;
for (Servlet ins : instanceList) {
ins.init();
}
// 利用我们之前学过的知识,启动一个 HTTP 服务器
// 并用线程池的方式分别处理每一个 Request
ServerSocket serverSocket = new ServerSocket(8080);
// 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
ExecuteService pool = Executors.newFixedThreadPool(100);
while (true) {
Socket socket = ServerSocket.accept();
// 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
pool.execute(new Runnable() {
doHttpRequest(socket);
});
}
// 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
for (Servlet ins : instanceList) {
ins.destroy();
}
}
public static void main(String[] args) {
new Tomcat().start();
}
}
小结:
1)让Tomcat先从指定的目录中找到所有要加载的Servlet类
部署的时候,是把Servlet代码编译成了.class,然后打了war包,然后拷贝到了webapps里面
Tomcat就会从webapps里来找到哪些.class对应的Servlet类,并且需要进行加载
当然啦,这里的加载不一定是Tomcat一启动就立即执行,也可能是"懒加载"
但是此处伪代码中就假设立即加载了2)根据刚才类加载的结果,通过反射的方式给这些类创建Servlet实例
3)实例创建好之后,就可以调用当前Servlet实例的init方法了
Servlet自带的方法,默认情况下init啥都不干。我们在继承个HttpServlet的时候,也可以自己重写init,就可以在这个阶段,帮我们做一些初始化工作了
4)创建TCP socket,监听8080端口,等待有客户端来连接
5)如果循环退出了,Tomcat也要结束了,就会依次循环调用每个Servlet的destroy方法
这个是属于收尾工作(Tomcat退出之前的事情)
细心的人可能会发现:明明前面是while(true),里面也并没有break,为啥执行流还能走到这里?
因为我们写的是一个伪代码,只包含了核心逻辑,没有太多的实现细节。实际上,Tomcat里面会有一些条件来退出这个循环。和init类似,这里的destroy默认也是啥也不干,可以在用户代码中重写这destroy
虽然这里有destroy这个环节,但这个环节并不是十分靠谱
如果Tomcat是通过“正常流程”退出的,才能主动结束循环,调用这里的destroy。如果是通过“非正常流程”退出的,此时就来不及调用destroy
“正常流程”比如:Tomcat的8005端口(管理端口,通过这个端口可以对这个Tomcat发号施令)。通过这个方式来关闭Tomcat,就属于“正常流程"
“非正常流程”:直接结束进程(大部分是这种情况)
class Tomcat {
void doHttpRequest(Socket socket) {
// 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析,和响应构建
HttpServletRequest req = HttpServletRequest.parse(socket);
HttpServletRequest resp = HttpServletRequest.build(socket);
// 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态内容
// 直接使用我们学习过的 IO 进行内容输出
if (file.exists()) {
// 返回静态内容
return;
}
// 走到这里的逻辑都是动态内容了
// 根据我们在配置中说的,按照 URL -> servlet-name -> Servlet 对象的链条
// 最终找到要处理本次请求的 Servlet 对象
Servlet ins = findInstance(req.getURL());
// 调用 Servlet 对象的 service 方法
// 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
try {
ins.service(req, resp);
} catch (Exception e) {
// 返回 500 页面,表示服务器内部错误
}
}
}
class Servlet {
public void service(HttpServletRequest req, HttpServletResponse resp) {
String method = req.getMethod();
if (method.equals("GET")) {
doGet(req, resp);
} else if (method.equals("POST")) {
doPost(req, resp);
} else if (method.equals("PUT")) {
doPut(req, resp);
} else if (method.equals("DELETE")) {
doDelete(req, resp);
}
......
}
}
小结:
1、构造req和resp
req,是通过读取socket中的数据,然后再按照HTTP协议的请求格式来解析的构造成了一个HttpServletRequest对象。resp这里则是相当于new了一个空的对象
2、判断当前请求的资源是否为静态文件
如果是静态文件,就读取文件内容,把文件内容构造到,resp对象的body中,并且返回这个resp对象
3、动态资源的处理
根据请求的URL,来获取到使用哪个类来处理
URL里要有两级路径:
第一级路径: Context Path,确定一个webapp
第二级路径: Servlet Path,确定一个Servlet类
如果没有找到匹配的Servlet类,就会返回404
4、找到对应的Servlet对象,进一步调用service方法
在service方法内部,又会进一步的调用 doGet / doPost等方法
如果在执行Servlet的service方法过程中出现未处理的异常,就会返回500
在讨论到上面这整套流程过程中,涉及到了关于Servlet的关键方法,主要有三个
init: 初始化阶段,对象创建好了之后,就会执行到.用户可以重写这个方法,来执行一些初始化逻辑
service: 在处理请求阶段来调用,每次来个请求都要调用一次service
destroy: 退出主循环,tomcat结束之前会调用,用来释放资源~
我们把这个三个关键方法以及它们的调用时机称为Servlet的生命周期
Servlet中有三个比较关键的类:HttpServlet、HttpServletRequest、HttpServletResponse
我们写 Servlet 代码的时候,首先第一步就是先创建类,继承自 HttpServlet,并重写其中的某些方法,然后才被Tomcat执行到的’
核心方法
方法名称 | 调用时机 |
---|---|
init | 在 HttpServlet 实例化之后被调用一次 |
destory | 在 HttpServlet 实例不再使用的时候调用一次 |
service | 收到 HTTP 请求的时候调用 |
doGet | 收到 GET 请求的时候调用(由 service 方法调用) |
doPost | 收到 POST 请求的时候调用(由 service 方法调用) |
doPut/doDelete/doOptions/… | 收到其他请求的时候调用(由 service 方法调用) |
例1:doGet方法
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//这个是让服务器在自己的控制台里打印
System.out.println("hello Servlet");
//在页面上也能打印hello Servlet
//把hello Servlet字符串放在http响应的body中,浏览器就会把body的内容显示到页面上
resp.getWriter().write("hello Servlet");
}
}
运行Tomcat,直接在浏览器中访问
例2:doPost方法
@WebServlet("/method")
public class MethodServlet extends HelloServlet{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//系统的默认编码是JDK
//显示的告诉浏览器,按照utf-8来读
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("POST 响应");
}
}
此时还要写一个test.html页面,并且将它放在webapp下
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Documenttitle>
head>
<body>
<script src="https://lib.baomitu.com/jquery/3.6.0/jquery.js">script>
<script>
$.ajax({
type:'post',
url:'method',//路径不加斜杠
success:function(body){
console.log(body);
}
});
script>
body>
html>
运行Tomcat,直接在浏览器中访问
注意:在同一个webapp里,多个Servlet关联的路径,不能相同,否则在启动Tomcat时,Tomcat就会自动退出
Tomcat 通过 Socket API 读取 HTTP 请求(字符串),并且按照 HTTP 协议的格式把字符串解析成
HttpServletRequest 对象
也就是说HttpServletRequest 对应一个HTTP响应,HTTP响应中有什么,这里就有什么
核心方法
方法名称 | 描述 |
---|---|
String getProtocol() | 返回请求协议的名称和版本 |
String getMethod() | 返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT |
String getRequestURI() | 从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分 |
String getContextPath() | 返回指示请求上下文的请求 URI 部分 |
String getQueryString() | 返回包含在路径后的请求 URL 中的查询字符串 |
Enumeration getParameterNames() | 返回一个 String 对象的枚举,包含在该请求中包含的参数的名称 |
String getParameter(Stringname) | 以字符串形式返回请求参数的值,或者如果参数不存在则返回null |
String[] getParameterValues(Stringname) | 返回一个字符串对象的数组,包含所有给定的请求参数的值,如果参数不存在则返回 null |
Enumeration getHeaderNames() | 返回一个枚举,包含在该请求中包含的所有的头名 |
String getHeader(Stringname) | 以字符串形式返回指定的请求头的值 |
String getCharacterEncoding() | 返回请求主体中使用的字符编码的名称 |
String getContentType() | 返回请求主体的 MIME 类型,如果不知道类型则返回 null |
int getContentLength() | 以字节为单位返回请求主体的长度,并提供输入流,或者如果长度未知则返回 -1 |
InputStreamgetInputStream() | 用于读取请求的 body 内容,返回一个 InputStream 对象 |
例3:打印请求信息
@WebServlet("/showRequest")
public class ShowRequestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("首行部分
");
//获取协议名和版本
stringBuilder.append(req.getProtocol());
stringBuilder.append("
");
//获取方法
stringBuilder.append(req.getMethod());
stringBuilder.append("
");
//拿到请求路径
stringBuilder.append(req.getRequestURI());
stringBuilder.append("
");
//获取上下文路径
stringBuilder.append(req.getContextPath());
stringBuilder.append("
");
//获取URL中的查询字符串
stringBuilder.append(req.getQueryString());
stringBuilder.append("
");
stringBuilder.append("header 部分
");
//获取请求中所有的头名,以枚举方式返回
Enumeration<String> headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
//获取name
String headerName = headerNames.nextElement();
//获取value
String headerValue = req.getHeader(headerName);
stringBuilder.append(headerName + ":" + headerValue + "
");
}
resp.setContentType("text/html; charset=utf8");
//将所有信息写回到响应body中
resp.getWriter().write(stringBuilder.toString());
}
}
运行Tomcat,直接在浏览器中访问
上述API能够让我们拿到HTTP请求的各个方面内容,但是却没那么常用,更常用的,其实是getParameter这个方法(获取到query string中的详细内容)
例4:通过getParameter获取GET请求中的参数
@WebServlet("/getParameter")
public class GetParameterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//预期浏览器传来一个形如这样的请求:/getParameter?userId=827&classId=2090008
String userId = req.getParameter("userId");
String classId = req.getParameter("classId");
resp.getWriter().write("userId=" + userId + ", classId" + classId);
}
}
运行Tomcat,直接在浏览器中访问
路径和参数之间用"?“作为分隔符,参数与参数之间用”&"作为分隔符
POST请求body的格式主要有三种:
相对来说,最长用的还是第一种和第三种方式,第二种主要是用于上传图片,上传文件用得比较多,这里有方式一和方式三案例,方式二的案例在文章最后
例5:请求是这种格式 x-www- form-urlencoded,服务器通过Post获取参数
获取参数的方式和GET一样,也是用getParameter
如何在前端构造一个这样的格式请求呢?
1)通过form表单
2)通过postman
这里我们采用form表单的方式
@WebServlet("/postGetParameter")
public class PostGetParameterServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//预期浏览器传来一个形如这样的请求:/getParameter?userId=827&classId=2090008
//服务器也是通过req.getParameter来获取到内容的
String userId = req.getParameter("userId");
String classId = req.getParameter("classId");
resp.getWriter().write("userId=" + userId + ", classId=" + classId);
}
}
现在还需要在webapp下构造一个html页面,和WEB-INF是同级目录,而不是在WEB-INF里面,否则大概率就会404
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Documenttitle>
head>
<body>
<form action="postGetParameter" method="post">
<input type="text" name="userId">
<input type="text" name="classId">
<input type="submit" value="提交">
form>
body>
html>
运行Tomcat,直接在浏览器中访问
通过Fiddler抓包,可以看到Post的详细细节
例6:请求是这json格式,服务器通过Post获取参数
对于这种body为json的格式来说,如果手动来解析,其实并不容易(JSON里面的字段是能嵌套的)
像这种情况,手动处理比较麻烦,可以使用第三方的库,来直接处理json格式数据
Java生态中,用来处理JSON的第三库,种类也是很多,我主要使用的库,叫做Jackson (Spring 官方推荐的库)
通过maven把jackson这个库,给下载到本地,并引入到项目中
这么多版本,随便挑一个就行,这里我选的是2.12.6.1版本
复制这段内容,然后粘贴到pom.xml中
在浏览器前端代码中,通过js构造出body为json格式的请求
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Documenttitle>
head>
<body>
<input type="text" id="userId">
<input type="text" id="classId">
<input type="button" value="提交" id="submit">
<script src="https://lib.baomitu.com/jquery/3.6.0/jquery.js">script>
<script>
let userIdInput = document.querySelector('#userId');
let classIdInput = document.querySelector('#classId');
let button = document.querySelector('#submit');
button.onclick = function() {
$.ajax({
type:'post',
url:'postJson',
contentType:'application/json',
data:JSON.stringify({
userId:userIdInput.value,
classId:classIdInput.value
}),
success:function(body){
console.log(body);
}
});
}
script>
body>
html>
在java后端代码中,通过jackson来进行处理
需要使用jackson ,把请求body中的数据读取出来,并且解析成Java中的对象
class User {
public int userId;
public int classId;
}
@WebServlet("/postJson")
public class PostJsonServlet extends HttpServlet {
//1.创建一个Jackson的核心对象
ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//2.读取body中的请求,然后使用ObjectMapper 来解析成需要的对象
//readValue 就是把Json格式的字符串({"userld":"827","classld":"2090008"}),转换成Java中的对象
//第一个参数,表示对哪个字符串进行转换,这个参数可以填写成一个String,也可以填一个InputStream对象,还可以填一个File对象
//第二个参数,表示要把这个Json格式的字符串,转换成哪个Java对象
User user = objectMapper.readValue(req.getInputStream(), User.class);//通过反射获取User类的属性
resp.getWriter().write("userId: " + user.userId + ", classId: " + user.classId);
}
}
readValue是怎么完成转换的?
此处就要求,类的属性名得和键值对中的key的名字匹配(要求名字匹配,这个只是jackson默认行为)
如果你就非得想搞个不匹配的名字,也不是不可以,jackson也提供了其他方式帮助我们进行映射,这里就不多赘述了
运行Tomcat,直接在浏览器中访问
当前使用的是ajax的方式来提交数据,这个操作默认不会产生页面跳转,就和咱们使用form风格差别很大
通过Fiddler抓包,可以看到Post的详细细节
Servlet 中的 doXXX 方法的目的就是根据请求计算得到相应,然后把响应的数据设置到HttpServletResponse 对象中,然后 Tomcat 就会把这个 HttpServletResponse 对象按照 HTTP 协议的格式,转成一个字符串,并通过Socket 写回给浏览器
核心方法
方法名称 | 描述 |
---|---|
void setStatus(int sc) | 为该响应设置状态码 |
void setHeader(String name, String value) | 设置一个带有给定的名称和值的 header。如果 name 已经存在,则覆盖旧的值 |
void addHeader(Stringname, String value) | 添加一个带有给定的名称和值的 header. 如果 name 已经存在,不覆盖旧的值, 并列添加新的键值对 |
void setContentType(Stringtype) | 设置被发送到客户端的响应的内容类型 |
void setCharacterEncoding(Stringcharset) | 设置被发送到客户端的响应的字符编码(MIME 字符集)例如,UTF-8 |
void sendRedirect(Stringlocation) | 使用指定的重定向位置 URL 发送临时重定向响应到客户端 |
PrintWriter getWriter() | 用于往 body 中写入文本格式数据 |
OutputStream getOutputStream() | 用于往 body 中写入二进制格式数据 |
例7:设置状态码为200
@WebServlet("/status")
public class StatusServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setStatus(200);
resp.getWriter().write("hello");
}
}
运行Tomcat,直接在浏览器中访问
通过Fiddler抓包
例8:自动刷新页面
@WebServlet("/autoRefresh")
public class AutoRefreshServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//每隔1秒钟刷新一次
resp.setHeader("Refresh", "1");
//加上时间戳,方便观察
resp.getWriter().write("timeStamp: " + System.currentTimeMillis());
}
}
运行Tomcat,直接在浏览器中访问
可以明显的看到,时间戳都不同
通过Fiddler抓包
例10:进行302重定向
@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//resp.setStatus(302);
//resp.setHeader("Location", "https://www.baidu.com");
//Servlet提供了一个更简便的实现重定向的写法,更推荐这种写法
resp.sendRedirect("https://www.baidu.com");
}
}
通过Fiddler抓包
HTTP 协议自身是属于 “无状态” 协议
无状态的含义:
默认情况下 HTTP 协议的客户端和服务器之间的这次通信,和下次通信之间没有直接的联系
但在实际的应用场景中,服务器是需要知道客户端最近有没有过访问,例如登录网站后,进行页面跳转,服务器就不应该让用户再次登录验证
图中的"令牌"通常就存在用户端的Cookie中
举个通俗易懂栗子,理解什么是Cookie
我们到食堂的一个窗口去吃麻辣烫,由于这个窗口人很多,服务员也不知道刚出锅的麻辣烫是哪个同学的,于是就会为每一个来吃麻辣烫的同学分发一个号码牌,自己再留一个对应的号码牌夹在对应的碗中,等到麻辣烫好了之后,就会有提示说,请xx号来取麻辣烫,而对应的同学就拿着号码牌去取麻辣烫
这里的号码牌就相当于客户端的Cookie
这个Cookie是服务器给的,自己不能伪造
此时在服务器这边就需要记录令牌信息, 以及令牌对应的用户信息, 这个就是 Session 机制所做的工作
核心方法
HttpServletRequest 类中的相关方法
方法名称 | 描述 |
---|---|
HttpSession getSession() | 在服务器中获取会话。参数如果为 true,如果会话存在,返回当前会话,会话不存在就新建会话。参数如果为 false,如果会话存在,返回当前会话,会话不存在返回 null |
Cookie[] getCookies() | 返回一个数组,包含客户端发送该请求的所有的 Cookie 对象,会自动把Cookie 中的格式解析成键值对 |
在调用getSession的时候具体要做的事情:
1.创建会话
首先先获取到请求中cookie里面的sessionld字段(相当于会话的身份标识),判定这个sessionld是否在当前服务器上存在。如果不存在,则进入创建会话逻辑
创建会话,会创建一个HttpSession对象,并且生成一个sessionld (是一个很长的数字,通常是用十六进制来表示,能够保证唯一性)。接下来就会把这个sessionld作为key,把这个HttpSession对象,作为value,把这个键值对,给保存到服务器内存的一个"哈希表" 这样的结构中,实际中也不一定就真是哈希表,但一定是类似的能够存储键值对的结构,并且这个数据是在内存中的
再然后,服务器就会返回一个HTTP响应,把sessionld通过Set-Cookie字段返回给浏览器,浏览器就可以保存这个sessionld到Cookie中了
2.获取会话
先获取到请求中的cookie里面的sessionld字段(也就是会话的身份标识),判定这个sessionld是否在当前服务器上存在(也就是在这个哈希表中是否有)。如果有,就直接查询出这个HttpSession对象,并且通过返回值返回回去
HttpServletResponse 类中的相关方法
方法名称 | 描述 |
---|---|
void addCookie(Cookie cookie) | 把指定的 cookie 添加到响应中 |
HttpSession 类中的相关方法
一个 HttpSession 对象里面包含多个键值对,我们可以往 HttpSession 中存任何我们需要的信息
方法名称 | 描述 |
---|---|
Object getAttribute(Stringname) | 该方法返回在该 session 会话中具有指定名称的对象,如果没有指定名称的对象,则返回 nul (获取键值对) |
void setAttribute(Stringname, Object value) | 该方法使用指定的名称绑定一个对象到该 session 会话(存储键值对) |
boolean isNew() | 判定当前是否是新创建出的会话 |
Cookie 类中的相关方法
每个 Cookie 对象就是一个键值对
方法名称 | 描述 |
---|---|
String getName() | 该方法返回 cookie 的名称,名称在创建后不能改变(这个值是 Set-Cooke 字段设置给浏览器的) |
String getValue() | 该方法获取与 cookie 关联的值 |
void setValue(StringnewValue) | 该方法设置与 cookie 关联的值 |
HTTP 的 Cooke 字段中存储的实际上是多组键值对,每个键值对在 Servlet 中都对应了一个 Cookie 对象
通过 HttpServletRequest.getCookies() 获取到请求中的一系列 Cookie 键值对
通过 HttpServletResponse.addCookie() 可以向响应中添加新的 Cookie 键值对
例9:网页登录
约定前后端交互接口
两组交互:登录+获取页面
整体有三步骤
1.先编写一个简单的登录页面,使用form表单来构造post请求
这个login.html页面需要放在webapp下
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<form action="login" method="post">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value="登录">
form>
body>
html>
2.编写一个Servlet来处理登录请求
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//处理用户请求
String username = req.getParameter("username");
String password = req.getParameter("password");
//判定用户名和密码是否正确
//正常来说数据都是放在数据库中进行操作的
//此处为了简单,就假设数据库中有用户名"fl"和对应的密码"123"
//这么写防止null异常
if("fl".equals(username) && "123".equals(password)) {
//登录成功
//创建好一个Session,并且在Session中填写一些身份信息,以供后面的逻辑使用
HttpSession httpSession = req.getSession(true);
//向会话中存储用户身份
httpSession.setAttribute("username", username);
//向会话中存储用户访问次数
httpSession.setAttribute("count", 0);
//重定向
resp.sendRedirect("index");
} else{
//登录失败
resp.getWriter().write("login failed");
}
}
}
3.编写一个Servlet来处理服务器端返回主页的逻辑
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//返回一个页面(简单的html页面)
//这里的HTTPSession对象和登录时的HTTPSession对象是同一个对象,
//登录时就已经创建了,这里就不需要重复创建,参数给为false即可
//因此,根据相同的key,拿到的value也一定是相同的
HttpSession session = req.getSession(false);
//返回一个页面(简单的html页面)
//此处需要得到用户名和访问次数,因为返回值类型是Object,因此需要类型转换
String username = (String) session.getAttribute("username");
Integer count = (Integer) session.getAttribute("count");
//访问次数加1,并修改会话中的count
count += 1;
session.setAttribute("count", count);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(" 名字:"
+ username +" 次数:"+ count +"");
}
}
运行Tomcat,通过Fiddler抓取客户端和服务器的三次交互过程
第一次交互:浏览器从服务器上拿到登录页面
第二次交互:点击登录之后,就要给服务器发送一个登录请求,服务器会返回响应
第三次交互:浏览器收到302响应之后,再次向服务器发起请求,访问主页
最终的页面结果:
例10:上传文件,请求是这种格式 form-data,服务器通过Post获取参数
上传文件也是开发中的一个典型需求。上传文件的时候,在前端需要用到form表单,form表单中需要使用特殊的类型 form-data
此时提交文件的时候,浏览器就会把文件内容以form-data的格式构造到HTTP请求中。服务器就可以通过getPart来获取参数
一个HTTP请求,可以一次性的提交多个文件的,每个文件都称为一个Part,每个Part都有一个name(身份标识),服务器代码中就可以根据name找到对应的Part
HttpServletRequest 类中关于Part的方法
方法名称 | 描述 |
---|---|
Part getPart(String name) | 获取请求中给定 name 的文件 |
Collection | 获取所有的文件 |
Part方法
方法名称 | 描述 |
---|---|
String getSubmittedFileName() | 获取提交的文件名 |
String getContentType() | 获取提交的文件类型 |
long getSize() | 获取文件的大小 |
void write(String path) | 把提交的文件数据写入磁盘文件 |
提交页面
该页面也要放在webapp下
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<form action="upload" method="post" enctype="multipart/form-data">
<input type="file" name="MyImage">
<input type="submit" value="提交">
form>
body>
html>
form表单中参数enctype="multipart/form-data不能缺
处理文件上传请求
//要给类加上这个@MultipartConfig注解,来开启对于上传文件的支持
//否则getPart调用的时候就会抛出异常
@MultipartConfig
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Part part = req.getPart("MyImage");
System.out.println(part.getSubmittedFileName());//文件名
System.out.println(part.getContentType());//文件类型
System.out.println(part.getSize());//文件大小
part.write("D:/aaa.png");//把上传过来的文件(上传图片)放到D盘中,并取名为aaa.png
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("上传成功");
}
}
运行Tomcat
随机选择一张图片,并提交上传
通过Fiddler抓包,查看请求
在这个boundary中间的部分,就是图片的二进制数据(二进制数据在记事本中打开就是乱码)