Response
概述
Servlet 最主要的作用就是处理客户端请求,并向客户端做出响应。为此,针对 Servlet 的每 次请求,Web 服务器在调用 service() 之前,都会创建两个对象,分别是 HttpServletRequest 和 HttpServletResponse,其中,
HttpServletRequest 用于封装 HTTP 请求消息,简称 request 对象。
HttpServletResponse 用于封装 HTTP 响应消息,简称 response 对象。
需要注意的是,在 Web 服务器运行阶段,每个 Servlet 都只会创建一个实例对象。然而, 每次 HTTP 请求,Web 服务器都会调用所请求 Servlet 实例的 service(HttpServletRequest request,HttpServletResponseresponse)方法,重新创建一个 request 对象和一个 response 对 象。
HttpServletResponse 对象
在 ServletAPI 中,定义了一个 HttpServletResponse 接口,它继承自 ServletResponse,专门 用来封装 HTTP 响应消息。
HTTP 响应消息分为三部分:
响应状态行
响应消息头
响应消息体
在 HttpservletResponse 接口中定义了向客户端发送响应状态码、响应消息头、响应消息体 的方法
发送状态码的方法
setStatus(intstatus)方法
该方法用于设置 HTTP 响应消息的状态码
由于响应状态行中的状态描述信息直接与状态码相关,而 HTTP 版本由服务器确定,因此, 只要通过 setStatus(intstatus) 方法设置了状态码,即可实现状态行的发送。
需要注意的是,正常情况下,Web 服务器会默认产生一个状态码为 200 的状态行
setError(intsc)方法
该方法用于发送表示错误信息的状态码,例如 404,状态码表示找不到客户端请求的资源, 在 response 对象中
发送响应消息头的相关方法
当 Servlet 向客户端回送响应消息时,由于 HTTP 的响应头字段有很多种,为此,在 HttpServletResponse 接口中,定义了一系列设置 HTTP 响应头的方法字段的方法。
一个 Key 对应一个 value:
setHeader(String name, String value):用于 value 是字符串的
setIntHeader(String name, int value):用于 value 是整数的
例如:response.setIntHeader(“Content-Length”,888); 表示响应了 888 字节
setHeader(“aa”,“bb”);
setHeader(“aa”,“cc”);
结果:aa:cc 相同的键后赋值的会覆盖先赋值的
一个 Key 对应多个 value:
addHeader(String name, String value):用于 value 是字符串的
addIntHeader(String name, int value):用于 value 是整数的
例如:
addHeader(“aa”,“bb”);
addHeader(“aa”,“cc”);
结果:aa:bb,cc 这个表示添加值
setContentLength(intlen) 设置响应消息的实体内容大小,单位为字节
setContentType(Stringtype) 设置 servlet 输出内容的 MIME 类型,对于 HTTP 来说,就是设置 Content-Type 响应头字 段的值
发送响应消息相关方法
由于在 HTTP 响应消息中,大量的数据都是通过响应消息体传递的,因此 ServletResponse 遵循以 IO 流传递大量数据的设计理念,在发送响应消息体时,定义了两个与输出流相关的 方法
- getOutputStream()方法
该方法所获取的字节输出流对象为 ServletOutputStream 类型。由于 ServletOut putStream 是 OutputStream 的子类,它可以直接输出字节数组中的二进制数据。因此,要 想输出二进制格式的响应正文,就需要使用 getOutputStream() 方法
- getWriter()方法
该方法所获取的字符输出流对象为 PrintWriter 类型。由于 PrintWriter 类型的对象可 以直接输出字符文本内容,因此,要想输出内容全为字符文本的网页文档,需要使用 getWriter()方法
代码示例
public class PrintServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
//方式一:专门输出二进制类型的数据
// String data = "woxihuanni";
// ServletOutputStream out = response.getOutputStream();
// out.write(data.getBytes());
//方式二:专门输出字符文本的类型数据
//解决中文乱码问题
String data1 = "我喜欢你";
PrintWriter writer = response.getWriter();
writer.write(data1);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
注意:对象的getOutputStream() 和 getWriter() 方法都可以输出数据, 但是两个方法是互斥的。
乱码解决
只需要保证响应数据的编码和浏览器解析的编码一致即可 response 缓冲区的默认编码是 iso8859-1,此码表中没有中文,可以通过 response 的 setCharacterEncoding(Stringcharset) 设置 response 的编码 将 response 缓冲区的编码设置成 UTF-8,但浏览器的默认编码是本地系统的编码,中文系统 默认编码是 GBK,我们可以手动修改浏览器的编码是 UTF-8。 还可以在代码中指定浏览器解析页面的编码方式,通过 response 的 setContentType(String type)方法指定页面解析时的编码是 UTF-8上面的代码不仅可以指定浏览器解析页面时的编码,同时也内含 setCharacterEncoding 的功 能 开发中只要编写 response.setContentType(“text/html;charset=UTF-8”);就可以解决页面输出中 文乱码问题。response.setContentType(“text/html;charset=UTF-8”);
代码示例
response.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
文件下载
文件下载的实质就是文件拷贝,将文件从服务器端拷贝到浏览器端。 所以文件需要使用到 IO 流将服务器端的文件使用 InputStream 读取到,再使用 ServletOutputStream 写到 response 缓冲区中文件下载的基本方式
示例代码
<body>
<h1>使用a标签直接指向服务器上的资源</h1>
<a href="/ResponseDemo/download/1.jfif">1.jfif</a><br>
<a href="/ResponseDemo/download/2.txt">2.txt</a><br>
<a href="/ResponseDemo/download/3.pdf">3.pdf</a><br>
<a href="/ResponseDemo/download/4.zip">4.zip</a>
</body>
- 文件存放结构图 ![](https://i.imgur.com/EC93fEZ.png)
访问 Servlet 直接进行下载
要实现文件下载单独用 a 标签是不那么完美的。那么,究竟该怎样实现文件下载功能呢? 此时,就需要使用 Servlet 编码读取要下载的文件,然后写到响应流中以达到用户下载文件 的目的。 ServletOutputStream 抽象类。利用这个输出流可以将数据返回到客户端 想浏览器直接输出字节流,浏览器会根据对应的格式进行解析 。示例代码
public class DownloadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//使用respnse获得字节输出流
ServletOutputStream out = response.getOutputStream();
//获得服务器上的图片,此时浏览器会直接解析并显示
String realpath = this.getServletContext().getRealPath("download/2.txt");
InputStream in = new FileInputStream(realpath);
int ch =0;
byte[] b = new byte[1024];
while((ch = in.read(b))!=-1){
out.write(b,0,ch);
}
in.close();
out.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
使用上面的代码还是有问题的,浏览器如果能够解析该文件,那么会直接将文件进行解析, 并显示,并且不能解析的文件下载后,而且文件名也不对。需要进行如下操作 - 告诉客户端(浏览器)以下载的方式打开文件
通过文件的 MIME 类型去区分类型(tomcat/conf/web.xml)
文件的名字
// 设置文件的类型
response.setContentType(this.getServletContext().getMimeType(“download/ b.png”));
//告诉客户端(浏览器)以下载的方式打开文件,设置 Context-Dispostion 头,并设置 文件名字
response.setHeader(“Content-Disposition”, “attachment;filename=b.png”);
设置好两个头后,就可以实现文件下载的功能,并且文件也已经有名字了
下载指定文件
编写新的 Servlet,访问 Servlet 时传递指定的文件名字,下载指定的文件
- HTML 代码如下(添加多一个中文名字的文件)
给Servlet传递参数只需要在URL后面加?并且拼接文件名字参数即可(键值对新式)
示例代码
<body>
<h1>使用a标签直接指向服务器上的资源</h1>
<a href="/ResponseDemo/download2servlet?filename=1.jfif">1.jfif</a><br>
<a href="/ResponseDemo/download2servlet?filename=2.txt">2.txt</a><br>
<a href="/ResponseDemo/download2servlet?filename=3.pdf">3.pdf</a><br>
<a href="/ResponseDemo/download2servlet?filename=4.zip">4.zip</a><br>
</body>
- Servlet 代码,主要改动的代码为:
获取传递过来的文件名,之后以该文件名获取 MIME 类型和读取文件
主要代码
public class Download2Servlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String filepath = "download/"+request.getParameter("filename");
response.setContentType(this.getServletContext().getMimeType("download/"+filepath));
response.setHeader("Content-Disposition", "attachment;filename="+filepath);
ServletOutputStream out = response.getOutputStream();
String file = this.getServletContext().getRealPath(filepath);
InputStream in = new FileInputStream(file);
int ch = 0;
byte[] b = new byte[1024];
while((ch = in.read(b)) !=-1){
out.write(b, 0, ch);
}
in.close();
out.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
中文名字乱码
上面的代码,如果下载中文文件,页面在下载时会出现中文乱码或不能显示文件名的情况(也 有可能发生文件找不到 404),对于 GET 请求,参数追加到地址栏,会使用 UTF-8 编码, 服务器(Tomcat)接受到请求之后,使用 ISO-8859-1 解码,所以会出现乱码,导致找不到 资源。
因此,我们在获取文件名时,必须将文件名用 UTF-8 解码 String filename = new String(request.getParameter(“filename”).getBytes(“iso-8859-1”), “utf-8”);
并且不同的浏览器默认对下载文件的编码方式不同, IE 是 UTF-8 编码方式,而火狐浏览器是 Base64 编码方式。所里这里还需要解决浏览器兼容性问题,解决浏览器兼容性问题的首要 任务是要辨别访问者是 IE 还是火狐(其他),通过 Http 请求体中的一个属性可以辨别
示例代码
//获得请求头中的 User-Agent
String agent = request.getHeader("User-Agent"); //根据不同浏览器进行不同的编码
String filenameEncoder = "";
if (agent.contains("MSIE")) {
// IE 浏览器
filenameEncoder = URLEncoder.encode(filename, "utf-8");
filenameEncoder = filenameEncoder.replace("+", " ");
} else if (agent.contains("Firefox")) {
// 火狐浏览器
BASE64Encoder base64Encoder = new BASE64Encoder();
filenameEncoder = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
} else {
// 其它浏览器
filenameEncoder = URLEncoder.encode(filename, "utf-8");
}
- 文件名
> // 告诉客户端(浏览器)以下载的方式打开文件,设置 Context-Dispostion 头,并设置 文件名字
response.setHeader(“Content-Disposition”, “attachment;filename=” + filenameEncoder);
完整代码
public class Download3Servlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
String filename = new String(request.getParameter("filename"));
response.setContentType(this.getServletContext().getMimeType("download/"+filename));
//获得请求头中的 User-Agent
String agent = request.getHeader("User-Agent"); //根据不同浏览器进行不同的编码
String filenameEncoder = "";
if (agent.contains("MSIE")) {
// IE 浏览器
filenameEncoder = URLEncoder.encode(filename, "utf-8");
filenameEncoder = filenameEncoder.replace("+", " ");
} else if (agent.contains("Firefox")) {
// 火狐浏览器
BASE64Encoder base64Encoder = new BASE64Encoder();
filenameEncoder = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
} else {
// 其它浏览器
filenameEncoder = URLEncoder.encode(filename, "utf-8");
}
response.setHeader("Content-Disposition", "attachment;filename"+filenameEncoder);
ServletOutputStream out = response.getOutputStream();
String file = this.getServletContext().getRealPath("download/"+filename);
InputStream in = new FileInputStream(file);
int ch = 0;
byte[] b = new byte[1024];
while((ch = in.read(b)) !=-1){
out.write(b, 0, ch);
}
in.close();
out.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
请求重定向
在某些情况下,针对客户端的请求,一个 Servlet 类可能无法完成全部工作。这时可以使用 请求重定向来完成。 所谓请求重定向,指的是 Web 服务器接受到客户端的请求后,可能由于某些条件限制,不 能访问当前请求 URL 所指向的 Web 资源,而是指定了一个新的资源路径,让客户端重新发 送请求。为了实现请求重定向,在 HttpServletResponse 接口中,定义了一个 sendRedirect() 方法,该方法用于生成 302 响应码和 Location 响应头,从而通知客户端重新访问 Location 响应头中指定的 URL。
- 重定向工作原理
当客户端访问 Servlet1 时,由于在 Servlet 中调用了 sendRedirect() 方法将请求重定向到 Servlet2 中,因此,Web 服务器在收到 Servlet1 的响应消息后,立刻向 Servlet2 发送请求 Servlet2 对请求处理完毕后,再将响应消息回送给客户端。
代码示例
public class Servlet1 extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 重定向到
Servlet2 response.sendRedirect("/Response/servlet2");
}