【HTTP 协议基础】
一、 HTTP 协议概述
概念:Hyper Text Transfer Protocol 超文本传输协议 传输协议:定义了客户端和服务器端通信时,发送数据的格式
1. 特点
- 基于 TCP/IP 的高级协议
- 默认端口号:80
- 基于请求/响应模型:一次请求对应一次响应
- 无状态的:每次请求之间相互独立,不能交互数据
2. 历史版本
区别(最大区分点):
- HTTP 1.0 版本:每一次请求响应都会建立新的连接
- HTTP 1.1 版本:可以复用连接(当每一个响应过后,会等待一段时间,如果仍有数据响应,继续复用上一个请求得到的连接,直到响应超时,没有响应)
二、 请求消息
客户端发送给服务器端的数据
1. 请求消息数据格式
(1) 请求行
- 请求方式:
- HTTP 协议中有7中请求方式,常用的有2种
- GET:
- 请求参数在请求行中,在 URL 后
- 请求的 URL 长度有限制
- 不太安全
- POST:
- 请求参数在请求体中
- 请求的 URL 长度没有限制
- 相对安全
请求方式 | 请求 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 对象原理
- Request 对象和 Response 对象是由服务器创建的,我们来使用它们
- Request 对象是用来获取请求消息的, Response 对象是用来设置响应消息的
工作原理过程: 客户端浏览器通过: http://localhost:端口号/虚拟目录/资源路径 向服务器请求发出请求消息:之后
- Tomcat 服务器会根据请求 URL 中的资源路径,(以创建 ServletTest 类并实现 Servlet 接口重写方法的实现类为例)创建对应的 ServletTest 对象
- Tomcat 服务器,会创建 Request 对象和 Response 对象,Request 对象中封装请求消息数据
- Tomcat 服务器将 Request 对象和 Response 对象传递给 service 方法,并且调用 service 方法
在创建的 ServletTest 类并实现 Servlet 接口重写方法的实现类中:之后
- 程序员可以通过 Request 对象获取请求消息数据,通过 Response 对象设置响应消息数据
之后服务器向客户端浏览器响应,进行响应消息:之后
- 服务器在浏览器做出响应之前,会从 Response 对象中拿程序员设置的响应消息数据
3. Request 对象继承体系结构
继承自
继承自
org.apache.catalinna.connection.RequestFacade 由 Tomcat 创建的类
HttpServletRequest 接口
ServletRequest 接口
4. Request 对象的功能
-
获取请求消息数据
-
其他功能
(1) 获取请求消息数据
① 获取请求行数据
- 请求格式:GET /test_tomcat/test01?name=zhangsan HTTP/1.1
- 方法:
- 获取请求方式:GET :
String getMethod() - 获取虚拟目录:/test_tomcat :
String getContextPath() - 获取 Servlet 路径:/test01 :
String getServletPath() - 获取 get 方式请求参数:name=zhangsan :
String getQueryString() - 获取请求 URI:/test_tomcat/test01 :
String getRequestURI() - 获取请求 URL:http://localhost/test_tomcat/test01 :
StringBuffer getRequestURL() - 获取协议以版本:HTTP/1.1 :
String getProtocol() - 获取客户据的 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 {
String method = request.getMethod();
System.out.println(method);
String contextPath = request.getContextPath();
System.out.println(contextPath);
String servletPath = request.getServletPath();
System.out.println(servletPath);
String queryString = request.getQueryString();
System.out.println(queryString);
String requestURI = request.getRequestURI();
System.out.println(requestURI);
StringBuffer requestURL = request.getRequestURL();
System.out.println(requestURL);
String protocol = request.getProtocol();
System.out.println(protocol);
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 {
}
}
@WebServlet(name = "ServletTest09", value = "/ServletTest09")
public class ServletTest09 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String agent = request.getHeader("User-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 {
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 请求的请求参数
- 步骤:
- 获取流对象
BufferReader getReader() :获取字符输入流,只能操作字符数据ServletInputStream getInputStream() :获取字节输入流,可以操作所有数据类型 - 再从流对象中拿数据
代码演示:
@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 请求方式都可以使用下列方法来获取请求参数
String getParameter(String name) :根据参数名称获取参数值(username=zhangsan&password=123456)String[] getParameterValues(String name) :根据参数名称获取参数值的数组(hobby=xxxx&hobby=game)Enumeration<String> getParameterNames() :获取所有请求的参数名称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 {
String username = request.getParameter("username");
System.out.println("get");
System.out.println(username);
}
@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="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<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 {
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);
}
}
③ 请求转发
一种在服务器内部的资源跳转方式
- 步骤:
- 通过 Request 对象获取请求转发器对象:
RequestDispatcher getRequestDispatcher(String path) - 使用 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 被访问了!");
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 两个类的效果
- 特点:
- 浏览器地址栏路径不发生变化
- 只能转发到当前服务器内部资源中
- 转发是一次请求
④ 共享数据
域对象:一个有作用范围的对象,可以在范围内共享数据 Request 域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据
方法:
void setAttribute(String name,Object obj) :存储数据Object getAttribute(String name) :通过键获取值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.setAttribute("share","Hello!");
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) 用户登录案例需求
- 编写
login.html 登陆界面:包含 username&password 两个输入框 - 使用 Druid 数据库连接池技术,操作 MySQL 数据库,test 数据库中的 user 表
- 使用 JDBCTemplate 技术封装 JDBC
- 登陆成功跳转到 SuccessServlet 页面展示:登陆成功!用户名,欢迎您!
- 登陆失败跳转到 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) 分析
- 用户登录界面:
login.html LoginServlet 类
- 设置编码
- 获取 username 和 password
- 将 username 和 password 封装为一个 User 对象
- 调用
UserDao 类的 login 方法查询,获取返回值 User 对象 - 判断 user 是否为 null:① 登陆失败 →
FailServlet 类 ② 登陆成功 → 将用户信息存到 Request 域,转发到 SuccessServlet 类 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');
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;
}
}
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 {
private static DataSource dataSource;
static {
try {
Properties properties = new Properties();
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;
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
public class UserDao {
private JdbcTemplate jdbcTemplate=new JdbcTemplate(JDBCUtils.getDataSource());
public User login(User loginUser){
try {
String sql="select * from user where username = ? and password = ?";
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);
}
@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 loginUser = new User();
loginUser.setUsername(username);
loginUser.setPassword(password);
UserDao dao = new UserDao();
User user = dao.login(loginUser);
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);
}
}
@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);
}
}
@WebServlet("/successServlet")
public class SuccessServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
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 类,用于封装数据 要求:
- 类必须被 public 修饰
- 类必须提供空参的构造器
- 成员变量必须使用 private 修饰
- 提供公共的 setter 和 getter 方法
-
方法:
setProperty() getProperty() populate(Object obj,Map map) :将 Map 集合的键值对信息,封装到对应的 JavaBean 对象中 -
使用 Maven 框架导入 jar 包
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
@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 loginUser = new User();
try {
BeanUtils.populate(loginUser,map);
}catch (IllegalAccessException e){
e.printStackTrace();
}catch (InvocationTargetException e){
e.printStackTrace();
}
UserDao dao = new UserDao();
User user = dao.login(loginUser);
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位数字
- 分类:
- 1xx :服务器接收客户端消息,但没有接收完成,等待一段时间后,发送 1xx 状态码
- 2xx :成功,代表数字:200
- 3xx :重定向,代表数字:302(重定向)304(访问缓存)
- 4xx :客户端错误,代表数字:404(请求路径没有对应的资源)405(请求方式没有对应的
doXxx() 方法) - 5xx :服务器端错误,代表数字:500(服务器内部出现异常)
重定向解释:假设浏览器向服务器发送请求,服务器中有 A B C 三类资源,为做出响应服务器先找到 A 类资源,但是 A 类资源无法解决问题,随后 A 类资源认为 C 类资源可以解决问题,响应浏览器响应状态码:302 ,并提出去找 C 类资源并且附有 C 类的资源路径,最后 C 类资源做出响应,这里 C 类资源是否解决问题,仍不确定。(相当于一次请求,请求到了两次资源)
访问缓存解释:假设服务器中有一张图片,浏览器请求这张图片,请求完成后,服务器将图片响应给浏览器,浏览器进行图片展示。但是由于该图片一般不会发生变化,图片数据已经固定,这时浏览器会自动将图片的数据缓存到本地,浏览器下一次再来请求相同的图片数据资源时,服务器发现图片并没有发生变化,而且浏览器本地缓存中有该图片,服务器告诉浏览器去访问本地的图片数据缓存,服务器响应状态码:304,去访问缓存(因为服务器与浏览器数据交互,发送图片方式为二进制数据,占用通信时间长),提升了整个请求和响应的速度,减轻了服务器的压力。如果图片更改,服务器会自动刷新图片,正常响应浏览器。
(2) 响应头
格式:
常见的响应头:
- Content-Type :服务器告诉客户端本次响应体数据格式以及编码格式
- 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 对象的功能
- 设置响应消息
- 设置响应行
- 格式:HTTP/1.1 200 ok
- 设置状态码:
setStatus(int sc) - 设置响应头 :
setHeader(String name,String value) - 设置响应体:
- 使用步骤:
- 获取输出流:
- 字符输出流:
PrintWriter getWriter() - 字节输出流:
ServletOutputStream getOutputStream() - 使用输出流,将数据输出到客户端浏览器
3. 案例:重定向
重定向:资源跳转的方式 重定向解释:假设浏览器向服务器发送请求,服务器中有 A B C 三类资源,为做出响应服务器先找到 A 类资源,但是 A 类资源无法解决问题,随后 A 类资源认为 C 类资源可以解决问题,响应浏览器响应状态码:302 ,并提出去找 C 类资源并且附有 C 类的资源路径,最后 C 类资源做出响应,这里 C 类资源是否解决问题,仍不确定。(相当于一次请求,请求到了两次资源)
(1) 代码实现
实现步骤:
- 服务器告诉浏览器重定向,状态码302
- 告诉浏览器 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 被访问了!");
response.setStatus(302);
response.setHeader("location","/response_test/responseTest02");
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)的特点:
- 地址栏发生变化
- 重定向可以访问其他站点(服务器)的资源
- 重定向是两次请求,不能使用 Request 对象来共享数据
- 转发(forward)的特点:
- 转发地址栏路径不变
- 转发只能访问当前服务器下的资源
- 转发是一次请求,可以使用 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 {
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>
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 应用,可以和程序的容器(服务器)进行通信
- 获取:
- 通过 Request 对象获取:
request.getServletContext(); - 通过 HttpServlet 类获取:
this.getServletContext(); - 功能:
- 获取 MIME 类型
- MIME 类型:在互联网通信过程中定义的一种文件数据类型
- 本身属于域对象:共享数据
- 获取文件的真实(服务器)路径
2. ServletContext 对象的获取
@WebServlet("/servletContextTest01")
public class ServletContextTest01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext servletContext01 = request.getServletContext();
ServletContext servletContext02 = this.getServletContext();
System.out.println(servletContext01);
System.out.println(servletContext02);
System.out.println(servletContext01==servletContext02);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
3. ServletContext 对象的功能
(1) 获取 MIME 类型
MIME 类型:在互联网通信过程中定义的一种文件数据类型
- 格式:大类型/小类型(例如:
text/html 、image/jpeg ) - 获取:
String getMimeType(String file)
@WebServlet("/servletContextTest02")
public class ServletContextTest02 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();
String fileName="a.jpg";
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) 域对象:共享数据
setAttribute(String name,Object value) getAttribute(String name) removeAttribute(String name)
- ServletContext 的域对象范围:所有用户所有请求的数据(生命周期很长,从服务器的开启到关闭一直存在,在其中存储的数据过多会导致内存压力过大,谨慎使用)
@WebServlet("/servletContextTest03")
public class ServletContextTest03 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
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 {
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");
System.out.println(realPath01);
File file = new File(realPath01);
String realPath02 = servletContext.getRealPath("/WEB-INF/web.xml");
System.out.println(realPath02);
String realPath03 = servletContext.getRealPath("/WEB-INF/classes/a.txt");
System.out.println(realPath03);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
ClassLoader 类加载器,只能加载 src 目录下的文件地址,无法加载 web(webapp)目录下的文件地址,具有局限性
4. 文件下载案例
- 文件下载需求:
- 页面显示超链接
- 点击超链接后弹出下载提示框
- 完成图片文件下载
- 分析:
- 超链接指向的资源如果能够被浏览器直接解析,则在浏览器中展示,如果不能够被解析,则弹出下载提示框,不满足案例需求
- 保证任何资源都必须弹出下载的提示框
- 使用响应头设置资源的打开方式:
content-disposition:attachment;filename=xxx - 步骤:
- 定义画面,编辑超链接 href 属性,指向 Servlet 类,传递资源名称为 filename
- 定义 Servlet 类:
- 获取文件名称
- 使用字节输入流加载文件进内存
- 指定 Response 对象的响应头:
content-disposition:attachment;filename=xxx - 将数据写出到 Response 对象输出流
@WebServlet("/downloadServlet")
public class DownloadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String filename = request.getParameter("filename");
ServletContext servletContext = this.getServletContext();
String realPath = servletContext.getRealPath("/img/" + filename);
FileInputStream fileInputStream = new FileInputStream(realPath);
String mimeType = servletContext.getMimeType(filename);
response.setHeader("content-type",mimeType);
response.setHeader("content-disposition","attachment;filename="+filename);
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>
-
中文文件问题解决:
- 获取客户端使用的浏览器版本信息
- 根据不同的版本信息,设置 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")){
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 {
String filename = request.getParameter("filename");
ServletContext servletContext = this.getServletContext();
String realPath = servletContext.getRealPath("/img/" + filename);
FileInputStream fileInputStream = new FileInputStream(realPath);
String mimeType = servletContext.getMimeType(filename);
response.setHeader("content-type",mimeType);
String agent = request.getHeader("user-agent");
filename=DownLoadUtils.getFileName(agent,filename);
response.setHeader("content-disposition","attachment;filename="+filename);
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);
}
}
|