1. response组成
- response有三部分组成:响应行、响应头、响应体
1.1 response对象
- HttpServletResponse 对象封装了向客户端发送数据、发送响应头,发送响应状态码的方法。
1.1.1 常用方法
- void setStatus(int sc); 设置此响应的状态代码;
- void setHeader(String name, String value); 用给定名称和值设置响应头
1.2 响应行(状态行)
1.2.1概述
- 响应行是http 响应内容的第一行
- 响应行分为三个部分:协议版本、响应状态码、对响应状态码的解释
- 对于Tomcat,一般数据为:HTTP/1.1 200(tomcat8.5) 或者HTTP/1.1 200 OK(tomcat7)
1.2.2 常见的响应状态码
200 OK
:请求和响应都已成功,服务器通信正常;- 302 Move temporarily :设置重定向页面跳转的动作执行;
304 Not Modified
:从浏览器缓存中读取数据,不从服务器重新获取数据;404 Not Found
:请求失败,请求所希望得到的资源未被在服务器上发现。一般是用户输错了url 导致;405 Method Not Allowed
:请求行中指定的请求方法不存在。例如,发送post 请求,服务器没有doPost方法,就会报这个错误;500 Internal Server Error
: 服务器发生了错误。一般服务器代码错误。
1.3 响应头
响应头包含但不仅限于以下几个属性:
refresh
:定时刷新跳转页面;location
:重定向操作:通常告知浏览器马上向该地址发送请求,通常和响应码302 一起使用;content-encoding
:设置当前数据的压缩格式,告知浏览器以何种压缩格式解压数据(目前浏览器只支持”gzip”格式解压);content-disposition
:通知浏览器以何种方式获取数据(直接解析数据(网页,图片文本),或者以附件方式(下载文件));content-type
:实体头部用于指示资源的MIME 类型。
注意:我们content-type 常用的设置一般都是“text/html;charset=utf-8”
其中“text/html;”——设置浏览器以文件格式解析数据;“charset=utf-8”——响应数据的编码表
1.3.1 响应头:Location
- 作用:通知浏览器要进行页面跳转的目标地址
- http 状态码302 的作用是通知浏览器进行页面跳转的动作执行,所以响应头location(跳转目标地址)和http 状态码302 (跳转动作执行)配合起来才可以完成页面跳转。
1.3.1.1 实现代码
方式一
//需求:跳转到资源CountServlet
//设置跳转目标地址
response.setHeader("location", "index.html");
//设置http状态码为302
response.setStatus(302);
优化代码(重要)
- response.sendRedirect("目标页面/目标Servlet");
发送重定向【原理就是方法一】
注意:目标页面/Servlet需要写工程名称,但是不能将工程名写死,需要用servletContext
来 获取工程名字
1.3.1.2 请求转发与请求重定向的区别及选择(重点)
请求转发的原理
请求重定向
总结(重要):
- 转发在一次请求中完成,转发地址栏不变(只有一次请求);重定向则是两次请求,重定向地址栏变化(两次请求,对应两个地址);
- 转发发生在服务器内部,只能访问服务器当前工程的内部资源,不能访问外部资源;重定向是浏览器执行跳转操作,可以访问任何资源;
- 转发前后均共享同一个request和response(由服务器收到第一次请求时创建);重定向前后有各自独立的request和response;
- 一次请求创建一个request和response,在服务器响应结束时就会销毁。不同请求不会共享request和response。
选择:
- 由于request 是请求域对象,如果页面跳转需要在一次请求域内传递数据的(即前后页面要传输数据)使用请求转发,否则建议使用重定向
注意事项:
- 重定向除了设置location实现之外,也能通过设置refresh实现。
1.3.2 响应头:Content-Type
- 格式:
Content-Type: text/html; charset=码表
解释:Content-Type是设置响应正文类型/报文类型,包含两部分
第一部分:设置响应的数据类型(Mime-Type),服务器
可以响应任何类型的资源给客户端,资源不同,Mime-Type 不同。
Mime-Type | 含义 |
---|---|
text/html | Html 代码 |
text/plain | Txt 文本文件 |
image/jpeg | Jpg 图片文件 |
Application/json | Json 数据 |
第二部分:响应字符码表:通知浏览器以什么码表解码数据
- 作用:用于服务器通知浏览器采用什么码表对服务器响应的数据进行解码。解决响应中文乱码问题
1.3.2.1 获取Mime-Type方法
- getServletContext().getMimeType(fileName)
根据Mime-Type设置文件媒体格式
- 用来告知浏览器文件的媒体格式,要根据哪种媒体格式解析资源
- response.setContentType(getServletContext().getMimeType(fileName));
1.3.2.2 服务器端输出中文数据有2 种方式
字节流输出
response.getOutputStream().write("输出字符串".getBytes());
字符流输出
response.getWriter().write("输出字符串");
1.3.2.3 解决服务器输出字符流中文乱码(重要)
- 产生乱码原因:服务器响应数据默认采用iso8859-1 码表,浏览器默认采用GBK码表。
浏览器与服务器传输中文数据的url 编码过程(了解)
- 对字节数组中的负数加256
- 将加后的字节数组转换为十六进制整型数据
- 之后在每个16 进制数据前加%
解决方案:设置响应头content-type
方案1:
//修改response默认输出中文数据码表
response.setCharacterEncoding("utf-8");
//通知浏览器以utf-8 解码
response.setHeader("content-type","text/html;charset=utf-8");
优化方案:
- response.setContentType("text/html;charset=utf8");
修改服务器响应的编码码表和浏览器解码码表【原理就是方案1】
1.3.2.4 response输出中文数据的区别
- 字节流输出中文不乱码原因是中国编码默认使用gbk,浏览器默认gbk解码;
- 但由于有可能部分浏览器不适用gbk,用字节流输出中文数据不安全,故建议输出中文数据使用字符流。
1.3.3 响应头:refresh
-
格式:
Refresh: 定时时间;url=跳转网址
- 定时时间:以秒为单位,不用引号;
- 跳转网址:整个网页的完整地址,不用引号;
-
作用:设置浏览器定时刷新页面或定时跳转到指定的资源;
1.3.3.1 设置方式
/*需求:3 秒以后跳转到demo.html*/
response.setHeader("refresh","3;url="+getServletContext().getContextPath()+"/demo.html");
- 注意,工程名不能写死
1.3.3.2 刷新和跳转案例
@WebServlet(urlPatterns = "/demo01")
public class Notes01Servlet extends HttpServlet {
/*
需求:倒计时5秒后跳转到新的页面succeed.html。
思路:首先直接进入demo01(Servlet),然后进入到倒计时的wait.html,5秒后跳转到新的页面
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//5秒后跳转到新的页面中,使用refresh重定向跳转
//这里修改第一次进入demo01时创建的response对象refresh属性实现跳转功能
response.setHeader("refresh", "2;url=succeed.html");
//1.这里必须使用请求转发页面跳转
//2.如果这里也使用重定向,那么重定向后上面修改refresh的response已经被销毁而创建了新的response,自然无法跳转
//3.上一行代码和这行代码的顺序不能调,因为本行代码已经将request和response转发了,那么就没有response能调用上行代码
request.getRequestDispatcher("/wait.html").forward(request, response);
/*
response.sendRedirect("/day29/wait.html"); //跳转后此时传入的response已经被销毁
response.setHeader("refresh", "2;url=succeed.html"); //这里的response仍是传入的response,已被销毁,该行代码无效,也不会修改到跳转后的refresh实现再度跳转
//上述两行代码也说明,即使是先重定向到wait.html,再refresh也无法跳转
*/
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//*如果post方式的执行内容与get不同,则删掉下面代码重写即可
doGet(request, response);
}
}
1.3.4 响应头:Content-Disposition
-
格式:
Content-Disposition: attachment; filename=下载文件名
- attachment,通知浏览器不要显示数据要以附件形式下载
- 下载文件名:下载的文件名字,包含后缀名
-
作用:默认浏览器查看数据是直接显示数据,而Content-Disposition可以通知浏览器不要直接显示数据,以附件形式下载数据
1.3.4.1 设置方式
response.setHeader("content-disposition","attachment;filename="+fileName);
1.3.4.2 注意事项
- 如果响应头传递中文默认不会进行url编码,故无法传递中文,需要手动进行url编码;
- 请求体和请求行、响应体在传递中文数据会自动进行url编码;
- 浏览器和服务器在传输中文数据时,中间过程还要进行url编码,才能执行中文数据传输。
手动进行URL编码的代码(不需强记,直接复制即可)
String fileName = "文件名.后缀名";
String ua = request.getHeader("User-Agent");
boolean IE_LT11 = ua.contains("MSIE"); // IE11 以下版本
boolean IE11 = ua.contains("rv:11.0) like Gecko"); // IE11
boolean Edge = ua.contains("Edge"); // win10 自带的Edge 浏览器
// 如果是微软的浏览器,直接进行UTF-8 编码
if (IE_LT11 || IE11 || Edge) {
fileName = URLEncoder.encode(fileName, "UTF-8");
// java 的编码方式和浏览器有略微的不同:对于空格,java 编码后的结果是加号,
// 而浏览器的编码结果是%20,因此将+替换成%20, 这样浏览器才能正确解析空格
fileName = fileName.replace("+", "%20");
}
// 标准浏览器使用Base64 编码
else {
Base64.Encoder encoder = Base64.getEncoder();
fileName = encoder.encodeToString(fileName.getBytes("utf-8"));
// =?utf-8?B?文件名?= 是告诉浏览器以Base64 进行解码
fileName = "=?utf-8?B?" + fileName + "?=";
}
1.3.4.3 扩展案例:以附件形式下载不同类型文件(类似于工具类)
前期准备:download页面由前端编写,这里只是简单的页面。
另外还要注意,本地可访问资源要放在IDEA的web文件夹内
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>下载页面</title>
<style type="text/css"></style>
<script type="text/javascript">
/*
除了IE浏览器(IE11及以下)默认不对路径中的中文字符进行编码以外,其余浏览器都会支持中文路径编码,
所以为了发送请求时解决中文乱码的问题,这里要对浏览器进行判断。如果是ie11及以下的要手动url编码
*/
function isIE() {
//获取当前浏览器相关信息
var explorer = window.navigator.userAgent.toLowerCase();
//判断是否是ie浏览器
if (explorer.indexOf("msie") >= 0 || explorer.indexOf("rv:11.0) like gecko") >= 0) {
return true;
} else {
false;
}
}
window.onload = function () {
if (isIE()) {
//在是IE浏览器的情况下,对中文请求参数编码
var str = document.getElementById("sn").href;
var str = encodeURI(str);
document.getElementById("sn").href = str;
}
};
</script>
</head>
<body>
<a href="download?fileName=1.txt">1.txt</a> <!--路径默认使用get方式提交,用?串联相当于提交该文件-->
<a href="download?fileName=Impactor_0.9.34.zip">Impactor_0.9.34.zip</a>
<a href="download?fileName=hq.jpg">hq.jpg</a>
<a id="sn" href="download?fileName=烧酒.png">烧酒.png</a>
</body>
</html>
后端代码
注意:如果文件名有中文和特殊字符,必须要先解决request的中文乱码问题,否则获取的文件名乱码,导致对应的资源无法下载
@WebServlet(urlPatterns = "/download")
public class Notes02DownloadServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//解决request中文乱码问题
request.setCharacterEncoding("utf-8");
/*1.获得下载的文件名字*/
String fileName = request.getParameter("fileName");
System.out.println(fileName);
/*2.获取资源的真实路径*/
ServletContext servletContext = getServletContext();
String realPath = servletContext.getRealPath("/download");
//根据真实路径获得file对象,用来创建输入流,读取文件
File file = new File(realPath,fileName);
/*3.根据Mime-Type设置文件媒体格式,告知浏览器传输的文件是什么格式*/
response.setContentType(servletContext.getMimeType(fileName));
/*4.解决中文文件名乱码的问题,复制黏贴即可*/
String ua = request.getHeader("User-Agent");
boolean IE_LT11 = ua.contains("MSIE"); // IE11以下版本
boolean IE11 = ua.contains("rv:11.0) like Gecko"); // IE11
boolean Edge = ua.contains("Edge"); // win10自带的Edge浏览器
// 如果是微软的浏览器,直接进行UTF-8编码
if (IE_LT11 || IE11 || Edge) {
fileName = URLEncoder.encode(fileName, "UTF-8");
// java的编码方式和浏览器有略微的不同:对于空格,java编码后的结果是加号,
// 而浏览器的编码结果是%20,因此将+替换成%20, 这样浏览器才能正确解析空格
fileName = fileName.replace("+", "%20");
}
// 标准浏览器使用Base64编码
else {
Base64.Encoder encoder = Base64.getEncoder();
fileName = encoder.encodeToString(fileName.getBytes("utf-8"));
// =?utf-8?B?文件名?= 是告诉浏览器以Base64进行解码
fileName = "=?utf-8?B?" + fileName + "?=";
}
/*5.提示浏览器以下载的形式获取服务器资源*/
response.setHeader("content-disposition","attachment;filename=" + fileName);
/*6.使用流输出数据*/
FileInputStream fis = new FileInputStream(file);
ServletOutputStream sos = response.getOutputStream();
byte[] bytes = new byte[1024];
int len = -1;
while ((len = fis.read(bytes))!= -1) {
sos.write(bytes,0,len);
}
/*7.记得关闭输入流释放资源*/
fis.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//*如果post方式的执行内容与get不同,则删掉下面代码重写即可
doGet(request, response);
}
}
1.4 响应体
- 浏览器将服务器输出的数据直接显示给用户
- 作用:
- 输出字符数据、字节数据
- 输出资源文件数据(资源图片)
- 输出缓存(内存中)图片(资源没有对应的物理资源,不是真实资源)–验证码
1.4.1 验证码案例(类似工具类)
实现验证码功能
@WebServlet(urlPatterns = "/CheckCodeServlet")
public class Notes03CheckCodeServlet extends HttpServlet {
//获取随机数
private Random random = new Random();
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//创建一个缓存图片
//BufferedImage(int width,int height,int imageType)
BufferedImage image = new BufferedImage(90,30,BufferedImage.TYPE_INT_RGB);
//获取画笔
Graphics g = image.getGraphics();
//设置画笔的颜色
g.setColor(Color.white);
//设置画布范围
g.fillRect(0,0,90,30);
//画干扰线
//g.drawLine(x1,y1,x2,y2);
for (int i=1;i<=4;i++){
//设置每条线的随机颜色
g.setColor(getColor());
int x1=random.nextInt(91);
int y1=random.nextInt(31);
int x2=random.nextInt(91);
int y2=random.nextInt(31);
g.drawLine(x1,y1,x2,y2);
}
//画验证码
String data = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890小天使";
//随机选取4个
StringBuilder checkCode = new StringBuilder();
for(int i=0;i<4;i++){
int index = random.nextInt(data.length());
char item = data.charAt(index);
checkCode.append(item);
}
System.out.println("验证码:"+checkCode.toString());
//将验证码画到图片上
//g.drawString(str,x,y);
int i=0;
for(char item : checkCode.toString().toCharArray()){
//设置每个字符随机颜色
g.setColor(getColor());
//将每个字符画到图片上
g.drawString(item+"",10+(i*20),15);
i++;
}
//将内存图片输出到浏览器中
ImageIO.write(image,"png",response.getOutputStream());
}
/*获取随机颜色*/
public Color getColor(){
int r = random.nextInt(256);
int g = random.nextInt(256);
int b = random.nextInt(256);
//Color(int r, int g, int b)
return new Color(r,g,b);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//*如果post方式的执行内容与get不同,则删掉下面代码重写即可
doGet(request, response);
}
}
2. ServletContext
2.1 ServletContext概述
1、ServletContext 是一个容器(**域对象**)可以存储**键值对数据(String key,Object value)**,保存在ServletContext 中的数据不仅可以**提供给所有的servlet 使用**,而且可以在**整个项目范围内使用**(后期的过滤器、监听器也可以使用ServletContext)
2、服务器会为每一个工程创建一个ServletContext 对象,这个对象就是ServletContext 对象。这个对象**全局唯一(一个工程仅一个)**,而且**工程内部的所有servlet 都共享**这个对象。所以叫**全局应用程序共享对象**。
3、服务器启动就创建ServletContext,服务器关闭了才销毁ServletContext
2.2 ServletContext的作用
- 可以读取资源在当前项目中的文件位置;
- 可以作为域对象在项目全局范围内提供共享数据。
2.3 使用ServletContext读取资源在当前项目中的文件位置
-
方法:
- String getServletContext().getRealPath(path); 根据相对路径获取项目部署位置上资源的绝对路径
2.3.1 示例代码
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
//servletContext可以获取当前项目下的资源文件
//可以根据相对路径获取在服务器上的绝对路径
String realPath = getServletContext().getRealPath("img/2.jpg");
FileInputStream fileInputStream = new FileInputStream(realPath);
int length = -1;
byte[] bytes = new byte[1024];
while((length = fileInputStream.read(bytes))!=-1){
//将读取到数据的字节数组输出到浏览器
response.getOutputStream().write(bytes, 0, length);
}
//关闭输入流
fileInputStream.close();
}
2.4 使用ServletContext作为域对象共享数据
2.4.1 常用API
- GenericServlet:
- ServletContext getServletContext();
获取ServletContext 对象
该方法直接调用即可
- ServletContext:
- void setAttribute(String name, Object object);
往servletcontext 容器中存入数据,name 为数据名称,object 为数据的值
- Object getAttribute(String name);
从ServletContext 中获取指定数据名称的数据
- void removeAttribute(String name);
从ServletContext 中移除指定数据名称的数据
2.4.2 request 域与servletContext 域的区别
- request 请求域对象,数据有效范围一次请求内,用于转发传递数据;
- servletContext 全局域(上下文域)对象,数据有效范围是整个应用程序内,所有资源共享.用于存储全局共享的数据。