HTTP
1、概念
Hyper Text Transfer Protocol超文本传输协议
传输协议
定义了客户端和服务器通信时,发送数据的格式 客户端请求,服务器响应
特点
(1)基于TCP/IP的高级协议
(2)默认端口号:80
(3)基于请求/响应模型 一次请求对应一次响应
(4)无状态:每次请求之间相互独立,不能交互数据
每张图片、css、js都是一次请求
历史版本
1.0 每次请求响应都会建立新连接
1.1 短时间内多次请求默认复用连接
2、请求消息数据格式
请求行
请求方式 请求url 请求协议/版本
GET /login.html HTTP/1.1
请求方式:HTTP协议有7中请求方式,常用2种
GET
请求参数在请求行中,在url后
请求的url长度有限制
参数可以在url中看见,不太安全
POST
请求参数在请求体中,单独的参数列表
请求的url长度没有限制
参数看不见,比较安全
请求头 浏览器告诉服务器一些信息
请求头名称:请求头值
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,/;q=0.8
Accept-Encoding: gzip, deflate
Referer:http://localhost/login.html
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Host 请求主机
User-Agent
访问服务器使用的浏览器版本 可以在服务器端获取该头信息,解决浏览器兼容性问题
不同浏览器访问同一个资源,由于版本不同,解析得到的结果也不一定一样
可以在服务器上对不同的浏览器编写不同的代码,使得不同浏览器最后解析的结果一样
if(firefox){}
if(chrome){}
Accept
浏览器可以接收的响应信息的格式 */*
表示什么格式都可以
Accept-Language
浏览器支持的语言
Accept-Encoding
浏览器支持的压缩格式
Referer
告诉服务器当前请求从哪里来
作用:
(1)防止盗取链接
盗版网站将自己网站上某部电影播放的超链接链接到优酷该部电影的
播放页面
优酷在电影播放页面使用referer来判断当前请求是否来自自己的首页,
也就是是否从首页跳转过来
if(referer.equals(“优酷首页”)){播放}
else{不播放,写一个超链接指向优酷首页}
play.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<a href="/day14/demo03referer">优酷播放</a>
</body>
</html>
@WebServlet("/demo03referer")
public class Demo03Referer extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String referer = request.getHeader("referer");
if (referer != null) {
if (referer.contains("/day14")) {
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("播放电影...");
} else {
//这里演示要再创建一个Module,再添加一个tomcat,同时启动两个项目的tomcat,从新模块的login页面链接到这里,才会显示来优酷看电影吧...
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("来优酷看电影吧...");
}
}
}
}
(2)统计
花钱在百度、腾讯、新浪进行广告推广,统计从哪个网站来的用户多
if(referer.equals(“百度”)){百度++}
if(referer.equals(“腾讯”)){腾讯++}
if(referer.equals(“新浪”)){新浪++}
Connection 连接状态 keep-alive连接还在,可以复用
Upgrade-Insecure-Requests 升级信息
请求空行
空行,分隔POST方式的请求头和请求体
请求体(正文)
封装POST请求消息的请求参数
在参数列表里
username=zhangsan
字符串格式
GET /login.html HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,/;q=0.8
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
3、Request对象
request和response对象的原理
(1)tomcat根据请求url中的资源路径,创建对应的Servlet实现类的对象
(2)tomcat创建request和response对象,request对象中封装请求消息数据
(3)tomcat将request和response对象传递给service方法,并调用service方法
(4)程序员可以通过request对象获取请求消息数据,
通过response对象设置响应消息数据
(5)服务器给浏览器做出响应之前,会从response对象中拿出程序员设置的
响应消息数据,放到响应消息数据体里,返回给浏览器
注意
(1)request和response对象是由服务器创建的,我们来使用
(2)request对象用来获取请求消息,response对象用来设置响应消息
request 获取请求消息
继承的体系结构
ServletRequest 接口
HttpServletRequest 接口,继承ServletRequest
org.apache.catalina.connector.RequestFacade
HttpServletRequest的实现类 由tomcat编写
功能
获取请求消息数据
获取请求行数据
GET /day14/requestdemo01?name=zhangsan&age=12 HTTP/1.1
方法:
获取请求方式 GET
String getMethod()
*获取虚拟目录 /day14
String getContextPath()
获取Servlet路径 /requestdemo01
String getServletPath()
获取get方式请求的参数 name=zhangsan&age=12
String getQueryString()
*获取请求URI
String getRequestURI() /day14/requestdemo01
String getRequestURL() http://localhost/day14/requestdemo01
URL 统一资源定位符
URI 统一资源标识符 URI比URL范围大
获取协议及版本 HTTP/1.1
String getProtocol()
获取客户机的IP
String getRemoteAddr()
@javax.servlet.annotation.WebServlet("/demo01request")
public class Demo01Request extends javax.servlet.http.HttpServlet {
protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
}
protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
System.out.println(request.getMethod());//GET
System.out.println(request.getContextPath());// /day14
System.out.println(request.getServletPath());// /demo01request
System.out.println(request.getQueryString());//name=zhangsan&age=20
System.out.println(request.getRequestURI());// /day14/demo01request
System.out.println(request.getRequestURL());//http://localhost:8080/day14/demo01request
System.out.println(request.getProtocol());//HTTP/1.1
System.out.println(request.getRemoteAddr());//0:0:0:0:0:0:0:1
}
}
获取请求头数据
方法:
通过请求头名称获取值
*String getHeader(String name) name不区分大小写
获取所有的请求头名称
Enumeration getHeaderNames()
Enumeration可以当作迭代器使用,可以拿出里面的数据
@WebServlet("/demo02header")
public class Demo02RequestHeader extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
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);
//host=localhost:8080
//connection=keep-alive
//upgrade-insecure-requests=1
//user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36
//accept=text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
//sec-fetch-site=none
//sec-fetch-mode=navigate
//sec-fetch-user=?1
//sec-fetch-dest=document
//accept-encoding=gzip, deflate, br
//accept-language=zh-CN,zh;q=0.9
//cookie=JSESSIONID=3745812F7557D9889635FA5688742FDD; Idea-8296e770=710ed074-221b-45d7-a3b5-9d177d11d78a
}
String agent = request.getHeader("user-agent");
if (agent.contains("Chrome")) {
System.out.println("谷歌");//谷歌
} else if(agent.contains("Firefox")) {
System.out.println("火狐");
}
}
}
获取请求体数据
只要POST请求才有请求体,在请求体中封装了POST请求的请求参数
request对象中将请求体数据封装为流
步骤:
(1)获取流对象
普通表单提交的数据是字符数据
BufferedReader getReader() 获取字符输入流,只能操作字符数据
如果要上传图片、视频就是字节数据
ServletInputStream getInputStream() 获取字节输入流,可以操作所有类型数据
(2)从流对象中取数据
@WebServlet("/demo04reader")
public class Demo04Reader extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求体数据
//1、获取流对象
request.setCharacterEncoding("utf-8");
BufferedReader br = request.getReader();
//2、从流对象中取数据
String line = null;
while((line = br.readLine()) != null) {
System.out.println(line);//username=zhangsan&password=123
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
其它功能
获取请求参数的通用方式 兼容get和post
根据参数名获取参数值
String getParameter(String name)
根据参数名获取参数值数组 多用于复选框
String[] getParameterValues(String name)
获取所有请求参数的名称
Enumeration getParameterNames()
获取所有请求参数的Map集合 键值对
Map<String,String> getParameterMap()
中文乱码问题:
get方式获取参数值不会乱码(tomcat8) post方式会乱码
虽然post使用的也是getParameter获取参数,
但是内部实现还是通过流的方式获取数据,
所有需要在获取参数前设置流的编码
request.setCharacterEncoding(“utf-8”);
@WebServlet("/demo05getparameters")
public class Demo05GetParameters extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取post方式的请求参数
//getParameter
request.setCharacterEncoding("utf-8");
String username = request.getParameter("username");
System.out.println(username);
//getParameterValues
String[] hobbies = request.getParameterValues("hobby");
for (String hobby : hobbies) {
System.out.println(hobby);
}
//getParameterNames
Enumeration<String> parameterNames = request.getParameterNames();
while(parameterNames.hasMoreElements()) {
String name = parameterNames.nextElement();
String[] value = request.getParameterValues(name);
for (String s : value) {
System.out.println(name + "=" + s);
}
}
//getParameterMap
Map<String, String[]> parameterMap = request.getParameterMap();
//map遍历有两种方法,keyset和entryset
for (String s : parameterMap.keySet()) {
String[] values = parameterMap.get(s);
for (String value : values) {
System.out.println(s + ":" + value);
}
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取get方式的请求参数 因为和doPost的代码一样,直接调用doPost方法
this.doPost(request, response);
}
}
请求转发
一种在服务器内部的资源跳转方式
步骤:
(1)通过request对象获取请求转发器对象
RequestDispatcher getRequestDispatcher(String path)
(2)使用RequestDispatcher对象进行转发
将当前Servlet对象中request和response对象传给下个Servlet对象
forward(ServletRequest request, ServletResponse response)
*特点:
(1)浏览器地址栏url不发生变化
(2)只能转发到当前服务器内部的资源中,不能访问服务器外部的资源
(3)转发是一次请求,多个资源间使用同一次请求
@WebServlet("/demo06dispatcher")
public class Demo06Dispatcher extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("demo06被访问了");
//在转发前将数据存储到request对象域中
request.setAttribute("msg", "hello");
//请求转发
request.getRequestDispatcher("/demo07dispatcherto").forward(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
@WebServlet("/demo07dispatcherto")
public class Demo07DispatcherTo extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("demo07被访问了");
//获取request域中的数据
Object msg = request.getAttribute("msg");
System.out.println(msg);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
共享数据
域对象 有作用范围的对象,可以在范围内共享数据
request对象的域 转发(一次请求)的情况下才能使用request域共享数据
一次请求的范围,一般用于请求转发的多个资源中共享数据
方法:
存储数据
void setAttribute(String name, Object obj)
通过键名获取值
Object getAttribute(String name)
通过键名移除键值对
void removeAttribute(String name)
获取ServletContext对象
ServletContext getServletContext()
4、案例:用户登录
注意
异常 java.lang.NoClassDefFoundError: org/springframework/dao/DataAccessException
在WEB-INF这个包下创建的依赖包文件夹名必须为lib。否则找不到包或类。
需求
(1)编写login.html登录页面
username password两个输入框
(2)使用Druid数据库连接池技术,操作MySQL,day14数据库中的user表
(3)使用JDBCTemplate技术封装JDBC
(4)登录成功跳转到SuccessServlet展示:登录成功!用户名,欢迎您
(5)登录失败跳转到FailServlet展示:登录失败,用户名或密码错误
分析
(1)用户填写用户名和密码,提交到LoginServlet
(2)LoginServlet中设置编码,获取用户名和密码,将用户名和密码封装为一个User对象
(3)调用UserDao类中的方法login
(4)操作数据库一般作为一个独立的动作,一般使用一个单独的类完成 UserDao
定义一个登录方法login,接收一个封装了用户名和密码的User对象,
查询后返回一个信息更加完整的User对象
(5)判断login返回的User对象是否为null
是则登录失败,跳转到FailServlet
否则登录成功,将用户信息存到request域中,转发到SuccessServlet
步骤
(1)创建项目,导入html页面,配置文件以及jar包
login.html中form表单的action路径的写法
虚拟目录+Servlet的资源路径
(2)创建数据库和表
CREATE DATABASE DAY14;
USE DAY14;
CREATE TABLE USER (
ID INT PRIMARY KEY AUTO_INCREMENT,
USERNAME VARCHAR(32) UNIQUE NOT NULL,
PASSWORD VARCHAR(32) NOT NULL
);
INSERT INTO USER(USERNAME, PASSWORD) VALUES(“张三”,“123”);
(3)创建数据库表对应的实体类User 放在domain包中
(4)编写操作数据库的代码 创建UserDao类 放在dao包中 定义login方法
(5)在编写操作数据库的代码之前,先创建一个工具包util,在其中创建一个类JDBCUtils,其使用了Druid连接池
JDBCUtils包括两个方法,获取连接池对象的方法和获取连接Connection对象的方法
(6)login方法 使用JDBCTemplate对象来执行sql语句,操作数据库
(7)编写Servlet
项目结构
–logincase
--dao
--UserDao 操作数据库中User表的类
--domain
--User 用户表的实体类
--test
--UserDaoTest
--util
--JDBCUtils JDBC工具类,使用Druid连接池
--web.servlet
--LoginServlet
--SuccessServlet
--FailServlet
User
public class User {
private int id;
private String username;
private String password;
public User() {
}
public User(int id, String username, String password) {
this.id = id;
this.username = username;
this.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;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
UserDao
public class UserDao {
//声明共用的JDBCTemplate对象 初始化需要传入数据库连接池对象
private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
//登录方法,参数User对象只有用户名和密码,返回的User对象包括用户全部数据
public User login(User loginUser) {
try {
//编写sql
String sql = "select * from user where username = ? and password = ?";
//调用query方法
User user = template.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), loginUser.getUsername(), loginUser.getPassword());
return user;
} catch (DataAccessException e) {
return null;//没有查询到数据返回null
}
}
}
UserDaoTest
public class UserDaoTest {
@Test
public void testLogin() {
UserDao dao = new UserDao();
User loginUser = new User();
loginUser.setUsername("张三");
loginUser.setPassword("111");
User user = dao.login(loginUser);
System.out.println(user);//User{id=1, username='张三', password='123'}
}
}
JDBCUtils
public class JDBCUtils {
//数据源对象
private static DataSource ds;
//根据配置文件使用Druid工厂初始化连接池对象ds
static {
try {
//加载配置文件
Properties pro = new Properties();
//使用ClassLoader加载配置文件,获取字节输入流
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
pro.load(is);
//初始化连接池对象
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接池对象
public static DataSource getDataSource() {
return ds;
}
//获取连接Connection对象
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
}
LoginServlet
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置编码
request.setCharacterEncoding("utf-8");
//获取用户名和密码
//String username = request.getParameter("username");
//String password = request.getParameter("password");
//简化,获取所有参数
Map<String, String[]> parameterMap = request.getParameterMap();
//将用户名和密码封装为User对象
User loginUser = new User();
//简化,将所有参数封装为对象
try {
//将map中的数据封装到loginUser中
BeanUtils.populate(loginUser, parameterMap);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
//loginUser.setUsername(username);
//loginUser.setPassword(password);
//调用UserDao的login方法,返回一个含有用户全部信息的User对象
UserDao dao = new UserDao();
User user = dao.login(loginUser);
if (user != null) {
request.setAttribute("user", user);
request.getRequestDispatcher("/success").forward(request, response);
} else {
request.getRequestDispatcher("/fail").forward(request, response);
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
SuccessServlet
@WebServlet("/success")
public class SuccessServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
User user = (User) request.getAttribute("user");
if (user != null) {
String username = user.getUsername();
response.getWriter().write("登录成功!" + username + ",您好");
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
FailServlet
@WebServlet("/fail")
public class FailServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("登录失败,用户名或密码错误");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
druid.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/day14
username=root
password=root
initialSize=5
maxActive=10
maxWait=3000
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="/day14/login" method="post">
<input type="text" name="username" placeholder="请输入用户名">
<input type="password" name="password" placeholder="请输入密码">
<input type="submit" value="登录">
</form>
</body>
</html>
5、BeanUtils类
简化获取用户输入和数据封装成对象的代码
简化获取用户输入 Map<String,String> getParameterMap()
简化数据封装成对象 BeanUtils
用于封装JavaBean(标准的Java类)
JavaBean(标准的Java类) 、一般放在domain包中
要求
(1)必须被public修饰
(2)必须提供空参构造器
(3)成员变量必须用private修饰
(4)提供公共的getter和setter方法
功能
封装数据
概念
成员变量
属性 getter和setter方法截取后的产物 一般与成员变量一样
如:getUsername() -> Username ->username
方法
setProperty(Object obj, String name, String value) 设置属性值
getProperty(Object obj, String name)
populate(Object obj, Map map) 将Map集合的键值对信息封装到对应的
JavaBean对象中,键就是属性名,值就是属性值
6、响应消息格式
响应消息 服务器端发送给客户端的数据
数据格式
响应行
协议/版本 响应状态码 状态码描述
HTTP/2.0 200 OK
响应状态码:服务器告诉客户端浏览器本次请求和响应的状态
分类 都是三位数
1-- 服务器接收客户端消息,但没有发完,
等待一段时间后,发送1–询问客户端是否继续发消息
2-- 成功 如200
3-- 重定向 如302(重定向)
浏览器请求A资源,A资源让浏览器去请求B资源,
返回一个302和B资源的路径,
然后浏览器请求B资源 这里浏览器请求了2次
304(访问缓存)
浏览器将不常变化的资源缓存在本地,
再次请求这个资源时服务器发现浏览器本地有该资源,
且服务器中的该资源没有变化,返回304让浏览器在本地拿资源
如果服务器中的资源发生了改变,则不会请求缓存
4-- 客户端错误
404(请求路径没有对应的资源)
405(请求方式get/post没有对应的doGet/doPost方法)
5-- 服务器错误
500(服务器内部出现异常,tomcat会将异常信息打印在页面)
响应头
常见
content-type 服务器告诉客户端本次响应体的数据格式及编码格式
content-type: text/html; charset=utf-8
content-length 本次响应体的字节数
content-length 53575
date 日期
date: Wed, 06 Jun 2018 07:06:38 GMT
content-disposition 服务器告诉客户端以什么格式打开响应体数据
in-line 默认值 在当前页面内打开
attachment;filename=*** 以附件形式打开 文件下载时使用
响应空行
响应体
服务器返回的数据
字符串格式
响应行
HTTP/2.0 200 OK
响应头
Server: fastmirror/2017-07-14_1.12.1.0
date: Wed, 06 Jun 2018 07:06:38 GMT
content-type: text/html; charset=utf-8
content-length 53575
expires: Wed, 06 Jun 2018 07:21:38 GMT
cache-control: max-age=900
vary: Accept-Encoding
content-security-policy: default-src ‘self’; script-src ‘self’ ‘unsafe-inline’
‘unsafe-eval’ http://a.alimama.cn
g.click.taobao.com platform.sina.com.cn suggestion.baidu.com
www.baidu.com hm.baidu.com nssug.baidu.com tui.cnzz.net
www.google-analytics.com *.googlesyndication.com
static.huohu123.com; img-src * data:; child-src ‘self’ *.firefoxchina.cn
*.17huohu.com; frame-src ‘self’ *.firefoxchina.cn
*.17huohu.com www.taobao.com; frame-ancestors ‘self’ *.firefoxchina.cn
tongji.baidu.com about:; style-src ‘self’ ‘unsafe-inline’; report-url /_/csp-reports
content-encoding: gzip
fw-cache-status: hit
fw-via: DISK HIT from 61.130.28.157, DISK HIT from 61.130.28.158,
DISK HIT from 124.192.143.187
x-cache: PASS from front.ssl.nginx
x-firefox: h2
7、Response
功能 设置响应消息
设置响应行
格式:HTTP/1.1 200 OK
设置状态码 setStatus(int sc)
设置响应头
setHeader(String name, String value)
设置响应体 通过流来设置
获取输出流
字符输出流 只能输出字符数据 PrintWriter getWriter()
字节输出流 可以输出任意数据 ServletOutputStream getOutputStream()
使用输出流将数据输出到客户端浏览器
案例
完成重定向
重定向:资源跳转的方式
步骤:
(1)A资源告诉浏览器重定向,状态码302
(2)A资源告诉浏览器B资源的路径,使用响应头location来完成
面试题:forward和redirect的区别
重定向特点:redirect
地址栏发生改变
可以访问其它站点(服务器)的资源
重定向是两次请求,不能使用request对象来共享数据
转发特点:forward
地址栏路径不变
只能访问当前服务器下的资源
转发是一次请求,可以使用request对象来共享数据
路径写法:
分类
相对路径 不可以确定唯一资源
不以/开头,以.开头的路径 ./表示当前路径,可以省略 …/表示上一级
规则:确定当前资源和目标资源之间的相对位置关系
绝对路径 可以确定唯一资源
http://localhost/day14/demo01redirect
可以简写为/day14/demo01redirect
以/开头的路径
规则:判断定义的路径给谁用,也就是请求从哪来
客户端 需要加虚拟目录(项目的访问路径)
如点击页面内的a标签跳转到另一页面,form表单路径和重定向
服务器 不需要加虚拟目录(项目的访问路径) 如转发
request.getRequestDispatcher("/demo07dispatcherto")
.forward();
一般加虚拟目录时使用动态获取的方式
String contextPath = request.getContextPath();
然后将虚拟目录和后面的资源路径进行拼接
jsp页面中虚拟目录也可以动态获取
jsp页面推荐使用绝对路径
@WebServlet("/demo01redirect")
public class Demo01Redirect extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("demo01...");
//访问本资源/demo01redirect,会自动跳转到资源/demo02redirect
//设置状态码为302
//response.setStatus(302);
//设置跳转目标地址,响应头location
//response.setHeader("location", "/day14/demo02redirect");
//简化方法
response.sendRedirect("/day14/demo02redirect");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
@WebServlet("/demo02redirect")
public class Demo02Redirect extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("demo02...");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
服务器输出字符数据到浏览器
获取字符输出流 PrintWriter pw = response.getWriter();
输出数据 pw.write()或pw.print()(response获取的流不需要刷新和关闭,print方法写入数据本来就不需要刷新)
中文乱码问题:乱码是由于编码和解码使用的字符集不一致
在servlet中写入/输出数据是编码,浏览器解析是解码
浏览器解码使用的字符集与当前使用的操作系统有关,中文OS就是GBK/GB2312
servlet中如果new一个流出来,则使用的字符集与OS的语言也有关,
但这里的流是由response获取的,response是由tomcat实现的,
使用的编码是拉丁编码ISO-8859-1
解决:在获取流之前设置流的默认编码为GBK response.setCharsetEncoding(“GBK”);
设置了流的编码之后,我们还应该告诉浏览器,
服务器发送的响应体数据的编码是什么,建议浏览器使用一样的编码解码。
这里使用响应头数据content-type
response.setHeader(“content-type”, “GBK”);
总结:只需要在获取流之前使用
response.setContentType(“text/html;charset=utf-8”);就可以解决乱码问题
@WebServlet("/demo03writer")
public class Demo03Writer extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置流的默认编码
//response.setCharacterEncoding("utf-8");//这一步可以省略,设置响应头的同时会设置流的编码
//设置响应头content-type,告诉浏览器,服务器使用的响应数据消息编码是什么,建议浏览器使用一样的编码格式,浏览器会读取这个响应头并进行设置
//response.setHeader("content-type", "text/html;charset=utf-8");
//简化后
response.setContentType("text/html;charset=utf-8");
//获取字符输出流 response对象在一次响应结束后会被自动销毁,所以由它获取的流不需要进行刷新和关闭
PrintWriter pw = response.getWriter();
pw.write("<h1>你好, response</h1>");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
服务器输出字节数据到浏览器
获取字节输出流 ServletOutputStream os = response.getOutputStream();
输出数据 os.write(“你好,response”.getBytes(“utf-8”));
在获取流之前同样要设置响应头content-type防止乱码
@WebServlet("/demo04output")
public class Demo04Output extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置响应头content-type
response.setContentType("text/html;charset=utf-8");
//获取字节输出流
ServletOutputStream os = response.getOutputStream();
os.write("你好,response".getBytes("utf-8"));//这里默认使用GBK解码,浏览器也默认使用GBK解码,所以中文不会乱码
//但是getBytes的参数传入utf-8,浏览器默认使用GBK就会乱码,所以上面要设置响应头,使得浏览器使用一样的格式解码
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
验证码
本质是图片
目的:防止恶意表单注册
在程序中动态生成
checkcode.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>验证码</title>
<script>
/*
点击图片或超链接,换一张图片
1、给图片和超链接绑定单击事件
2、重新设置图片的src属性值
*/
window.onload = function () {
//获取图片对象,绑定单击事件
var image = document.getElementById("checkcode");
image.onclick = function () {
//需要在资源地址后面加上永远在改变的时间戳,才能使得浏览器重新加载图片,否则浏览器会访问本地的缓存图片,不会改变图片
var date = new Date().getTime();
image.src = "/day14/demo05checkcode?" + date;//会重新加载页面,切换图片
};
//获取超链接,绑定单击事件
var a = document.getElementById("change");
a.onclick = function () {
var date = new Date().getTime();
image.src = "/day14/demo05checkcode?" + date;
}
}
</script>
</head>
<body>
<img id="checkcode" src="/day14/demo05checkcode" />
<a id="change" href="">看不清?换一张</a>
</body>
</html>
@WebServlet("/demo05checkcode")
public class Demo05CheckCode extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//创建一个对象,在内存中代表图片(验证码图片对象)
int width = 100;
int height = 50;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
//美化图片
//填充背景色
Graphics g = image.getGraphics();//获取画笔对象
g.setColor(Color.pink);//设置画笔颜色
g.fillRect(0, 0, width, height);
//画边框
g.setColor(Color.blue);
g.drawRect(0, 0, width - 1, height - 1);//边框有一个像素的宽度
//写验证码
String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
Random r = new Random();//随机角标
for (int i = 0; i < 4; i++) {
int index = r.nextInt(str.length());//随机生成字符串中的索引
char c = str.charAt(index);//随机字符
g.drawString(c + "", width / 5 * (i + 1), height / 2);
}
//画干扰线
g.setColor(Color.green);
for (int i = 0; i < 10; i++) {
int x1 = r.nextInt(width);
int x2 = r.nextInt(width);
int y1 = r.nextInt(height);
int y2 = r.nextInt(height);
g.drawLine(x1, x2, y1, y2);
}
//将图片输出到页面展示
ImageIO.write(image, "jpg", response.getOutputStream());
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
8、ServletContext对象
概念
代表整个web应用,可以和web应用的容器(服务器)来通信
获取
通过request对象获取 request.getServletContext()
通过HTTPServlet对象获取 在创建的Servlet文件中使用this.getServletContext()
@WebServlet("/demo01servletcontext")
public class Demo01ServletContext extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取ServletContext对象
ServletContext servletContext1 = request.getServletContext();
ServletContext servletContext2 = this.getServletContext();
System.out.println(servletContext1);
System.out.println(servletContext2);//两个对象的地址值一样
System.out.println(servletContext1 == servletContext2);//true
//获取MIME类型
String filename = "a.jpg";
String mimeType = servletContext1.getMimeType(filename);
System.out.println(mimeType);//image/jpeg
//在域中共享数据
servletContext1.setAttribute("msg", "haha");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
@WebServlet("/demo02servletcontext")
public class Demo02ServletContext extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();
Object msg = servletContext.getAttribute("msg");
System.out.println(msg);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
功能
获取MIME类型:互联网通信过程中定义的一种文件数据类型
格式:大类型/小类型 如html/text image/jpeg
获取:String getMimeType(String file);
实际上是通过文件的后缀名来获取的,
所有的对应关系都存储在服务器中,
ServletContext对象可以和服务器进行通信,
所以ServletContext对象可以获取到MIME类型
存储在服务器安装地址/conf/web.xml中<mime-mapping> <extension>html</extension> <mime-type>text/html</mime-type> </mime-mapping>
域对象:共享数据
setAttribute(String name, Object obj);
getAttribute(String name);
removeAttribute(String name);
ServletContext对象域范围:共享所有用户所有请求的数据
获取文件的真实(服务器)路径
我们创建的web项目会在tomcat服务器中和本地工作空间中各存在一份
浏览器访问找的是服务器中的web项目
String getRealPath(String path);
@WebServlet("/demo03realpath")
public class Demo03RealPath extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取ServletContext对象
ServletContext servletContext = this.getServletContext();
//获取服务器路径
//一般配置文件放在3个地方 src下 web下 WEB-INF下
//web目录下
String realPath1 = servletContext.getRealPath("/b.txt");
System.out.println(realPath1);//G:\java\idea\JavaWeb1\out\artifacts\day14_HTTP_war_exploded\b.txt
//WEB-INF下
String realPath2 = servletContext.getRealPath("/WEB-INF/c.txt");
System.out.println(realPath2);//G:\java\idea\JavaWeb1\out\artifacts\day14_HTTP_war_exploded\WEB-INF\c.txt
//src下
String realPath3 = servletContext.getRealPath("/WEB-INF/classes/a.txt");
System.out.println(realPath3);//G:\java\idea\JavaWeb1\out\artifacts\day14_HTTP_war_exploded\WEB-INF\classes\a.txt
//获取了配置文件路径后,就可以通过new File(path)创建文件对象,并读取到内存中了
//通过ClassLoader类加载器可以获取src下的文件的真实目录,但获取不了web下的文件的真实目录
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
9、文件下载案例
需求
(1)页面显示超链接
(2)点击超链接后弹出下载提示框
(3)完成图片文件下载
分析
(1)超链接指向的资源如果能够被浏览器解析,则在页面中显示;不能被解析,则弹出下载提示框
(2)需求是任何资源都要弹出下载提示框,使用响应头content-disposition设置资源的打开方式
content-disposition:attachment;filename=***(下载提示框显示的文件名)
实现
(1)定义一个页面,超链接指向一个servlet,页面上的所有资源都指向同一个servlet,后面连接上文件名,用来区分不同的资源,
<a href="/day14/demo04download?filename=1.jpg">下载地址</a>
<a href="/day14/demo04download?filename=1.avi">下载地址</a>
将后面连接的文件名filename传递给servlet,从而加载不同的资源进内存,将文件写入客户端
(2)定义一个servlet
获取文件名称
使用字节输入流加载文件进内存
指定response的响应头 content-disposition:attachment;filename=***
将数据写出到response的输出流
中文文件名问题:
解决:获取客户端使用的浏览器版本信息
根据不同的浏览器,响应不同的数据
download.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件下载案例</title>
</head>
<body>
<!--如果href直接指向图片的地址,点开后会将图片显示在页面上,因为浏览器可以直接解析图片-->
<a href="/day14/demo04download?filename=壁纸.jpeg">图片下载</a>
<a href="/day14/demo04download?filename=电影.mp4">视频下载</a>
</body>
</html>
DownloadUtils
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")) {
Base64.Encoder encoder = Base64.getEncoder();
filename = "=?utf-8?B?" + encoder.encodeToString(filename.getBytes("utf-8")) + "?=";
} else {
filename = URLEncoder.encode(filename, "utf-8");
}
return filename;
}
}
@WebServlet("/demo04download")
public class Demo04Download extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1、获取请求参数,即a标签指向的文件名
String filename = new String(request.getParameter("filename").getBytes("utf-8"),"utf-8");
//2、使用字节输入流将文件读入内存
//找到文件的真实(服务器)路径
ServletContext context = this.getServletContext();
String path = context.getRealPath("/WEB-INF/" + filename);
//将输入流和路径关联
FileInputStream fis = new FileInputStream(path);
//指定response的响应头content-type和content-disposition
String mimeType = context.getMimeType(filename);
response.setContentType(mimeType);
//解决中文文件名问题
//获取user-agent请求头
String agent = request.getHeader("user-agent");
//使用工具类DownloadUtils的方法编码文件名
filename = DownloadUtils.getFileName(agent, filename);
response.setHeader("content-disposition", "attachment;filename=" + filename);
//response.setHeader("Content-disposition","attachment;filename=\""+new String(filename.getBytes("GB2312"),"ISO8859_1")+"\"");
//4、将输入流中的数据写出到response的输出流
ServletOutputStream sos = response.getOutputStream();
byte[] buff = new byte[1024 * 8];
int len = 0;
while ((len = fis.read(buff)) != -1) {
sos.write(buff, 0, len);
}
fis.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}