Tomcat JavaWeb中URL重写用Forward方式并使其后filter可以拦截内部转发

众所周知,在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
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值