众所周知,在Servlet2.3规范之前Filter会拦截包括内部转发和外部转发,也就是会拦截Forward方式的转发请求。
在2.4规范之后,Filter只会拦截外部请求,诸如以下方式转发的请求,Filter不会拦截
request.getRequestDispatcher(newUri).forward(req, response);
如果需要拦截Foward方式的转发则需要配置web.xml内filter的配置,需要指明拦截dispatcher为FORWARD的方式。
<filter-mapping>
<filter-name>ActionFilter</filter-name>
<url-pattern>*.do</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
一般情况下,开发者都能控制这个流程,但是倘若做一些url的重写,下面举一个例子:
现有一个struts项目配置的filter拦截请求的地址为*.action没有配置dispacher为FORWARD方式的请求,在struts的filter前有一个filter做url重写,需求是将请求地址栏的比如http://localhost/a.html转发到http://localhost/a.action做伪静态化。
那么当url rewrite filter将请求改写为a.aciton时,由于请求进入tomcat时为a.html,struts拦截的是*.action请求,filterchain并没有将struts filter加入到其中,如果只是重写getRequestUri,getRequestUrl,请求还是无法到达struts。
另一种方式就是做FORWARD转发,结果也是可想而知,struts filter将不会拦截FORWARD方式的转发。
有的同学会说,那我把struts filter的dispacher加上FORWARD方式不就好了?
但是得考虑到,这可能是一个庞大的项目,不一定可以这样随意修改。
下面说下思路:
在跟踪了tomcat dispacher那一块的源码后发现,在请求进入tomcat后,tomcat 在request请求实现上会创建filterchain,然后根据路径是否匹配filter
public ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {
// get the dispatcher type
DispatcherType dispatcher = null;
if (request.getAttribute(DISPATCHER_TYPE_ATTR) != null) {
dispatcher = (DispatcherType) request.getAttribute(DISPATCHER_TYPE_ATTR);
}
String requestPath = null;
Object attribute = request.getAttribute(DISPATCHER_REQUEST_PATH_ATTR);
if (attribute != null){
requestPath = attribute.toString();
}
// If there is no servlet to execute, return null
if (servlet == null)
return (null);
boolean comet = false;
// Create and initialize a filter chain object
ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
Request req = (Request) request;
comet = req.isComet();
if (Globals.IS_SECURITY_ENABLED) {
// Security: Do not recycle
filterChain = new ApplicationFilterChain();
if (comet) {
req.setFilterChain(filterChain);
}
} else {
filterChain = (ApplicationFilterChain) req.getFilterChain();
if (filterChain == null) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
// Request dispatcher in use
filterChain = new ApplicationFilterChain();
}
filterChain.setServlet(servlet);
filterChain.setSupport
(((StandardWrapper)wrapper).getInstanceSupport());
// Acquire the filter mappings for this Context
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();
// If there are no filter mappings, we are done
if ((filterMaps == null) || (filterMaps.length == 0))
return (filterChain);
// Acquire the information we will need to match filter mappings
String servletName = wrapper.getName();
// Add the relevant path-mapped filters to this filter chain
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMaps[i], requestPath))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
boolean isCometFilter = false;
if (comet) {
try {
isCometFilter = filterConfig.getFilter() instanceof CometFilter;
} catch (Exception e) {
// Note: The try catch is there because getFilter has a lot of
// declared exceptions. However, the filter is allocated much
// earlier
}
if (isCometFilter) {
filterChain.addFilter(filterConfig);
}
} else {
filterChain.addFilter(filterConfig);
}
}
// Add filters that match on servlet name second
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersServlet(filterMaps[i], servletName))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
boolean isCometFilter = false;
if (comet) {
try {
isCometFilter = filterConfig.getFilter() instanceof CometFilter;
} catch (Exception e) {
// Note: The try catch is there because getFilter has a lot of
// declared exceptions. However, the filter is allocated much
// earlier
}
if (isCometFilter) {
filterChain.addFilter(filterConfig);
}
} else {
filterChain.addFilter(filterConfig);
}
}
// Return the completed filter chain
return (filterChain);
}
仔细观察如下代码
//注意这个地方
//在遍历所有的filter判断当前请求是否需要添加filter
//matchDispatcher则是实现判断的地方
// Add filters that match on servlet name second
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersServlet(filterMaps[i], servletName))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
boolean isCometFilter = false;
if (comet) {
try {
isCometFilter = filterConfig.getFilter() instanceof CometFilter;
} catch (Exception e) {
// Note: The try catch is there because getFilter has a lot of
// declared exceptions. However, the filter is allocated much
// earlier
}
if (isCometFilter) {
filterChain.addFilter(filterConfig);
}
} else {
filterChain.addFilter(filterConfig);
}
}
接下来看下matchDispatcher方法的实现
//很明显这里判断了下filter的dispacher type和参数type是否匹配
//也就是刚说的在filter的filter-mapping里指明的Dispacher type
//如果是在filter-mapping里有指明当前转发方式则返回true否则返回false
private boolean matchDispatcher(FilterMap filterMap, DispatcherType type) {
switch (type) {
case FORWARD : {
if ((filterMap.getDispatcherMapping() & FilterMap.FORWARD) > 0) {
return true;
}
break;
}
case INCLUDE : {
if ((filterMap.getDispatcherMapping() & FilterMap.INCLUDE) > 0) {
return true;
}
break;
}
case REQUEST : {
if ((filterMap.getDispatcherMapping() & FilterMap.REQUEST) > 0) {
return true;
}
break;
}
case ERROR : {
if ((filterMap.getDispatcherMapping() & FilterMap.ERROR) > 0) {
return true;
}
break;
}
case ASYNC : {
if ((filterMap.getDispatcherMapping() & FilterMap.ASYNC) > 0) {
return true;
}
break;
}
}
return false;
}
这样子看来就很明显了,只要把type参数改为REQUEST就可以实现让转发的FORWARD请求伪装成REQUEST请求了,也就是这样请求就可以和一个新的请求一样再走一遍正常流程了。
那么这个dispatcher参数是从哪里获取的呢?
回到第一段代码的开始,可以看到
// get the dispatcher type
DispatcherType dispatcher = null;
if (request.getAttribute(DISPATCHER_TYPE_ATTR) != null) {
dispatcher = (DispatcherType) request.getAttribute(DISPATCHER_TYPE_ATTR);
}
/**
* Request dispatcher state.
*/
public static final String DISPATCHER_TYPE_ATTR = "org.apache.catalina.core.DISPATCHER_TYPE";
这样子就明朗了
请求在被进行分发的时候,分发器会读取请求的类型,也就是DispacherType,具体就是读取请求的"org.apache.catalina.core.DISPATCHER_TYPE"的属性,然后再装载filterchain的时候根据这个请求类型来装载filter,没有指明拦截FORWARD的filter不会装载在FORWAD的请求上,基于此种方式,我们只要将request的"org.apache.catalina.core.DISPATCHER_TYPE"属性改为DispacherType.REQUEST就可以达到让FORWARD请求变成REQUEST请求了,从而让我们的内部转发可以达到外部转发的效果。
接下来放两个实现类
public class FitrstFilter implements Filter {
private String servletPath;
public FitrstFilter() {
}
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
DispatcherWrapper req = new DispatcherWrapper((HttpServletRequest) request);
System.out.println("第一个filter");
// 表明是请求第一次进入filter
if (req.getAttribute("com.zhibei.filter.FORWARD") == null) {
// 设置参数
req.setAttribute("com.zhibei.filter.FORWARD", Boolean.TRUE);
// 请求分发
String newUri = req.getRequestURI().replace(this.servletPath, "").replace("index", "index2");
request.getRequestDispatcher(newUri).forward(req, response);
} else {
// 设置为false防止请求进入死循环
req.setAttribute("com.zhibei.filter.FORWARD", Boolean.FALSE);
chain.doFilter(request, response);
}
}
public void init(FilterConfig fConfig) throws ServletException {
this.servletPath = fConfig.getServletContext().getContextPath();
}
private class DispatcherWrapper extends HttpServletRequestWrapper {
public DispatcherWrapper(HttpServletRequest request) {
super(request);
}
@Override
public Object getAttribute(String name) {
// TOMCAT内部的请求分发属性
if (name.equals("org.apache.catalina.core.DISPATCHER_TYPE")) {
Object forward = this.getAttribute("com.zhibei.filter.FORWARD");
if (forward == Boolean.TRUE) {
//这里返回变量有两种,在Tomcat6中并没有DispacherType这个类,与REQUEST相对应的是一个int类型的数值8
//这个属性在Tomcat7后才被抽象为一个类
//所以这里要判断下如果有这个类则返回DisPacherType.REQUEST否则返回8即可
try {
Class.forName("javax.servlet.DispatcherType");
return javax.servlet.DispatcherType.REQUEST;
} catch (Exception e) {
return 8;
}
}
}
return super.getAttribute(name);
}
}
}
DispacherWrapper重写getAttribute,当获取request的"org.apache.catalina.core.DISPATCHER_TYPE"属性时,返回DispacherType.REQUEST即可,为了防止请求被无限转发进入死循环,加一个标志来判断即可,实现非常轻松。
public class SecondFilter implements Filter {
public SecondFilter() {
}
public void destroy() {
}
//在第二个filter中打印相关信息
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("第二个filter");
HttpServletRequest req = (HttpServletRequest) request;
System.out.println("URI:" + req.getRequestURI());
System.out.println("URL" + req.getRequestURL());
System.out.println("QueryString:" + req.getQueryString());
Map<String, String[]> paramMap = req.getParameterMap();
for (String key : paramMap.keySet()) {
for (String value : paramMap.get(key)) {
System.out.println("key:" + key + ",value:" + value);
}
}
chain.doFilter(request, response);
}
public void init(FilterConfig fConfig) throws ServletException {
}
}
请求地址:
http://localhost:8080/filter/index.html?sdsd45=dw48d
打印结果:
第一个filter
第一个filter
第二个filter
URI:/filter/index2.html
URLhttp://localhost:8080/filter/index2.html
QueryString:sdsd45=dw48d
key:sdsd45,value:dw48d
和预想的一样,FirstFilter进入了两次,第一次为原始请求,第二次为FORWARD的请求
工程下载:https://download.csdn.net/download/yy417168602/10445381