IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 【HTTP 协议基础】 -> 正文阅读

[网络协议]【HTTP 协议基础】

【HTTP 协议基础】

一、 HTTP 协议概述

概念:Hyper Text Transfer Protocol 超文本传输协议
传输协议:定义了客户端和服务器端通信时,发送数据的格式

1. 特点

  1. 基于 TCP/IP 的高级协议
  2. 默认端口号:80
  3. 基于请求/响应模型:一次请求对应一次响应
  4. 无状态的:每次请求之间相互独立,不能交互数据

2. 历史版本

区别(最大区分点):

  1. HTTP 1.0 版本:每一次请求响应都会建立新的连接
  2. HTTP 1.1 版本:可以复用连接(当每一个响应过后,会等待一段时间,如果仍有数据响应,继续复用上一个请求得到的连接,直到响应超时,没有响应)

二、 请求消息

客户端发送给服务器端的数据

1. 请求消息数据格式

(1) 请求行

  • 请求方式:
    • HTTP 协议中有7中请求方式,常用的有2种
      • GET:
        1. 请求参数在请求行中,在 URL 后
        2. 请求的 URL 长度有限制
        3. 不太安全
      • POST:
        1. 请求参数在请求体中
        2. 请求的 URL 长度没有限制
        3. 相对安全
请求方式请求 URL请求协议/版本
GET 或 POST/login.html(资源路径)HTTP/1.1

(2) 请求头

客户端浏览器告诉服务器一些信息

  • 常见的请求头:
    • User-Agent :浏览器告诉服务器,访问该信息使用的浏览器版本信息
      • 可以在服务器端获取该头的信息,解决浏览器的兼容性问题
    • Referer :http://localhost/login.html
      • 告诉服务器:我当前的请求从哪里来
        • 作用:① 防盗链 ②统计工作
请求头名称请求头值
① 防盗链

举例:
你使用正版视频网站的首页跳转到被电影出版商认证的你选择播放的电影页面,可以正常播放
如果你自己写出一个盗版的视频网站首页,采用盗链的方式也可以跳转到该认证播放地址中选择的电影播放页面,这就是盗链行为
如果官方在播放页面中加入 Referer 判断,使得非授权授权页面主页跳转到此播放地址的行为无法播放,这就是防盗链

② 统计工作

类似的:如果网站进行广告推广,可以使用 Referer 统计,哪个广告位跳转到该页面的次数多,进行定向投资广告业务

(3) 请求空行

空行,用于分割 POST 请求的请求头和请求体

空行

(4) 请求体(正文)

请求体(正文)是用来封装 POST 请求消息的请求参数

(5) 请求字符串格式

空行之后属于请求体

POST	/login.html	HTTP/1.1
Host:	localhost
User-Agent:		……
Accept:		……
Accept-Language:		……
Accept-Encoding:		……
Referer:	http://localhost/login.html
Connection:			……
Upgrade-Insecure-Requests:		……

username=zhangsan

2. Request 对象和 Response 对象原理

  1. Request 对象和 Response 对象是由服务器创建的,我们来使用它们
  2. Request 对象是用来获取请求消息的, Response 对象是用来设置响应消息

工作原理过程:
客户端浏览器通过: http://localhost:端口号/虚拟目录/资源路径服务器请求发出请求消息:之后

  1. Tomcat 服务器会根据请求 URL 中的资源路径,(以创建 ServletTest 类并实现 Servlet 接口重写方法的实现类为例)创建对应的 ServletTest 对象
  2. Tomcat 服务器,会创建 Request 对象和 Response 对象,Request 对象中封装请求消息数据
  3. Tomcat 服务器将 Request 对象和 Response 对象传递给 service 方法,并且调用 service 方法

在创建的 ServletTest 类并实现 Servlet 接口重写方法的实现类中:之后

  1. 程序员可以通过 Request 对象获取请求消息数据,通过 Response 对象设置响应消息数据

之后服务器向客户端浏览器响应,进行响应消息:之后

  1. 服务器在浏览器做出响应之前,会从 Response 对象中拿程序员设置的响应消息数据

3. Request 对象继承体系结构

继承自
继承自
org.apache.catalinna.connection.RequestFacade 由 Tomcat 创建的类
HttpServletRequest 接口
ServletRequest 接口

4. Request 对象的功能

  1. 获取请求消息数据

    • 获取请求行数据
    • 获取请求头数据
    • 获取请求体数据
  2. 其他功能

(1) 获取请求消息数据

① 获取请求行数据
  • 请求格式:GET /test_tomcat/test01?name=zhangsan HTTP/1.1
  • 方法:
    1. 获取请求方式:GET :String getMethod()
    2. 获取虚拟目录:/test_tomcat :String getContextPath()
    3. 获取 Servlet 路径:/test01 :String getServletPath()
    4. 获取 get 方式请求参数:name=zhangsan :String getQueryString()
    5. 获取请求 URI:/test_tomcat/test01 :String getRequestURI()
    6. 获取请求 URL:http://localhost/test_tomcat/test01 :StringBuffer getRequestURL()
    7. 获取协议以版本:HTTP/1.1 :String getProtocol()
    8. 获取客户据的 IP 地址: String getRemoteAddr()

URL :统一资源定位符
URI :统一资源标识符(范围较大)

代码演示:

  • 可以直接按照下图中直接创建 Servlet 类,默认继承 HttpServlet 抽象类,并进行方法重写,我们这里只使用 doGet() 方法
    创建
@WebServlet(name = "ServletTest07", value = "/ServletTest07")
public class ServletTest07 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取请求方式 GET
        String method = request.getMethod();
        System.out.println(method);
        // 2. 获取虚拟目录 /tomcat_test
        String contextPath = request.getContextPath();
        System.out.println(contextPath);
        // 3. 获取 Servlet 路径 /ServletTest07
        String servletPath = request.getServletPath();
        System.out.println(servletPath);
        // 4. 获取 get 方式请求参数 name=zhangsan
        String queryString = request.getQueryString();
        System.out.println(queryString);
        // 5. 获取请求 URI /tomcat_test/ServletTest07
        String requestURI = request.getRequestURI();
        System.out.println(requestURI);
        // 6. 获取请求 URL http://localhost:8080/tomcat_test/ServletTest07
        StringBuffer requestURL = request.getRequestURL();
        System.out.println(requestURL);
        // 7. 获取协议及版本 HTTP/1.1
        String protocol = request.getProtocol();
        System.out.println(protocol);
        // 8. 获取客户机的 IP 地址 0:0:0:0:0:0:0:1(由于是自己访问自己)
        String remoteAddr = request.getRemoteAddr();
        System.out.println(remoteAddr);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}
② 获取请求头数据
  • 方法:
    • String getHeader(String name) : 通过请求头的名称获取请求头的值
    • Enumeration<String> getHeaderNames() :获取所有的请求头名称

代码演示:

  • 获取所有的请求头名称
@WebServlet(name = "ServletTest08", value = "/ServletTest08")
public class ServletTest08 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取所有请求头名称
        Enumeration<String> headerNames = request.getHeaderNames();
        // 遍历
        while (headerNames.hasMoreElements()){
            String name = headerNames.nextElement();
            // 根据名称获取请求头的值
            String value = request.getHeader(name);
            System.out.println(name+"---"+value);
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}
  • 获取请求头数据:User-Agent
@WebServlet(name = "ServletTest09", value = "/ServletTest09")
public class ServletTest09 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 演示获取请求头数据:user-agent 不区分大小写
        String agent = request.getHeader("User-Agent");
        // 判断 agent 的浏览器版本
        if (agent.contains("Chrome")){
            System.out.println("谷歌来了!");
        }else if (agent.contains("Firefox")){
            System.out.println("火狐来了!");
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}
  • 演示获取头数据:Referer ,并且判断反应是否有盗链行为
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 演示获取头数据:referer
        String referer = request.getHeader("Referer");
        System.out.println(referer);
        // 防盗链
        if (referer!=null){
            if (referer.contains("tomcat_test")){ // 是否直接通过浏览器的规定虚拟目录位置访问
                // 正常访问
                System.out.println("正常访问!");
                // 浏览器页面显示
                response.setContentType("text/html;charset=utf-8");
                response.getWriter().write("正常访问!");
            }else {
                // 盗链
                System.out.println("盗链!");
                // 浏览器页面显示
                response.setContentType("text/html;charset=utf-8");
                response.getWriter().write("盗链行为!");
            }
        }
    }
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
</head>
<body>
<a href="http://localhost:8080/tomcat_test/ServletTest09">正常访问</a>
</body>
</html>
③ 获取请求体数据
  • 请求体:只有 POST 请求方式中,才有请求体,在请求体中封装了 POST 请求的请求参数
  • 步骤:
    1. 获取流对象
      • BufferReader getReader() :获取字符输入流,只能操作字符数据
      • ServletInputStream getInputStream() :获取字节输入流,可以操作所有数据类型
    2. 再从流对象中拿数据

代码演示:

@WebServlet(name = "ServletTest10", value = "/ServletTest10")
public class ServletTest10 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取请求消息体:请求参数
        // 获取字符流
        BufferedReader reader = request.getReader();
        // 读取数据
        String line=null;
        while((line= reader.readLine())!=null){
            System.out.println(line);
        }
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册页面</title>
</head>
<body>
<form action="http://localhost:8080/tomcat_test/ServletTest10" method="post">
    <input type="text" placeholder="请输入用户名" name="username"><br>
    <input type="text" placeholder="请输入密码" name="password"><br>
    <input type="submit" value="注册">
</form>
</body>
</html>

(2) 其他功能

① 获取请求参数的通用方式

不论是 GET 还是 POST 请求方式都可以使用下列方法来获取请求参数

  1. String getParameter(String name) :根据参数名称获取参数值(username=zhangsan&password=123456)
  2. String[] getParameterValues(String name) :根据参数名称获取参数值的数组(hobby=xxxx&hobby=game)
  3. Enumeration<String> getParameterNames() :获取所有请求的参数名称
  4. Map<String,String[]> getParameterMap() :获取所有参数的 Map 集合

代码演示:第一个方法

@WebServlet(name = "ServletTest11", value = "/ServletTest11")
public class ServletTest11 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // get 获取请求参数
        // 根据参数名称获取参数值
        String username = request.getParameter("username");
        System.out.println("get");
        System.out.println(username);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // post 获取请求参数
        // 根据参数名称获取参数值
        // String username = request.getParameter("username");
        // System.out.println("post");
        // System.out.println(username);
        // 既然两个方法的业务逻辑相同,没必要再写一遍,只需要调用即可
        this.doGet(request,response);
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册页面</title>
</head>
<body>
<form action="http://localhost:8080/tomcat_test/ServletTest11" method="post">
    <input type="text" placeholder="请输入用户名" name="username"><br>
    <input type="text" placeholder="请输入密码" name="password"><br>
    <input type="submit" value="注册">
</form>
</body>
</html>

代码演示:第二个方法

@WebServlet(name = "ServletTest11", value = "/ServletTest11")
public class ServletTest11 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 根据参数名称获取参数值的数组
        String[] hobbies = request.getParameterValues("hobby");
        for (String hobby : hobbies) {
            System.out.println(hobby);
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request,response);
    }
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册页面</title>
</head>
<body>
<form action="http://localhost:8080/tomcat_test/ServletTest11" method="post">
    <input type="text" placeholder="请输入用户名" name="username"><br>
    <input type="text" placeholder="请输入密码" name="password"><br>
    <input type="checkbox" name="hobby" value="game">游戏
    <input type="checkbox" name="hobby" value="sports">运动
    <input type="checkbox" name="hobby" value="music">音乐
    <input type="checkbox" name="hobby" value="study">学习<br>
    <input type="submit" value="注册">
</form>
</body>
</html>

代码演示:第三个方法

@WebServlet(name = "ServletTest11", value = "/ServletTest11")
public class ServletTest11 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取所有请求的参数名称
        Enumeration<String> parameterNames = request.getParameterNames();
        while (parameterNames.hasMoreElements()){
            String name = parameterNames.nextElement();
            System.out.println(name);
            String[] values = request.getParameterValues(name);
            for (String value : values) {
                System.out.println(value);
                System.out.println("------");
            }
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request,response);
    }
}

代码演示:第四个方法

@WebServlet(name = "ServletTest11", value = "/ServletTest11")
public class ServletTest11 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取所有参数的 Map 集合
        Map<String, String[]> parameterMap = request.getParameterMap();
        // 遍历
        Set<String> keySet = parameterMap.keySet();
        for (String name : keySet) {
            // 获取键获取值
            String[] values = parameterMap.get(name);
            System.out.println(name);
            for (String value : values) {
                System.out.println(value);
            }
            System.out.println("------");
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request,response);
    }
}
② 获取请求参数中文乱码问题
  • GET 方式:Tomcat 8 已经将 GET 方式的乱码问题解决
  • POST 方式:会产生中文乱码问题
    • 解决:在获取参数之前,设置 Request 对象的编码request.setCharacterEncoding("utf-8"); ,即此时设置的编码格式与所编写的 HTML 页面编写的编码相同
@WebServlet(name = "ServletTest11", value = "/ServletTest11")
public class ServletTest11 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取请求参数 username
        // 设置流的编码
        request.setCharacterEncoding("utf-8");
        String username = request.getParameter("username");
        System.out.println(username);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request,response);
    }
}
③ 请求转发

一种在服务器内部的资源跳转方式

  • 步骤:
    1. 通过 Request 对象获取请求转发器对象:RequestDispatcher getRequestDispatcher(String path)
    2. 使用 RequestDispatcher 对象来进行转发:forward(ServletRequest request,ServletRequest response)

代码演示:

@WebServlet(name = "ServletTest12", value = "/ServletTest12")
public class ServletTest12 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("ServletTest12 被访问了!");
        // 转发到 ServletTest13 资源
        request.getRequestDispatcher("/ServletTest13").forward(request,response);
        // 由于该对象的该种方法一般只使用一次,所以采用链式编程
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
@WebServlet(name = "ServletTest13", value = "/ServletTest13")
public class ServletTest13 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("ServletTest13 被访问了!");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request,response);
    }
}

采用这种请求转发的方式,只使用浏览器访问 ServletTest12 这一个类,同时达到访问 ServletTest12 和 ServletTest13 两个类的效果

  • 特点:
    1. 浏览器地址栏路径不发生变化
    2. 只能转发到当前服务器内部资源中
    3. 转发是一次请求
④ 共享数据

域对象:一个有作用范围的对象,可以在范围内共享数据
Request 域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据

方法:

  1. void setAttribute(String name,Object obj) :存储数据
  2. Object getAttribute(String name) :通过键获取值
  3. void removeAttribute(String name) :通过键移除键值对
@WebServlet(name = "ServletTest12", value = "/ServletTest12")
public class ServletTest12 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("ServletTest12 被访问了!");
        // 存储数据到 Request 域中
        request.setAttribute("share","Hello!");
        // 转发到 ServletTest13 资源
        request.getRequestDispatcher("/ServletTest13").forward(request,response);
        // 由于该对象的该种方法一般只使用一次,所以采用链式编程
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
@WebServlet(name = "ServletTest13", value = "/ServletTest13")
public class ServletTest13 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取数据
        Object share = request.getAttribute("share");
        System.out.println(share);
        System.out.println("ServletTest13 被访问了!");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request,response);
    }
}
⑤ 获取 ServletContext 对象

方法:

  • ServletContext getServletContext() 获取ServletContext 对象
@WebServlet(name = "ServletTest14", value = "/ServletTest14")
public class ServletTest14 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext servletContext = request.getServletContext();
        System.out.println(servletContext);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request,response);
    }
}

5. 登录案例

(1) 用户登录案例需求

  1. 编写 login.html 登陆界面:包含 username&password 两个输入框
  2. 使用 Druid 数据库连接池技术,操作 MySQL 数据库,test 数据库中的 user 表
  3. 使用 JDBCTemplate 技术封装 JDBC
  4. 登陆成功跳转到 SuccessServlet 页面展示:登陆成功!用户名,欢迎您!
  5. 登陆失败跳转到 FailServlet 页面展示:登陆失败!用户名或密码错误!

登陆界面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录界面</title>
</head>
<body>
<form action="" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    <input type="submit" value="登录">
</form>
</body>
</html>

MySQL 数据库连接池(Druid 连接池)配置文件:

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///test
username=root
password=root
initialSize=5
maxActive=10
maxWait=3000

(2) 分析

  • 三部分:
  1. 用户登录界面:login.html
  2. LoginServlet
    1. 设置编码
    2. 获取 username 和 password
    3. 将 username 和 password 封装为一个 User 对象
    4. 调用 UserDao 类的 login 方法查询,获取返回值 User 对象
    5. 判断 user 是否为 null:① 登陆失败 → FailServlet 类 ② 登陆成功 → 将用户信息存到 Request 域,转发到 SuccessServlet
  3. UserDao 操作数据库的类

(3) 代码实现

开发步骤:

  • 创建一个 JavaEE 模块使用 Maven 和 Junit 框架
  • webapp 目录下,编写登陆界面(注意:不要误放到 WEB-INF 目录下)
  • druid.properties 放到 resource (资源)目录下
  • pom.xml 中导入所需要的 jar 包和插件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>login_test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>login_test</name>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
        <junit.version>5.8.1</junit.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>mchange-commons-java</artifactId>
            <version>0.2.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.3.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.3.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.3.6</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
            </plugin>
        </plugins>
    </build>
</project>
  • 创建数据库环境
create table user(
    id int primary key auto_increment,
    username varchar(32) unique not null ,
    password varchar(32) not null
);
insert into user values (1,'张三','123');
insert into user values (2,'李四','234');
insert into user values (3,'王五','345');
  • 创建实体类 User 类
public class User {
    private int id;
    private String username;
    private String password;

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    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;
    }
}
  • 编写 JDBC 工具类,使用 Druid 连接池
import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

public class JDBCUtils {
    /**
     * JDBC 工具类,使用 Druid 连接池
     */
    private static DataSource dataSource;
    static {
        try {
            // 加载配置文件
            Properties properties = new Properties();
            // 使用 ClassLoader 加载配置文件,获取字节输入流
            InputStream resourceAsStream = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
            properties.load(resourceAsStream);
            // 初始化连接池对象
            dataSource= DruidDataSourceFactory.createDataSource(properties);
        }catch (IOException e){
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    // 获取连接池对象
    public static DataSource getDataSource(){
        return dataSource;
    }
    // 获取连接 Connection 对象
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }
}
  • 编写操作数据库中 User 表的类
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

public class UserDao {
    /**
     * 操作数据库中 User 表的类
     */
    // 声明 JDBCTemplate 对象共用
    private JdbcTemplate jdbcTemplate=new JdbcTemplate(JDBCUtils.getDataSource());
    /* 登陆方法:loginUser:只有用户名和密码,user:包含用户全部数据,没有查询都,返回 null */
    public User login(User loginUser){
        try {
            String sql="select * from user where username = ? and password = ?";
            // 调用 query 方法
            User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), loginUser.getUsername(), loginUser.getPassword());
            return user;
        } catch (DataAccessException e) {
            e.printStackTrace(); // 一般在这里写入日志
            return null;
        }
    }
}
  • 进行一个简短的测试方法,测试代码的运行
    @Test
    public void testLogin(){
        User loginUser = new User();
        loginUser.setUsername("李四");
        loginUser.setPassword("234");
        UserDao dao = new UserDao();
        User user = dao.login(loginUser);
        System.out.println(user);
    }
  • login.html 中的 <form> 表单中 action 路径的写法:虚拟目录 + Servlet 类的资源路径(例如此处应为:<form action="/login_test/loginServlet" method="post">

  • 编辑配置 Tomcat 服务器,部署项目【Tomcat 服务器安装及基础配置使用】
    部署

  • 编写 LoginServlet 类

@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 设置编码
        request.setCharacterEncoding("utf-8");
        // 获取请求参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        // 封装为 User 对象
        User loginUser = new User();
        loginUser.setUsername(username);
        loginUser.setPassword(password);
        // 调用 UserDao 的 login 方法
        UserDao dao = new UserDao();
        User user = dao.login(loginUser);
        // 判断 user
        if (user==null){
            // 登陆失败
            request.getRequestDispatcher("/failServlet").forward(request,response);
        }else {
            // 登陆成功
            // 存储数据
            request.setAttribute("user",user);
            //转发
            request.getRequestDispatcher("successServlet").forward(request,response);
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
  • 编写 FailServlet 类
@WebServlet("/failServlet")
public class FailServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 页面显示
        // 设置编码
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write("登录失败,用户名或密码错误!");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
  • 编写 SuccessServlet 类
@WebServlet("/successServlet")
public class SuccessServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取 Request 域中共享的 User 对象
        User user = (User) request.getAttribute("user");
        if (user!=null){
            // 页面显示
            response.setContentType("text/html;charset=utf-8");
            // 设置编码
            response.getWriter().write("登陆成功!"+user.getUsername()+",欢迎您!");
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request,response);
    }
}
  • 代码实现完成,部分封装行为过于繁琐

(4) BeanUtils 工具类

BeanUtils 工具类,简化数据封装,用于封装 JavaBean
JavaBean :标准的 Java 类,用于封装数据
要求:

  1. 类必须被 public 修饰
  2. 类必须提供空参的构造器
  3. 成员变量必须使用 private 修饰
  4. 提供公共的 setter 和 getter 方法
  • 方法:

    1. setProperty()
    2. getProperty()
    3. populate(Object obj,Map map) :将 Map 集合的键值对信息,封装到对应的 JavaBean 对象中
  • 使用 Maven 框架导入 jar 包

        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.4</version>
        </dependency>
  • 优化 LoginServlet 类
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 设置编码
        request.setCharacterEncoding("utf-8");
        // 获取所有的请求参数
        Map<String, String[]> map = request.getParameterMap();
        // 创建 User 对象
        User loginUser = new User();
        // 使用 BeanUtils 封装
        try {
            BeanUtils.populate(loginUser,map);
        }catch (IllegalAccessException e){
            e.printStackTrace();
        }catch (InvocationTargetException e){
            e.printStackTrace();
        }
        // 调用 UserDao 的 login 方法
        UserDao dao = new UserDao();
        User user = dao.login(loginUser);
        // 判断 user
        if (user==null){
            // 登陆失败
            request.getRequestDispatcher("/failServlet").forward(request,response);
        }else {
            // 登陆成功
            // 存储数据
            request.setAttribute("user",user);
            //转发
            request.getRequestDispatcher("successServlet").forward(request,response);
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

概念区分:

  • 成员变量:大多数情况下,属性与成员变量区别不大
  • 属性:(严格区分)setter 和 getter 方法截取后的产物(例如:getUsername() → Username → username)
  • 解释:假设设置 setXingbie(String gender); getXingbie(); ,此时 xingbie 这个词组就成为了属性,而不是 gender,属于 xingbie 这个属性操作 gender 这个值

三、 响应消息

服务器端发送给客户端的数据

1. 响应消息数据格式

(1) 响应行

  • 组成:
协议/版本响应状态码状态码描述
  • 响应状态码:服务器告诉客户端浏览器本次请求和响应的一个状态
    • 状态码都是3位数字
    • 分类:
      1. 1xx :服务器接收客户端消息,但没有接收完成,等待一段时间后,发送 1xx 状态码
      2. 2xx :成功,代表数字:200
      3. 3xx :重定向,代表数字:302(重定向)304(访问缓存)
      4. 4xx :客户端错误,代表数字:404(请求路径没有对应的资源)405(请求方式没有对应的 doXxx() 方法)
      5. 5xx :服务器端错误,代表数字:500(服务器内部出现异常)

重定向解释:假设浏览器向服务器发送请求,服务器中有 A B C 三类资源,为做出响应服务器先找到 A 类资源,但是 A 类资源无法解决问题,随后 A 类资源认为 C 类资源可以解决问题,响应浏览器响应状态码:302 ,并提出去找 C 类资源并且附有 C 类的资源路径,最后 C 类资源做出响应,这里 C 类资源是否解决问题,仍不确定。(相当于一次请求,请求到了两次资源)

访问缓存解释:假设服务器中有一张图片,浏览器请求这张图片,请求完成后,服务器将图片响应给浏览器,浏览器进行图片展示。但是由于该图片一般不会发生变化,图片数据已经固定,这时浏览器会自动将图片的数据缓存到本地浏览器下一次再来请求相同的图片数据资源时,服务器发现图片并没有发生变化,而且浏览器本地缓存中有该图片,服务器告诉浏览器去访问本地的图片数据缓存,服务器响应状态码:304,去访问缓存(因为服务器与浏览器数据交互,发送图片方式为二进制数据,占用通信时间长),提升了整个请求和响应的速度,减轻了服务器的压力。如果图片更改,服务器会自动刷新图片,正常响应浏览器。

(2) 响应头

格式:

头名称 :

常见的响应头:

  1. Content-Type :服务器告诉客户端本次响应体数据格式以及编码格式
  2. Content-disposition :服务器告诉客户端以什么格式打开响应体数据
    • 常见的值:
      • in-line :默认值,在当前页面内打开
      • attachment;filename=xxx :以附件形式打开响应体,一般用于文件下载

(3) 响应空行

空行

(4) 响应体

传输的对象

(5) 响应字符串格式

空行之后属于响应体

HTTP/1.1  200  OK
Content - Type: text/html;charset=UTF-8
Content - Length: 101
Date:  ……

<html>
	<head>
		<title>页面</title>
	</head>
	<body>
		Hello,Response!
	</body>
</html>

2. Response 对象的功能

  • 设置响应消息
    • 设置响应行
      1. 格式:HTTP/1.1 200 ok
      2. 设置状态码:setStatus(int sc)
    • 设置响应头 :setHeader(String name,String value)
    • 设置响应体:
      • 使用步骤:
        1. 获取输出流:
          • 字符输出流:PrintWriter getWriter()
          • 字节输出流:ServletOutputStream getOutputStream()
        2. 使用输出流,将数据输出到客户端浏览器

3. 案例:重定向

重定向:资源跳转的方式
重定向解释:假设浏览器向服务器发送请求,服务器中有 A B C 三类资源,为做出响应服务器先找到 A 类资源,但是 A 类资源无法解决问题,随后 A 类资源认为 C 类资源可以解决问题,响应浏览器响应状态码:302 ,并提出去找 C 类资源并且附有 C 类的资源路径,最后 C 类资源做出响应,这里 C 类资源是否解决问题,仍不确定。(相当于一次请求,请求到了两次资源)

(1) 代码实现

实现步骤:

  1. 服务器告诉浏览器重定向,状态码302
  2. 告诉浏览器 C 类资源的路径,使用响应头 location: C 类资源的路径
@WebServlet("/responseTest01")
/**
 * 重定向
 */
public class ResponseTest01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("Test01 被访问了!");
        // 访问 /responseTest01,会自动跳转到 /responseTest02 资源
        // 设置状态码为302
        response.setStatus(302);
        // 设置响应头 location
        response.setHeader("location","/response_test/responseTest02");

        // 由于上述三个参数有两个是固定不变的,所以 API 提供了一种简单的方法
        // 简单的重定向方法
        response.sendRedirect("/response_test/responseTest02");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request,response);
    }
}
@WebServlet("/responseTest02")
public class ResponseTest02 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("Test02 被访问了!");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request,response);
    }
}

(2) 特点对比

  • 重定向与转发特点对比:
    • 重定向(redirect)的特点:
      1. 地址栏发生变化
      2. 重定向可以访问其他站点(服务器)的资源
      3. 重定向是两次请求,不能使用 Request 对象来共享数据
    • 转发(forward)的特点:
      1. 转发地址栏路径不变
      2. 转发只能访问当前服务器下的资源
      3. 转发是一次请求,可以使用 Request 对象来共享数据

(3) 路径分类

① 相对路径

相对路径:通过相对路径不可以确定唯一资源(如:./index.html简易判断:不以 / 开头,而以 . 开头的路径

  • 规则:找到当前资源和目标资源之间的相对位置
    • ./ :当前目录
    • ../ :后退一级目录(多了一个句点)(就是向上翻阅一个目录)
② 绝对路径

绝对路径:通过绝对路径可以确定唯一资源(如:http://localhost:8080/虚拟目录/资源路径/虚拟目录/资源路径简易判断:以 / 开头的路径

  • 规则:判断定义的路径是给谁使用,判断路径请求从哪里发出来
    • 给客户端浏览器使用:需要加虚拟目录(项目的访问路径)(例如:重定向、 <a> 标签、 <form> 表单标签等)
      • 建议:虚拟目录动态获取:request.getContentPath()(防止服务器部署中后期更改虚拟目录,导致代码无法使用)
    • 给服务器使用:不需要加虚拟目录(例如:转发)
@WebServlet("/responseTest01")
/**
 * 重定向
 */
public class ResponseTest01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("Test01 被访问了!");
        // 动态获取虚拟目录
        String contextPath = request.getContextPath();
        // 简单的重定向方法
        response.sendRedirect(contextPath+"/responseTest02");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request,response);
    }
}

4. 案例:服务器输出字符数据到浏览器

步骤:

  • 获取字符输出流
  • 输出数据
@WebServlet("/responseTest03")
public class ResponseTest03 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取字符输出流
        PrintWriter printWriter = response.getWriter();
        // 输出数据
        printWriter.write("<h1>Hello,Response!</h1>");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
  • 具有中文乱码的问题:乱码的原因,编码解码使用的字符集不一致
  • 浏览器的默认使用的字符集和当前操作系统的语言环境有关(中文的 Windows 操作系统),浏览器打开默认的字符集就是 GBK(国标扩展码)(GB2312 国标码)
  • 服务器中的流对象是获取出来的而不是创建出来的(本地创建(new)出来的对象也和操作系统有关,就会是 GBK 字符集),获取出来的流对象是 Tomcat 反馈出来的(Response 对象是 Tomcat 实现的),而 Tomcat 使用的编码是 ISO-8859-1(拉丁编码)
  • 解决:获取流对象之前,设置流的默认编码,但是不能从根本解决问题,因为浏览器未必就是默认的字符集编码,所以就要在代码中建议浏览器使用与编写代码一致的字符集解码
@WebServlet("/responseTest03")
public class ResponseTest03 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取流对象之前,设置流的默认编码,ISO-8859-1,变化为 UTF-8
        // response.setCharacterEncoding("utf-8");
        // 告诉浏览器,服务器发送的消息体数据的编码,建议浏览器使用该编码解码
        // response.setHeader("content-type","text/html;charset=utf-8");
        // 简单形式,代替上述两行代码
        response.setContentType("text/html;charset=utf-8");
        // 获取字符输出流
        PrintWriter printWriter = response.getWriter();
        // 输出数据
        printWriter.write("<h1>你好,Response!</h1>");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

5. 案例:服务器输出字节数据到浏览器

步骤:

  • 获取字节输出流
  • 输出数据
@WebServlet("/responseTest04")
public class ResponseTest04 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html; charset=UTF-8");
        // 获取字节输出流
        ServletOutputStream servletOutputStream = response.getOutputStream();
        // 输出数据
        servletOutputStream.write("你好!".getBytes("utf-8"));
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

6. 案例:验证码

本质:图片
目的:防止恶意表单注册

  • 生成简单验证码
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        int width=100;
        int height=50;
        // 创建一个对象,在内存中的验证码图片对象
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        // 美化图片
        // 填充背景色
        Graphics graphics = image.getGraphics(); // 画笔对象
        graphics.setColor(Color.CYAN); // 设置画笔颜色
        graphics.fillRect(0,0,width,height);
        // 画边框
        graphics.setColor(Color.BLUE);
        graphics.drawRect(0,0,width-1,height-1);
        // 写验证码
        String str="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        // 生成随机角标
        Random random = new Random();
        for (int i = 1; i <= 4; i++) {
            int index = random.nextInt(str.length());
            // 获取字符,随机字符
            char charAt = str.charAt(index);
            graphics.drawString(charAt+"",width/5*i,height/2);
        }
        // 画干扰线
        graphics.setColor(Color.pink);
        // 随机生成坐标点
        for (int i = 0; i < 10; i++) {
            int x1 = random.nextInt(width);
            int x2 = random.nextInt(width);
            int y1 = random.nextInt(height);
            int y2 = random.nextInt(height);
            graphics.drawLine(x1,x2,y1,y2);
        }
        // 将图片输出到页面展示
        ImageIO.write(image,"jpg",response.getOutputStream());
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
  • 点击切换验证码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        // 点击超链接或者图片更换图片
        // 给超链接和图片绑定单击事件
        // 重新设置图片的 src 属性
        window.onload=function () {
            // 获取图片对象
            var img = document.getElementById("checkCode");
            // 绑定单击事件
            img.onclick=function () {
                // 加入时间戳,欺骗服务器,防止浏览器缓存图片后,不进行更新
                var data=new Date().getTime();
                img.src="/response_test/checkCodeServlet?"+data;
            }
        }
    </script>
</head>
<body>
<img id="checkCode" src="/response_test/checkCodeServlet">
<a id="change" href="">看不清,换一张!</a>
</body>
</html>

四、 ServletContext 对象

1. ServletContext 对象概述

概念:代表整个 Web 应用,可以和程序的容器(服务器)进行通信

  • 获取:
    1. 通过 Request 对象获取:request.getServletContext();
    2. 通过 HttpServlet 类获取:this.getServletContext();
  • 功能:
    1. 获取 MIME 类型
      • MIME 类型:在互联网通信过程中定义的一种文件数据类型
    2. 本身属于域对象:共享数据
    3. 获取文件的真实(服务器)路径

2. ServletContext 对象的获取

@WebServlet("/servletContextTest01")
public class ServletContextTest01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 通过 Request 对象获取
        ServletContext servletContext01 = request.getServletContext();
        // 2. 通过 HttpServlet 类获取
        ServletContext servletContext02 = this.getServletContext();
        System.out.println(servletContext01);
        System.out.println(servletContext02);
        // 判断两者是否相同
        System.out.println(servletContext01==servletContext02); // true
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

3. ServletContext 对象的功能

(1) 获取 MIME 类型

MIME 类型:在互联网通信过程中定义的一种文件数据类型

  • 格式:大类型/小类型(例如:text/htmlimage/jpeg
  • 获取:String getMimeType(String file)
@WebServlet("/servletContextTest02")
public class ServletContextTest02 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 通过 HttpServlet 类获取
        ServletContext servletContext = this.getServletContext();
        // 定义文件名称
        String fileName="a.jpg";
        // 获取 MIME 类型
        String mimeType = servletContext.getMimeType(fileName);
        System.out.println(mimeType);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

(2) 域对象:共享数据

  1. setAttribute(String name,Object value)
  2. getAttribute(String name)
  3. removeAttribute(String name)
  • ServletContext 的域对象范围:所有用户所有请求的数据(生命周期很长,从服务器的开启到关闭一直存在,在其中存储的数据过多会导致内存压力过大,谨慎使用)
@WebServlet("/servletContextTest03")
public class ServletContextTest03 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 通过 HttpServlet 获取
        ServletContext servletContext = this.getServletContext();
        // 设置数据
        servletContext.setAttribute("msg","存储数据!");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
@WebServlet("/servletContextTest04")
public class ServletContextTest04 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 通过 HttpServlet 获取
        ServletContext servletContext = this.getServletContext();
        // 获取数据
        Object msg = servletContext.getAttribute("msg");
        System.out.println(msg);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

(3) 获取文件的真实(服务器)路径

首先了解:编写的 Web 项目,具有两份,一份存储在 Tomcat 服务器中,另一份在本地工作空间中,而客户端浏览器访问的肯定是 Tomcat 服务器中的那一份 Web 项目。
所以一切的路径都是指的服务器端路径(或者称为真实路径或者文件运行的路径)

  • 方法:String getRealPath(String path)
@WebServlet( "/servletContextTest05")
public class ServletContextTest05 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext servletContext = this.getServletContext();
        // 获取文件的服务器路径
        String realPath01 = servletContext.getRealPath("/index.jsp"); // webapp 目录下资源访问
        System.out.println(realPath01);
        File file = new File(realPath01);

        String realPath02 = servletContext.getRealPath("/WEB-INF/web.xml"); // WEB-INF 目录下的资源访问
        System.out.println(realPath02);

        String realPath03 = servletContext.getRealPath("/WEB-INF/classes/a.txt"); // src 目录下的资源访问
        System.out.println(realPath03);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
  • ClassLoader 类加载器,只能加载 src 目录下的文件地址,无法加载 web(webapp)目录下的文件地址,具有局限性

4. 文件下载案例

  • 文件下载需求:
    1. 页面显示超链接
    2. 点击超链接后弹出下载提示框
    3. 完成图片文件下载
  • 分析:
    1. 超链接指向的资源如果能够被浏览器直接解析,则在浏览器中展示,如果不能够被解析,则弹出下载提示框,不满足案例需求
    2. 保证任何资源都必须弹出下载的提示框
    3. 使用响应头设置资源的打开方式:content-disposition:attachment;filename=xxx
  • 步骤:
    1. 定义画面,编辑超链接 href 属性,指向 Servlet 类,传递资源名称为 filename
    2. 定义 Servlet 类:
      1. 获取文件名称
      2. 使用字节输入流加载文件进内存
      3. 指定 Response 对象的响应头:content-disposition:attachment;filename=xxx
      4. 将数据写出到 Response 对象输出流
@WebServlet("/downloadServlet")
public class DownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取请求参数,文件名称
        String filename = request.getParameter("filename");
        // 2. 使用字节输入流加载文件进入内存
        // 找到文件的服务器真实路径
        ServletContext servletContext = this.getServletContext();
        String realPath = servletContext.getRealPath("/img/" + filename);
        // 使用字节流关联
        FileInputStream fileInputStream = new FileInputStream(realPath);
        // 3. 设置 Response 对象的响应头
        // 设置响应头类型:contest-type
        String mimeType = servletContext.getMimeType(filename); // 获取文件的 MIME 类型
        response.setHeader("content-type",mimeType);
        // 设置响应头打开方式:content-disposition
        response.setHeader("content-disposition","attachment;filename="+filename);
        // 4. 将输出流的数据写出到输出流中
        ServletOutputStream servletOutputStream = response.getOutputStream();
        byte[] bytes = new byte[1024 * 8];
        int length=0;
        while ((length=fileInputStream.read(bytes))!=-1){
            servletOutputStream.write(bytes,0,length);
        }
        fileInputStream.close();
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a href="/response_test/picture.jpg">图片下载</a>
<a href="/response_test/video.avi">视频下载</a>
<!-- 这种方式由于浏览器可以直接解析,图片就变为了直接预览,而视频浏览器不可以直接解析,就变为了浏览器直接下载 -->
<hr>
<a href="/response_test/downloadServlet?filename=picture.jpg">图片下载</a>
<a href="/response_test/downloadServlet?filename=video.avi">视频下载</a>
</body>
</html>
  • 中文文件问题解决:

    1. 获取客户端使用的浏览器版本信息
    2. 根据不同的版本信息,设置 filename 的编码方式不同(使用工具类)
  • 工具类:DownLoadUtils

import sun.misc.BASE64Encoder;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

public class DownLoadUtils {
    public static String getFileName(String agent,String filename) throws UnsupportedEncodingException {
        if (agent.contains("MSIE")){
            // IE 浏览器
            filename = URLEncoder.encode(filename, "utf-8");
            filename=filename.replace("+"," ");
        }else if (agent.contains("Firefox")){
            // 火狐浏览器
            BASE64Encoder base64Encoder = new BASE64Encoder();
            filename="=?utf-8?B"+base64Encoder.encode(filename.getBytes("utf-8"))+"?=";
        }else {
            // 其他浏览器
            filename=URLEncoder.encode(filename,"utf-8");
        }
        return filename;
    }
}
  • 优化:
@WebServlet("/downloadServlet")
public class DownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取请求参数,文件名称
        String filename = request.getParameter("filename");
        // 2. 使用字节输入流加载文件进入内存
        // 找到文件的服务器真实路径
        ServletContext servletContext = this.getServletContext();
        String realPath = servletContext.getRealPath("/img/" + filename);
        // 使用字节流关联
        FileInputStream fileInputStream = new FileInputStream(realPath);
        // 3. 设置 Response 对象的响应头
        // 设置响应头类型:contest-type
        String mimeType = servletContext.getMimeType(filename); // 获取文件的 MIME 类型
        response.setHeader("content-type",mimeType);
        // 解决中文文件名问题
        // 获取 user-agent 请求头
        String agent = request.getHeader("user-agent");
        // 使用工具类方法编码文件名即可
        filename=DownLoadUtils.getFileName(agent,filename);
        // 设置响应头打开方式:content-disposition
        response.setHeader("content-disposition","attachment;filename="+filename);
        // 4. 将输出流的数据写出到输出流中
        ServletOutputStream servletOutputStream = response.getOutputStream();
        byte[] bytes = new byte[1024 * 8];
        int length=0;
        while ((length=fileInputStream.read(bytes))!=-1){
            servletOutputStream.write(bytes,0,length);
        }
        fileInputStream.close();
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-08-06 11:14:36  更:2022-08-06 11:16:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 21:46:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计