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才会调用

  1. 创建的类需要继承自HttpServlet
  2. 这个类需要使用@WebServlet注解关联上一个HTTP的路径
  3. 这个类需要实现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协议作为一个应用层协议,需要底层协议来支持工作

接收请求:

  1. 用户在浏览器中输入一个URL,此时浏览器就会构造一个HTTP请求
  2. 这个HTTP请求会经过网络协议栈逐层进行封装成二进制bit流,最终通过物理层的硬件设备转换成光电信号传输出去
  3. 这个承载信息的光电信息通过互联网上的一系列网络设备,最终到达目的主机,这个过程也需要网络层和数据链路层的参与
  4. 服务器收到这些信号,通过网络协议栈的逐层分用,层层解析,最终还原成HTTP请求,交给Tomcat进程进行处理,根据端口号确定进程
  5. Tomcat通过Socket读取到这个请求,并按照HTTP请求的格式来解析这个请求,根据请求中的Context Path确定一个webapp,再通过Servlet Path确定一个具体的类,再根据当前请求的方法,决定调用这个类中的哪个方法,get对应doGet,post对应doPost…此时我们的代码中这些处理请求的方法中的第一个参数HttpServletRequest就包含了这个HTTP请求的详细信息

根据请求计算响应:

  • 我们的代码会根据请求中的信息,给HttpServletResponse对象设置属性,例如状态码,header。body等

返回响应:

  1. 代码执行完毕后,Tomcat就会自动把HttpServletResponse这个对象转换成一个符合HTTP协议的字符串,通过Socket把这个响应发送出去
  2. 和接收请求一样,通过网络协议栈层层封装和分用,最终到达浏览器,浏览器通过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生命周期:

  1. Servlet在实例化之后被调用一次init
  2. Servlet每次收到HTTP请求,调用一次service
  3. 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中写入二进制格式数据

注意

  1. 响应对象是服务器要返回给浏览器的内容,这里的重要信息都是程序猿设置的,因此上面的方法都是写方法
  2. 对于状态码和响应头的设置要放到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的区别

  1. Cookie是客户端的机制,Session是服务器端的机制
  2. 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

  1. 第一次访问index时,用户还没有登录,直接重定向到login.html页面
  2. 输入用户名密码登陆成功后,跳转到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相关推荐

  1. WEB阶段3:Response响应组成常见状态码ServletContexturl编码文件下载案例

    Response响应组成&常见状态码&ServletContext&url编码&文件下载案例 回顾 请求有哪三个的组成部分 请求行:提交方式 URI HTTP/1.1 ...

  2. http常见状态码及设置方法

    Http状态码 代码 消息 描述 100 Continue 只有请求的一部分已经被服务器接收,但只要它没有被拒绝,客户端应继续该请求. 101 Switching Protocols 服务器切换协议. ...

  3. HTTP协议、HTTP请求方法、常见状态码、HTTP消息

    HTTP协议 客户端请求,服务端响应.浏览器与服务器不建立持久连接,响应后连接失效. HTTP请求方法 一.GET GET方法用于获取请求页面的指定信息. 二.HEAD 除了服务器不能在响应里返回消息 ...

  4. http常见状态码详解

    http 中有许多状态码,如果不做些总结,真的很容易搞混,比如,201.404.301.202.504等等.要是一个个记是不是很难记,那么现在总结下规律,来帮助我们记忆 所有的状态码可以分为: 1xx ...

  5. 3-3:HTTP协议之request和respond及常见请求方法和常见状态码

    文章目录 一:request (1)请求报文基本构成 (2)请求方法 二:respond (1)响应报文基本构成 (2)HTTP常见状态码 三:HTTP常见Header(字段) 注意下面需要分析请求报 ...

  6. python post与get的区别_Python Requests库 Get和Post的区别和Http常见状态码

    (1)   在客户端,Get方式在通过URL提交数据,数据在URL中可以看到:POST方式,数据放置在HTML HEADER内提交. (2)   GET方式提交的数据最多只能有1024 Byte,而P ...

  7. web应用F12查看报错(前后端bug判断、2XX/3XX/4XX/5XX常见状态码解析)

    chrom浏览器为例 (1)打开开发者工具,在浏览器菜单栏选择工具-开发者工具,快捷键是F12 (2)打开之后切换到Network页签,操作就可以看到请求响应 (3)再选择响应的链接,切换到Previ ...

  8. 网络常见状态码大全。收藏这一篇就够了(HTTP Status Code)

    网络常见状态码大全 2开头(请求成功) 3开头(请求被重定向) 4开头(请求错误) 5开头(服务器错误) -------------------------------------------- 2开 ...

  9. TCP/IP协议及常见状态码说明(SYN,FIN,ACK,PSH,RST)

    TCP/IP协议及常见状态码(SYN,FIN,ACK,PSH,RST) TCP/IP协议\TCP协议原理\报文格式\三次握手的状态码 1.TCP/IP协议 TCP/IP协议(Transmission ...

  10. 笔记:DB2常见状态码

    笔记:DB常见状态码 sqlcode sqlstate 说明 000 00000 SQL语句成功完成 01xxx SQL语句成功完成,但是有警告 +012 01545 未限定的列名被解释为一个有相互关 ...

最新文章

  1. 网易哲学气质H5看似眼熟,为什么大家还是忍不住转发?
  2. js实现无缝循环滚动
  3. [vue] 怎么捕获组件vue的错误信息?
  4. Nagios监控平台完全攻略 (二)
  5. 数据结构-树与二叉树
  6. 四元数姿态解算c语言例程_(21)用四元数插值来对齐IMU和图像帧
  7. python实现推荐系统(一)
  8. python soup findall 第几个元素_BeautifulSoup库findAll()、find()方法详解
  9. 单尺度retinex算法 matlab,单尺度retinex算法
  10. 判断文件是否为图片格式
  11. 摩尔投票(包含题目讲解)
  12. Android渠道包生成工具(支持V1、V2签名)
  13. 星巴克急了,瑞幸就稳了?
  14. android raw相机,最高大上的安卓相机App?专业拍摄ProShot
  15. 使用微信web开发者工具调试接口数据【开发记录】
  16. LZ77压缩算法原理剖析
  17. Gephi画无向图和有向图(显示节点和边序号)
  18. mysql mts_MySQL新特性MTS
  19. Paddle2.0实现PSPNet进行人体解析(图像分割)
  20. 论文查重字数和重复率有什么关系吗?

热门文章

  1. @staticmethod静态方法
  2. Ubuntu 14.04 安装Draftsight
  3. c语言第一行include,[C语言]为什么要有include?——从Hello World说起
  4. android 文本框 大文件,关于解决安卓手机超大文件夹(thumbnails)
  5. 单位根检验、协整检验和格兰杰因果…
  6. microbit题目和规范文档
  7. python汉字转拼音代码,一个将汉字转换成汉语拼音的python库的代码
  8. 西安电子科技大学数据结构考点大纲解析
  9. 计算机毕设Python+Vue疫情期间优化旅游平台(程序+LW+部署)
  10. js删除数组对象中的某个属性的方法