benzhou 发表于 2017-2-6 07:19:30

转:tomcat源码—redirect和forward的实现

  网上已经有很多关于redirect和forward区别的文章,更多的都是只是一些概念上的描述,虽然在大多情况下,知道这些就已经足够了。但也有例外:forward not working for struts2,why?我也是在工作中碰到了这个问题,才特意看了下tomcat有关这部分的源代码。深刻的了解下也无妨。 
redirect和forward都是属于servlet规范的,不同的servlet容器的实现可能会有一些区别,但原理都是类似的。 

redirect和forward的定义: 
1. redirect(重定向):服务端发送给客户端一个重定向的临时响应头,这个响应头包含重定向之后的URL,客户端用新的URL重新向服务器发送一个请求。 
2. forward(请求转向):服务器程序内部请求转向,这个特性允许前一个程序用于处理请求,而后一个程序用来返回响应。 

Redirect的原理比较简单,它的定义也已经描述的很清楚了,我也不想多讲什么,就贴一段简单的代码吧! 

org.apache.catalina.connector.Response#sendRedirect(String): 
  
Java代码  


[*] public void sendRedirect(String location)   
[*]        throws IOException {  
[*]  
[*]        if (isCommitted())  
[*]            throw new IllegalStateException  
[*]                (sm.getString("coyoteResponse.sendRedirect.ise"));  
[*]  
[*]        // Ignore any call from an included servlet  
[*]        if (included)  
[*]            return;   
[*]  
[*]        // Clear any data content that has been buffered  
[*]        resetBuffer();  
[*]  
[*]        // Generate a temporary redirect to the specified location  
[*]        try {  
[*]            String absolute = toAbsolute(location);  
[*]            setStatus(SC_FOUND);  
[*]            setHeader("Location", absolute);  
[*]        } catch (IllegalArgumentException e) {  
[*]            setStatus(SC_NOT_FOUND);  
[*]        }  
[*]  
[*]        // Cause the response to be finished (from the application perspective)  
[*]        setSuspended(true);  
[*]}  

  
方法行为:先把相对路径转换成绝对路径,再包装一个包含有新的URL的临时响应头,“SC_FOUND”的值是302,就是重定向临时响应头的状态码。如果传入的“location”值不合法,就包装一个404的响应头。 

下面就来看看tomcat是如何实现forward的,forward为什么在struts2下会无效(注解:其实是可以设置的)。 

先看下程序是如何调用forward的: 
Java代码  


[*]req.getRequestDispatcher("testForward").forward(req, resp);  

  
整个过程分两个步骤来执行 
1. 得到一个请求调度器 
2. 通过调度器把请求转发过去。 


第一步骤,获取请求调度器。 
org.apache.catalina.connector.Request#getRequestDispatcher(String) 
Java代码  


[*]      
[*]public RequestDispatcher getRequestDispatcher(String path) {  
[*]  
[*]        if (request == null) {  
[*]            throw new IllegalStateException(  
[*]                            sm.getString("requestFacade.nullRequest"));  
[*]        }  
[*]  
[*]        if (Globals.IS_SECURITY_ENABLED){  
[*]            return (RequestDispatcher)AccessController.doPrivileged(  
[*]                new GetRequestDispatcherPrivilegedAction(path));  
[*]        } else {  
[*]             return request.getRequestDispatcher(path);    
[*]        }  

  
方法行为:把获取RequestDispatcher的任务交个内部的request。它们之间的关系如下所示 

 


org.apache.catalina.connector.RequestFacade和类org.apache.catalina.connector.Request都是实现了javax.servlet.http.HttpServletRequest接口,而RequestFacade内部有包装了个Request,对Request的访问做了些控制,应该是代理模式 

org.apache.catalina.connector.Request#getRequestDispatcher(String) 
Java代码  


[*]public RequestDispatcher getRequestDispatcher(String path) {  
[*]         if (path.startsWith("/"))  
[*]           return (context.getServletContext().getRequestDispatcher(path));  
[*]  
[*]       //省略了部分代码  
[*]       return (context.getServletContext().getRequestDispatcher(relative));   
[*]  
[*]   }  

  
方法行为:把绝对路径转换成相对路径,最终的格式如“/testForward”。若已经是这种格式的相对路径,就无需再转换了。 
接下来就转交给ServletContext来处理,ServletContext是web项目的一个上下文,包含所有的Servlet集合,还定义了一些Servlet与容器之间交互的接口。 
org.apache.catalina.core.ApplicationContext#getRequestDispatcher(String) 
Java代码  


[*]public RequestDispatcher getRequestDispatcher(String path) {  
[*]          //省去部分代码  
[*]          context.getMapper().map(uriMB, mappingData);  
[*]          //省去部分代码  
[*]      Wrapper wrapper = (Wrapper) mappingData.wrapper;  
[*]      String wrapperPath = mappingData.wrapperPath.toString();  
[*]      String pathInfo = mappingData.pathInfo.toString();  
[*]  
[*]      mappingData.recycle();  
[*]        
[*]      // Construct a RequestDispatcher to process this request  
[*]      return new ApplicationDispatcher  
[*]          (wrapper, uriCC.toString(), wrapperPath, pathInfo,   
[*]           queryString, null);   
[*]  }  

  
方法行为:根据路径名“path”找到一个包含有Servlet的Wrapper,最后实例化一个ApplicationDispatcher,并且返回该ApplicationDispatcher。 

该方法里非常关键的一行:context.getMapper().map(uriMB, mappingData)。 
Mapper的类定义我不知道如何描述,就贴上原文吧:Mapper, which implements the servlet API mapping rules (which are derived from the HTTP rules)。 
不过只想了解forward的原理,熟悉map函数就够了。 

org.apache.tomcat.util.http.mapper.Mapper#map(org.apache.tomcat.util.buf.MessageBytes, org.apache.tomcat.util.http.mapper.MappingData): 
Java代码  


[*]public void map(MessageBytes uri, MappingData mappingData)  
[*]    throws Exception {  
[*]  
[*]    uri.toChars();  
[*]    CharChunk uricc = uri.getCharChunk();  
[*]    uricc.setLimit(-1);  
[*]    internalMapWrapper(context, uricc, mappingData);  
[*]  
[*]}  

  
方法行为:。。。。。。。就介绍下参数吧,uri可以理解是path(“/testforward”)的一个变形,而mappingData用于存储当前线程用到的部分数据。该函数是没有返回值的,处理之后的结果就是存放到mappingData里的。 

org.apache.tomcat.util.http.mapper.Mapper#internalMapWrapper(Mapper$Context,org.apache.tomcat.util.buf.CharChunk, org.apache.tomcat.util.http.mapper.MappingData): 
Java代码  


[*]private final void internalMapWrapper(Context context, CharChunk path,  
[*]                                         MappingData mappingData)  
[*]       throws Exception {  
[*]  
[*]       int pathOffset = path.getOffset();  
[*]       int pathEnd = path.getEnd();  
[*]       int servletPath = pathOffset;  
[*]       boolean noServletPath = false;  
[*]  
[*]       int length = context.name.length();  
[*]       if (length != (pathEnd - pathOffset)) {  
[*]           servletPath = pathOffset + length;  
[*]       } else {  
[*]           noServletPath = true;  
[*]           path.append('/');  
[*]           pathOffset = path.getOffset();  
[*]           pathEnd = path.getEnd();  
[*]           servletPath = pathOffset+length;  
[*]       }  
[*]  
[*]       path.setOffset(servletPath);  
[*]  
[*]       // Rule 1 -- Exact Match  
[*]       Wrapper[] exactWrappers = context.exactWrappers;  
[*]       internalMapExactWrapper(exactWrappers, path, mappingData);  
[*]  
[*]       // Rule 2 -- Prefix Match  
[*]       boolean checkJspWelcomeFiles = false;  
[*]       Wrapper[] wildcardWrappers = context.wildcardWrappers;  
[*]       if (mappingData.wrapper == null) {  
[*]           internalMapWildcardWrapper(wildcardWrappers, context.nesting,   
[*]                                      path, mappingData);  
[*]           if (mappingData.wrapper != null && mappingData.jspWildCard) {  
[*]               char[] buf = path.getBuffer();  
[*]               if (buf1] == '/') {  
[*]                   /* 
[*]                    * Path ending in '/' was mapped to JSP servlet based on 
[*]                    * wildcard match (e.g., as specified in url-pattern of a 
[*]                    * jsp-property-group. 
[*]                    * Force the context's welcome files, which are interpreted 
[*]                    * as JSP files (since they match the url-pattern), to be 
[*]                    * considered. See Bugzilla 27664. 
[*]                    */   
[*]                   mappingData.wrapper = null;  
[*]                   checkJspWelcomeFiles = true;  
[*]               } else {  
[*]                   // See Bugzilla 27704  
[*]                   mappingData.wrapperPath.setChars(buf, path.getStart(),  
[*]                                                    path.getLength());  
[*]                   mappingData.pathInfo.recycle();  
[*]               }  
[*]           }  
[*]       }  
[*]  
[*]       if(mappingData.wrapper == null && noServletPath) {  
[*]           // The path is empty, redirect to "/"  
[*]           mappingData.redirectPath.setChars  
[*]               (path.getBuffer(), pathOffset, pathEnd);  
[*]           path.setEnd(pathEnd - 1);  
[*]           return;  
[*]       }  
[*]  
[*]       // Rule 3 -- Extension Match  
[*]       Wrapper[] extensionWrappers = context.extensionWrappers;  
[*]       if (mappingData.wrapper == null && !checkJspWelcomeFiles) {  
[*]           internalMapExtensionWrapper(extensionWrappers, path, mappingData);  
[*]       }  
[*]  
[*]       // Rule 4 -- Welcome resources processing for servlets  
[*]       if (mappingData.wrapper == null) {  
[*]           boolean checkWelcomeFiles = checkJspWelcomeFiles;  
[*]           if (!checkWelcomeFiles) {  
[*]               char[] buf = path.getBuffer();  
[*]               checkWelcomeFiles = (buf1] == '/');  
[*]           }  
[*]           if (checkWelcomeFiles) {  
[*]               for (int i = 0; (i < context.welcomeResources.length)  
[*]                        && (mappingData.wrapper == null); i++) {  
[*]                   path.setOffset(pathOffset);  
[*]                   path.setEnd(pathEnd);  
[*]                   path.append(context.welcomeResources, 0,  
[*]                               context.welcomeResources.length());  
[*]                   path.setOffset(servletPath);  
[*]  
[*]                   // Rule 4a -- Welcome resources processing for exact macth  
[*]                   internalMapExactWrapper(exactWrappers, path, mappingData);  
[*]  
[*]                   // Rule 4b -- Welcome resources processing for prefix match  
[*]                   if (mappingData.wrapper == null) {  
[*]                       internalMapWildcardWrapper  
[*]                           (wildcardWrappers, context.nesting,   
[*]                            path, mappingData);  
[*]                   }  
[*]  
[*]                   // Rule 4c -- Welcome resources processing  
[*]                   //            for physical folder  
[*]                   if (mappingData.wrapper == null  
[*]                       && context.resources != null) {  
[*]                       Object file = null;  
[*]                       String pathStr = path.toString();  
[*]                       try {  
[*]                           file = context.resources.lookup(pathStr);  
[*]                       } catch(NamingException nex) {  
[*]                           // Swallow not found, since this is normal  
[*]                       }  
[*]                       if (file != null && !(file instanceof DirContext) ) {  
[*]                           internalMapExtensionWrapper(extensionWrappers,  
[*]                                                       path, mappingData);  
[*]                           if (mappingData.wrapper == null  
[*]                               && context.defaultWrapper != null) {  
[*]                               mappingData.wrapper =  
[*]                                   context.defaultWrapper.object;  
[*]                               mappingData.requestPath.setChars  
[*]                                   (path.getBuffer(), path.getStart(),   
[*]                                    path.getLength());  
[*]                               mappingData.wrapperPath.setChars  
[*]                                   (path.getBuffer(), path.getStart(),   
[*]                                    path.getLength());  
[*]                               mappingData.requestPath.setString(pathStr);  
[*]                               mappingData.wrapperPath.setString(pathStr);  
[*]                           }  
[*]                       }  
[*]                   }  
[*]               }  
[*]  
[*]               path.setOffset(servletPath);  
[*]               path.setEnd(pathEnd);  
[*]           }  
[*]                                         
[*]       }  

  
方法行为:通过“path”从“context”里找到对应的Servlet,存放到“mappingData”里。 
可以看到这里有7个匹配Servlet规则: 
1. Rule 1 -- Exact Match:精确匹配,匹配web.xml配置的格式如“<url-pattern>/testQiu</url-pattern>”的Servlet 
2. Rule 2 -- Prefix Matcha:前缀匹配,匹配的Servlet格式如“<url-pattern>/testQiu/*</url-pattern>” 
3. Rule 3 -- Extension Match:扩展匹配,匹配jsp或者jspx 
4. ---Rule 4a -- Welcome resources processing for exact macth: 
5. ---Rule 4b -- Welcome resources processing for prefix match: 
6. ---Rule 4c -- Welcome resources processing for physical folder: 
7. Rule 7 --如果前面6条都没匹配到,那就返回org.apache.catalina.servlets.DefaultServlet。 

其实这里真正的匹配的是Wapper,而不是Servlet,因为Wapper最重要的一个属性就是Servlet,说成“匹配Servlet”是为了更容易的表达。 

至此返回RequestDispatcher就结束了。 



接下来就是讲解RequestDispatcher.forward了。Forward的就不贴出全部的源代码,只贴一些重要的片段,绝大部分的逻辑都在org.apache.catalina.core.ApplicationDispatcher类里。 
先描述下过程: 
1. 设置request里的部分属性值,如:请求的路径、参数等。 
2. 组装一个FilterChain链,调用doFilter方法。 
3. 最后根据实际情况调用Filter的doFilter函数或者Servlet的service函数。 

注:FilterChain和Filter是两个不同的接口,两个接口的UML 



org.apache.catalina.core.ApplicationDispatcher#doForward(ServletRequest,ServletResponse): 
Java代码  


[*]private void doForward(ServletRequest request, ServletResponse response)  
[*]        throws ServletException, IOException  
[*]         //省略了部分代码  
[*]        // Handle an HTTP named dispatcher forward  
[*]        if ((servletPath == null) && (pathInfo == null)) {  
[*]//省略了部分代码  
[*]        } else {// Handle an HTTP path-based forward  
[*]            ApplicationHttpRequest wrequest =  
[*]                (ApplicationHttpRequest) wrapRequest(state);  
[*]            String contextPath = context.getPath();  
[*]            HttpServletRequest hrequest = state.hrequest;  
[*]            if (hrequest.getAttribute(Globals.FORWARD_REQUEST_URI_ATTR) == null) {  
[*]                wrequest.setAttribute(Globals.FORWARD_REQUEST_URI_ATTR,  
[*]                                      hrequest.getRequestURI());  
[*]                wrequest.setAttribute(Globals.FORWARD_CONTEXT_PATH_ATTR,  
[*]                                      hrequest.getContextPath());  
[*]                wrequest.setAttribute(Globals.FORWARD_SERVLET_PATH_ATTR,  
[*]                                      hrequest.getServletPath());  
[*]                wrequest.setAttribute(Globals.FORWARD_PATH_INFO_ATTR,  
[*]                                      hrequest.getPathInfo());  
[*]                wrequest.setAttribute(Globals.FORWARD_QUERY_STRING_ATTR,  
[*]                                      hrequest.getQueryString());  
[*]            }  
[*]   
[*]            wrequest.setContextPath(contextPath);  
[*]            wrequest.setRequestURI(requestURI);  
[*]            wrequest.setServletPath(servletPath);  
[*]            wrequest.setPathInfo(pathInfo);  
[*]            if (queryString != null) {  
[*]                wrequest.setQueryString(queryString);  
[*]                wrequest.setQueryParams(queryString);  
[*]            }  
[*]  
[*]            processRequest(request,response,state);  
[*]        }  
[*]        }  
[*]//省略了部分代码  
[*]    }  

  
第1步:设置新的request的属性: 
         
Java代码  


[*]wrequest.setContextPath(contextPath);  
[*]          wrequest.setRequestURI(requestURI);  
[*]          wrequest.setServletPath(servletPath);  
[*]          wrequest.setPathInfo(pathInfo);  
[*]          if (queryString != null) {  
[*]              wrequest.setQueryString(queryString);  
[*]              wrequest.setQueryParams(queryString);  
[*]          }  

  


第2步:组装FitlerChain链,根据web.xml配置信息,是否决定添加Filter---- 
<filter-mapping> 
<filter-name>struts2</filter-name> 
<url-pattern>/*</url-pattern> 
<dispatcher>REQUEST</dispatcher> 
</filter-mapping> 


org.apache.catalina.core.ApplicationFilterFactory#createFilterChain(ServletRequest, Wrapper, Servlet): 
Java代码  


[*]public ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {  
[*]        //省略部分代码  
[*]            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 ,dispatcher)) {  
[*]                continue;  
[*]            }  
[*]            if (!matchFiltersURL(filterMaps, requestPath))  
[*]                continue;  
[*]            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)  
[*]                context.findFilterConfig(filterMaps.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);  
[*]  
[*]}  

  


如果是<dispatcher>REQUEST</dispatcher>,那就不添加Filter,默认设置是REQUEST 
如果是<dispatcher>FORWARD</dispatcher>,添加Filter到FilterChain。 

第3步:调用doFilter或者service,代码删减了很多。 

org.apache.catalina.core.ApplicationFilterChain#doFilter(ServletRequest, ServletResponse): 
Java代码  


[*]  public void doFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {  
[*]            internalDoFilter(request,response);  
[*]  }  
[*]  
[*]  
[*]org.apache.catalina.core.ApplicationFilterChain#internalDoFilter(ServletRequest, ServletResponse)  
[*]private void internalDoFilter(ServletRequest request,   
[*]                                  ServletResponse response)  
[*]        throws IOException, ServletException {  
[*]  
[*]        // Call the next filter if there is one  
[*]        if (pos < n) {  
[*]                    filter.doFilter(request, response, this);  
[*]            return;  
[*]        }  
[*]       servlet.service((HttpServletRequest) request,(HttpServletResponse) response);              
[*]}  

  




如果我对Filter非常了解的,根本就不需要花那么多时间去查看tomcat源代码。只要在web.xml增加一点配置就OK了。 
Java代码  


[*]<filter-mapping>  
[*]        <filter-name>struts2</filter-name>  
[*]        <url-pattern>/*</url-pattern>  
[*]        <dispatcher>REQUEST</dispatcher>  
[*]        <dispatcher>FORWARD</dispatcher>  
[*]</filter-mapping>  
页: [1]
查看完整版本: 转:tomcat源码—redirect和forward的实现