Bootstrap

【JavaWeb】Servlet详解

目录

1. 什么是Servlet?

2. 第一个Servlet程序 

2.1 准备环境 

2.2 编写代码

2.3 部署到Tomcat中 

2.3.1 打包

2.3.2 部署 

2.3.3 运行验证结果 

3. 更方便的部署方式 

4. 一般访问出错的情况

4.1 出现404 

4.2 出现405 

4.3 出现500 

5. HttpServlet API解析

5.1 核心方法(Servlet生命周期方法)

5.2 处理GET请求

5.3 处理POST请求 

6. HttpServletRequest API解析

6.1 核心方法 

6.2 获取请求数据

6.2.1 获取URL中queryString的数据 

6.2.2 获取表单格式body的数据 

6.2.3 获取form-data格式中简单类型的数据 

6.2.4 获取form-data格式中上传的文件 

6.2.5 获取json格式请求正文的数据 

6.3 获取请求数据总结 

7. HttpServletResponse API解析 

7.1 核心方法 

7.2 设置响应状态码

7.3 设置响应头Header 

7.4 设置响应内容类型setContentType 

7.4.1 返回网页

7.4.2 返回文件 

7.4.3 返回json数据 

7.5 重定向与转发 

7.5.1 观察重定向与转发 

7.5.2 重定向与转发的特点及区别(面试常考) 

8. 用户登陆功能的实现 

8.1 回顾Cookie和Session 

8.2 登陆功能实现

8.3 敏感资源访问  

8.4 效果展示 


1. 什么是Servlet?

在了解Servlet前,先了解一下为什么需要Servlet

Servlet产生的背景

前面学习了Tomcat,它是一个Web服务器,提供Web程序处理服务端对请求的解析和对响应的封装,也就是请求的解析和响应的封装都不需要我们自己手动写程序来完成 

比如我们将一个webapp(Web应用)部署到Tomcat中,如果自己写程序来处理请求和返回响应的内容,就需要调用Tomcat提供的API(Tomcat提供的类和接口中的方法和属性),如果此时更改Web服务器,我们自己写的请求解析和响应内容就无法使用了

Servlet是什么? 

Servlet是一种实现动态页面的技术,所谓动态页面就是用户不同,时间不同,输入的参数不同,页面内容也不会发生变化,而静态页面是内容始终固定不变的,html就是静态的资源文件

Servlet为不同的JavaWeb服务器规定了响应的编程规范,它屏蔽Web服务器实现的细节(不同的服务器对请求的解析和响应的封装可以是不同的),但是定义好了统一的编程规范(统一的类,接口,方法),也就是换一个Web服务器,还可以使用 

Servlet主要做的工作 

  • 允许程序员注册一个类,在服务器收到某个特定的HTTP请求的时候,执行这个类中的一些代码
  • 帮助程序员解析HTTP请求,把HTTP请求从一个字符串解析为一个HttpRequest对象
  • 帮助程序员构造HTTP响应,程序员只要给指定的HttpResponse对象填写一些属性字段,Servlet就会自动按照HTTP协议构造HTTP响应字符串,并通过Socket写回给客户端

2. 第一个Servlet程序 

2.1 准备环境 

创建项目 

使用IDEA创建一个Maven项目

引入依赖 

Maven项目创建完,会生成一个pom.xml文件,我们需要在pom.xml中引入Servlet API依赖的jar包 

注意:Servlet的版本要和Tomcat匹配,如果我们使用Tomcat 8.5,就需要使用Servlet 3.1.0

附上查询版本对应关系链接:Tomcat与Servlet版本对应链接 

编写pom.xml:

注意:修改了pom.xml后,记得刷新Maven面板,否则不会生效

创建目录 

项目创建好,IDEA会自动创建出一些目录:

这些目录还不够,我们还需要创建新的目录和文件:

  1. 在main目录下创建webapp目录 
  2. 在webapp目录下,创建WEB-INF目录
  3. 在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,js等
  • web.xml,Tomcat找到这个文件才能正确处理webapp中的动态资源 

2.2 编写代码

在java目录下,创建一个HelloServlet类,内容如下:

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("hello servlet");
    }
}

对代码的说明

  • 创建一个类HelloServlet,继承HttpServlet
  • 在类上方加上@WebServlet("/hello")注解,表示Tomcat收到的请求中,路径为/hello的请求才会调用HelloServlet这个类的代码
  • 重写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调用的三个条件:

  • 创建的类都需要继承HttpServlet
  • 这个类需要用@WebServlet("xxx")注解关联上一个路径
  • 这个类需要实现doXXX方法 

2.3 部署到Tomcat中 

2.3.1 打包

使用Maven的package命令打包,打包后可以看到在target目录下生成了一个jar格式的包 

但是jar格式的包不是部署Tomcat需要的,Tomcat需要识别的是另一种格式war包

jar包和war包的区别 

  • jar包是普通java程序打包的结果,里面包含一些.class文件
  • war包是java web程序,里面除了.class文件,还包含HTML,CSS,JS,图片,以及其他的jar包,打成war包格式才能被Tomcat识别 

如何打war格式的包 

  • 在pom.xml中新增一个packaging标签,表示打包的方式是打一个war格式的包 

  • 在pom.xml中再新增一个build标签,内置一个finalName标签,标签的内容为打的war包的名字  
    <build>
        <finalName>HelloServlet</finalName>
    </build>
  • 先使用clean命令清除target,再重新使用package命令打包:

2.3.2 部署 

将war包拷贝到Tomcat的webapps目录下,运行Tomcat,会自动的把webapps目录下的war包解压到相同的文件夹中,文件夹名相同

2.3.3 运行验证结果 

通过浏览器访问:127.0.0.1:8080/HelloServlet/hello,看到结果如下:

注意:URL中的path为两个部分,HelloServlet为Context Path,hello为Servlet Path 

3. 更方便的部署方式 

手动拷贝war包到Tomcat的过程比较麻烦,还有更为方便的办法:在IDEA中安装Smart Tomcat插件来完成这工作

安装Smart Tomcat插件

注意:安装过程需要联网,安装完后会提示重启IDEA 

配置Smart Tomcat插件 

完成上述步骤后会出现以下页面:

启动程序,访问页面 

配置完后,这里变成了这样:

点击绿色的三角,IDEA就会自动进行编译,部署,启动Tomcat

点击蓝色的链接,就可以使用浏览器访问,但是会出现404,因为没有加具体访问的路径,要加上具体访问的路径

4. 一般访问出错的情况

4.1 出现404 

404表示用户访问的资源不存在,一般是访问资源的URL不正确 

错误示例:少写了Context Path

  

错误示例:少写了Servlet Path

  

错误示例:Servlet中的路径和URL中的路径不匹配

  

错误示例:web.xml写错了 

Tomcat启动也会有相关的提示信息 

4.2 出现405 

405表示方法不支持,也就是对应HTTP请求的方法没有实现 

错误示例:没有提供doGet方法 

4.3 出现500 

500表示服务器内部出错

错误示例 

注意:一定要检查异常堆栈信息(页面上可能有,idea控制台,Tomcat/logs中的日志文件) 

5. HttpServlet API解析

我们创建类继承HttpServlet,第一步就是重写其中的某些方法

5.1 核心方法(Servlet生命周期方法)

方法名称调用时机
init初始化方法,在HttpServlet实例化之后调用一次
destory销毁方法,在HttpServlet实例不在使用时调用一次
service收到HTTP请求时调用,一次请求一次调用
doGet收到GET请求时,由service方法调用
doPost收到POST请求时,由service方法调用
doPut/doDelete/doOptions/...收到其他请求时,由service方法调用

实际开发的时候主要重写doXXX方法,很少重写init/destory/service方法 

注意:HttpServlet的实例只在程序启动时创建一次,而不是每次收到HTTP请求都重新创建实例

5.2 处理GET请求

1. 在webapp目录下,创建一个hello.html,注意存放的位置:src/main/webapp,不是放在WEB-INF目录下的,WEB-INF目录下的文件是不能直接访问的

<!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>
</body>
<script>
    function send(){
        ajax({
            method: "GET",
            url: "getMethod",
            callback: function(status,responseText){
                console.log(responseText);
            }
        });
    }
    function ajax(args) {
        let xhr = new XMLHttpRequest();
        //设置回调函数
        xhr.onreadystatechange = function () {
            //4:客户端接收到服务端响应
            if (xhr.readyState == 4) {
                //回调函数可能会使用响应的内容,作为传入参数
                args.callback(xhr.status, xhr.responseText);
            }
        }
        xhr.open(args.method, args.url);
        //如果args中contentType有内容,就设置Content-Type请求头
        if (args.contentType) {//js中if可以判断是否有值
            xhr.setRequestHeader("Content-Type", args.contentType);
        }
        //如果args中body有内容,设置body请求正文
        if (args.body) {
            xhr.send(args.body);
        } else {
            xhr.send();
        }
    }
</script>
</html>

2. 创建GetServlet类,继承HttpServlet,重写doGet方法 

@WebServlet("/getMethod")
public class GetServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("get response");
    }
}

3. 启动程序,在浏览器输入:localhost:8080/Servlet-Study-Blog/hello.html,访问页面

4. 点击按钮发送GET请求,在控制台即可看到响应内容

5.3 处理POST请求 

1. 在hello.html中增加一个“发送POST请求”的按钮

    <button onclick="sendPost()">发送POST请求</button>

2. 并使用ajax构造POST请求

    function sendPost(){
        ajax({
            method: "POST",
            url: "postMethod",
            callback: function(status,responseText){
                console.log(responseText)
            }
        });
    }

3. 创建PostServlet类,继承HttpServlet,重写doPost方法

@WebServlet("/postMethod")
public class PostServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("post response");
    }
}

4. 启动程序,在浏览器输入:localhost:8080/Servlet-Study-Blog/hello.html,访问页面

5. 点击按钮发送POST请求,在控制台即可看到响应内容

6. HttpServletRequest API解析

Tomcat通过Socket API读取HTTP请求(字符串),并且按照HTTP协议的格式把字符串解析成HttpServletRequest对象,通过这个对象就可以获取HTTP请求报文的内容

6.1 核心方法 

方法说明
String getProtocol()获取请求协议的名称和版本号
String getMethod()获取请求方法名称
String getContextPath()获取应用上下文路径
String getParameter(String name)获取请求参数的值
String getHeader(String name)获取指定请求头的值
String getServletPath()获取资源路径
InputStream getInputStream()用于读取请求body中的内容

6.2 获取请求数据

6.2.1 获取URL中queryString的数据 

1. 创建request.html

<body>
    <h3>get with queryString</h3>
    <a href="request?username=abc&password=123">get</a>
</body>

2. 创建一个RequestServlet类,重写doGet方法,获取queryString数据并打印

@WebServlet("/request")
public class RequestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("username:"+req.getParameter("username"));
        System.out.println("password:"+req.getParameter("password"));
    }
}

3. 启动程序,输入URL,访问页面

4. 点击get,观察输出结果 

6.2.2 获取表单格式body的数据 

1. 在request.html中创建form表单标签

    <h3>form表单</h3>
    <!-- 如果不提供方法,默认是GET方法 -->
    <form action="request" method="POST"> 
        <input type="text" placeholder="请输入用户名" name="username">
        <input type="password" placeholder="请输入密码" name="password">
        <input type="submit">
    </form>

2. 在RequestServlet类中,重写doPost方法,获取到表单的数据并打印

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8"); //设置body解析的编码格式
        System.out.println("username:"+req.getParameter("username"));
        System.out.println("password:"+req.getParameter("password"));
    }

3. 启动程序,输入URL,访问页面 

  

4. 输入参数,点击提交,观察输出结果

6.2.3 获取form-data格式中简单类型的数据 

1. 在request.html中创建form-data提交的格式

    <h3>form-data提交</h3>
    <form action="form-data" enctype="multipart/form-data" method="POST"> 
        <input type="text" placeholder="请输入用户名" name="username">
        <input type="password" placeholder="请输入密码" name="password">
        <input type="submit">
    </form>

2. 创建一个FormDataServlet类,这个类必须加一个注解:@MutipartConfig,重写doPost方法,获取到form-data的数据并打印

@WebServlet("/form-data")
@MultipartConfig //form-data格式必须添加这个注解
public class FormDataServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        System.out.println("username:"+req.getParameter("username"));
        System.out.println("password:"+req.getParameter("password"));
    }
}

3. 启动程序,输入URL,访问页面

  

4. 输入参数,点击提交,观察输出结果 

6.2.4 获取form-data格式中上传的文件 

1. 在request.html中创建form-data提交文件的格式

    <h3>form-data提交文件</h3>
    <form action="form-data-file" enctype="multipart/form-data" method="POST">
        <!-- file为文件类型  accept指定文件的类型,这里指任意类型的图片 -->
        <input type="file" name="head" accept="image/*">
        <input type="submit">
    </form>

2. 创建FormDataFile类,这个类必须加一个注解:@MutipartConfig,重写doPost方法,获取文件的二进制数据,转换为字符串打印

@WebServlet("/form-data-file")
@MultipartConfig
public class FormDataFileServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //getPart也可以获取简单类型的数据,但是还需要Part的API来获取内容,比较复杂
        Part head = req.getPart("head");
        //获取文件的二进制数据,转换为字符串打印
        InputStream is = head.getInputStream();
        byte[] bytes = new byte[is.available()];
        is.read(bytes);
        System.out.println(new String(bytes,"utf-8")); //第二个参数为编码格式
    }
}

3. 启动程序,输入URL,访问页面

4. 选择好文件,点击提交,观察输出结果为二进制格式的字符串

6.2.5 获取json格式请求正文的数据 

这里借助第三方库jackson,可以将json字符串和java对象相互转换

1. 在pom.xml引入依赖

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.12.3</version>
    </dependency>

2. 在request.html中写前端内容

<body>
    <h3>json格式提交</h3>
    <input type="text" id="text" name="username" placeholder="请输入用户名">
    <input type="password" id="password" name="password" placeholder="请输入密码">
    <input type="submit" onclick="sendAjax()">
</body>
<script>
    function sendAjax(){
        let username = document.querySelector("#text");
        let password = document.querySelector("#password");
        var json = {
            username: username.value,
            password: password.value
        }
        ajax({
            method: "POST",
            url: "ajax-servlet",
            //JSON.stringify(json),将json对象序列化为一个字符串,格式为json格式
            body: JSON.stringify(json),
            callback: function(status,responseText){
                alert("请在idea控制台查看内容");
            }
        });
    }
    function ajax(args) {
        let xhr = new XMLHttpRequest();
        //设置回调函数
        xhr.onreadystatechange = function () {
            //4:客户端接收到服务端响应
            if (xhr.readyState == 4) {
                //回调函数可能会使用响应的内容,作为传入参数
                args.callback(xhr.status, xhr.responseText);
            }
        }
        xhr.open(args.method, args.url);
        //如果args中contentType有内容,就设置Content-Type请求头
        if (args.contentType) {//js中if可以判断是否有值
            xhr.setRequestHeader("Content-Type", args.contentType);
        }
        //如果args中body有内容,设置body请求正文
        if (args.body) {
            xhr.send(args.body);
        } else {
            xhr.send();
        }
    }
</script>

3. 创建AjaxServlet类,重写doPost方法,使用getInputStream获取body数据,在利用第三方库把json字符串转化为一个java对象

@WebServlet("/ajax-servlet")
public class AjaxServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        //getInputStream可以获取任意格式的body,但是如果用于表单格式还要解析多组键值对
        //太麻烦,所以一般用于获取json格式的请求body
        InputStream is = req.getInputStream();
        //借助第三方库jackson,可以将Java对象和json字符串相互转换
        ObjectMapper mapper = new ObjectMapper();
        //json转java对象,需要json的键和java对象的成员变量相对应
        User user = mapper.readValue(is,User.class);
        System.out.println(user);
    }
    //必须提供getter和setter方法
    static class User{
        private String username;
        private String password;

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        @Override
        public String toString() {
            return "User{" +
                    "username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }
}

4. 启动程序,输入URL,访问页面

  

5. 输入数据,点击提交,观察输出结果

6.3 获取请求数据总结 

  • getParameter():获取queryString,body(表单格式,form-data格式简单数据)
  • getPart():获取form-data格式上传的文件
  • getInputStream():获取任意格式的body,常用于获取请求body为json格式

7. HttpServletResponse API解析 

Servlet的doXXX方法就是根据请求然后处理HTTP响应的对象,调用该对象的方法,将数据设置到对象的属性中,Tomcat会把HttpServletResponse对象按照HTTP协议的格式,转成一个字符串,并通过Socket写回给浏览器,也就是Tomcat组织为HTTP响应报文

7.1 核心方法 

方法描述
void setStatus(int sc)设置响应状态码
void setHeader(String name,Stringvalue)设置header头,如果name已经存在,则覆盖
void addHeader(String name,String value)设置header头,如果name已经存在,并列添加新的键值对
void setContentType(String type)设置响应内容类型
void setCharacterEncoding(String charset)设置响应的字符编码
void sendRedirect(String location)重定向,指定路径为location
PrintWriter getWriter()往body中写文本格式数据
OutputStream getOutputStream()往body中写二进制格式数据

注意:

  • 响应对象是服务器要返回给浏览器的内容,这里的重要信息一般是程序员设置的
  • 对于状态码/响应头要放到getWriter/getOutputStream之前,否则可能失效 

7.2 设置响应状态码

1. 创建一个response.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>Document</title>
</head>
<body>
    <h3>设置响应状态码</h3>
    <input type="text" id="status">
    <button onclick="setStatus()">提交</button>
</body>
<script>
    function setStatus(){
        var status = document.querySelector("#status");
        window.location.href = "response?status="+status.value;
    }
</script>
</html>

2. 创建一个ResponseServlet类,重写doGet方法,获取到请求数据,设置响应状态码

@WebServlet("/response")
public class ResponseServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String status = req.getParameter("status");
        if(status != null){
            resp.setCharacterEncoding("utf-8");
            resp.setStatus(Integer.parseInt(status));
            resp.getWriter().write(status);
        }
    }
}

3. 启动程序,输入URL,访问页面 

4. 输入参数,点击提交,页面展示设置的响应状态码

5. 使用抓包工具,抓响应包,可以看到响应状态码为我们设置的状态码

7.3 设置响应头Header 

1. 在response.html中构造一个链接,点击跳转到后端的路径

    <h3>设置响应头</h3>
    <a href="response">设置响应头</a>

2. 在ResponseServlet类中设置响应头,响应头可以设置为自定义类型 

        resp.setHeader("Location","http://www.baidu.com");
        resp.setHeader("name","panda");

3. 启动程序,输入URL,访问页面

  

4. 点击链接,使用抓包工具,抓响应包 

注意:我们设置了Location,但是没有跳转到设置的路径,是因为只有响应状态码是301/302/307才会跳转,此处的响应状态码是200

7.4 设置响应内容类型setContentType 

设置相应头Content-Type的值,因为Content-Type是标识body的数据格式,所以还需要设置body的内容,等同于setHeader("Content-Type","type") 

7.4.1 返回网页

1. 在response.html中创建一个链接,点击返回一个简单的网页,再创建一个输入框和按钮,点击按钮返回的网页内容会随着输入的变化而变化

<body>
    <h3>返回简单网页</h3>
    <a href="html-response?type=1">返回简单网页</a>
    <h3>返回动态变化的网页</h3>
    <input type="text" id="username" name="username" placeholder="请输入姓名">
    <input type="submit" onclick="toWelcome()">
</body>
<script>
    function toWelcome(){
        let username = document.querySelector("#username");
        window.location.href = "html-response?type=2&username="+username.value;
    }
</script>

2. 创建一个HTMLServlet类,重写doGet方法,对上述两个请求设置响应内容返回给浏览器

@WebServlet("/html-response")
public class HTMLServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        String type = req.getParameter("type");
        if("1".equals(type)){
            resp.getWriter().write("<p>"+"简单HTML"+"</p>");
        }else if("2".equals(type)){
            String name = req.getParameter("username");
            resp.getWriter().write(name+"欢迎您!");
        }
    }
}

3. 启动程序,输入URL,访问页面

5. 点击返回简单页面

  • 输入张三,点击提交

  • 输入孙猴子,点击提交

7.4.2 返回文件 

1. 在response.html中创建img和audio 

    <h3>图片展示</h3>
    <img src="file?type=photo">
    <br>
    <h3>音乐播放</h3>
    <audio src="file?type=music" controls></audio>

2. 创建FileServlet类,重写doGet方法,设置图片和音乐为响应内容

@WebServlet("/file")
public class FileServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        OutputStream ops = resp.getOutputStream();
        String type = req.getParameter("type");
        File file = null;
        if("photo".equals(type)){
            file = new File("D:\\photo\\11.jpg");
            resp.setContentType("image/jpeg");
        }else if("music".equals(type)){
            file = new File("D:\\音乐\\下载\\梦的光点.mp3");
            resp.setContentType("audio/mp3");
        }
        byte[] bytes = Files.readAllBytes(file.toPath());
        resp.setContentLength(bytes.length);
        ops.write(bytes);
    }
}

3. 启动程序,输入URL,访问页面

7.4.3 返回json数据 

1. 在response.html中创建一个按钮,点击就能获得消息

    <h3>获取ajax响应内容</h3>
    <button onclick="gen()">点击获取</button>
    <div id="content"></div>

2. 发送ajax请求,对应的js代码

    function gen(){
        let content = document.querySelector("#content");
        ajax({
            url: "ajax-response",
            method: "GET",
            callback: function(status,responseText){
                console.log(responseText); //resp是一个json字符串
                //转换为json对象
                let array = JSON.parse(responseText);
                for(json of array){
                    //每一个json,创建一个dom保存信息
                    let p = document.createElement("p");
                    p.innerHTML = json.from+"对"+json.to+"说:"+json.info;
                    content.appendChild(p);
                }
            }
        });
    }
    function ajax(args){
        let xhr = new XMLHttpRequest();
        //设置回调函数
        xhr.onreadystatechange = function(){
            //4:客户端接收到服务端响应
            if(xhr.readyState == 4){
                //回调函数可能会使用响应的内容,作为传入参数
                args.callback(xhr.status,xhr.responseText);
            }
        }
        xhr.open(args.method,args.url);
        //如果args中contentType有内容,就设置Content-Type请求头
        if (args.contentType) {//js中if可以判断是否有值
            xhr.setRequestHeader("Content-Type", args.contentType);
        }
        //如果args中body有内容,设置body请求正文
        if(args.body){
            xhr.send(args.body);
        }else {
            xhr.send();
        }
    }

3. 创建AjaxServlet类,重写doGet方法,创建一个消息类,将消息对象转化为json字符串返回给前端

@WebServlet("/ajax-response")
public class AjaxServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        List<Message> messages = new ArrayList<>();
        Message m1 = new Message("旺旺","喵喵","想你了");
        Message m2 = new Message("喵喵","旺旺","滚粗");
        messages.add(m1);
        messages.add(m2);
        ObjectMapper mapper = new ObjectMapper();
        //将java对象转json字符串
        String json = mapper.writeValueAsString(messages);
        resp.setContentType("application/json; charset=utf-8");
        resp.getWriter().write(json);
    }
    static class Message{
        private String from;
        private String to;
        private String info;

        public Message(String from, String to, String info) {
            this.from = from;
            this.to = to;
            this.info = info;
        }

        public String getFrom() {
            return from;
        }

        public void setFrom(String from) {
            this.from = from;
        }

        public String getTo() {
            return to;
        }

        public void setTo(String to) {
            this.to = to;
        }

        public String getInfo() {
            return info;
        }

        public void setInfo(String info) {
            this.info = info;
        }
    }
}

4. 启动程序,输入URL,访问页面,点击按钮

7.5 重定向与转发 

7.5.1 观察重定向与转发 

1. 在response.html构建两个链接,一个是重定向,一个是转发 

    <a href="goto?type=1">重定向到hello.html</a>
    <a href="goto?type=2">转发到hello.html</a>

2. 创建GoToServlet类,实现重定向与转发

@WebServlet("/goto")
public class GoToServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String type = req.getParameter("type");
        if(type.equals("1")){
//            resp.setStatus(301);
//            resp.setHeader("Location","hello.html");
            resp.sendRedirect("hello.html"); //以上代码的简化
        }else if(type.equals("2")){
            req.getRequestDispatcher("hello.html").forward(req,resp);
        }
    }
}

3. 启动程序,输入URL,访问页面

  • 点击重定向访问hello.html页面,发现地址栏不是goto?type=1结尾而是hello.html

  • 点击转发访问hello.html页面,发现地址栏还是goto?type=2结尾 

7.5.2 重定向与转发的特点及区别(面试常考) 

重定向

  • 特点:url地址会改变,发起两次请求
  • 原理:第一次请求的响应返回301/302/307状态码,并设置响应头Location的地址,第二次请求浏览器自动跳转到Location的地址

转发

  • 特点:url地址栏不会改变,发起一次请求
  • 原理:当次请求servlet时,由servlet获取到转发路径的资源,把这个路径的内容设置到响应正文

8. 用户登陆功能的实现 

8.1 回顾Cookie和Session 

Cookie

Cookie是客户端保存数据的技术

如何设置Cookie:服务端返回的响应头中有一个属性Set-Cookie:xxx=xxx,客户端收到响应后,将值保存在本地

如何使用Cookie:本地保存的Cookie数据,每次请求时都携带在请求头Cookie中

Session 

Session是服务端保存会话的技术

web服务器启动后,创建了一个Map<String,Session>数据结构保存会话,一个Session对象表示一次会话,Session也是一个Map<String,Object>数据结构

8.2 登陆功能实现

登陆页面

创建一个login.html登陆页面

<body>
    <h3>登陆页面</h3>
    <form action="login" method="POST">
        <input type="text" name="username" placeholder="请输入用户名">
        <br>
        <input type="password" name="password" placeholder="请输入密码">
        <br>
        <input type="submit">
    </form>
</body>

  

后端校验

@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");
        if("abc".equals(username) && "123".equals(password)){
            //获取session信息,true表示如果获取不到就创建一个
            HttpSession session = req.getSession(true);
            session.setAttribute("user",username);
            //登陆成功跳转到success.html
            resp.sendRedirect("success.html");
        }else {
            resp.setContentType("text/html; charset=utf-8");
            resp.getWriter().write("登陆失败");
        }
    }
}

如果登陆成功则跳转到success.html页面,如果登陆失败,则展示登陆失败

8.3 敏感资源访问  

敏感资源在用户未登录时不允许访问,那么在每次http请求时如何校验登陆呢?查看数据结构中是否有Session对象 

@WebServlet("/sensitive")
public class SensitiveServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        //如果获取不到就为null
        HttpSession session = req.getSession(false);
        if(session != null){
            String username = (String)session.getAttribute("user");
            if(username != null){ //检测username是否为空
                resp.getWriter().write("用户已经登陆,允许访问");
                return;
            }
        }
        resp.getWriter().write("用户未登录,不允许访问");
    }
}

8.4 效果展示 

  • 用户名或密码错误,登陆失败

  • 用户名跟密码正确,登陆成功 

  • 访问敏感资源,未登陆时不允许访问

  • 访问敏感资源,登陆时允许访问 

;