Filter ServletResponse拿到返回数据 -- 响应 header -Expires - charset,filter 获取 response返回参数 filter直接返回

一个完整的http响应包括响应行,若干响应头和响应数据主体三部分构成。如果我们能用响应对象来进行这三部分的处理,就能向客户发送特定的响应数据包。

  先从HttpServletResponse对象的方法中可以看到有如下方法(部分):

  

Filter ServletResponse拿到返回数据_响应头

  这只是一部分,但是我们却可以看出,通过响应对象的方法,我们就能设置响应客户端数据的一些信息。比如setStatus(int sc)方法,我们从HttpServletResponse的API中的字段定义可找到已经设置好的响应码(部分):

  

Filter ServletResponse拿到返回数据_数据_02

  我们通过setHeader或者addHeader就能对一些数据进行跟客户端的告知,比如我想让某个页面的数据在客户端保存一天,也就是如果客户端再向我请求的话,则它应该去缓存中获取,直到一天之后才能重新向我请求,那么我就必须使用到了“Expires”响应头,将这个响应头的值设为一天后的时间告诉给客户端:

  在MyEclipse中的【myservlet】web工程下,创建名为ServletDemo1的Servlet,代码如下:

public class ServletDemo1 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        long expiresTime = System.currentTimeMillis()+1*24*60*60*1000; //将缓存截止时间设置为一天后
        response.setDateHeader("expires", expiresTime);
        
    }
}
 
 

这时候用浏览器来访问这个Servlet(访问之前最好现将浏览器中的缓冲清除干净),访问之后我们再来看看这个缓存的页面文件:

  

Filter ServletResponse拿到返回数据_响应头_03

右键查看其属性:

  

Filter ServletResponse拿到返回数据_码表_04

可以看到我们是在9月28日访问的这个文件,而服务器已经将这个文件的缓冲时间设置为了29日。

  注意:对于”Expires”响应头的设置必须使用setDateHeader方法,使用setHeader方法无效。

 

通过HttpServletResponse对象的父类ServletResponse对象的getOutputStream方法和getWriter方法可以获取向响应对象的数据实体中写入数据。这里如果传送的是英文数据一般都没有什么问题,而中文编码则是会令人头疼的。

getOutputStream

首先我们来看响应对象的getOutputStream方法,我们在一个Servlet程序中代码如下:

1     String data = "银魂";
2     OutputStream out = response.getOutputStream();
3     out.write(data.getBytes());
 
 

而在浏览器中访问这个Servlet,看到的是:

  

Filter ServletResponse拿到返回数据_码表_05

是的,没有出现乱码的问题,这主要有两点原因:

  一个是浏览器本身的解码方式是根据平台语言环境来设置的,我的操作系统是Windows的中文版本,因此浏览器的解码采用的编码表为“GB2312”:

  

Filter ServletResponse拿到返回数据_数据_06

 

  另一个原因是因为,在将字符串转为字节数组时,采用了getBytes()方法,这个方法在String类的API文档中明确说明了采用平台默认的字符集,根据我的系统这个方法也采用了“GB2312”编码表。

  因此服务器编码和客户端解码都采用同一编码表这就不会出现中文乱码问题。

 

  如果我在getBytes方法中采用UTF-8编码,那么结果自然会出错:

1     String data = "银魂";
2     OutputStream out = response.getOutputStream();
3     out.write(data.getBytes("UTF-8"));
 
 

  

Filter ServletResponse拿到返回数据_响应头_07

除非你也在浏览器中更改编码方式,改成UTF-8就可以重新看到正确的中文数据了:

  

Filter ServletResponse拿到返回数据_数据_08

  

Filter ServletResponse拿到返回数据_数据_09

当然这肯定不适合给用户这么操作,毕竟不是谁都懂浏览器的编码。

  如果我们一定要将中文数据采用“UTF-8”的方式(UTF-8有利于国际化),有这么两种解决采用UTF-8编码方式的中文乱码问题:

  第一种解决方式:使用HttpServletResponse响应对象的setHeader的方法,将“Content-type”这个响应头中设置编码方式,关于这个这个响应头请看之前的博客 《HTTP协议的请求和响应学习》。同时,sun公司也提供了更便捷的代码语句setContentType给编程人员使用。

  在Servlet中的代码:

1   response.setHeader("content-type", "text/html;charset=UTF-8");
2   //response.setContentType("text/html;charset=UTF-8");   //这句功能同上一句
3     String data = "银魂";
4     OutputStream out = response.getOutputStream();
5     out.write(data.getBytes("UTF-8"));
 
 

这样在浏览器中可以看到正确的中文数据,并且浏览器自动将编码方式采用UTF-8:

  

Filter ServletResponse拿到返回数据_码表_10

附带从HttpWatch中观察到的数据包:

  

Filter ServletResponse拿到返回数据_码表_11

注意:如果response.setHeader("content-type", "text/html;charset=UTF-8");中将"text/html;charset=UTF-8"中的分号“;”写成了逗号“,”就会变成下载该Servlet文件。

  所以书写要注意。

  第二种解决方式:我们不直接在响应对象中设置“Content-type”这个响应头,而是通过HTML的<meta>标签,该标签的作用就是模拟一个响应头,这样在回传的响应对象中,某些响应头就不会被设置,但是还是有这个响应头的功能,例如我们在HTML页面中经常能见到的<meta http-equiv="content-type" content="text/html;charset=utf-8">这个标签,是不是和第一种方式很像。相关代码为:

1     String data = "银魂";
2     OutputStream out = response.getOutputStream();
3     out.write("<meta http-equiv='content-type' content='text/html;charset=utf-8'>".getBytes());
4     out.write(data.getBytes("UTF-8"));
 
 

这时候在浏览器中同样能观察到正确的中文数据,同时可以看到浏览器已经自动采用“UTF-8”编码方式:

  

Filter ServletResponse拿到返回数据_数据_12

同时,在浏览器浏览源代码和观察HttpWatch窗口:

  

Filter ServletResponse拿到返回数据_响应头_13

从上面可以看出,服务器发回的响应中没有“Content-type”这个响应头,但是在响应数据实体中有<meta>标签,浏览器能解析这个HTML语言,得到这个标签中设置的“Content-type”模拟响应头,因此能根据这个模拟响应头中的编码方式来设置浏览器应该采用的码表。

 

         如果我们用输出流直接输出数字的话,会是输出这个数字在编码表中代表的字符,如代码为:

 

1 public void doGet(HttpServletRequest request, HttpServletResponse response)
2             throws ServletException, IOException {
3         
4       response.getOutputStream().write(97);
5     }
6
 
 

 

在浏览器中得到:a。

Writer对象

说完了从响应对象中得到OutputSteam对象,接着我们来讨论从响应对象中得到Writer对象,众所周知,字符流是由字节流加编码表组成,那么在响应对象中的字符流采用什么编码表方式呢?我们来看看HttpServletResponse对象的getWriter()方法的API手册说明:

  

Filter ServletResponse拿到返回数据_响应头_14

从这里面看出如果没有为这个getWriter()方法设置编码表,那么则默认采用 “ISO-8859-1”编码表。或者采用响应对象的getCharacterEncoding()方法查看也可以。

 

  那么在服务器端如果要改变对封装数据的编码格式可以有两种方式:

  第一种:使用响应对象的setCharacterEncoding()方法来设置服务器采用的编码表,接着使用setContendType或者setHeader告知客户端服务器采用的编码表,后者在上面已经说过。

  示例代码:

 

1 public class ServletResponse2 extends HttpServlet {
 2 
 3     public void doGet(HttpServletRequest request, HttpServletResponse response)
 4             throws ServletException, IOException {
 5         
 6         response.setCharacterEncoding("UTF-8");
 7         response.setContentType("text/html;charset=UTF-8");
 8         PrintWriter writer = response.getWriter();
 9         String data = "银魂";
10         writer.write(data);    
11     }
12 }
 
 

 

注意,光只有setCharacterEncoding()方法只能改变服务器端采用的编码表,而没能通知客户端,所以需要setContentType设置“Content-type”响应头,或者如之前所说的写入<meta>标签来模拟“Content-type”响应头。

 

  第二种:直接使用setContentType方法,通过这种方法,可以在服务器和客户端同时设置编码表,也就是第一种方式中两个方法的结合,因此上述示例的代码如下:

 

1 public void doGet(HttpServletRequest request, HttpServletResponse response)
2             throws ServletException, IOException {
3         response.setContentType("text/html;charset=UTF-8");
4         
5         PrintWriter writer = response.getWriter();
6         String data = "银魂";
7         writer.write(data);    
8 }
9 }
 
 

 

  以上分别从响应对象HttpServletResponse中根据获取的字节流或者字符流向客户端输出数据时会碰到的中文乱码问题做出了分析和解决。关于响应对象和请求对象能实现的更多方法和功能请在之后的篇章中说明。

 

下载文件

使用setHeader方法结合HTTP协议的content-disposition响应头可以将某些web资源以下载方式回传给客户端。但是在下载中文文件的时候会有一些问题,这问题会怎么发生呢?

  我们现在来进行从客户端向服务器端下载一个图片文件,先在MyEclipse的自创建【myservlet】web工程下准备一个图片文件,放置在web目录下的【download】文件夹中:

  

Filter ServletResponse拿到返回数据_响应头_15

创建名为ServletResponse的Servlet,代码如下:

public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        //1,获取将要下载的文件路径和文件名
        ServletContext context = this.getServletContext();
        String filePath = context.getRealPath("/download/银魂全家福.jpg");
        String fileName = filePath.substring(filePath.lastIndexOf("\\")+1);
        
        //2,使用HTTP协议的Content-Disposition请求头告诉客户端以下载方式接受这个数据(文件)
        response.setHeader("content-disposition", "attachment;filename="+fileName);
        
        //3,通过流向响应对象写数据实体,然后由响应对象提交给客户端    
        FileInputStream fis = null;
        try{
            OutputStream out = response.getOutputStream();
            fis = new FileInputStream(filePath);
            byte[] buff = new byte[1024];
            int len = 0;
            while((len =fis.read(buff))>0) {
                out.write(buff, 0, len);
            }
        }
        finally {
            if(fis!=null) {
                fis.close();
            }
        }
    }
 
 

而这样,当在浏览器中访问该Servlet时就会出现下载请求的弹窗:

  

Filter ServletResponse拿到返回数据_响应头_16

这里就有一个问题了,下载文件的名称不对,如果是英文文件名就不会出现这个问题,而中文文件名就必须通过URL编码:

将上述代码中的response.setHeader("content-disposition", "attachment;filename="+fileName);

改为:response.setHeader("content-disposition", "attachment;filename="+URLEncoder.encode(fileName, "UTF-8")); 即可。

  我们重新发布该工程,然后再在浏览器中访问该Servlet,看到这回的下载弹窗:

  

Filter ServletResponse拿到返回数据_响应头_17

这回就可以下载了,当我们下载后就可以看到确实是服务器上得资源:

  

Filter ServletResponse拿到返回数据_码表_18

(真棒!)

所以中文文件下载方式要先通过URLEncoder进行编码后再写入相应对象中。

 

写入数据前要设置编码表

首先来看响应对象控制浏览器定时刷新,在我的web应用【myservlet】中创建Servlet,在该Servlet中设置响应头,定时刷新的代码很简单:

 

response.setHeader("refresh", "3 ");    //3秒刷新一次
 
 

 

就可告知浏览器3秒刷新一次网页。当然“Refresh”响应头还是可以定时跳转到指定页面,如下代码:

 

response.setHeader("refresh", "3;url='/myservlet/index.jsp'");
 
 

 

将会在访问我这个Servlet的3秒后访问我的【myservlet】web应用下的index.jsp资源。

 

  之前说过,使用<meta>标签可以模拟响应头,同样在这里可以使用Servlet向客户端写数据时将<meta>标签写入响应数据实体,由浏览器解析后再来控制:

 

1 String data = "银魂";2 response.setContentType("text/html;charset=UTF-8");
3 response.getWriter().write("<meta http-equiv='refresh' content='3;url=/myservlet/index.jsp' >");
4 response.getWriter().write(data);
 
 

 

 

  这里请注意:在向响应对象写入任何数据之前,请先设置好使用的编码表,如果在写入数据之后再设置编码表则无效,例如下面代码:

 

1 String data = "银魂";2 response.getWriter().write(data);
3 response.setContentType("text/html;charset=UTF-8");
 
 

 

通过查看HttpWathch可以知道,编码表并没有设置成功:

  

Filter ServletResponse拿到返回数据_码表_19

把设置编码表的代码放置在向响应对象写入数据也是不行的,如下:

 

1 String data = "银魂";2 response.getWriter().write("<meta http-equiv='refresh' content='3;url=/myservlet/index.jsp' >");
3 response.setContentType("text/html;charset=UTF-8");
4 response.getWriter().write(data);
 
 

 

这样也会设置编码表无效,造成中文乱码问题,所以在向响应对象写入任何数据之前请确保先将编码表设置好。

 

请求重定向:

之前在http协议中学习了响应码302和“Location”响应头可以设置请求重定向。

一个简单的代码示例(web工程名为【myservlet】):

 

1   response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);  //302状态码2   response.setHeader("location", "/myservlet/index.jsp");
 
 

 

对于不太熟悉HTTP协议的编程人员来说,当然还有更快捷的方式:

 

1 response.sendRedirect("/myservlet/index.jsp");
 
 

 

验证:在浏览器中的地址栏中键入该Servlet的地址,浏览器则是会跳转至该web目录下的index.jsp,这个可以从浏览器地址栏上看出。

  关于请求重定向中的细节:

  一次请求重定向会向服务器发送两次请求。也就是说会产生两次response响应对象和request请求对象;同时浏览器的地址栏是会发生变化的,URL将跳转到重定向后的页面地址。这点和转发不同。

所有响应的内容都在respones里,我们可以在过滤器这里获取到response里所有的内容。我可以看到servlet和filter中的方法都是没有返回值的,他们只是负责操作request和response,对response的操作就是把我们想要给浏览器的内容放到response里,然后tomcat会把response返回给浏览器。我们所有的响应内容不管是重定向还是转发还是通过response获取流后进写操作(其实转发也是通过获取response流把内容写到response里的。),最终都是把内容写到reponse后由tomcat返回给浏览器的。

请求过来后,tomcat帮我们创建好request假设是A对象和response假设是B对象,然后把tomcat调用filter和servlet中的方法并把对象传递给filter servlet中的方法参数里。我们通过filter和servlet操作request即A对象和response即B对象里的内容,我们操作完后即filter和servlet中的方法执行完后,tomcat对filter和servlet调用完毕,这时request即A对象和response即B对象里的内容都变成我们操作后的内容了。tomcat再把response即B对象(还是之前tomcat帮我们创建的对象只是对象里的内容变成我们通过filter和serlvet方法操作后的内容了)返回给浏览器。 因为tomcat传给servlet和filter的response 不能获取到response里的内容,所以我们可以在filter中对tomcat的response进行包装,我们可以自己定义一个继承自HttpServletResponseWrapper的类,然后在里面把我们自己定义的writer流和outpustream流替换原先的流。再在filter中用我们的自定义的HttpServletResponseWrapper的子类对response进行包装,将包装后的response传到dofilter(request,response)方法中,这样后端的过滤器和servlet中获取到的response就是我们自己包装后的response,那么后面的filter和servlet都在使用我们包装后的response,我们就可以通过我们的response获得response里的内容了。

第一步包装类: 下面的类只是替换了writer没有替换outputstrem,所以我们只能获取到后端使用gerWriter方法写的内容。

package respones;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class WrapperResponse extends HttpServletResponseWrapper {   
	   private MyPrintWriter tmpWriter;   
	   private ByteArrayOutputStream output;   
	   public WrapperResponse(HttpServletResponse httpServletResponse) {   
	      super(httpServletResponse);   
	      output = new ByteArrayOutputStream();   
	      tmpWriter = new MyPrintWriter(output);   
	   }   
	   public void finalize() throws Throwable {   
	      super.finalize();   
	      output.close();   
	      tmpWriter.close();   
	   }   
	   public String getContent() {   
	      try {   
	         tmpWriter.flush();   //刷新该流的缓冲,详看java.io.Writer.flush()   
	         String s = tmpWriter.getByteArrayOutputStream().toString("UTF-8");   
	         //此处可根据需要进行对输出流以及Writer的重置操作   
	       //比如tmpWriter.getByteArrayOutputStream().reset()   
	         return s;   
	      } catch (UnsupportedEncodingException e) {   
	         return "UnsupportedEncoding";   
	      }   
	   }   
	  
	   //覆盖getWriter()方法,使用我们自己定义的Writer   
	   public PrintWriter getWriter() throws IOException {   
	      return tmpWriter;   
	   }   
	   public void close() throws IOException {   
	      tmpWriter.close();   
	   }   
	  
	   //自定义PrintWriter,为的是把response流写到自己指定的输入流当中   
	   //而非默认的ServletOutputStream   
	   private static class MyPrintWriter extends PrintWriter {   
	      ByteArrayOutputStream myOutput;   //此即为存放response输入流的对象   
	  
	      public MyPrintWriter(ByteArrayOutputStream output) {   
	         super(output);   
	         myOutput = output;   
	      }   
	      public ByteArrayOutputStream getByteArrayOutputStream() {   
	         return myOutput;   
	      }   
	   }   
	}
 
 

filter类:

package respones;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebFilter("/*")
public class GavinFilter implements Filter {
	@Override
	public void destroy() {
		// TODO 自动生成方法存根

	}

	@Override
	public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) arg0;
		HttpServletResponse response = (HttpServletResponse) arg1;
		WrapperResponse wrapperResponse = new WrapperResponse(response);
		chain.doFilter(request, wrapperResponse);
		String html = wrapperResponse.getContent();
		response.getWriter().print(html);
		System.out.println(html);

	}

	@Override
	public void init(FilterConfig arg0) throws ServletException {
		// TODO 自动生成方法存根

	}

}
 
 

 

posted @ 2025-01-07 11:09  CharyGao  阅读(370)  评论(0)    收藏  举报