【Servlet】什么是Servlet;常见状态码;Servlet API;Cookie和Session
Servlet
文章目录
- Servlet
- 1. Servlet是什么
- 2. 第一个Servlet程序
- 2.1 创建项目
- 2.2 引入依赖
- 2.3 创建目录
- 2.4 编写代码
- 2.5 打包程序
- 2.6 部署程序
- 2.7 验证程序
- 3. 更方便的部署方式(Smart Tomcat)
- 3.1 安装Smart Tomcat插件
- 3.2 配置Smart Tomcat插件
- 4. 常见的访问出错
- 4.1 出现404
- 4.2 出现405
- 4.3 出现500
- 4.4 出现"空白页面"
- 4.5 出现"无法访问此网站"
- 4.6 小结
- 5. Servlet运行原理
- 6. Tomcat执行流程
- 7. Servlet API
- 7.1 HttpServlet
- 1.核心方法
- 2. 代码实例:处理GET请求
- 3. 处理POST请求
- 7.2 HttpServletRequest
- 1. 核心方法
- 2. 打印请求信息
- 3. 获取GET请求中的参数
- 4. 获取POST请求中的参数(1)
- 5. 获取POST请求中的参数(2)
- 6. 获取POST请求中的参数(3)
- 7.3 HttpServletResponse
- 1. 核心方法
- 2. 示例:设置状态码
- 3. 示例:自动刷新
- 4. 示例:重定向
- 8. Cookie和Session
- 8.1Cookie
- 8.2 会话机制(Session)
- 8.3 Cookie和Session的区别
- 8.4 核心方法
- 9. 上传文件
- 9.1 核心方法
- 9.2 示例
1. Servlet是什么
Servlet是一种实现动态页面的技术,是一组Tomcat提供给程序猿的API,帮助程序猿简单高效的开发一个web app,主要是开发动态页面
静态页面也就是内容始终固定的页面,即使用户不同/时间不同/输入的参数不同,页面内容也不会发生变化。
动态页面就是用户不同/时间不同/输入的参数不同,页面内容会发生变化
构造动态页面的奇数有很多,每种语言都有意思额相关的库/框架来做这件事
Servlet就是Tomcat这个HTTP服务器提供给Java的一组API,来完成构建动态页面这个任务
Servlet主要做的工作
- 允许程序猿注册一个类,在Tomcat收到某个特定的HTTP请求的时候,执行这个类中的一些代码
- 帮助程序猿解析HTTP请求,把HTTP请求从一个字符串解析成一个HttpRequest对象
- 帮助程序猿构造HTTP响应,程序猿只要给指定的HttpResponse对象填写一些属性字段,Servlet就会自动的按照HTTP协议的方式构造出一个HTTP响应字符串,并通过Socket写回给客户端
2. 第一个Servlet程序
2.1 创建项目
使用IDEA创建一个Maven项目
2.2 引入依赖
Maven项目创建完毕后,会自动生成一个pom.xml文件,在这个文件中,引入Servlet API依赖的jar包
在中央仓库中搜索servlet,选择3.1.0版本,Servlet版本和Tomcat版本匹配,Tomcat选择8.5版本,Servlet就要使用3.1.0版本
把中央仓库中提供的xml复制到项目的pom.xml中
修改后的pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>servlet</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><!-- scope这个标签表示当前这个jar包只是在开发阶段使用,不需要打包到最终的发布包中,因为Tomcat里面内置了Servlet--><scope>provided</scope></dependency></dependencies>
<!-- tomcat不能识别jar包,所以需要在这里设置打包格式,tomcat能识别war包,所以设置打包格式为war包--><packaging>war</packaging>
<!-- 为了方便测试访问,设置打包后的名字,--><build><finalName>helloservlet</finalName></build>
</project>
标签内部放置项目依赖的jar包,maven会自动下载赖以到本地,
2.3 创建目录
当项目创建好了之后,IDEA会自动帮我们创建一些目录:
- src:表示源代码所在的目录
- main/java:表示源代码的根目录,后续创建.java文件就放到这个目录里
- main/resources:表示项目的一些资源文件所在的目录
- test/java:表示测试代码的根目录
1、除了这些目录,还需要创建webapp目录,
在main目录下,和java目录并列,创建一个webapp目录
2、创建WEB-INF
再在webapp目录中创建一个WEB-INF目录
3、创建web.xml
在WEB-INF目录中创建一个web.xml文件
往web.xml文件中拷贝以下代码:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>
webapp目录就是未来部署到Tomcat中的一个重要的目录,当前我们可以往webapp中放一些静态资源,比如html,css等
在这个目录中还有一个重要的文件web.xml,Tomcat找到这个文件才能正确处理webapp中的动态资源
2.4 编写代码
在java目录中创建一个HelloServlet类:
@WebServlet("/hello")
//这个/hello就是HTTP请求中URL的路径,只要是这个路径的HTTP请求,就会调用这个HttpServlet对应的方法,如果是GET请求,就调用doGET方法,如果是POST请求,就调用doPOST方法,这个路径没有包含(Content Path,就是当前webapp的名字)
//保证当前的类和一个特定的HTTP请求关联起来,
public class HelloServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().write("hello world");//这个操作就是往HTTP响应的body中写了一个字符串//不管请求是什么 ,都返回一个hello world}
}
- 首先创建一个类HelloServlet,继承自HttpServlet
- 在类上方加上注解,
- 重写doGet方法,doGet方法有两个参数,分别表示收到的HTTP请求和要构造的HTTP响应,这个方法会在Tomcat收到GET请求时触发
- HttpServletRequest表示HTTP请求,Tomcat按照HTTP请求的格式把字符串格式的请求转成了一个HttpServletRequest对象,后续想要获取请求中的信息(例如方法,url,header,body等)都是通过这个对象来获取
- HttpServletResponse表示HTTP响应,代码中把响应对象构造好(构造响应的状态码,header,body等)
- resp.getWriter() 会获取到一个流对象,通过这个流对象就可以写入一些数据,写入的数据会被构造成一个HTTP响应的body部分,Tomcat会把整个响应转成字符串,通过socket写回给浏览器
此时我们的代码不是通过main方法作为入口了,main方法已经被包含在Tomcat里,我们写的代码会被Tomcat在合适的实际调用起来,这里写的代码并不是一个完整的程序,而是Tomcat这个程序的一小部分逻辑,也就是根据请求处理响应的逻辑,其他部分都由Tomcat完成。
类要满足以下条件,Tomcat才会调用
- 创建的类需要继承自HttpServlet
- 这个类需要使用@WebServlet注解关联上一个HTTP的路径
- 这个类需要实现doXXX方法
当这三个条件都满足之后,Tomcat就可以找到这个类,并且在合适的时机进行调用
2.5 打包程序
使用maven进行打包,打开maven窗口,展开Lifecycle,双击package即可进行打包
打包成功后,可以看到在target目录下,生成了一个jar包
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mpTv2H6a-1655822589958)(C:\Users\Freedom\Desktop\CSDN\JAVA EE\Servlet图片\打包.jpg)]
但是这样的jar包并不是我们所需要的,Tomcat需要识别的是另一种war包格式,还有一点,打包生成的war包名字太复杂了,所以要在pom.xml中设置打包格式和生成的war包的名称
在pom.xml中新增一个packing标签,表示打包的方式是打一个war包,再新增一个build标签,内置一个finalName标签,表示打出的war包的名字是helloservlet
<packaging>war</packaging>
<build><finalName>helloservlet</finalName>
</build>
重新打包结果:
2.6 部署程序
把war包拷贝到Tomcat的webapps目录下,启动Tomcat(在解压后的Tomcat目录的bin目录中,找到startup.bat,双击即可启动Tomcat),Tomcat就会自动把war包解压缩
2.7 验证程序
通过浏览器访问http://127.0.0.1:8080/helloservlet/hello 就可以看到结果了
URL中的PTH分成两个部分,其中helloservlet为Context Path,hello为WebServlet注解中的路径
3. 更方便的部署方式(Smart Tomcat)
手动拷贝war包到Tomcat的过程比较麻烦,我们可以使用IDEA中的Smart Tomcat插件来完成这个工作
3.1 安装Smart Tomcat插件
File->Settings->plugins,搜索tomcat
3.2 配置Smart Tomcat插件
点击IDEA右上角的"ADD Configuration",选择左侧的"Smart Tomcat"
Name这一栏可以随便填写,在Tomcat Server这一栏选择Tomcat所在的目录
Context Path默认填写的值是项目的名称
配置完成后,启动程序,IDEA就会自动进行过编译,部署,启动Tomcat的过程
在浏览器中使用http://127.0.0.1:8080/servlet/hello 访问页面
路径的对应关系:
使用Smart Tomcat部署的时候,可以发现Tomcat的webapps内部并没有拷贝一个war包,也没有看到解压缩的内容,Smart Tomcat相当于是在Tomcat启动的时候直接引用了项目中的webapp和target目录
4. 常见的访问出错
4.1 出现404
404表示用户访问的资源不存在,大概率是URL的路径写的不正确,
- 例如少写了Context Path:127.0.0.1:8080/hello
- 少写了Servlet Path:127.0.0.1:8080/servlet
- Servlet Path写的和URL不匹配:代码中@WebServlet注解中路径为(“/helloServlet”),此时再用127.0.0.1:8080/servlet/hello 访问就访问不到了,两个路径要匹配才行
- web.xml写错了,也会出现404
4.2 出现405
405表示对象的HTTP请求方法没有实现。例如:没有实现doGet方法
在浏览器地址栏直接输入URL,会发送一个HTTP GET请求
此时就会根据/servlet/hello这个路径找到HelloServlet这个类,并且尝试调用类中的doGet方法,如果没有实现doGet方法,就会出现405
4.3 出现500
500状态码一般时Servlet代码中抛出异常导致的
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {String s = null;resp.getWriter().write(s.length());}
}
这里设置一个String类型的s,引用为null,尝试将s的长度写入响应时会发生空指针异常。此时页面就会出现500
4.4 出现"空白页面"
如果不往响应中写内容,就会出现空白页面,通过抓包可以看到响应的body为空
4.5 出现"无法访问此网站"
一般就是Tomcat启动失败了
例如:Servlet Path 写错了,将@WebServlet注解中写为(“hello”),没有加斜杠,
4.6 小结
- 4xx的状态码表示路径不存在,需要检查URL是否正确,和代码中设定的Context Path以及 Servlet Path是否一致
- 5xx的状态码表示服务器出现错误,需要观察页面的提示内容和Tomcat自身的日志,观察是否存在报错
- 出现连接失败一般是Tomcat没有正确启动,也需要观察Tomcat的自身日志是否有错误提示
- 空白页面这种情况需要我们使用抓包工具来分析HTTP请求响应的具体交互过程
5. Servlet运行原理
Tomcat的定位
当浏览器给服务器发送请求的时候,Tomcat作为HTTP服务器就可以接收到这个请求,HTTP协议作为一个应用层协议,需要底层协议来支持工作
接收请求:
- 用户在浏览器中输入一个URL,此时浏览器就会构造一个HTTP请求
- 这个HTTP请求会经过网络协议栈逐层进行封装成二进制bit流,最终通过物理层的硬件设备转换成光电信号传输出去
- 这个承载信息的光电信息通过互联网上的一系列网络设备,最终到达目的主机,这个过程也需要网络层和数据链路层的参与
- 服务器收到这些信号,通过网络协议栈的逐层分用,层层解析,最终还原成HTTP请求,交给Tomcat进程进行处理,根据端口号确定进程
- Tomcat通过Socket读取到这个请求,并按照HTTP请求的格式来解析这个请求,根据请求中的Context Path确定一个webapp,再通过Servlet Path确定一个具体的类,再根据当前请求的方法,决定调用这个类中的哪个方法,get对应doGet,post对应doPost…此时我们的代码中这些处理请求的方法中的第一个参数HttpServletRequest就包含了这个HTTP请求的详细信息
根据请求计算响应:
- 我们的代码会根据请求中的信息,给HttpServletResponse对象设置属性,例如状态码,header。body等
返回响应:
- 代码执行完毕后,Tomcat就会自动把HttpServletResponse这个对象转换成一个符合HTTP协议的字符串,通过Socket把这个响应发送出去
- 和接收请求一样,通过网络协议栈层层封装和分用,最终到达浏览器,浏览器通过Socket读到这个响应,按照HTTP响应的格式解析,并且把body中的数据按照一定的格式显示在浏览器的界面上
6. Tomcat执行流程
Tomcat初始化逻辑:
- Tomcat内置了main方法,当我们启动Tomcat的时候就是从main方法开始执行的
- 被@WebServlet注解修饰的类会在Tomcat启动的时候就被获取到,并集中管理
- Tomcat通过反射这样的语法机制来创建被@WebServlet注解修饰的类的实例
- 这些实例被创建完了之后,会调用其中的init方法进行初始化,这些实例销毁之前,会调用destory方法进行收尾工作,这两个方法我们自己写的类都可以重写,但是注意destory这个方法,如果我们重写了,但是直接结束掉Tomcat进行,那么destory就来不及执行,
- Tomcat内部也是通过Socket API进行网络通信的
- 为了能同时响应多个HTTP请求,采取多线程的方式实现,因此Servlet是运行在多线程环境下的
Tomcat处理请求逻辑:
- Tomcat从Socket中读取到的HTTP请求是一个字符串,然后会按照HTTP协议的格式解析成一个HttpServletRequest对象
- 根据URL中的path判定这个请求是要请求一个静态资源还是动态资源,如果是静态资源,直接找到对应的文件,把文件的内容通过socket返回,如果是动态资源,才会执行到Servlet的相关逻辑
- 根据URL中的Context Path 和Servlet Path确定要调用哪个Servlet实例的service方法
- 通过service方法,就会进一步调用到我们之前写的doGet或者doPost
Servlet的service方法内部会根据当前请求的方法,决定调用其中某个doxxx方法,在调用doxxx方法的时候,因为我们创建的类继承了HttpServlet类,HttpServlet又继承自Servlet类,并且重写了doxxx方法,所以就会触发多态机制,从而执行到我们自己写的子类中的doxxx方法
7. Servlet API
7.1 HttpServlet
我们写Servlet代码的时候,首先第一步就是创建类,继承自HttpServlet,并且重写其中的某些方法(例如doGet,doPost)
1.核心方法
方法名称 | 调用时机 |
---|---|
init | 在HttpServlet实例化之后被调用一次 |
destory | 在HttpServlet实例不再使用的时候调用一次 |
service | 收到HTTP请求时候调用 |
doGet | 收到GET请求的调用(由service方法调用) |
doPost | 收到POST请求的时候调用(由service方法调用) |
doPut/… | 收到其他请求的时候调用(由service方法调用) |
Servlet生命周期:
- Servlet在实例化之后被调用一次init
- Servlet每次收到HTTP请求,调用一次service
- Servlet在销毁之前,调用一次destory
HttpServlet的实例只是在程序启动时创建一次,而不是每次收到HTTP请求都重新创建实例
2. 代码实例:处理GET请求
创建MethodServlet.java 重写doGet方法,当接收到一个HTTP GET请求时,返回一个响应,内容为"GET 响应"
@WebServlet("/method")
public class MethodServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("text/html; charset=utf-8");resp.getWriter().write("GET 响应");}
}
创建testMethod.html,放到webapp目录中,
一个Servlet程序中可以同时部署静态文件,静态文件放在webapp目录中
通过一个按钮,为这个按钮创建一个回调函数,当按钮被点击时,向服务器发送一个HTTP GET请求,HTTP请求通过ajax的方式构造,type表示请求的方法,url表示请求的路径,回调函数中的data表示服务器返回的响应内容
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body>
<button onclick="sendGet()">发送GET请求</button>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>function sendGet(){$.ajax({type: "GET",url: "method",success:function(data,status){console.log(data);}})}</script>
</body>
</html>
重新部署程序,使用URL 127.0.0.1:8080/servlet/testMethod.html访问页面,点击"发送GET请求"按钮,可以在控制台中看到响应内容,通过Fiddler抓包可以看到,当浏览器中输入URL之后,浏览器先给服务器发送了一个HTTP GET请求,服务器返回一个HTML页面的响应,当点击"发送GET请求"按钮后,浏览器又通过ajax给服务器发送一个HTTP GET请求,响应的body中内容为"GET 响应"
3. 处理POST请求
在MethodServlet.java中,新增doPOST方法
@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("text/html; charset=utf-8");resp.getWriter().write("POST 响应");}
在testMethod.html中新增一个按钮,和对应的点击事件处理函数
<button onclick="sendPost()">发送POST请求</button>
<script>
function sendPost(){$.ajax({type: "POST",url: "method",// POST请求的bodydata: "request body",success:function(data,status){console.log(data);}})}
</script>
重新部署程序,用相同的方法访问testMethod.html页面,点击"发送POST请求",也可以在控制台中看到”POST 响应“
7.2 HttpServletRequest
Tomcat通过Socket API读取HTTP请求,并且按照HTTP协议的格式把字符串解析成HttpServletRequest对象
1. 核心方法
方法 | 描述 |
---|---|
String getProtocol() | 返回请求协议的名称和版本 |
String getMethod() | 返回请求的HTTP方法的名称,例如GET,POST |
String getRequestURI() | 从协议名称直到HTTP请求的第一行的查询字符串中,返回该请求的URL的一部分 |
String getContextPath() | 返回值是请求上下文的请求URI部分 |
String getQueryString() | 返回包含在路径后的请求URL中的查询字符串 |
Enumeration getParameterNames() | 返回一个String对象的枚举,相当于返回query string中所有键值对的key |
String getParameter(String name) | 以字符串形式返回指定key对应的value,如果参数不存在就返回null |
String [] getParameterValues(String name) | 返回一个字符串对象数组,包含指定key对应的所有value,针对key重复的情况,如果参数不存在则返回null |
Enumeration getHeaderNames() | 返回一个枚举,包含请求中header中所有的key |
String getHeader(String name) | 以字符串形式返回header中指定key的value |
String getCharacterEncoding() | 返回请求body中使用的字符编码的名称 |
String getContentType() | 返回请求body中数据格式类型,如果不知道类型则返回null |
int getContentLength() | 以字节为单位返回请求body的长度,并提供输入流。如果长度未知则返回-1 |
InputStream getInputStream() | 用于读取请求的body内容,返回一个InputStream对象 |
通过这些方法可以获取到一个请求中的各个方面的信息
2. 打印请求信息
创建ShowRequestServlet类
@WebServlet("/showRequest")
public class ShowRequestServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//设置body格式resp.setContentType("text/html; charset=utf-8");//把生成的响应的body放入respBodyStringBuilder respBody = new StringBuilder();respBody.append(req.getProtocol());//获取到请求的版本号respBody.append("<br>");respBody.append(req.getMethod());respBody.append("<br>");respBody.append(req.getRequestURI());respBody.append("<br>");respBody.append(req.getContextPath());respBody.append("<br>");respBody.append(req.getQueryString());respBody.append("<br>");respBody.append("<h3>headers</h3>");Enumeration<String> headerNames = req.getHeaderNames();while (headerNames.hasMoreElements()){String headerName = headerNames.nextElement();respBody.append(headerName+": ");respBody.append(req.getHeader(headerName));respBody.append("<br>");}//respBody转为字符串写入响应resp.getWriter().write(respBody.toString());}
}
重新部署程序。在浏览器中输入127.0.0.1:8080/servlet/showRequest?a=10&b=20
3. 获取GET请求中的参数
GET请求中的参数一般是通过query string传递给服务器的,在服务器端可以通过getParameter来获取到参数的值,query string里面的内容是程序猿自定义的内容,每个键值对的键和值都是程序猿自己定义好的
约定query string 中键值对例如:userId=100&classId=200
创建GetParameterServlet类
@WebServlet("/getParameter")
public class GetParameterServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//这是获取GET请求URL中的query stringString userId = req.getParameter("userId");String classId = req.getParameter("classId");//对query string 进行判定是否存在 key不存在 或者key存在value不存在 都视为query string不存在if (userId == null || userId.equals("")){//具体处理}resp.getWriter().write(String.format("userId:%s , classId: %s",userId,classId));}
}
重新部署程序,在浏览器中输入127.0.0.1:8080/servlet/getParameter
可以看到当query string为空时,getParameter获取的值为null
当在浏览器中输入127.0.0.1:8080/servlet/getParameter/userId=123&classId=456 时可以看到:
此时说明服务器已经获取到客户端传过来的参数,注意:getParameter的返回值类型为String,如果需要对这个值进行算术运算,就需要手动将它转为int类型,Integer.parseInt
4. 获取POST请求中的参数(1)
POST请求的参数一般通过body传递给服务器,body中的数据格式有很多种,
如果采用form表单的形式,body中的数据格式为application/x-www-form-urlencoded,仍然可以通过getParameter获取参数的值
约定请求形如:
创建PostParameterServlet类,
@WebServlet("/postParameter")
public class PostParameterServlet extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//POST请求中的body类型是application/x-www-form-urlencoded时,和获取query string的方式一样resp.setContentType("text/html;charset=utf-8");String userId = req.getParameter("userId");String classId = req.getParameter("classId");resp.getWriter().write(String.format("userId:%s;classId:%s <br>",userId,classId));}
}
创建testPost.html,放到webapp目录中
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>testPost</title>
</head>
<body><form action="postParameter" method="POST"><input type="text" name="userId"><input type="text" name="classId"><input type="submit" value="提交"></form>
</body>
</html>
重新部署程序,在浏览器中输入 127.0.0.1:8080/servlet/testPost.html 可以看到HTML,
输入内容,点击提交,可以看到跳转到了新的页面,并显示出了刚刚提交的数据
通过抓包可以看到,form表单构造的body数据的格式为:
5. 获取POST请求中的参数(2)
如果POST请求中的body是按照json的格式来传递,可以先把整个body的字符串读取出来,再进行解析
约定请求形如:
/servlet/postParameterJson
{“userId”: 123,“classId”: 456} ,“key”: value格式
创建PostParameterJsonServlet类
@WebServlet("/postParameterJson")
public class PostParameterJsonServlet extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//因为body类型是json,所以要先把POST请求的整个body读出来String body = readBody(req);//这里只是读出来了,并没有解析resp.getWriter().write(body);private String readBody(HttpServletRequest req) throws IOException {//读取body需要根据req getInputStream得到一个流对象,从这个流对象中获取bodyInputStream inputStream = req.getInputStream();//具体读多少个字节,可以通过Content-Length 可以拿到请求body中的字节数int contentlength = req.getContentLength();byte [] buffer = new byte[contentlength];inputStream.read(buffer);return new String(buffer,"utf-8");}
}
创建testPostJson.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>testPostJson</title>
</head>
<body><!-- 构造body为json格式的数据,就只能用ajax的方式来实现 --><button onclick="sendJson()">发送请求</button><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script><script>function sendJson(){let body = {userId: 123,classId: 456};$.ajax({url: "postParameterJson",type: "POST",//设置请求中body数据的格式类型contentType: "application/json;charset=utf-8",data: JSON.stringify(body),success: function(body,status){console.log(body);}})}</script>
</body>
</html>
通过浏览器可以看到响应的数据仍为一个整体的String类型,如果想要获取到userId和classId的具体值,还需要搭配json库进一步解析
6. 获取POST请求中的参数(3)
引入Jackson这个库,进行JSON解析
把中央仓库中的依赖配置添加到pom.xml中,
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.12.3</version>
</dependency>
修改代码:
//通过这个类来表示解析后的结果
class JsonData{public int userId;public int classId;
}@WebServlet("/postParameterJson")
public class PostParameterJsonServlet extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//因为body类型是json,所以要先把POST请求的整个body读出来String body = readBody(req);//使用jackson解析,先创建一个jackson的核心对象,objectMapperObjectMapper objectMapper = new ObjectMapper();JsonData jsonData = objectMapper.readValue(body,JsonData.class);resp.getWriter().write(String.format("userId: %d;classId: %d <br>",jsonData.userId,jsonData.classId));}private String readBody(HttpServletRequest req) throws IOException {//读取body需要根据req getInputStream得到一个流对象,从这个流对象中获取bodyInputStream inputStream = req.getInputStream();//具体读多少个字节,可以通过Content-Length 可以拿到请求body中的字节数int contentlength = req.getContentLength();byte [] buffer = new byte[contentlength];inputStream.read(buffer);return new String(buffer,"utf-8");}
}
- JsonData这个类用来表示解析之后生成的json对象,这个类的属性的名字和类型要和json字符串的key对应
- jackson库的核心类为ObjectMapper,其中的readVaule方法把json字符串转成java对象,其中的writeVauleAsString方法把一个Java对象转成json格式的字符串
- readVaule的第二个参数为JsonData的类对象,通过这个类对象,在readVaule的内部就可以借助反射机制来构造出JsonData对象,并且根据json中key的名字,把对应的value赋值给JsonData的对应字段
7.3 HttpServletResponse
Servlet中的doxxx方法的目的就是根据请求计算得到响应,然后把响应的数据设置到HttpServletResponse对象中
然后Tomcat就会把这个HttpServletResponse对象按照HTTP协议的格式,转成一个字符串,并通过Socket写回给浏览器
1. 核心方法
方法 | 描述 |
---|---|
void setStatus(int sc) | 为该响应设置状态码 |
void setHeader(String name,String value) | 设置一个带有给定名称和值的header,如果name已经存在,则覆盖旧的值 |
void addHeader(String name,String value) | 添加一个带有给定名称和值的header,如果name已经存在,不覆盖旧的值,并列添加新的键值对 |
void setContentType(String type) | 设置被发送到客户端的响应的body类型 |
void setCharacterEncoding(String charset) | 设置被发送到客户端的响应的字符编码,例如utf-8 |
void sendRedirect(String location) | 设置重定向位置URL,发送临时重定向响应到客户端 |
PrintWriter getWriter() | 用于往body中写入文本格式数据 |
OutputStream getOutputStream() | 用于往body中写入二进制格式数据 |
注意
- 响应对象是服务器要返回给浏览器的内容,这里的重要信息都是程序猿设置的,因此上面的方法都是写方法
- 对于状态码和响应头的设置要放到getWriter/getOutputStream之前,否则可能设置失效
2. 示例:设置状态码
实现一个程序,用户在浏览器通过参数指定要返回响应的状态码
创建StatusServlet类
@WebServlet("/status")
public class StatusServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//让用户传入一个请求,在query string中带一个参数,代表响应的状态码。//根据用户不同的输入,返回不同的状态码resp.setContentType("text/html;charset=utf-8");String statusString = req.getParameter("status");if (statusString == null || statusString.equals("")){resp.getWriter().write("当前的请求的参数status缺失");return;}resp.setStatus(Integer.parseInt(statusString));//设置响应码为用户传入的参数,将字符串转成intresp.getWriter().write("status: "+statusString);//响应body内容}
}
部署程序,在浏览器中输入127.0.0.1:8080/servlet/status?status=200 可以看到
使用不同的status值,可以看到不同的响应结果,通过Fiddler抓包也可以看到响应结果
3. 示例:自动刷新
实现一个程序,让浏览器每秒钟自动刷新一次,并显示当前的时间戳
创建AutoRefreshServlet类
@WebServlet("/autoRefresh")
public class AutoRefreshServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("text/html;charset=utf-8");resp.setHeader("Refresh","1");long timeStamp = System.currentTimeMillis();//这个方法可以获取到当前的毫秒级时间戳resp.getWriter().write("timestamp: "+ timeStamp);}
}
通过设置HTTP响应报头中的Refresh字段,可以控制浏览器自动刷新的时间
部署程序,浏览器中输入 127.0.0.1:8080/servlet/autoRefresh 访问页面,可以看到浏览器每秒钟自动刷新一次
4. 示例:重定向
实现一个程序,返回一个重定向HTTP响应,自动跳转到另一个页面
创建RedirectServlet类
@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// resp.setStatus(302);
// resp.setHeader("Location","https://www.sogou.com/");resp.sendRedirect("https://www.sogou.com/");//直接设置url发送临时重定向响应到客户端}
}
可以通过设置状态码为302,header中Location的值完成页面跳转
也可以直接设置url发送临时重定向响应到客户端
部署程序,浏览器中输入 127.0.0.1:8080/servlet/redirect 访问页面,可以看到页面自动跳转到搜狗主页了。通过抓包可以看到HTTP请求的具体流程
可以看到当浏览器中输入url时,服务器返回了一个状态码为302的响应,header中Location表示页面将跳转到www.sogou.com, 紧接着又发送了一个获取到搜狗主页的HTTP请求
8. Cookie和Session
HTTP协议自身是属于"无状态"协议,无状态指的是:默认情况下HTTP协议的客户端和服务器之间的这次通信,和下次通信之间没有直接的联系
但是实际开发中,我们需要知道请求之间的关联关系
8.1Cookie
Cookie是HTTP协议中的一个字段,是浏览器在客户端这边保存数据的方式,浏览器会根据域名/地址分别存储cookie。
Cookie是通过HTTP响应的set-Cookie字段来进行设置,是服务器返回给浏览器的。
Cookie会在下次请求中自动添加到请求中,发送给服务器,
Cookie中存储的是键值对结构的字符串,这些都是程序猿自定义的
8.2 会话机制(Session)
服务器同一时刻收到的请求是很多的,服务器需要清楚的区分每个请求是属于哪个用户,就需要服务器这边记录每个用户的信息的对应关系
会话的本质就是一个"哈希表",存储了一些键值对结构,key就是sessionId,vaule是用户信息,用户信息可以根据需求设计
Session是由服务器生成的一个"唯一性字符串",从session机制的角度来看,这个唯一性字符串称为"sessionId",站在整个登录流程的角度来看,也可以把这个唯一性字符串称为"token"
- 当用户登录的时候,服务器在Session中新增一个新记录,并且把sessionId/token返回给客户端,例如通过HTTP响应中的Set-Cookie字段返回
- 客户端后续再给服务器发送请求的时候,需要在请求中带上sessionId/token,例如通过HTTP请求中的Cookie字段带上
- 服务器收到请求之后,根据请求中的sessionId/tonke在Session信息中获取到对应的用户的信息,再进行后续操作
Servlet的Session默认是保存在内存中的,如果重启服务器则Session数据就会丢失
8.3 Cookie和Session的区别
- Cookie是客户端的机制,Session是服务器端的机制
- Cookie和Session经常在一起配合使用,但是不是必须配合
- 可以用Cookie来保存一些数据在客户端,这些数据不一定是用户身份信息,也不一定是sessionId/token
- Session中的sessionId/token也不是非得通过Cookie/Set-Cookie传递
8.4 核心方法
HttpServletRequest类中的相关方法
方法 | 描述 |
---|---|
HttpSession getSession() | 在服务器中获取会话,参数如果为true,则当不存在会话时新建会话;参数如果为false,则当不存在会话时返回null |
Cookie[] getCookie() | 返回一个数组,包含客户端发送该请求的所有的Cookie对象,会自动把Cookie中的格式解析成键值对 |
HttpServletResponse类中的相关方法
方法 | 描述 |
---|---|
void addCookid(Cookie cookie) | 把指定的cookie添加到响应中 |
HttpSession类中的相关方法
一个HttpSession对象里面包含多个键值对,我们可以往HttpSession中存任何我们需要的信息
方法 | 描述 |
---|---|
Object getAttribute(String name) | 该方法返回在该session会话中具有指定名称的对象,也就是返回指定key的value,如果没有指定名称的对象,则返回null |
void setAttribute(String name,Object value) | 该方法使用指定的名称绑定一个对象到该session会话 |
boolean isNew() | 判定当前是否是新创建出的会话 |
Cookie类中的相关方法
每个Cookie对象都是一个键值对
方法 | 描述 |
---|---|
String getName() | 该方法返回cookie的名称,名称在创建后不能改变,这个值是Set-Cookie字段设置给浏览器的 |
String getValue() | 该方法获取与cookie关联的值 |
void setVaule(String newVaule) | 该方法设置与cookie关联的值 |
HTTP的Cookie字段中存储的实际上是多组键值对,每个键值对在Servlet中都对应了一个Cookie对象
通过HttpServletRequest.getCookies()获取到请求中的一系列Cookie键值对
通过HttpServletResponse.addCookies()可以向响应中添加新的Cookie键值对
示例:实现用户登录
实现简单的用户登录逻辑,主要是通过HttpSession类完成,并不需要我们手动操作Cookie对象
先有一个html,包含用户名密码的输入框,登录按钮
LoginServlet类来处理登录请求
IndexServlet类,模拟登陆完成后,跳转到的主页,在这个主页里面就能够获取到用户的身份信息,比如用户的用户名和访问次数
login.html,放到webapp目录中
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><form action="login" method="POST">用户名:<input type="text" name="username"><br>密码:<input type="password" name="password"><br><input type="submit" value="登录"></form>
</body>
</html>
LoginServlet
@WebServlet("/login")
public class LoginServlet extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("text/html;charset=utf-8");//1.从请求的body中读取用户名和密码String username = req.getParameter("username");String password = req.getParameter("password");//2.判断用户名密码是否正确,此处不读数据库,固定用户名为zhangsan密码为123,if(!"zhangsan".equals(username) || !"123".equals(password)){//登录失败resp.getWriter().write("用户名密码不正确");return;}System.out.println("登陆成功");//3.登录成功就创建一个会话信息,参数为true,则会话不存在就创建//会话是根据请求中的sessionId查找的,sessionId在Cookie中,//此处是首次登录,没有Cookie,Cookie是服务器返回的,所以就触发创建"会话",//这里的创建"会话",就是创建一个HttpSession对象作为value//再生成一个随机的字符串作为sessionId 也就是key//把这个key value插入到表中。同时把这个生成的sessionId同构Cookie字段返回给浏览器HttpSession httpSession = req.getSession(true);//还可以存入程序猿自定义的数据,比如存入身份信息(用户名和登录次数)httpSession.setAttribute("username","zhangsan");httpSession.setAttribute("loginCount",0);//因为这里的setAttribute的第两个参数是Object类型,不能直接写int0// 但是此处的0会触发自动装箱,将int0构造成Integer 0//4.让页面跳转到主页,使用重定向resp.sendRedirect("index");}
}
此处的getSession参数为true,表示查不到HttpSession时会创建新的HttpSession对象,并且生成一个sessionId,插入到哈希表中,并且把sessionId通过Set-Cookie返回给浏览器
IndexServlet
@WebServlet("/index")
public class IndexServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//根据当前用户请求中的sessionId,获取用户信息,并显示到页面上resp.setContentType("text/html;charset=utf-8");//1.判定当前用户是否已经登录了.也就是请求中有没有sessionId,以及是否合法//如果会话不存在,就不创建,这里只是查询是否存在。而不是不存在就创建,HttpSession httpSession = req.getSession(false);if (httpSession == null){//当前没有合法会话,当前用户没有登录,就重定向到登陆页面,让用户进行登录resp.sendRedirect("login.html");return;}//2.用户已经登录,就从httpsession中获取到用户信息,String username = (String) httpSession.getAttribute("username");Integer loginCount = (Integer) httpSession.getAttribute("loginCount");loginCount = loginCount + 1;//将访问次数加1,并写回httpSession.setAttribute("loginCount",loginCount);//3.返回一个HTML页面。显示获取到的信息,。StringBuilder html = new StringBuilder();html.append("<div>用户: "+username+"</div>");html.append("<div>访问次数: "+ loginCount + "</div>");resp.getWriter().write(html.toString());}
}
在这个代码中看不到"哈希表",也看不到sessionId这样的概念,getSession操作内部提取到请求中的Cookie里的sessionId,然后查找哈希表,获取到对应的HttpSession对象
getSession参数为false,则获取不到HttpSession,不会创建新的HttpSession,而是返回null,此时说明用户没有登录
部署程序,通过127.0.0.1:8080/messagewall/index 访问
首次访问的时候可以看到,当前用户尚未登录,此时页面自动重定向到login.html
在login.html中输入用户名密码之后,会跳转到/login路径,此时服务器返回了一个sessionId,并在Session中记录用户信息,然后重定向到/index
- 第一次访问index时,用户还没有登录,直接重定向到login.html页面
- 输入用户名密码登陆成功后,跳转到login页面,并且生成了一个sessionId,再跳转到index主页
可以看到第一次登录成功时,响应Set-Cookie字段中有JESSIONID属性,随机生成了一个唯一性的字符串,后续跳转到主页面index时,请求中就会通过Cookie带着这个字段,
服务器返回的JESSIONID和后续用户请求的Cookie中所传递的JESSIONID是一样的,这就相当于是用户的唯一身份标识
9. 上传文件
在Servlet中也支持上传文件
9.1 核心方法
HttpServletRequest类方法
方法 | 描述 |
---|---|
Part getPart(String name) | 获取请求中给定name的文件 |
Collection getParts() | 获取所有的文件 |
Part类方法
方法 | 描述 |
---|---|
String getSubmittedFileName() | 获取提交的文件名 |
String getContentTypr() | 获取提交的文件类型 |
long getSize() | 获取文件的大小 |
void writy(String path) | 把提交的文件数据写入磁盘文件 |
9.2 示例
实现程序,通过网页提交一个图片到服务器上
创建upload.html,放到webapp目录中
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>上传图片</title>
</head>
<body><form action="upload" enctype="multipart/form-data" method="POST"><input type="file" name="MyImage"><input type="submit" value="上传图片"></form>
</body>
</html>
- 上传文件一般通过POST请求的表单实现
- 在form中要加上multipart/form-data
创建UploadServlet类
@MultipartConfig//加上这个注解 servlet才能正确读取文件中的内容
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//1.从req对象中读取中part对象Part part = req.getPart("MyImage");//参数是HTML中form表单input标签中的name属性值//2.读取part对象中的一些参数System.out.println(part.getSubmittedFileName());//读取出上传的文件的真实文件名System.out.println(part.getContentType());//文件类型System.out.println(part.getSize());//文件大小//3.把文件写入到指定目录中part.write("c:/MyImage.jpg");//4.返回响应resp.getWriter().write("upload ok");}
}
- 需要给UploadServlet加上@MultipartConfig注解,否则服务器代码无法使用getiPart方法
- getPart的参数需要和form中input标签的name属性对应
- 客户端一次可以提交多个文件(使用多个input标签),此时服务器就可以通过getParts获取所有的Part对象
部署程序,在浏览器中通过 127.0.0.1:8080/messagewall/upload,html 访问
可以看到Content-Type为multipart/form-data这样的请求中带有一个boundary,值为随机生成的字符串,这个字符串在body这边作为一个"分割线",分割线下面是上传的文件的属性和文件的内容
【Servlet】什么是Servlet;常见状态码;Servlet API;Cookie和Session相关推荐
- WEB阶段3:Response响应组成常见状态码ServletContexturl编码文件下载案例
Response响应组成&常见状态码&ServletContext&url编码&文件下载案例 回顾 请求有哪三个的组成部分 请求行:提交方式 URI HTTP/1.1 ...
- http常见状态码及设置方法
Http状态码 代码 消息 描述 100 Continue 只有请求的一部分已经被服务器接收,但只要它没有被拒绝,客户端应继续该请求. 101 Switching Protocols 服务器切换协议. ...
- HTTP协议、HTTP请求方法、常见状态码、HTTP消息
HTTP协议 客户端请求,服务端响应.浏览器与服务器不建立持久连接,响应后连接失效. HTTP请求方法 一.GET GET方法用于获取请求页面的指定信息. 二.HEAD 除了服务器不能在响应里返回消息 ...
- http常见状态码详解
http 中有许多状态码,如果不做些总结,真的很容易搞混,比如,201.404.301.202.504等等.要是一个个记是不是很难记,那么现在总结下规律,来帮助我们记忆 所有的状态码可以分为: 1xx ...
- 3-3:HTTP协议之request和respond及常见请求方法和常见状态码
文章目录 一:request (1)请求报文基本构成 (2)请求方法 二:respond (1)响应报文基本构成 (2)HTTP常见状态码 三:HTTP常见Header(字段) 注意下面需要分析请求报 ...
- python post与get的区别_Python Requests库 Get和Post的区别和Http常见状态码
(1) 在客户端,Get方式在通过URL提交数据,数据在URL中可以看到:POST方式,数据放置在HTML HEADER内提交. (2) GET方式提交的数据最多只能有1024 Byte,而P ...
- web应用F12查看报错(前后端bug判断、2XX/3XX/4XX/5XX常见状态码解析)
chrom浏览器为例 (1)打开开发者工具,在浏览器菜单栏选择工具-开发者工具,快捷键是F12 (2)打开之后切换到Network页签,操作就可以看到请求响应 (3)再选择响应的链接,切换到Previ ...
- 网络常见状态码大全。收藏这一篇就够了(HTTP Status Code)
网络常见状态码大全 2开头(请求成功) 3开头(请求被重定向) 4开头(请求错误) 5开头(服务器错误) -------------------------------------------- 2开 ...
- TCP/IP协议及常见状态码说明(SYN,FIN,ACK,PSH,RST)
TCP/IP协议及常见状态码(SYN,FIN,ACK,PSH,RST) TCP/IP协议\TCP协议原理\报文格式\三次握手的状态码 1.TCP/IP协议 TCP/IP协议(Transmission ...
- 笔记:DB2常见状态码
笔记:DB常见状态码 sqlcode sqlstate 说明 000 00000 SQL语句成功完成 01xxx SQL语句成功完成,但是有警告 +012 01545 未限定的列名被解释为一个有相互关 ...
最新文章
- 网易哲学气质H5看似眼熟,为什么大家还是忍不住转发?
- js实现无缝循环滚动
- [vue] 怎么捕获组件vue的错误信息?
- Nagios监控平台完全攻略 (二)
- 数据结构-树与二叉树
- 四元数姿态解算c语言例程_(21)用四元数插值来对齐IMU和图像帧
- python实现推荐系统(一)
- python soup findall 第几个元素_BeautifulSoup库findAll()、find()方法详解
- 单尺度retinex算法 matlab,单尺度retinex算法
- 判断文件是否为图片格式
- 摩尔投票(包含题目讲解)
- Android渠道包生成工具(支持V1、V2签名)
- 星巴克急了,瑞幸就稳了?
- android raw相机,最高大上的安卓相机App?专业拍摄ProShot
- 使用微信web开发者工具调试接口数据【开发记录】
- LZ77压缩算法原理剖析
- Gephi画无向图和有向图(显示节点和边序号)
- mysql mts_MySQL新特性MTS
- Paddle2.0实现PSPNet进行人体解析(图像分割)
- 论文查重字数和重复率有什么关系吗?
热门文章
- @staticmethod静态方法
- Ubuntu 14.04 安装Draftsight
- c语言第一行include,[C语言]为什么要有include?——从Hello World说起
- android 文本框 大文件,关于解决安卓手机超大文件夹(thumbnails)
- 单位根检验、协整检验和格兰杰因果…
- microbit题目和规范文档
- python汉字转拼音代码,一个将汉字转换成汉语拼音的python库的代码
- 西安电子科技大学数据结构考点大纲解析
- 计算机毕设Python+Vue疫情期间优化旅游平台(程序+LW+部署)
- js删除数组对象中的某个属性的方法