來自:
http://jiayanjujyj.iteye.com/blog/1028914
網上已經有很多關於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):
- 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的:
- req.getRequestDispatcher("testForward").forward(req, resp);
整個過程分兩個步驟來執行
1. 得到一個請求調度器
2. 通過調度器把請求轉發過去。
第一步驟,獲取請求調度器。
org.apache.catalina.connector.Request#getRequestDispatcher(String)
- 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)
- 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)
- 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):
- 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):
- 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 (buf[pathEnd - 1] == '/') {
- /*
- * 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 = (buf[pathEnd - 1] == '/');
- }
- if (checkWelcomeFiles) {
- for (int i = 0; (i < context.welcomeResources.length)
- && (mappingData.wrapper == null); i++) {
- path.setOffset(pathOffset);
- path.setEnd(pathEnd);
- path.append(context.welcomeResources[i], 0,
- context.welcomeResources[i].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):
- 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的屬性:
- 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):
- 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[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);
- }
- }
- //省略部分代碼
- // 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):
- 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了。
- <filter-mapping>
- <filter-name>struts2</filter-name>
- <url-pattern>/*</url-pattern>
- <dispatcher>REQUEST</dispatcher>
- <dispatcher>FORWARD</dispatcher>
- </filter-mapping>