概述
request和response这个一对容器是一一对应,他们是在客户端请求服务器数据时由Web服务器创建,他们具有相同的生命周期,就是在用户一次请求中。当Web服务器收到客户端的Http请求时,分别创建代表请求的request对象和代表响应的response对象。所以得出结论:如果我们要找关于客户端提交过来的信息就找request,而找关于服务器传到客户端的信息就找response。下面来介绍一下Response。
Response对象
我们通常说的response是指实现HttpServletResponse接口的类的对象,此接口提供特定于Http的发送响应功能。它封装了服务器向客户端发送数据、发送响应头,发送响应状态的方法。让我们看一下它的方法:
- addCookie(Cookie cookie) 将指定的cookie添加到响应
- containsHeader(String name) 判断是否已经设置了指定的响应头
- encodeURL(String url) 通过将会话ID包含在指定URL中对该URL进行编码,用于url改写的功能的,这个和session有关
- encodeRedirect(String url) 对URL进行编码,以便在sendRedirect方法中使用它
- sendRedirect(String location) 请求重定向,和响应头location属性的作用相同
- setHeader(String name, String value)/ addHeader(String name , String value)两者都是用给定的名称和值设置响应头,前者修改响应头信息后者增加响应头信息。
- setStatus(int value)设置响应码,如状态码200代表请求OK,404代表请求的资源未找到(基本就是页面写的不对,地址栏写错了,jsp、html、js写错了之类),500代表服务器端出错(java代码写错了)。
- getOutputStream() 通过该方法获得一个字节流,可以向response容器写入数据,从而客户端可以获得该数据并显示。
- getWriter() 通过该方法获得一个字符流。
- setContentType() 可以直接设置响应头的content-type字段。
在浏览器上输出中文数据
作为一个中国人肯定是要看中文的,然而java,servlet、Response这些技术都是外国人发明,为了兼容中文格式,自然而然编码集问题总是虐我们千百遍。在这里记住一条:服务器以什么样的编码集方式输出到客户端,那么客户端就要设置什么样的编码集显示。接下来我们使用OutputStream和PrintWriter输出中文.
OutputStream字节流:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String text = "杭州";//要在浏览器显示的文字
//利用response对象获得输出流
ServletOutputStream outputStream = resp.getOutputStream();
//为了让浏览器显示中文而不导致显示乱码,我们告诉浏览器以utf-8编码显示数据,这里有3种方法
//1.设置浏览器响应头contentType信息
// resp.setHeader("content-type", "text/html;charset=UTF-8");
//2.直接设置contentType信息
// resp.setContentType("text/html;charset=UTF-8");
//3.使用HTML语言里面的<meta>标签来控制浏览器行为,模拟通过设置响应头控制浏览器行为
out.print("<meta http-equiv='content-type' content='text/html;charset=UTF-8'/>");
//将字符转换成字节数组,指定以UTF-8编码进行转换
byte[] texts = text.getBytes("utf-8");
outputStream.write(texts);
}
PrintWriter字符流:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String text = "杭州";
//服务器向浏览器发送的数据以utf-8编码
response.setCharacterEncoding("utf-8");//必须要放在前面,否则浏览器还是乱码
PrintWriter out = response.getWriter();
//通过设置响应头来控制浏览器以utf-8编码来显示数据
response.setHeader("content-type", "text/html;charset=UTF-8");
//使用HTML语言里面的<meta>标签来控制浏览器行为,模拟通过设置响应头控制浏览器行为
// out.print("<meta http-equiv='content-type' content='text/html;charset=UTF-8'/>");
out.write(text);
//PrintWriter相比于OutputStream 省去字符转换为字节数组的过程
//writer() 与 print() 的区别
//print方法可以将各种类型的数据转换成字符串的形式输出。
//重载的write方法只能输出字符、字符数组、字符串等与字符相关的数据。
}
对比PrintWriter、OutputStream 我们可以发现PrintWriter省去字符转换为字节数组的过程,此外PrintWriter要使用response.setCharacterEncoding(“编码集”)设置字符集编码来控制服务器端输出到客户端的数据编码。
这里还有一个点:浏览器显示文字都是必须要字符串的形式输出,所以我们在使用writer()方法,一定要将数据事先转换为字符串形式。不然会无法显示或者乱码。
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//假如是数字
String text = "杭州的区号:";
//服务器向浏览器发送的数据以utf-8编码
response.setCharacterEncoding("utf-8");//必须要放在前面,否则浏览器还是乱码
PrintWriter out = response.getWriter();
//通过设置响应头来控制浏览器以utf-8编码来显示数据
response.setHeader("content-type", "text/html;charset=UTF-8");
//使用HTML语言里面的<meta>标签来控制浏览器行为,模拟通过设置响应头控制浏览器行为
// out.print("<meta http-equiv='content-type' content='text/html;charset=UTF-8'/>");
out.write(text);
out.write(0571);//错误写法
// out.write(0571+"")//错误,以0开头的是八进制
// out.print(0571); // 错误,以0开头的是八进制
// out.write("0571")//正确写法
//writer() 与 print() 的区别
//print方法可以将各种类型的数据转换成字符串的形式输出。
//重载的write方法只能输出字符、字符数组、字符串等与字符相关的数据。
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
错误结果:
文件下载
使用response实现文件下载,并且文件名包含中文。
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setHeader("content-type", "text/html;charset=UTF-8");
response.setCharacterEncoding("utf-8");
//获取要下载的绝对路径
String realPath = this.getServletContext().getRealPath("/images/壁纸.jpg");
//定义要下载的文件的文件名
String fileName = realPath.substring(realPath.lastIndexOf("\\")+1);
// 根据request的locale 得出可能的编码,中文操作系统通常是gb2312
fileName = new String(fileName.getBytes("GB2312"),"ISO_8859_1");
//设置以content-disposition下载形式的响应头 ,中文文件名要使用URLEncoder.encode(文件名,编码集)来进行编码 否则文件名会乱码或者无法显示。
// response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));//然而这样还是乱码
//这种方式在FireFox没有用处 chrome可以 IE也可以
response.setHeader("content-disposition", "attachment;filename=" + fileName);
//获取要下载文件的文件输入流
InputStream in = new FileInputStream(realPath);
//获取输出流
ServletOutputStream out = response.getOutputStream();
//创建缓存区
byte[] buffer = new byte[1024];
int len = 0;
//将in读入到缓冲区
while(( len = in.read(buffer) ) > 0){
//使用out将缓冲区的数据输出到客户端
out.write(buffer, 0, len);
}
in.close();
}
刚开始我使用URLEncoder.encode(fileName, “UTF-8”)将文件进行编码,当时我是在FireFox下调试的发现文件名无法显示(如:下图),下载之后图片也无法打开(因为它就叫jpg),然后在网上查找资料使用了new String(fileName.getBytes(“GB2312”),”ISO_8859_1”)这种方式,发现可以了。后来,我一想可能第一种方式在其他浏览器上可以(毕竟ie、chrome、ff的内核不同),于是试了一下还真的可以。大家可以试试,看看你们的结果是什么。
接下来我们来用PrintWriter的字符流来传输图片。
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.获取文件的真实路径
String realPath = this.getServletContext().getRealPath("/images/壁纸.jpg");
//2.获取文件名
String fileName = realPath.substring(realPath.lastIndexOf("\\")+1);
//设置content-disposition响应头控制浏览器以下载的形式打开文件,中文文件名要使用URLEncoder.encode方法进行编码
response.setHeader("content-disposition", "attachment;filename="+URLEncoder.encode(fileName, "UTF-8"));
//这种方式在FireFox没有用处 chrome可以 IE也可以
//3.获取PrintWriter
PrintWriter out = response.getWriter();
//4.获取文件的FileReader
FileReader in = new FileReader (realPath);
//5.读取数据到缓存区里
char [] buffer = new char[1024];
int len=0;
while( (len=in.read(buffer))>0 ){
//6.将数据以PrintWriter方式(字符流)输出到客户端
out.write(buffer, 0, len);
}
//字符流文件发生损坏 无法打开。
}
结果:
注意:编写文件下载功能时推荐使用OutputStream流,避免使用PrintWriter流,因为OutputStream流是字节流,可以处理任意类型的数据,而PrintWriter流是字符流,只能处理字符数据,如果用字符流处理字节数据,会导致数据丢失。
使用response实现简单验证码图片
直接上代码:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setHeader("refresh", "1");//每1秒刷新一次
//在内存中创建一张图片
BufferedImage image = new BufferedImage(60, 20, BufferedImage.TYPE_INT_RGB);
//得到图片
Graphics2D g = (Graphics2D) image.getGraphics();
g.setColor(Color.GRAY);//背景色
g.fillRect(0, 0, 60, 20);
//向图片上写数据
g.setColor(Color.WHITE);
g.setFont(new Font(null, Font.BOLD, 20));
g.drawString(getNum(), 0, 20);
//设置响应头控制浏览器以图片的方式打开
resp.setContentType("image/jpeg");
resp.setHeader("content-type", "image/jpeg");
//不要缓存
resp.setDateHeader("expires", -1);
resp.setHeader("Cache-contril", "no-cache");
resp.setHeader("Pragma", "no-cache");
//将图片写给浏览器
ImageIO.write(image, "jpg", resp.getOutputStream());
}
private String getNum() {
Random r = new Random();
String num = r.nextInt(99999) +"";
StringBuffer sb = new StringBuffer(num);
for(int i = 0; i < 5 - num.length(); i++){
sb.append("0");
}
return sb.toString();
}
结果:
关于验证码图片涉及到浏览器的缓存问题,如果不控制浏览器不要缓存的话浏览器会自动读取缓存中的图片那么图片就无法刷新。
关于重定向
重定向地址栏会发生变化,也就是说会跳转到另一个地址,所以它会创建两对request、response,包括重定向前和重定向后。而转发则不一样,地址栏不会变化,也就是说只是创建一对response、request。这好比:
- 重定向:顾客(客户端)去超市(服务器)买东西,这个超市没有这件东西,让顾客去另一个超市买这个东西。
- 转发:顾客去超市买东西,这个超市没有这个东西,超市去另一个超市把该东西领过来给顾客。
实现重定向有两种方式:
- 直接设置响应头的location字段并利用setStatus字段设置302状态码实现重定向
- 利用sendRedirect(String url)方法实现重定向
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.直接用senRedirect()方法
// response.sendRedirect("/JavaWeb_Servlet_Response_20160804/index.jsp");
//2.设置响应头
response.setHeader("location", "/JavaWeb_Servlet_Response_20160804/index.jsp");
response.setStatus(HttpServletResponse.SC_FOUND);//302状态码 获取到HTTP响应报文头部的Location字段信息,并发起一个GET请求
}
源码地址:https://github.com/xuda27/StudyHttpServletResponse
欢迎大家Clone or Download