【Servlet】什么是Servlet;常见状态码;Servlet API;Cookie和Session

Servlet

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 {
    @Override
    protected 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 {
	@Override
	protected 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 {
    @Override
    protected 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方法

@Override
    protected 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请求的body
                data: "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 {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置body格式
        resp.setContentType("text/html; charset=utf-8");
        //把生成的响应的body放入respBody
        StringBuilder 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 {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //这是获取GET请求URL中的query string
        String 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 {
    @Override
    protected 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 {
    @Override
    protected 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得到一个流对象,从这个流对象中获取body
        InputStream 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 {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //因为body类型是json,所以要先把POST请求的整个body读出来
        String body = readBody(req);
        //使用jackson解析,先创建一个jackson的核心对象,objectMapper
        ObjectMapper 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得到一个流对象,从这个流对象中获取body
        InputStream 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 {
    @Override
    protected 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));//设置响应码为用户传入的参数,将字符串转成int
        resp.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 {
    @Override
    protected 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 {
    @Override
    protected 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 {
    @Override
    protected 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 {
    @Override
    protected 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 {
    @Override
    protected 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这边作为一个"分割线",分割线下面是上传的文件的属性和文件的内容

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值