今天在来看一下Response容器的相关知识,其实这篇blog早就应该编写了,只是最近有点忙,所以被中断了。下面我们就来看一下Response容器的相关知识吧。Response和我们即将在后面说到的Request容器是一一对应的,他是web容器在用户每次请求服务端的时候,创建的一对容器对象,Response容器是服务端返还给客户机的一个响应内容对象容器,比如说:响应头,响应行,实体数据等信息,而Request容器对象是,代表用户请求服务端的的一个容器对象,比如客户机的请求头,请求行,以及携带的参数信息等。所以说Request容器对象和Response容器对象是一一对应的,他们两的生命周期也是一样的,就是在一次用户请求中。下面就来详细说一下Response容器对象的相关知识吧!
下面先来看一下Response对象的相关方法吧:
addCookie(Cookie cookie):这个方法是向Response容器中添加一个Cookie,然后服务器容器会自动的将这个Cookie回写给客户机的,至于Cookie的相关知识我们会在后面的文章中进行详解,这篇文章中这个方法暂时用不到。
addDateHeader(String name ,long date):这个是向客户机添加一个时间值属性的响应头信息,比如那个缓存的响应头expires
addHeader(String name,String value):这个是向客户机添加一个字符串值属性的响应头信息,比如重定向的响应头location
addIntHeader(String name ,int value):这个是向客户机添加一个字符串属性的响应头信息
containsHeader(String name):这个方法是判断是否含有这个响应头信息字段
encodeURL(String name):这个方法是用于url改写的功能的,这个和session有关,等到说session那篇文章的时候在详细说明
sendRedirect(String name):这个方法是用于请求重定向的,和响应头中的location字段的作用相同
setHeader(String name,String value)/setIntHeader(String name,int value)/setDateHeader(String name,long date):这些方法和addHeader方法是相对应的,唯一和addHeader不同的是,addHeader是向Response中添加一个响应头信息,而setHeader是修改一个响应头信息的。
setStatus(int value):通过这个方法是设置响应码的,比如:200,304,404等。
getOutputStream():通过这个方法可以拿到一个字节流,然后可以向Response容器中写入字节数据,最后客户机向Response容器中拿去数据进行显示
getWriter():通过这个方法可以拿到一个字符流(PrintWriter),然后可以向Response容器中写入字符数据,最后客户机向Response容器中拿去数据进行显示
setContentLength():通过这个方法设置服务器向用户返回的数据长度,我们在HTTP协议详解这篇blog中的那个压缩数据的返回的例子中有说到
setContentType():方法可以直接设置响应头content-type的内容
下面我们就来详细介绍一下这些方法的使用吧:
第一个例子:通过Response进行数据的输出,下面是一个测试的方法,这个方法只要在service方法中调用即可,传递一个HttpServletResponse对象,就可以进行输出的
public void test(HttpServletResponse response) throws Exception{ // 使用OutputStream字节流进行数据的输出 response.getOutputStream().write("Hello World".getBytes()); }
这个例子很简单就是将字符串写到response容器中,然后客户机从容器中拿取数据进行显示即可,但是这里我们需要注意的,当我们在使用response.getOutputStream()这样获取一个OutputStream流的时候,我们在使用完之后,并不需要手动的去关闭,系统会自动关闭它,如果我们手动去关闭这个流的话,还会引发一些问题。
这里我们输出的是"Hello World"英文,使用浏览器去访问的时候是没有乱码问题,下面我们在将代码改写一下,
response.getOutputStream.write("中国".getBytes("utf-8"));这时候我们使用浏览器去访问数据的时候,当然我们可能会看到"中国",也有可能看到的是乱码,原因很简单,如果浏览器使用的是gb2312码表打开的话,就是乱码,如果是使用utf-8码表打开的话,就是正常的数据。当然我们可以手动的去设置浏览器的打开码表,默认的是gb2312(系统默认码表),那么如果我们使用utf-8将数据写入到Response中,然后浏览器使用的是默认的码表去拿取数据进行显示,那肯定是乱码了,那么我们该怎么办呢?,让用户手动的去修改浏览器的打开码表,那貌似太恶心了,所以这里我们就要介绍一个响应头字段的作用了,Content-type:这个头就是告诉浏览器以什么方式打开数据,并且指定相应的码表,具体代码如下:
response.setHeader("Content-type", "text/html;charset=utf-8");这样,我们就可以告诉浏览器以utf-8码表去显示数据,这样也就不会再有乱码的问题了。
这里我们在扩展一下就是还有一种方式控制浏览器的打开码表,那就是使用<meta>标签来实现:
response.getOutputStream().write("<meta http-equiv='content-type' content='text/html;charset=utf-8'>".getBytes());这里我们组建了一个<meta>标签,并将这个标签写入到Response容器中,当客户机使用去拿取这段数据的时候,发现有html中的标签<meta>所以会通过一些处理会把上面的字符串当做是是html代码来显示。这里使用了<meat>标签来解决乱码问题的。
上面使用的是字节流的方式来给客户机发送数据的,有时候我们可能会使用字符流来显示数据,因为字符流在特定场合下回比字节流更方便的输出,其实这里使用response.getWriter()来获取一个PrintWriter字符流对象,然后我们可以使用PrintWriter对象的write方法直接写字符串数据,但是这里也是需要来解决乱码的问题,而且这里的需要解决的问题比上面的字节流更麻烦。下面来看一下吧:
response.getWriter.write("中国");通过上面的方法进行输出,显示的是乱码,首先servlet将"中国"字符串写入到Response容器中,但是这里需要注意的一个问题就是,将"中国"写入到Response中,那么Response容器中是怎么存储"中国"字符串的,因为Response这些技术都是老外发明的,所以他们肯定是使用iso8859-1编码来进行存储字节数据的,所以这里就会有一个大问题,因为我们知道iso8859-1使用的是单个字节表示一个字符的,而gb2312使用的是两个字节,utf-8使用的是三个字节,所以Response将使用iso8859-1码表进行编码,那么存储的是两个乱码字节,所以,当客户机从Response容器中去拿取数据的时候显示的肯定是乱码,那么当我们使用
response.setHeader("Content-type","text/html;charset=utf-8")来设置浏览器的打开数据的码表,但是我们会发现还是显示两个??,这个也是很简单的,因为我们在编写JavaSE中,也会知道这个问题就是将utf-8这种多字节的码表转到低字节码表iso8859-1,当我这时候在将结果转成utf-8的数据,这时候是不可能在转回原始数据的。所以说我们应该去修改Response容器的码表,将其码表改成utf-8,这时候就可以"中国"写入到Response容器中,而且会以utf-8的码表进行存储的,当客户机在用utf-8码表打开的时候就不会有问题了,
上面我们使用字节流来进行书写数据的时候,是没有问题的,因为是将"中国"的字节数据直接写到Response容器中的,所以不会涉及到Response容器编码的问题。
所以说当我们在使用字符流写入数据的时候,我们一定要记得修改Response容器的编码,不然会出现乱码的
response.setCharacterEncoding("utf-8"); response.setHeader("content-type", "text/html;charset=utf-8");所以要用这两行代码的,同时response对象中还有一个方法:setContentType,这个方法可以直接设置Content-type字段的值
response.setContentType("text/html;charset=utf-8");其实这一行代码就相当于上面的两行代码的效果,因为在setContentType方法中已经调用了setCharacterEncoding方法设置了Response容器的编码了。
下面我们在来看一下怎么通过响应头实现下载的,这里我们只是来解决一下下载的文件名是中文的情况:
下面图片就是我们没有去解决文件名是中文的问题。浏览器会过滤中文,并且浏览器会认为其是html类型的数据
下面我们使用URLEncoder.encode(filename,"utf-8");来将中文名进行编码
//实现下载 public void test3(HttpServletResponse response) throws Exception{ /** * 这时候就需要通过url进行编码 */ ServletContext context = this.getServletContext(); //通过context方式直接获取文件的路径 String path = context.getRealPath("/download/美女.jpeg"); //获取文件名 String filename = path.substring(path.lastIndexOf("\")+1); //将文件名进行URL编码 filename = URLEncoder.encode(filename,"utf-8"); //告诉浏览器用下载的方式打开图片 response.setHeader("content-disposition", "attachment;filename="+filename); //将图片使用字节流的形式写给客户机 InputStream is = this.getServletContext().getResourceAsStream("/download/美女.jpeg"); OutputStream out = response.getOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while((len=is.read(buffer))!=-1){ out.write(buffer, 0, len); } }处理之后的效果:
这次就可以正常显示文件名了,而且类型也是jpeg的类型
下面在来看一个例子,就是使用response.getOutputStream流做一个验证码图片的实现案例:
原理:首先我们使用BufferedImage对象获取一个特定高和宽的缓存图片,然后我们将对这个图片对象进行一些修饰加工
setBorder()来设置图片的边框
setBackground来设置图片的背景颜色
drawRandomLine来设置随机线条
drawRandomNumber来设置随机数字
然后通过响应头Content-type告诉浏览器以图片的方式打开数据
最后在使用ImageIO对象的write方法将图片写入到response.getOutputStream流中
代码如下:
//实现随机图片的产生
public void test4(HttpServletResponse response) throws Exception{
final int width = 120;//宽
final int height = 35;//高
BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
//设置背景色
setBackground(g,width,height);
//设置边框
setBorder(g,width,height);
//设置随机线条
drawRandomLine(g,width,height);
//设置随机数
drawRandomNumber((Graphics2D)g,width,height);
//不要缓存
/*response.setDateHeader("expires", -1);
//告诉所有浏览器不要缓存
response.setHeader("Cache-control", "no-cache");
response.setHeader("Pragma", "no-cache");*/
response.setHeader("content-type", "image/jpeg");
ImageIO.write(image,"jpg",response.getOutputStream());
}
/**
* 设置背景色
* @param g
* @param width
* @param height
*/
public void setBackground(Graphics g,int width,int height){
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
}
/**
* 设置随机线条
* @param g
* @param width
* @param height
*/
public void drawRandomLine(Graphics g,int width,int height){
g.setColor(Color.GREEN);
for(int i=0;i<5;i++){
int x1 = new Random().nextInt(width);
int y1 = new Random().nextInt(height);
int x2 = new Random().nextInt(width);
int y2 = new Random().nextInt(height);
g.drawLine(x1, y1, x2, y2);
}
}
/**
* 设置边框
* @param g
* @param width
* @param height
*/
public void setBorder(Graphics g,int width,int height){
g.setColor(Color.BLUE);
g.drawRect(1, 1, width-2, height-2);
}
/**
* 设置随机数
* @param g
* @param width
* @param height
*/
public void drawRandomNumber(Graphics2D g,int width,int height){
g.setColor(Color.RED);
g.setFont(new Font("宋体",Font.BOLD,20));
//常见的中文字
//[u4e00-u9fa5]
String base = "u7684u4e00u4e86u662fu6211u4e0du5728u4ebau4eecu6709u6765u4ed6u8fd9u4e0au7740u4e2au5730u5230u5927u91ccu8bf4u5c31u53bbu5b50u5f97u4e5fu548cu90a3u8981u4e0bu770bu5929u65f6u8fc7u51fau5c0fu4e48u8d77u4f60u90fdu628au597du8fd8u591au6ca1u4e3au53c8u53efu5bb6u5b66u53eau4ee5u4e3bu4f1au6837u5e74u60f3u751fu540cu8001u4e2du5341u4eceu81eau9762u524du5934u9053u5b83u540eu7136u8d70u5f88u50cfu89c1u4e24u7528u5979u56fdu52a8u8fdbu6210u56deu4ec0u8fb9u4f5cu5bf9u5f00u800cu5df1u4e9bu73b0u5c71u6c11u5019u7ecfu53d1u5de5u5411u4e8bu547du7ed9u957fu6c34u51e0u4e49u4e09u58f0u4e8eu9ad8u624bu77e5u7406u773cu5fd7u70b9u5fc3u6218u4e8cu95eeu4f46u8eabu65b9u5b9eu5403u505au53ebu5f53u4f4fu542cu9769u6253u5462u771fu5168u624du56dbu5df2u6240u654cu4e4bu6700u5149u4ea7u60c5u8defu5206u603bu6761u767du8bddu4e1cu5e2du6b21u4eb2u5982u88abu82b1u53e3u653eu513fu5e38u6c14u4e94u7b2cu4f7fu5199u519bu5427u6587u8fd0u518du679cu600eu5b9au8bb8u5febu660eu884cu56e0u522bu98deu5916u6811u7269u6d3bu90e8u95e8u65e0u5f80u8239u671bu65b0u5e26u961fu5148u529bu5b8cu5374u7ad9u4ee3u5458u673au66f4u4e5du60a8u6bcfu98ceu7ea7u8ddfu7b11u554au5b69u4e07u5c11u76f4u610fu591cu6bd4u9636u8fdeu8f66u91cdu4fbfu6597u9a6cu54eau5316u592au6307u53d8u793eu4f3cu58ebu8005u5e72u77f3u6ee1u65e5u51b3u767eu539fu62ffu7fa4u7a76u5404u516du672cu601du89e3u7acbu6cb3u6751u516bu96beu65e9u8bbau5417u6839u5171u8ba9u76f8u7814u4ecau5176u4e66u5750u63a5u5e94u5173u4fe1u89c9u6b65u53cdu5904u8bb0u5c06u5343u627eu4e89u9886u6216u5e08u7ed3u5757u8dd1u8c01u8349u8d8au5b57u52a0u811au7d27u7231u7b49u4e60u9635u6015u6708u9752u534au706bu6cd5u9898u5efau8d76u4f4du5531u6d77u4e03u5973u4efbu4ef6u611fu51c6u5f20u56e2u5c4bu79bbu8272u8138u7247u79d1u5012u775bu5229u4e16u521au4e14u7531u9001u5207u661fu5bfcu665au8868u591fu6574u8ba4u54cdu96eau6d41u672au573au8be5u5e76u5e95u6df1u523bu5e73u4f1fu5fd9u63d0u786eu8fd1u4eaeu8f7bu8bb2u519cu53e4u9ed1u544au754cu62c9u540du5440u571fu6e05u9633u7167u529eu53f2u6539u5386u8f6cu753bu9020u5634u6b64u6cbbu5317u5fc5u670du96e8u7a7fu5185u8bc6u9a8cu4f20u4e1au83dcu722cu7761u5174u5f62u91cfu54b1u89c2u82e6u4f53u4f17u901au51b2u5408u7834u53cbu5ea6u672fu996du516cu65c1u623fu6781u5357u67aau8bfbu6c99u5c81u7ebfu91ceu575au7a7au6536u7b97u81f3u653fu57ceu52b3u843du94b1u7279u56f4u5f1fu80dcu6559u70edu5c55u5305u6b4cu7c7bu6e10u5f3au6570u4e61u547cu6027u97f3u7b54u54e5u9645u65e7u795eu5ea7u7ae0u5e2eu5566u53d7u7cfbu4ee4u8df3u975eu4f55u725bu53d6u5165u5cb8u6562u6389u5ffdu79cdu88c5u9876u6025u6797u505cu606fu53e5u533au8863u822cu62a5u53f6u538bu6162u53d4u80ccu7ec6";
int x = 5;
for(int i=0;i<4;i++){
int degree = new Random().nextInt(61)-30;//new Random().nextInt()%30;//-30---30
String content = base.charAt(new Random().nextInt(base.length()))+"";
g.rotate(degree*Math.PI/180,x,20);
g.drawString(content, x, 20);
g.rotate(-degree*Math.PI/180, x, 20);
x += 30;
}
}
这里我们的随机图片中显示的是常见中文。
这里我们看到了,但是我们这时候点击刷新的时候,发现图片是不变的,这个很纠结的,原因也很简单,因为浏览器去拿取缓存的图片的了,所以我们要解决这个问题,我们只需要告诉浏览器不要去拿取缓存:
//不要缓存 response.setDateHeader("expires", -1); //告诉所有浏览器不要缓存 response.setHeader("Cache-control", "no-cache"); response.setHeader("Pragma", "no-cache");
缓存的问题就是涉及到了几个字段,这里面的内容很多,所以在这篇文章中就不解释了,可以参考另外一篇blog:
http://blog.csdn.net/jiangwei0910410003/article/details/22917645
下面在来看一下重定向的问题:
//请求重定向:能不用尽量不用,因为需要向服务器再发一次请求,加重server的压力,像用户登录完之后要跳转到首页、购物买完之后跳转到购物车页面 //从定向的特点:1.浏览器会向服务器发送两次请求,意味着就有2个request esponse;2.用重定向技术,浏览器地址栏会发生变化 public void test7(HttpServletResponse response) throws Exception{ //使用Http响应头实现重定向 response.setStatus(302); response.setHeader("location", "/ServletDemo/index.jsp"); //使用以下的api也是可以的,相当于执行了上面的两行代码 //response.sendRedirect("/ServletDemo/index.jsp"); }实现重定向的话,有两种方式,第一种是通过响应头字段location和响应状态码302来实现的,第二种是直接调用response的sendRedirect方法实现的,当然最重要的是要看重定向和转发的区别:
重定向的特点:浏览器的地址栏会发生改变,用户是再次向服务器发送一次请求,这时候就相当于浏览器创建了两对Response/Request对象,
转发的特点:浏览器的地址栏不会发生改变,还是在一个请求的范围内,所以服务器不会再去创建一对Response/Request对象,始终是在一个请求内。
最后在来看一下在使用response对象的时候需要注意的问题:
就是在使用response进行输出数据的时候,我们上面讲到了使用两种方式:一种是使用字节流,一种是使用字符流;但是这两种流是不能同时操作的,否在会报异常的,这个只是在使用同一个Response对象的时候会出现这样的问题,比如你在使用转发技术的时候,在一个servlet中使用字节流写数据,然后转发到另一个servlet,在这个servlet中使用字符流写数据,那这样就会有问题,但是如果你是使用了重定向的话,就不会有这样的问题了,因为重定向的话,是在两个Response对象中。