Tomcat的过滤诀窍
副标题#e#
过滤是 Tomcat 4 的新成果。它是 Servlet 2.3 类型的一部门,而且最终将 为所有支持此尺度的 J2EE 容器的厂商所回收执行。开拓人员将可以或许用过滤器来 实现以前利用未便的或难以实现的成果,这些成果包罗:
资源会见(Web 页、JSP 页、servlet)的定制身份认证
应用措施级的会见资源的审核和记录
应用措施范畴内对资源的加密会见,它成立在定制的加密方案基本上
对被会见资源的实时转换,包罗从 servlet 和 JSP 的动态输出
这个清单虽然并没有一一摆列,但它让您劈头体验到了过滤所带来的特别价 值。在本文中,我们将具体接头 Servlet 2.3 的过滤,来看一看过滤器是如何 共同 J2EE 处理惩罚模子的。不像其它传统的过滤方案,Servlet 2.3过滤是成立在 嵌套挪用的基本上的。我们来研究一下这一不同是奈何在架构上与新的高机能 Tomcat 4 设计取得一致的。最后,我们将得到一些编写及测试两个 Servlet 2.3过滤器的实际履历。这些过滤器只完成很简朴的成果,使我们得以将留意力 会合于编写过滤器以及如何将它们集成进 Web 应用措施的机制。
作为 Web 应用措施构建模块的过滤器
在物理布局上,过滤器是 J2EE Web 应用措施中的应用措施级的 Java 代码 组件。除了 servlet 和 JSP 页以外,遵循 Servlet 2.3 类型编码的开拓人员 能将过滤器作为在 Web 应用措施中插手勾当行为的机制。与在特定的 URL 上工 作的 servlet 和 JSP 页差异,过滤器接入 J2EE 容器的处理惩罚管道,并能超过由 Web 应用措施提供的 URL 子集(或所有 URL)举办事情。图 1 说明白过滤是在 那边共同 J2EE 请求处理惩罚的。
图 1.过滤器与 J2EE 请求处理惩罚
#p#副标题#e#
兼容 Servlet 2.3 的容器答允过滤器在请求被处理惩罚(通过 Servlet 引擎) 以前以及请求得处处理惩罚 今后(过滤器将可以会见响应)会见 Web 请求。
在这些环境下,过滤器可以:
在请求得处处理惩罚以前修改请求的标题
提供它本身的请求版本以供处理惩罚
在请求处理惩罚今后和被传回给用户以前修改响应
先取得由容器举办的所有请求处理惩罚,并发生本身的响应
比过滤器的可用性更为重要的是,接入 J2EE 处理惩罚管道需要建设不行移植的 、容器专用的和系统范畴的扩展机制(如 Tomcat 3 拦截器)。
观念上的 Tomcat过滤
差异于在 Apache、IIS 或 Netscape 处事器中能找到的熟悉的过滤机制, Servlet 2.3过滤器并非成立在挂钩式函数挪用上。事实上, Tomcat 4 级此外 引擎架构离开了传统的 Tomcat 3.x 版本。新的 Tomcat 4 引擎代替了在请求处 理的差异阶段挪用挂钩式要领的整体式引擎,它在内部利用了一系列的嵌套挪用、包装请求及响应。差异的过滤器和资源处理惩罚器组成了一个链。
在传统架构中:
每次接管到请求,挂钩式要领就被挪用,岂论它们是否执行(有时甚至是空 的)。
要领的浸染域及并发干系(每个要领大概在差异的线程上被挪用)不答允在 处理惩罚沟通的请求时简朴、高效地共享差异挂钩式要领挪用间的变量和信息。
在新架构中:
嵌套的要领挪用通过一系列过滤器实现,它仅有应用于当前请求的过滤器组 成;基于挂钩式挪用的传统执行方法需要在处理惩罚短句中挪用挂钩式例程,纵然一 个特定短句的处理惩罚逻辑不起任何浸染。
局部变量在实际的过滤要领返回之前都作保存,而且可用(因为上游过滤器 的挪用总在仓库上,期待后续挪用的返回)。
这一新架构为此后的 Tomcat 机能调解与优化提供了一个新的、更 工具友好 的基本。Servlet 2.3过滤器是这个新的内部架构的自然扩展。该架构为 Web 应用措施设计人员提供了一个可移植的执行过滤行为的要领。
挪用链
所有过滤器都听从挪用的过滤器链,并通过界说明晰的接口获得执行。一个 执行过滤器的 Java 类必需执行这一 javax.servlet.Filter 接口。这一接口含 有三个过滤器必需执行的要领:
doFilter(ServletRequest, ServletResponse, FilterChain) :这是一个完 成过滤行为的要领。这同样是上游过滤器挪用的要领。引入的 FilterChain 对 象提供了后续过滤器所要挪用的信息。
init(FilterConfig) :这是一个容器所挪用的初始化要领。它担保了在第一 次 doFilter() 挪用前由容器挪用。您能获取在 web.xml 文件中指定的初始化 参数。
destroy() :容器在粉碎过滤器实例前, doFilter() 中的所有勾当都被该 实例终止后,挪用该要领。
请留意: Filter 接口的要领名及语义在最近的几个 beta 周期中曾有过不 断的改变。Servlet 2.3 类型仍未处于最后的草案阶段。在 Beta 1 中,该接口 包罗 setFilterConfig() 和 getFilterConfig() 要领,而不是 init() 和 destroy() 。
#p#分页标题#e#
嵌套挪用在 doFilter() 要领执行中产生。除非您成立一个过滤器明晰阻止 所有后续处理惩罚(通过其它过滤器及资源处理惩罚器),不然过滤器必然会在 doFilter 要领中作以下的挪用:
FilterChain.doFilter(request, response);
安装过滤器:界说与映射
容器通过 Web 应用措施中的设置描写符 web.xml 文件相识过滤器。有两个 新的标志与过滤器相关: <filter> 和 <filter-mapping> 。应该 指定它们为 web.xml 文件内 <web-app> 标志的子标志。
过滤器界说的元素
<filter> 标志是一个过滤器界说,它肯定有一个 <filter- name> 和 <filter-class> 子元素。<filter-name> 子元素给 出了一个与过滤器实例相关的、基于文本的名字。<filter-class> 指定 了由容器载入的实际类。您能随意地包括一个 <init-param> 子元素为过 滤器实例提供初始化参数。譬喻,下面的过滤器界说指定了一个叫做 IE Filter 的过滤器:
清单 1.过滤器界说标志
<web-app>
<filter>
<filter-name>IE Filter</filter-name>
<filter-class>com.ibm.devworks.filters.IEFilter</filter- class>
</filter>
</web-app>
容器处理惩罚 web.xml 文件时,它凡是为找到的每个过滤器界说建设一个过滤器 实例。这一实例用来处事所有的可用URL 请求;因此,以线程安详的方法编写过滤器是最为重要的。
过滤器映射及子元素
<filter-mapping> 标志代表了一个过滤器的映射,指定了过滤器会对 其发生浸染的 URL 的子集。它必需有一个 <filter-name> 子元素与能找 到您但愿映射的过滤器的过滤器界说相对应。接下来,您可以利用<servlet-name> 或 <url-pattern> 子元素来指定映射。 <servlet-name> 指定了一个过滤器应用的 servlet (在 web.xml 文件 中的其它处所已界说)。您能利用<url-pattern> 来指定一个该过滤器 应用的 URL 的子集。譬喻, /* 的样式用来代表该过滤器映射应用于该应用程 序用到的每个 URL,而 /dept/humanresources/* 的样式则表白该过滤器映射只 应用于人力资源部专有的 URL。
容器利用这些过滤器映射来确定一个特定的过滤器是否应参加某个特定的请 求。清单 1 是为应用措施的所有 URL 界说的应用于 IE Filter 的一个过滤器 映射:
清单 2.过滤器映射标志
<filter-mapping>
<filter-name>IE Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
建设一个简朴的过滤器
此刻该来界说我们的第一个过滤器了。这是一个不重要的过滤器,查抄请求 标题以确定是不是利用Internet Explorer 欣赏器来查察 URL 的。假如是 Internet Explorer 欣赏器,过滤器就显示“拒绝会见”的信息。尽量操纵并不 重要,但这个示例演示了:
一个过滤器的一般分解
一个在请求达到资源处理惩罚器前查抄其标题信息的过滤器
如何编写一个过滤器来阻止基于运行时间检测到的条件(验证参数、源 IP、 时间…等等)的后续处理惩罚
此过滤器的源代码作为 IEFilter.java , com.ibm.devworks.filters 包的 一部门位于源代码宣布区中。此刻就让我们来仔细研究一下该过滤器的代码。
清单 3. 利用Filter 接口
public final class IEFilter implements Filter {
private FilterConfig filterConfig = null;
所有的过滤器都须执行 Filter 接口。我们建设了一个局部变量以容纳由容 器在初始化过滤器时通报进来的 filterConfig 。这有时产生在第一次挪用doFilter() 前。
清单 4. doFilter 要领
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
String browserDet =
((HttpServletRequest) request).getHeader("User- Agent").toLowerCase();
if ( browserDet.indexOf("msie") != -1) {
PrintWriter out = response.getWriter();
out.println ("<html><head></head><body>");
out.println("<h1>Sorry, page cannot be displayed!</h1>");
out.println("</body></html>");
out.flush();
return;
}
#p#分页标题#e#
doFilter() 完成了大部门事情。我们来查抄一下叫做“用户署理”标题的请 求标题。所有的欣赏器都提供这个标题。我们将其转换成小写字母,然后查找说 明问题的标识字符串 "msie"。假如检测到了 Internet Explorer,我们就从响 应工具中获取一个 PrintWriter 来写出本身的响应。在写出了定制的响应后, 要领无需连到其它过滤器就能返回。这就是过滤器阻止后续处理惩罚的要领。
假如欣赏器并非 Internet Explorer,我们就能举办正常的链式操纵,让后 续过滤器和处理惩罚器能在获得请求时得到执行的时机:
清单 5. 举办正常链式操纵
chain.doFilter(request, response);
}
随后,我们大致地执行该过滤器中的 init() 和 destroy() 要领:
清单 6. init() 和 destroy() 要领
public void destroy() {
}
public void init(FilterConfig filterConfig) {
this.filterConfig = filterConfig;
}
}
测试 IEFilter
假设您安装了 Tomcat 4 beta 3 (或更新版本)并能利用,请按下列步调启 动 IEFilter 并运行:
在 $TOMCAT_HOME/conf 目次下的 server.xml 文件里建设一个新的应用措施 上下文,如下所示:
<!-- Tomcat Examples Context -->
<Context path="/examples" docBase="examples" debug="0"
reloadable="true">
...
</Context>
<Context path="/devworks" docBase="devworks" debug="0"
reloadable="true">
<Logger className="org.apache.catalina.logger.FileLogger"
prefix="localhost_devworks_log." suffix=".txt"
timestamp="true"/>
</Context>
编辑代码区的 devworks/WEB-INF 下的 web.xml 文件,以包罗下列的过滤器 界说及映射:
<web-app>
<filter>
<filter-name>IE Filter</filter-name>
<filter-class>com.ibm.devworks.filters.IEFilter</filter- class>
</filter>
<filter-mapping>
<filter-name>IE Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
在 $TOMCAT_HOME/webapps 目次下建设一个叫做 devworks 的新目次,并将 所有 devworks 目次下的对象(包罗所有子目次)从源代码区复制到该位置。现 在就筹备好启动 Tomcat 4 了。
利用下面的 URL 来会见一个简朴的 index.html 页面: http://<hostname>/devworks/index.html
假如您利用的是 Internet Explorer,就能瞥见如图 2 所示的定制的“拒绝 会见”信息。
图 2. IEFilter 在碰着 Internet Explorer 的运行结果
假如您利用的是 Netscape,那就能瞥见如图 3 所示简直切的 HTML 页面。
图 3. IEFilter 用Netscape 欣赏器的欣赏结果
编写转换资源的过滤器
此刻该来试一下更巨大的过滤器了。该过滤器:
从过滤器界说的实例初始化参数中读取一组 "search" 及 "replace" 文本
过滤被会见的 URL,将呈现的第一个 "search" 文本替代为 "replace" 文本
在我们深入研究这个过滤器的进程中,您将对内容转换/替代过滤器的架构加 深相识。沟通的架构能用于任何加密、压缩及转换(如由 XSLT 转换来的 SML)过滤器。
焦点机要是在链式处理惩罚的进程中通报一个定制的响应工具的包装版本。该定 制的包装响应工具须埋没原响应工具(从而对其实现 包装),并提供一个定制 的流以供后续处理惩罚器写入。假如事情(文本替换、转换、压缩、加密…等)能迅 速完成,定制流的执行就能中止后续记录并完成需要的事情。然后定制的流就会 将经转换的数据写入包装的响应工具(也就是说,简朴的字符替换加密)。假如 事情无法迅速完成,定制的流就需期待,直到后续处理惩罚器完成对流的写入(也就 是说,当其封锁或刷新流时)。然后它才完成转换事情,并将经转换的输出功效 写入“真正的”响应中。
#p#分页标题#e#
在我们的过滤器( ReplaceTextFilter )中,定制的包装响应工具叫作 ReplaceTextWrapper 。定制流的执行叫做 ReplaceTextStream 。您能在 com.ibm.devworks.filters 包中的 ReplaceTextFilter.java 文件里找到源代 码。此刻就让我们来研究一下源代码吧。
清单 7. ReplaceTextStream 类
class ReplaceTextStream extends ServletOutputStream {
private OutputStream intStream;
private ByteArrayOutputStream baStream;
private boolean closed = false;
private String origText;
private String newText;
public ReplaceTextStream(OutputStream outStream,
String searchText,
String replaceText) {
intStream = outStream;
baStream = new ByteArrayOutputStream();
origText = searchText;
newText = replaceText;
}
这是定制的输出流代码。intStream 变量包括了对来自响应工具的实际流的 引用。baStream 是我们输出流的缓冲版本,后续处理惩罚器就写入这里。closed 标 记标明白 close() 是否在此实例流中被挪用。结构器未来自响应工具的流引用存储起来并建设了缓冲流。它还将文本字符串存储起来供今后的替代操纵利用。
清单 8. write() 要领
public void write(int i) throws java.io.IOException {
baStream.write(i);
}
我们须提供本身的源于 ServletOutputStream 的 write() 要领。在此,我 们虽然是写入缓冲流。所有来自后续处理惩罚器的更高级输出要领都将以最初级别使 用该要领,以担保所有的写入都指向缓冲流。
清单 9. close() 及 flush() 要领
public void close() throws java.io.IOException {
if (!closed) {
processStream();
intStream.close();
closed = true;
}
}
public void flush() throws java.io.IOException {
if (baStream.size() != 0) {
if (! closed) {
processStream(); // need to synchronize the flush!
baStream = new ByteArrayOutputStream();
}
}
}
close() 及 flush() 要领是我们完成转换的语句。按照后续处理惩罚器差异,其 中的一个或两个措施都有大概被挪用。我们利用布尔型的 closed 标识来制止异 常环境。请留意,我们将实际的替代事情委托给了 processStream() 要领。
清单 10. processStream() 要领
public void processStream() throws java.io.IOException {
intStream.write(replaceContent(baStream.toByteArray ()));
intStream.flush();
}
processStream() 要领将经转换的输出功效从 baStream 写入其已经配有的 intStream 中去。转换事情独立于 replaceContent() 要领。
清单 11. replaceContent() 要领
public byte [] replaceContent(byte [] inBytes) {
String retVal ="";
String firstPart="";
String tpString = new String(inBytes);
String srchString = (new String (inBytes)).toLowerCase();
int endBody = srchString.indexOf(origText);
if (endBody != -1) {
firstPart = tpString.substring(0, endBody);
retVal = firstPart + newText +
tpString.substring(endBody + origText.length ());
} else {
retVal=tpString;
}
return retVal.getBytes();
}
}
replaceContent() 是产生搜索与替换的语句。它将一个字节数组作为输入并 返回一个字节数组,建设一个原始的观念接口。事实上,我们能通过替换该要领 中的逻辑部门来完成任何形式的转换。这里,我们举办很是简朴的文本替换。
清单 12. ReplaceTextWrapper 类
class ReplaceTextWrapper extends HttpServletResponseWrapper {
private PrintWriter tpWriter;
private ReplaceTextStream tpStream;
public ReplaceTextWrapper(ServletResponse inResp, String searchText,
String replaceText)
throws java.io.IOException {
super((HttpServletResponse) inResp);
tpStream = new ReplaceTextStream (inResp.getOutputStream(),
searchText,
replaceText);
tpWriter = new PrintWriter(tpStream);
}
public ServletOutputStream getOutputStream() throws java.io.IOException {
return tpStream;
}
public PrintWriter getWriter() throws java.io.IOException {
return tpWriter;
}
}
#p#分页标题#e#
我们定制的包装响应能利便地从辅佐类 HttpServletResponseWrapper 中导 出。这一类大致地执行很多要领,答允我们简朴地包围 getOutputStream() 方 法以及 getWriter() 要领,提供了定制输出流的实例。
清单 13. ReplaceTextWrapper() 要领
public final class ReplaceTextFilter implements Filter {
private FilterConfig filterConfig = null;
private String searchText = ".";
private String replaceText = ".";
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
ReplaceTextWrapper myWrappedResp = new ReplaceTextWrapper( response,
searchText, replaceText);
chain.doFilter(request, myWrappedResp);
myWrappedResp.getOutputStream().close();
}
public void destroy() {
}
最后,尚有过滤器自己。它所做的不外是利用FilterChain 为递交响应后续 建设一个定制的包装响应实例,如下所示:
清单 14. 建设一个定制的包装响应实例
public void init (FilterConfig filterConfig) {
String tpString;
if (( tpString = filterConfig.getInitParameter("search") ) != null)
searchText = tpString;
if (( tpString = filterConfig.getInitParameter("replace") ) != null)
replaceText = tpString;
this.filterConfig = filterConfig;
}
}
在 init 要领中,我们取回了过滤器界说中指定的初始参数。filterConfig 工具中的 getInitParameter() 要领便于用来实现这个目标。
测试 ReplaceTextFilter
如果您利用先前提及的步调测试了 IEFilter ,并将所有文件复制到了 $TOMCAT/webapps/devworks 下,您就能用以下的步调来测试 ReplaceTextFilter :
编辑 $TOMCAT/wepapps/devworks/WEB-INF 目次下的 web.xml 文件,以包括 下列过滤器的界说及映射:
<web-app>
<filter>
<filter-name>Replace Text Filter</filter-name>
<filter- class>com.ibm.devworks.filters.ReplaceTextFilter</filter- class>
<init-param>
<param-name>search</param-name>
<param-value>cannot</param-value>
</init-param>
<init-param>
<param-name>replace</param-name>
<param-value>must not</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Replace Text Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
从头启动 Tomcat。
此刻,请用下面的 URL 来会见 index.html 页面: http://<host name>:8080/devworks/index.html
请留意, ReplaceTextFilter 是如何迅速地将 cannot变为 must not 的。 想确信过滤利用了所有资源,您可以实验编写输出功效含有字符串 cannot的 JSP 页或 servlet。
过滤器链分列顺序的重要性
过滤器链式分列的顺序取决于 web.xml 描写信息内 <filter- mapping> 语句的顺序。在大大都环境下,过滤器链式分列的顺序长短常重要 的。也就是说,在应用A过滤器前利用B过滤器与在利用B过滤器前利用A过滤器所获得的功效是完全差异的。假如一个应用措施中利用了一个以上的过滤 器,那么在写入 <filter-mapping> 语句的时候要小心。
我们能等闲地通过分列 web.xml 文件中 <filter-mapping> 的顺序看 到这一结果:
清单 15.过滤的顺序 — IE Filter 为先
#p#分页标题#e#
<web-app>
<filter-mapping>
<filter-name>IE Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Replace Text Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
此刻,用Internet Explorer 载入 index.html 页。您能看到由于 IE Filter 处于过滤器链中的第一位,所以 Replace Text Filter 没有时机执行。 因此,输出的信息是 "Sorry, page cannot be displayed!"
此刻,将 <filter-mapping> 标志的顺序颠倒过来,变为:
清单 16.过滤的顺序 — Replace Text Filter 为先
<web- app>
<filter-mapping>
<filter-name>Replace Text Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>IE Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
再次用Internet Explorer 载入 index.html 页面。这次, Replace Text Filter 先执行,将包装的响应工具提供应 IE Filter 。在 IE Filter 写入了 其定制的响应后,专用的响应工具在输出功效达到最终用户处以前完成转换。故 而,我们看到了这条信息:Sorry, page must not be displayed!
在应用措施中利用过滤器
写这篇文章的时候, Tomcat 4 正处于 beta 周期的后期,正式刊行的日子 已为期不远。主要的 J2EE 容器厂商都筹备好了将 Servlet 2.3 类型整合到其 产物中去。对付 Servlet 2.3过滤器如何事情有一个根基的相识有助于您在设 计及编写基于 J2EE 的应用措施时往本身的东西库中再插手一件多成果的东西。