[译]The Essentials of Filters (II)
原文:http://java.sun.com/products/servlet/Filters.html
编写定制的Request和Response
到目前位置我们已经见过一些简单的例子了。现在我们可以老练一点,来看看一个过滤器改变从客户端传过来的Request或改变返回至客户端的Response的情况。过滤器有很多方法改变Request或Response。例如,过滤器可以添加一个属性到Request或者它可以插入数据或转换数据到Response。
一个过滤器要改变一个Response对象通常必须在Response返回客户端之前截获它。实现的方法是产生一个Response的代替流(stand-in stream)用来传递到Servlet。这个代替流可以防止原Response流完成之后被Servlet关闭,并且允许过滤器改变Servlet的Response。
为了将这个代替流传递到Servlet中,过滤器通过创建一个Response的"wrapper"覆盖getWriter或getOutPutStream方法来返回这个代替流。 wrapper被传递到过滤器链的doFilter方法。Wrapper的方法默认调用被包装的Request或Response对象。这个方法遵循《设计模式-可复用面向对象软件的基础》(英文书名:Design Patterns, Elements of Reusable Object-Oriented Software)中所描述的Wraper或Decorator模式。以下部分描述了前面所说的点击计数器的过滤器和其他类型的过滤器是如何使用wrapper的。
要包装Request方法,你应该在一个从ServletRequestWrapper或HttpServletRequestWrapper继承的对象中进行包装。要包装Response方法,你应该在一个从ServletResponseWrapper或HttpServletResponseWrapper继承的对象中进行包装。
在前面“编写过滤器”(Programming Filters)一节提到点击计数过滤器往Response插入计数器的值。在HitCounterFilter中被省略掉的代码如下:
PrintWriter out = response.getWriter();
CharResponseWrapper wrapper = new CharResponseWrapper((HttpServletResponse)response);
chain.doFilter(request, wrapper);
if(wrapper.getContentType().equals("text/html")) {
CharArrayWriter caw = new CharArrayWriter();
caw.write(wrapper.toString().substring(0,wrapper.toString().indexOf("</body>")-1));
caw.write("<p>\nYou are visitor number <font color='red'>" + counter.getCounter() + "</font>");
caw.write("\n</body></html>");
response.setContentLength(caw.toString().length());
out.write(caw.toString());
} else
out.write(wrapper.toString());
out.close();
}HitCounterFilter在一个CharResponseWrapper对象中包装Response。CharResponseWrapper覆盖了getWriter方法,使它返回一个代替流,Servlet在过滤器链的最后将Response写入这个代替流。当chain.doFilter方法返回的时候,HitCounterFilter从PrintWriter重新得到Servlet的Response,且如果Response是一个HTML Response的话就将它写到缓冲区中。过滤器将计数器的值塞到缓冲区中,重置Response头中的“内容长度”(content length header),最后将缓冲区的内容写到Response流中。
public class CharResponseWrapper extends HttpServletResponseWrapper {
private CharArrayWriter output;
public String toString() {
return output.toString();
}
public CharResponseWrapper(HttpServletResponse response){
super(response);
output = new CharArrayWriter();
}
public PrintWriter getWriter(){
return new PrintWriter(output);
}
}
例子:压缩Response
另外一个修改Response的过滤器包含在与Tomcat Servlet引擎一同发布的例子中。尽管高速Internet已经变得更加的稀松平常了,但是还是很有必要有效的利用带宽。写一个压缩过滤器是很简单的,因为你可以把他挂到任何的Servlet上来缩小Response的大小。
就像计数器过滤器一样,压缩过滤器创建了一个代替流来让Servlet写入,在这里是CompressionResponseStream,并且包装传递到Servlet的Response。
只有在客户端可以接受一个压缩的Response的时候,过滤器才创建Wrapper和代替流。Servlet把它的Response写到它从Wrapper中得到的压缩流中。一旦Response的数据的大小比传递给过滤器的一个初始化参数的值大的话,CompressionResponseStream就覆盖write方法来将Response的数据写到GZIPOutputStream中。
例子:转换Response
最后我们要讨论的是一个XSLT过滤器,XSLT是一种转换XML数据的语言。你可以使用XSLT来转换一个XML文档到最终面向用户的格式,如HTML或PDF,或者另一种XML格式。有一些例程包括:
●把XML文档从一个公司要求的格式转换到另外一个公司要求的格式。
●按照用户的喜好来定制网页的感观。
●通过查看User-Agent头来选择一个样式表,让同一个Web应用响应不同类型的客户端,例如,WML电话和cHTML电话。
考虑一个响应产品清单请求的web service。下面的XML文档是这种Response的一个例子:
<book>
<isbn>123</isbn>
<title>Web Servers for Fun and Profit</title>
<quantity>10</quantity>
<price>$17.95</price>
</book>下面的XSL样式表将这个XML文档加工成面向用户描述的HTML格式的清单,和XML格式的面向机器的版本。
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="book">
<html>
<body>There are <xsl:value-of select="quantity"/>
copies of <i><xsl:value-of select="title"/></i> available.
</body>
</html>
</xsl:template>
</xsl:stylesheet>
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="no"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="book">
<xsl:element name="book">
<xsl:attribute name="isbn">
<xsl:value-of select="isbn"/>
</xsl:attribute>
<xsl:element name="quantity">
<xsl:value-of select="quantity"/>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

下面的XSLT过滤器依靠Request的参数值来使用样式表转换Response。过滤器根据Request的参数设置Response的ContentType。然后这个Response在CharResponseWrapper被包装并传递到过滤器链的doFilter方法。过滤器链的最后的元素是前面提到的一个返回清单的Response的Servlet。当doFilter方法返回时,过滤器从Wrapper中重新得到Response的数据并使用样式表来转换。
public void doFilter(ServletRequest request,ServletResponse response,
FilterChain chain)throws IOException, ServletException {
String contentType;
String styleSheet;
String type = request.getParameter("type");
if (type == null || type.equals("")) {
contentType = "text/html";
styleSheet = "/xml/html.xsl";
} else {
if (type.equals("xml")) {
contentType = "text/plain";
styleSheet = "/xml/xml.xsl";
} else {
contentType = "text/html";
styleSheet = "/xml/html.xsl";
}
}
response.setContentType(contentType);
String stylepath=filterConfig.getServletContext().getRealPath(styleSheet);
Source styleSource = new StreamSource(stylePath);
PrintWriter out = response.getWriter();
CharResponseWrapper responseWrapper =
new CharResponseWrapper((HttpServletResponse)response);
chain.doFilter(request, wrapper);
// Get response from servlet
StringReader sr = new StringReader(new String(wrapper.getData()));
Source xmlSource = new StreamSource((Reader)sr);
try {
TransformerFactory transformerFactory =TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer(styleSource);
CharArrayWriter caw = new CharArrayWriter();
StreamResult result = new StreamResult(caw);
transformer.transform(xmlSource, result);
response.setContentLength(caw.toString().length());
out.write(caw.toString());
} catch(Exception ex) {
out.println(ex.toString());
out.write(wrapper.toString());
}
}

(未完待续)

浙公网安备 33010602011771号