内容简介:注意: 本文基于Struts2.2.1版本进行的代码分析首先先大致看一下StrutsPrepareAndExecuteFilter.java的doFilter的实现上面对dofilter进行了简单分析,下面我们进行详细分析,先整理出几个点:
注意: 本文基于Struts2.2.1版本进行的代码分析
首先先大致看一下StrutsPrepareAndExecuteFilter.java的doFilter的实现
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { // 设置request/response编码 prepare.setEncodingAndLocale(request, response); // 设置Action上下文 prepare.createActionContext(request, response); // 将当前Dispatcher加入线程管理,用于解决线程安全问题 prepare.assignDispatcherToThread(); // 排除拦截路径,具体在<constant name="struts.action.excludePattern" value=".*validcode.*,.*tohtml.*"/>中设置 if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { chain.doFilter(request, response); } else { // 对request进行封装 request = prepare.wrapRequest(request); // 根据Request的url获取映射的Action ActionMapping mapping = prepare.findActionMapping(request, response, true); // 找不到映射就认为是静态文件,继续下一个filter if (mapping == null) { boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { chain.doFilter(request, response); } } else { // 执行action execute.executeAction(request, response, mapping); } } } finally { prepare.cleanupRequest(request); } }
上面对dofilter进行了简单分析,下面我们进行详细分析,先整理出几个点:
- 设置request/response编码
- 设置Action上下文
- Dispatcher加入线程管理
- 排除除拦截路径
- 对request进行封装
- 根据request获取action映射
- 执行Action
0x01 设置request/response编码
首先看一下 prepare.setEncodingAndLocale(request, response); 的代码实现:
public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) { dispatcher.prepare(request, response); }
其实还是调用dispatcher对象, 我们继续看prepare的实现:
public void prepare(HttpServletRequest request, HttpServletResponse response) { String encoding = null; // 搜索代码defaultEncoding并没有被初始化设置 if (defaultEncoding != null) { encoding = defaultEncoding; } Locale locale = null; if (defaultLocale != null) { locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale()); } if (encoding != null) { try { // 设置编码 request.setCharacterEncoding(encoding); } catch (Exception e) { LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e); } } // 设置response编码 if (locale != null) { response.setLocale(locale); } // 该变量是根据struts.dispatcher.parametersWorkaround进行设置 // 对于某些Java EE服务器,不支持HttpServletRequest调用getParameterMap()方法,此时可以设置该属性值为true来解决该问题。 // 该属性的默认值是false。对于WebLogic、Orion和OC4J服务器,通常应该设置该属性为true。 if (paramsWorkaroundEnabled) { request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request } }
0x02 设置Action上下文
prepare.createActionContext(request, response); 代码实现:
/** * * 创建action的context和初始化本地线程 */ public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) { ActionContext ctx; // 这个计数器是来干什么的??? Integer counter = 1; // 计数器累加 Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER); if (oldCounter != null) { counter = oldCounter + 1; } ActionContext oldContext = ActionContext.getContext(); if (oldContext != null) { // detected existing context, so we are probably in a forward ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap())); } else { // 创建ValueStack对象,关于该对象之前我们分析过, // ValueStack是XWork对OGNL的计算进行扩展的一个特殊的数据结构, 主要是对OGNL三要素中的Root对象进行扩展. ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); // 首先看到的createContextMap是将Request对象转换成map上下文对象对象,具体我们下方分析. // 并将这个context放进stack内,也就是放到root对象内 stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext)); // 初始化个Action上下文对象 ctx = new ActionContext(stack.getContext()); } // 存放计数器 request.setAttribute(CLEANUP_RECURSION_COUNTER, counter); // 设置当前线程的操作上下文 ActionContext.setContext(ctx); return ctx; }
上面我们说到了createContextMap,我们看一下代码实现:
/** * Create a context map containing all the wrapped request objects * * @param request The servlet request * @param response The servlet response * @param mapping The action mapping * @param context The servlet context * @return A map of context objects */ public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping, ServletContext context) { // 下方注释写的非常清晰,也就是对reqeust对象进行map封装(包括request的参数、session、等等)最后创建个上下文。 // request map wrapping the http request objects Map requestMap = new RequestMap(request); // 下面注释写的都非常清晰,没有什么特殊关注点 // parameters map wrapping the http parameters. ActionMapping parameters are now handled and applied separately Map params = new HashMap(request.getParameterMap()); // session map wrapping the http session Map session = new SessionMap(request); // application map wrapping the ServletContext Map application = new ApplicationMap(context); Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context); if (mapping != null) { extraContext.put(ServletActionContext.ACTION_MAPPING, mapping); } return extraContext; } /** * Merge all application and servlet attributes into a single <tt>HashMap</tt> to represent the entire * <tt>Action</tt> context. * * @param requestMap a Map of all request attributes. * @param parameterMap a Map of all request parameters. * @param sessionMap a Map of all session attributes. * @param applicationMap a Map of all servlet context attributes. * @param request the HttpServletRequest object. * @param response the HttpServletResponse object. * @param servletContext the ServletContextmapping object. * @return a HashMap representing the <tt>Action</tt> context. */ public HashMap<String,Object> createContextMap(Map requestMap, Map parameterMap, Map sessionMap, Map applicationMap, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) { HashMap<String,Object> extraContext = new HashMap<String,Object>(); extraContext.put(ActionContext.PARAMETERS, new HashMap(parameterMap)); extraContext.put(ActionContext.SESSION, sessionMap); extraContext.put(ActionContext.APPLICATION, applicationMap); Locale locale; if (defaultLocale != null) { locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale()); } else { locale = request.getLocale(); } extraContext.put(ActionContext.LOCALE, locale); //extraContext.put(ActionContext.DEV_MODE, Boolean.valueOf(devMode)); extraContext.put(StrutsStatics.HTTP_REQUEST, request); extraContext.put(StrutsStatics.HTTP_RESPONSE, response); extraContext.put(StrutsStatics.SERVLET_CONTEXT, servletContext); // helpers to get access to request/session/application scope extraContext.put("request", requestMap); extraContext.put("session", sessionMap); extraContext.put("application", applicationMap); extraContext.put("parameters", parameterMap); AttributeMap attrMap = new AttributeMap(extraContext); extraContext.put("attr", attrMap); return extraContext; }
代码很直观,我们在Struts2的模板里用到的session对象就是在这里初始化的,例如
<s:property value="#session.user.name">
0x03 Dispatcher加入线程管理
prepare.assignDispatcherToThread(); 代码实现:
/** * 将调度程序分配给调度程序线程本地 */ public void assignDispatcherToThread() { Dispatcher.setInstance(dispatcher); } // Dispatcher.java /** * Provide a thread local instance. */ private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>(); /** * Store the dispatcher instance for this thread. * * @param instance The instance */ public static void setInstance(Dispatcher instance) { Dispatcher.instance.set(instance); }
0x04 排除除拦截路径
prepare.isUrlExcluded(request, excludedPatterns) 实现代码:
/** * Check whether the request matches a list of exclude patterns. * * @param request The request to check patterns against * @param excludedPatterns list of patterns for exclusion * * @return <tt>true</tt> if the request URI matches one of the given patterns */ public boolean isUrlExcluded( HttpServletRequest request, List<Pattern> excludedPatterns ) { if (excludedPatterns != null) { String uri = getUri(request); for ( Pattern pattern : excludedPatterns ) { if (pattern.matcher(uri).matches()) { return true; } } } return false; }
也就是个正则match, 我们看一下excludedPatterns是在哪里初始化的
public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter { // protected PrepareOperations prepare; // protected ExecuteOperations execute; protected List<Pattern> excludedPatterns = null; public void init(FilterConfig filterConfig) throws ServletException { InitOperations init = new InitOperations(); try { // FilterHostConfig config = new FilterHostConfig(filterConfig); // init.initLogging(config); // Dispatcher dispatcher = init.initDispatcher(config); // init.initStaticContentLoader(config, dispatcher); // prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher); // execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher); this.excludedPatterns = init.buildExcludedPatternsList(dispatcher); // postInit(dispatcher, filterConfig); } finally { // init.cleanup(); } }
init.buildExcludedPatternsList(dispatcher);的实现代码:
/** * Extract a list of patterns to exclude from request filtering * * @param dispatcher The dispatcher to check for exclude pattern configuration * * @return a List of Patterns for request to exclude if apply, or <tt>null</tt> * * @see org.apache.struts2.StrutsConstants#STRUTS_ACTION_EXCLUDE_PATTERN */ public List<Pattern> buildExcludedPatternsList( Dispatcher dispatcher ) { // 设置排除拦截路径 return buildExcludedPatternsList(dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN)); } private List<Pattern> buildExcludedPatternsList( String patterns ) { if (null != patterns && patterns.trim().length() != 0) { List<Pattern> list = new ArrayList<Pattern>(); String[] tokens = patterns.split(","); for ( String token : tokens ) { list.add(Pattern.compile(token.trim())); } return Collections.unmodifiableList(list); } else { return null; } }
看到是通过StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN也就是struts.action.excludePattern配置的,如:
<constant name="struts.action.excludePattern" value="/res/.*,/css/.*,/images/.*,/js/.*,/services/.*" />
0x05 对request进行封装
request = prepare.wrapRequest(request); 代码实现:
/** * Wraps the request with the Struts wrapper that handles multipart requests better * @return The new request, if there is one * @throws ServletException */ public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException { HttpServletRequest request = oldRequest; try { // Wrap request first, just in case it is multipart/form-data // parameters might not be accessible through before encoding (ww-1278) request = dispatcher.wrapRequest(request, servletContext); } catch (IOException e) { String message = "Could not wrap servlet request with MultipartRequestWrapper!"; throw new ServletException(message, e); } return request; }
看一下 request = dispatcher.wrapRequest(request, servletContext); 的实现:
/** * Wrap and return the given request or return the original request object. * </p> * This method transparently handles multipart data as a wrapped class around the given request. * Override this method to handle multipart requests in a special way or to handle other types of requests. * Note, {@link org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper} is * flexible - look first to that object before overriding this method to handle multipart data. * * @param request the HttpServletRequest object. * @param servletContext Our ServletContext object * @return a wrapped request or original request. * @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper * @throws java.io.IOException on any error. */ public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException { // don't wrap more than once if (request instanceof StrutsRequestWrapper) { return request; } String content_type = request.getContentType(); // 如果request的header中content-type的value包含multipart/form-data // 封装成MultiPartRequestWrapper对象 // 这部分代码太经典,s2-045安全漏洞就是从这里开始的 if (content_type != null && content_type.indexOf("multipart/form-data") != -1) { MultiPartRequest mpr = null; //check for alternate implementations of MultiPartRequest Set<String> multiNames = getContainer().getInstanceNames(MultiPartRequest.class); if (multiNames != null) { for (String multiName : multiNames) { if (multiName.equals(multipartHandlerName)) { mpr = getContainer().getInstance(MultiPartRequest.class, multiName); } } } if (mpr == null ) { mpr = getContainer().getInstance(MultiPartRequest.class); } request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext)); } else { // 封装成StrutsRequestWrapper request = new StrutsRequestWrapper(request); } return request; }
上面代码我们拆成两个部分进行分析:
1. 封装成MultiPartRequestWrapper
if (content_type != null && content_type.indexOf("multipart/form-data") != -1) { MultiPartRequest mpr = null; //check for alternate implementations of MultiPartRequest // 获取Struts-default.xml里的配置,也就是 // <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="struts" class="org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest" scope="default"/> // <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="jakarta" class="org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest" scope="default" /> Set<String> multiNames = getContainer().getInstanceNames(MultiPartRequest.class); if (multiNames != null) { for (String multiName : multiNames) { // 根据multipartHandlerName配置的multipart Name决定采用那个MultiPartRequest类的实现类 // 通过阅读代码,我们知道multipartHandlerName是由 <constant name="struts.multipart.handler" value="jakarta" /> // 也就是默认是jakarta,实现类是org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest, 再次怀念一下s2-045神洞 if (multiName.equals(multipartHandlerName)) { // 获取org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest类的实例化 mpr = getContainer().getInstance(MultiPartRequest.class, multiName); } } } if (mpr == null ) { mpr = getContainer().getInstance(MultiPartRequest.class); } // 这里的第三个参数是文件保存目录 // 下方我们看一下MultiPartRequestWrapper的大致实现 request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext)); } else { .... } // MultiPartRequestWrapper.java 实现 public class MultiPartRequestWrapper extends StrutsRequestWrapper { protected static final Logger LOG = LoggerFactory.getLogger(MultiPartRequestWrapper.class); Collection<String> errors; MultiPartRequest multi; /** * Process file downloads and log any errors. * * @param request Our HttpServletRequest object * @param saveDir Target directory for any files that we save * @param multiPartRequest Our MultiPartRequest object */ public MultiPartRequestWrapper(MultiPartRequest multiPartRequest, HttpServletRequest request, String saveDir) { super(request); multi = multiPartRequest; try { // 用.JakartaMultiPartRequest类的parse方法解析reqeust对象,具体代码就不看了,也就是调用commons-fileupload组件进行上传文件 multi.parse(request, saveDir); for (Object o : multi.getErrors()) { String error = (String) o; addError(error); } } catch (IOException e) { addError("Cannot parse request: "+e.toString()); } }
Content-Type为multipart/form-data的情况,我们已经分析完了,我们看一下非上传请求的正常http请求是怎么处理的吧.
2. 封装成StrutsRequestWrapper
if (content_type != null && content_type.indexOf("multipart/form-data") != -1) { // .... 省略 } else { // 封装成StrutsRequestWrapper request = new StrutsRequestWrapper(request); }
看一下StrutsRequestWrapper的实现:
public class StrutsRequestWrapper extends HttpServletRequestWrapper { /** * The constructor * @param req The request */ public StrutsRequestWrapper(HttpServletRequest req) { super(req); } /** * Gets the object, looking in the value stack if not found * * @param s The attribute key */ public Object getAttribute(String s) { if (s != null && s.startsWith("javax.servlet")) { // don't bother with the standard javax.servlet attributes, we can short-circuit this // see WW-953 and the forums post linked in that issue for more info return super.getAttribute(s); } ActionContext ctx = ActionContext.getContext(); Object attribute = super.getAttribute(s); if (ctx != null) { // 如果原生的getAttribute获取不到数据,则从ValueStack中获取 if (attribute == null) { boolean alreadyIn = false; Boolean b = (Boolean) ctx.get("__requestWrapper.getAttribute"); if (b != null) { alreadyIn = b.booleanValue(); } // note: we don't let # come through or else a request for // #attr.foo or #request.foo could cause an endless loop if (!alreadyIn && s.indexOf("#") == -1) { try { // If not found, then try the ValueStack ctx.put("__requestWrapper.getAttribute", Boolean.TRUE); ValueStack stack = ctx.getValueStack(); if (stack != null) { attribute = stack.findValue(s); } } finally { ctx.put("__requestWrapper.getAttribute", Boolean.FALSE); } } } } return attribute; } }
也就是对servlet的HttpServletRequestWrapper对象的实现,目的就是为了实现了getAttribute方法, 如果原生的getAttribute获取不到数据,则从ValueStack中获取。
0x06 根据request获取action映射
这种分析思路并不完美,容易让人阅读起来,思路中断,建议多看几遍
ActionMapping mapping = prepare.findActionMapping(request, response, true); 具体实现代码:
/** * Finds and optionally creates an {@link ActionMapping}. It first looks in the current request to see if one * has already been found, otherwise, it creates it and stores it in the request. No mapping will be created in the * case of static resource requests or unidentifiable requests for other servlets, for example. */ public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response) { return findActionMapping(request, response, false); } private static final String STRUTS_ACTION_MAPPING_KEY = "struts.actionMapping"; /** * Finds and optionally creates an {@link ActionMapping}. if forceLookup is false, it first looks in the current request to see if one * has already been found, otherwise, it creates it and stores it in the request. No mapping will be created in the * case of static resource requests or unidentifiable requests for other servlets, for example. * @param forceLookup if true, the action mapping will be looked up from the ActionMapper instance, ignoring if there is one * in the request or not */ public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) { ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY); if (mapping == null || forceLookup) { try { // 根据reqeust获取action映射,存放到Attribute,目的是为了性能优化 // 这里的ActionMapper.class的具体实现类是org.apache.struts2.dispatcher.mapper.DefaultActionMapper // 下方我们分析一下DefaultActionMapper的getMapping实现 mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager()); if (mapping != null) { request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping); } } catch (Exception ex) { dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); } } return mapping; }
分析一下DefaultActionMapper的getMapping实现:
/* * (non-Javadoc) * * @see org.apache.struts2.dispatcher.mapper.ActionMapper#getMapping(javax.servlet.http.HttpServletRequest) */ public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) { ActionMapping mapping = new ActionMapping(); // 获取uri // 例如我们访问的是http://localhost/index.action,那么uri就是/index.action String uri = getUri(request); // 这段代码的意思应该是为了处理这样的uri /index;xxxxx.action // 具体可以看tomcat的uri处理 int indexOfSemicolon = uri.indexOf(";"); uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri; // 删除扩展,如传递的是/index.action,最后保留的是index uri = dropExtension(uri, mapping); if (uri == null) { return null; } // 根据uri解析name和Namespace, parseNameAndNamespace(uri, mapping, configManager); // 这个函数实现了Struts2的DynamicMethod功能,具体下方有详细分析 handleSpecialParameters(request, mapping); if (mapping.getName() == null) { return null; } // 这里实现了Struts2另外一种动态方法调用功能,具体下方有详细分析 parseActionName(mapping); return mapping; }
上方代码我们分为2个部分分析
1. parseNameAndNamespace方法分析
/** * Parses the name and namespace from the uri * * @param uri The uri * @param mapping The action mapping to populate */ protected void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) { String namespace, name; int lastSlash = uri.lastIndexOf("/"); if (lastSlash == -1) { namespace = ""; name = uri; } else if (lastSlash == 0) { // ww-1046, assume it is the root namespace, it will fallback to // default // namespace anyway if not found in root namespace. namespace = "/"; name = uri.substring(lastSlash + 1); } else if (alwaysSelectFullNamespace) { // Simply select the namespace as everything before the last slash namespace = uri.substring(0, lastSlash); name = uri.substring(lastSlash + 1); } else { // Try to find the namespace in those defined, defaulting to "" // 获取配置信息 Configuration config = configManager.getConfiguration(); String prefix = uri.substring(0, lastSlash); namespace = ""; boolean rootAvailable = false; // Find the longest matching namespace, defaulting to the default // 通过配置文件,根据uri获取到的namespace,去查找配置里的namespace for (Object cfg : config.getPackageConfigs().values()) { String ns = ((PackageConfig) cfg).getNamespace(); if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) { if (ns.length() > namespace.length()) { namespace = ns; } } if ("/".equals(ns)) { rootAvailable = true; } } // 得到name name = uri.substring(namespace.length() + 1); // Still none found, use root namespace if found if (rootAvailable && "".equals(namespace)) { namespace = "/"; } } if (!allowSlashesInActionNames && name != null) { int pos = name.lastIndexOf('/'); if (pos > -1 && pos < name.length() - 1) { name = name.substring(pos + 1); } } mapping.setNamespace(namespace); mapping.setName(name); }
这里我们不得不贴上我们的应用Struts.xml配置:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN" "http://struts.apache.org/dtds/struts-2.5.dtd"> <struts> <constant name="struts.enable.DynamicMethodInvocation" value="true"/> <constant name="struts.devMode" value="true" /> <package name="myPackage" extends="struts-default"> <default-action-ref name="index" /> <action name="index" class="com.jd.IndexAction"> <result>/WEB-INF/jsp/index.jsp</result> </action> </package> </struts>
所以当我们访问/index.action的时候,parseNameAndNamespace方法处理过后的name是”index”,而namespace是”/“.
2. handleSpecialParameters方法分析
handleSpecialParameters(request, mapping); 代码实现:
/** * Special parameters, as described in the class-level comment, are searched * for and handled. * * * 搜索和处理特殊参数,如 class-level 注释中所述 * * @param request The request * @param mapping The action mapping */ public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping) { // handle special parameter prefixes. Set<String> uniqueParameters = new HashSet<String>(); Map parameterMap = request.getParameterMap(); for (Iterator iterator = parameterMap.keySet().iterator(); iterator .hasNext();) { String key = (String) iterator.next(); // Strip off the image button location info, if found // 翻译:剥离图像按钮位置信息(如果找到) // 懵逼了,这是什么鬼? 可能是兼容某个struts2的image标签库的吧。。我猜的,总之也不是关键代码,不会造成什么影响 if (key.endsWith(".x") || key.endsWith(".y")) { key = key.substring(0, key.length() - 2); } // Ensure a parameter doesn't get processed twice // 参数去重 if (!uniqueParameters.contains(key)) { // 这是是个骚操作,我们单独分析 ParameterAction parameterAction = (ParameterAction) prefixTrie.get(key); if (parameterAction != null) { parameterAction.execute(key, mapping); uniqueParameters.add(key); break; } } } }
上面这个函数中有一段特殊代码,如下:
ParameterAction parameterAction = (ParameterAction) prefixTrie.get(key); if (parameterAction != null) { parameterAction.execute(key, mapping); uniqueParameters.add(key); break; }
首先我们看一下这个prefixTrie是什么鬼?
public class DefaultActionMapper implements ActionMapper { protected static final String METHOD_PREFIX = "method:"; protected static final String ACTION_PREFIX = "action:"; protected static final String REDIRECT_PREFIX = "redirect:"; protected static final String REDIRECT_ACTION_PREFIX = "redirectAction:"; protected boolean allowDynamicMethodCalls = true; protected boolean allowSlashesInActionNames = false; protected boolean alwaysSelectFullNamespace = false; protected PrefixTrie prefixTrie = null; public DefaultActionMapper() { // 初始化prefixTrie // 注意这里的key, 分别为method:、action:、redirect: // 以及这些key对应的ParameterAction对象的实现 // 这里的allowDynamicMethodCalls变量是由配置文件中struts.enable.DynamicMethodInvocation设置,默认开启 prefixTrie = new PrefixTrie() { { put(METHOD_PREFIX, new ParameterAction() { public void execute(String key, ActionMapping mapping) { // 设置Action映射的方法为method:后的value // 访问http://localhost/index.action?method:add,则 // 调用indexAction的add方法 if (allowDynamicMethodCalls) { mapping.setMethod(key.substring( METHOD_PREFIX.length())); } } }); put(ACTION_PREFIX, new ParameterAction() { public void execute(String key, ActionMapping mapping) { // 这里和method有所不同 // 访问http://localhost/index.action?action:user!add , 则 // 调用userAction的add方法 String name = key.substring(ACTION_PREFIX.length()); if (allowDynamicMethodCalls) { int bang = name.indexOf('!'); if (bang != -1) { String method = name.substring(bang + 1); mapping.setMethod(method); name = name.substring(0, bang); } } mapping.setName(name); } }); put(REDIRECT_PREFIX, new ParameterAction() { // 这里不是方法的调用 // 访问 http://localhost/index.action?redirect:user , 则 // 重定向到user.jsp public void execute(String key, ActionMapping mapping) { ServletRedirectResult redirect = new ServletRedirectResult(); container.inject(redirect); redirect.setLocation(key.substring(REDIRECT_PREFIX .length())); mapping.setResult(redirect); } }); put(REDIRECT_ACTION_PREFIX, new ParameterAction() { // 这里的redirectAction: 和 redirect: 的区别是带不带后缀 public void execute(String key, ActionMapping mapping) { String location = key.substring(REDIRECT_ACTION_PREFIX .length()); ServletRedirectResult redirect = new ServletRedirectResult(); container.inject(redirect); String extension = getDefaultExtension(); if (extension != null && extension.length() > 0) { location += "." + extension; } redirect.setLocation(location); mapping.setResult(redirect); } }); } }; }
额…这是Struts2的奇葩设计,”动态方法调用“功能,通过struts.enable.DynamicMethodInvocation设置为true大概该功能, 这里简单介绍一下,具体功能使用google一下:
也就是当我们访问 http://localhost/index.action?method:foo的时候,就会调用index这个Action中的foo方法, 具体阅读上方代码注释.
话说s2-16漏洞问题就是出在redirect:这个重定向功能, 具体参考[1]
TIPS: 这里的PrefixTrie值得研究一下,里面涉及到了Trie树的实现,具体参考[2]
3. parseActionName方法分析
protected ActionMapping parseActionName(ActionMapping mapping) { if (mapping.getName() == null) { return mapping; } if (allowDynamicMethodCalls) { // handle "name!method" convention. String name = mapping.getName(); int exclamation = name.lastIndexOf("!"); if (exclamation != -1) { mapping.setName(name.substring(0, exclamation)); mapping.setMethod(name.substring(exclamation + 1)); } } return mapping; }
从代码中可以看得到,也是一种Dynamic Method的调用方式,例如我们访问: http://locahost/index!add.action , 则调用了indexAction的add方法.
TIPS: 实在受不了为毛一个Dynamic Method功能用各种花样方式实现???
好了,我们已经分析完prepare.findActionMapping(request, response, true)的实现了,成功得到Action的映射关系了,下面开始最后一个环节,执行Action
0x07 执行Action
execute.executeAction(request, response, mapping); 的代码实现
/** * Contains execution operations for filters */ public class ExecuteOperations { /** * Executes an action * @throws ServletException */ public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException { dispatcher.serviceAction(request, response, servletContext, mapping); } // Dispatcher.java public class Dispatcher { /** * Load Action class for mapping and invoke the appropriate Action method, or go directly to the Result. * <p/> * This method first creates the action context from the given parameters, * and then loads an <tt>ActionProxy</tt> from the given action name and namespace. * After that, the Action method is executed and output channels through the response object. * Actions not found are sent back to the user via the {@link Dispatcher#sendError} method, * using the 404 return code. * All other errors are reported by throwing a ServletException. * * @param request the HttpServletRequest object * @param response the HttpServletResponse object * @param mapping the action mapping object * @throws ServletException when an unknown error occurs (not a 404, but typically something that * would end up as a 5xx by the servlet container) * @param context Our ServletContext object */ public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context, ActionMapping mapping) throws ServletException { // 上方分析过这个createContextMap,当时的mapping为空,现在已经拿到mapping,重新设置一下上下文 Map<String, Object> extraContext = createContextMap(request, response, mapping, context); // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); boolean nullStack = stack == null; if (nullStack) { ActionContext ctx = ActionContext.getContext(); if (ctx != null) { stack = ctx.getValueStack(); } } if (stack != null) { // 这里使用了OgnlValueStackFactory对象,该对象实例化了OgnlValueStack对象,并对OgnlValueStack对象进行初始化 // 之前我们对OgnlValueStack做过分析,OgnlValueStack其实就是对ValueStack接口的具体实现,用来对root对象进行ognl计算使用 // 具体可以翻看之前的文章 http://dean.csoio.com/posts/struts2-internals-readbook-note_06/ extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack)); } String timerKey = "Handling request from Dispatcher"; try { UtilTimerStack.push(timerKey); String namespace = mapping.getNamespace(); String name = mapping.getName(); String method = mapping.getMethod(); Configuration config = configurationManager.getConfiguration(); // 这里的ActionProxyFactory的实现类是org.apache.struts2.impl.StrutsActionProxyFactory" // StrutsActionProxyFactory的createActionProxy方法我们下方会做具体分析, 这里就认为返回了一个ActionProxy类, // 具体实现类是org.apache.struts2.impl.StrutsActionProxyFactory ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy( namespace, name, method, extraContext, true, false); request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack()); // 这里代码太关键,下方我们作为第二部分单独分析. // if the ActionMapping says to go straight to a result, do it! if (mapping.getResult() != null) { Result result = mapping.getResult(); result.execute(proxy.getInvocation()); } else { // 调用org.apache.struts2.impl.StrutsActionProxyFactory的execute方法执行 proxy.execute(); } // If there was a previous value stack then set it back onto the request if (!nullStack) { request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); } } catch (ConfigurationException e) { // WW-2874 Only log error if in devMode if(devMode) { String reqStr = request.getRequestURI(); if (request.getQueryString() != null) { reqStr = reqStr + "?" + request.getQueryString(); } LOG.error("Could not find action or result\n" + reqStr, e); } else { LOG.warn("Could not find action or result", e); } sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e); } catch (Exception e) { sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } finally { UtilTimerStack.pop(timerKey); } }
上方我们做了简单分析,其中有多个疑惑,下方我们逐个详细分析.
1. StrutsActionProxyFactory类的createActionProxy方法分析
首先我们看一下createActionProxy方法的调用:
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy( namespace, name, method, extraContext, true, false);
通过调用可以看到这个createActionProxy方法并未在StrutsActionProxyFactory中实现,而是在其父类DefaultActionProxyFactory中实现,我们看一下DefaultActionProxyFactory类的createActionProxy方法实现代码:
// DefaultActionProxyFactory.java public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) { // 将实例化的Action调用器存入容器 ActionInvocation inv = new DefaultActionInvocation(extraContext, true); container.inject(inv); // 这里调用的是StrutsActionProxyFactory类的createActionProxy方法 return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext); }
我们看一下 StrutsActionProxyFactory类的createActionProxy方法实现:
// 其实就是重写了DefaultActionProxyFactory的createActionProxy方法 public class StrutsActionProxyFactory extends DefaultActionProxyFactory { @Override public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) { StrutsActionProxy proxy = new StrutsActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext); container.inject(proxy); // 预处理StrutsActionProxy对象,下方我们看一下prepare方法的具体实现 proxy.prepare(); // 返回预处理完成的StrutsActionProxy对象 return proxy; } }
StrutsActionProxy类的prepare方法的具体实现:
public class StrutsActionProxy extends DefaultActionProxy { private static final long serialVersionUID = -2434901249671934080L; public StrutsActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) { super(inv, namespace, actionName, methodName, executeResult, cleanupContext); } public String execute() throws Exception { ActionContext previous = ActionContext.getContext(); ActionContext.setContext(invocation.getInvocationContext()); try { return invocation.invoke(); } finally { if (cleanupContext) ActionContext.setContext(previous); } } @Override protected void prepare() { super.prepare(); } }
额欧~ 其实就是调用了DefaultActionProxy父类的prepare方法预处理,我们在回去看看了DefaultActionProxy父类的prepare方法实现:
public class DefaultActionProxy implements ActionProxy, Serializable { protected DefaultActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) { this.invocation = inv; this.cleanupContext = cleanupContext; if (LOG.isDebugEnabled()) { LOG.debug("Creating an DefaultActionProxy for namespace " + namespace + " and action name " + actionName); } this.actionName = actionName; this.namespace = namespace; this.executeResult = executeResult; this.method = methodName; } protected void prepare() { String profileKey = "create DefaultActionProxy: "; try { UtilTimerStack.push(profileKey); // 得到action的配置信息 config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName); if (config == null && unknownHandlerManager.hasUnknownHandlers()) { config = unknownHandlerManager.handleUnknownAction(namespace, actionName); } // 没有配置action就抛出异常 if (config == null) { String message; if ((namespace != null) && (namespace.trim().length() > 0)) { message = LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_PACKAGE_ACTION_EXCEPTION, Locale.getDefault(), new String[]{ namespace, actionName }); } else { message = LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_ACTION_EXCEPTION, Locale.getDefault(), new String[]{ actionName }); } throw new ConfigurationException(message); } // 这里比较有意思,如果访问的action没有指定method,那么就调用action的execute方法 // this.method = "execute"; resolveMethod(); if (!config.isAllowedMethod(method)) { throw new ConfigurationException("Invalid method: "+method+" for action "+actionName); } // 这里的invocation就是DefaultActionInvocation对象,调用DefaultActionInvocation的init方法 // 这里init做了太多的事情,里面最关键的是初始化了Struts2拦截器。 // 具体下方会贴出代码,简单看一下就行,就是个action执行前的初始化 invocation.init(this); } finally { UtilTimerStack.pop(profileKey); } }
DefaultActionInvocation对象的init方法:
public void init(ActionProxy proxy) { this.proxy = proxy; Map<String, Object> contextMap = createContextMap(); // Setting this so that other classes, like object factories, can use the ActionProxy and other // contextual information to operate ActionContext actionContext = ActionContext.getContext(); if (actionContext != null) { actionContext.setActionInvocation(this); } createAction(contextMap); if (pushAction) { stack.push(action); contextMap.put("action", action); } invocationContext = new ActionContext(contextMap); // 设置要调用的ActionName invocationContext.setName(proxy.getActionName()); // get a new List so we don't get problems with the iterator if someone changes the list // 初始化拦截器列表 List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors()); interceptors = interceptorList.iterator(); }
Action代理类实例化已分析完成, 我们回去继续看Dispatcher的serviceAction的实现.
2. Action的执行
我们在回顾一下serviceAction的代码实现:
// Dispatcher.java public class Dispatcher { // ... 省略n行代码 public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context, ActionMapping mapping) throws ServletException { // ... 省略n行代码 try { // ... 省略n行代码 ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy( namespace, name, method, extraContext, true, false); // ... 省略n行代码 // result不为空,则调用result类的execute方法执行Action if (mapping.getResult() != null) { Result result = mapping.getResult(); result.execute(proxy.getInvocation()); } else { // 调用org.apache.struts2.impl.StrutsActionProxyFactory的execute方法执行 proxy.execute(); } // If there was a previous value stack then set it back onto the request if (!nullStack) { request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); } } catch (ConfigurationException e) { // ... 省略n行代码 } }
如上我们拆分为2个部分分析:
1. result.execute(proxy.getInvocation());
如果我们正常访问 http://localhost/index.action,这里的result一定为空,只有我们在用Struts2动态方法调用的时候,才会设置result,下方贴上动态方法调用的部分代码,回顾一下:
put(REDIRECT_PREFIX, new ParameterAction() { public void execute(String key, ActionMapping mapping) { ServletRedirectResult redirect = new ServletRedirectResult(); container.inject(redirect); redirect.setLocation(key.substring(REDIRECT_PREFIX .length())); mapping.setResult(redirect); } }); put(REDIRECT_ACTION_PREFIX, new ParameterAction() { public void execute(String key, ActionMapping mapping) { String location = key.substring(REDIRECT_ACTION_PREFIX .length()); ServletRedirectResult redirect = new ServletRedirectResult(); container.inject(redirect); String extension = getDefaultExtension(); if (extension != null && extension.length() > 0) { location += "." + extension; } redirect.setLocation(location); mapping.setResult(redirect); } });
我们看一ServletRedirectResult类的execute方法实现:
public class ServletRedirectResult extends StrutsResultSupport implements ReflectionExceptionHandler { public void execute(ActionInvocation invocation) throws Exception { if (anchor != null) { anchor = conditionalParse(anchor, invocation); } super.execute(invocation); }
可以看到其实是调用其父类StrutsResultSupport的execute方法:
public abstract class StrutsResultSupport implements Result, StrutsStatics { public void execute(ActionInvocation invocation) throws Exception { // 执行location得到string返回结果 lastFinalLocation = conditionalParse(location, invocation); // 调用其子类ServletRedirectResult的doExecute doExecute(lastFinalLocation, invocation); } protected String conditionalParse(String param, ActionInvocation invocation) { if (parse && param != null && invocation != null) { // 执行location得到string返回结果 // 通过OGNL表达式从invocation对象的stack中进行查找location // 这里也就是s2-016漏洞出现的原因 // 这里重写了ParsedValueEvaluator的evaluate,目的是为了对stack.findValue(localtion)返回的string进行编码 // 关于stack.findValue之前有分析过,就是调用Ognl表达式 return TextParseUtil.translateVariables(param, invocation.getStack(), new TextParseUtil.ParsedValueEvaluator() { public Object evaluate(Object parsedValue) { if (encode) { if (parsedValue != null) { try { // use UTF-8 as this is the recommended encoding by W3C to // avoid incompatibilities. return URLEncoder.encode(parsedValue.toString(), "UTF-8"); } catch(UnsupportedEncodingException e) { LOG.warn("error while trying to encode ["+parsedValue+"]", e); } } } return parsedValue; } }); } else { return param; } } protected abstract void doExecute(String finalLocation, ActionInvocation invocation) throws Exception;
从上方代码我们得出执行location得到的stirng。
TIPS : 这里之所以用ognl进行查找,我猜想可能是加强一下重定向功能,但是拿localtion作为表达式。。。呵呵哒
我们继续看一下ServletRedirectResult类的doExecute方法对这个string都做了什么?
/** * Redirects to the location specified by calling {@link HttpServletResponse#sendRedirect(String)}. * * @param finalLocation the location to redirect to. * @param invocation an encapsulation of the action execution state. * @throws Exception if an error occurs when redirecting. */ protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { ActionContext ctx = invocation.getInvocationContext(); HttpServletRequest request = (HttpServletRequest) ctx.get(ServletActionContext.HTTP_REQUEST); HttpServletResponse response = (HttpServletResponse) ctx.get(ServletActionContext.HTTP_RESPONSE); // 这里确定是我们重定向的不是一个外部地址 if (isPathUrl(finalLocation)) { // 加入redirect:的value前没有/, 则获取配置文件中的namespace,追加到location前方 if (!finalLocation.startsWith("/")) { ActionMapping mapping = actionMapper.getMapping(request, Dispatcher.getInstance().getConfigurationManager()); String namespace = null; if (mapping != null) { namespace = mapping.getNamespace(); } if ((namespace != null) && (namespace.length() > 0) && (!"/".equals(namespace))) { finalLocation = namespace + "/" + finalLocation; } else { finalLocation = "/" + finalLocation; } } // if the URL's are relative to the servlet context, append the servlet context path // 将Request的上下文路径也追加到location前方 if (prependServletContext && (request.getContextPath() != null) && (request.getContextPath().length() > 0)) { finalLocation = request.getContextPath() + finalLocation; } // 获取配置文件中Result标签里param标签的配置,进行解析参数 ResultConfig resultConfig = invocation.getProxy().getConfig().getResults().get(invocation.getResultCode()); if (resultConfig != null ) { Map resultConfigParams = resultConfig.getParams(); for (Iterator i = resultConfigParams.entrySet().iterator(); i.hasNext();) { Map.Entry e = (Map.Entry) i.next(); if (!getProhibitedResultParams().contains(e.getKey())) { // 解析参数 // <result name="success" type="redirect"> // <param name="location">foo.jsp</param> // <param name="parse">false</param><!--不解析OGNL--> // </result> // 又调用conditionalParse解析 requestParameters.put(e.getKey().toString(), e.getValue() == null ? "" : conditionalParse(e.getValue().toString(), invocation)); String potentialValue = e.getValue() == null ? "" : conditionalParse(e.getValue().toString(), invocation); if (!supressEmptyParameters || ((potentialValue != null) && (potentialValue.length() > 0))) { requestParameters.put(e.getKey().toString(), potentialValue); } } } } // 将localtion和参数合并 StringBuilder tmpLocation = new StringBuilder(finalLocation); UrlHelper.buildParametersString(requestParameters, tmpLocation, "&"); // add the anchor if (anchor != null) { tmpLocation.append('#').append(anchor); } // 对要重定向的location进行编码 finalLocation = response.encodeRedirectURL(tmpLocation.toString()); } if (LOG.isDebugEnabled()) { LOG.debug("Redirecting to finalLocation " + finalLocation); } // 开始重定向 sendRedirect(response, finalLocation); } // 写入response protected void sendRedirect(HttpServletResponse response, String finalLocation) throws IOException { if (SC_FOUND == statusCode) { response.sendRedirect(finalLocation); } else { response.setStatus(statusCode); response.setHeader("Location", finalLocation); response.getWriter().write(finalLocation); response.getWriter().close(); } }
result执行,分析结束.
2. proxy.execute();
终于要大结局了,来,我们看一下 StrutsActionProxy.execute方法的实现,
public class StrutsActionProxy extends DefaultActionProxy { private static final long serialVersionUID = -2434901249671934080L; public StrutsActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) { super(inv, namespace, actionName, methodName, executeResult, cleanupContext); } public String execute() throws Exception { ActionContext previous = ActionContext.getContext(); ActionContext.setContext(invocation.getInvocationContext()); try { // 这里我们调用的是DefaultActionInvocation的invoke方法 return invocation.invoke(); } finally { if (cleanupContext) ActionContext.setContext(previous); } } @Override protected void prepare() { super.prepare(); } }
DefaultActionInvocation的invoke方法的实现:
public class DefaultActionInvocation implements ActionInvocation { public String invoke() throws Exception { String profileKey = "invoke: "; try { UtilTimerStack.push(profileKey); if (executed) { throw new IllegalStateException("Action has already executed"); } // 遍历Struts2的拦截器,并调用每个拦截器的intercept方法,返回每个拦截器的resultCode // 由于拦截器太多了,我们这里就不分析了 if (interceptors.hasNext()) { final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next(); String interceptorMsg = "interceptor: " + interceptor.getName(); UtilTimerStack.push(interceptorMsg); try { resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this); } finally { UtilTimerStack.pop(interceptorMsg); } } else { // 拦截器调用完后,才是我们用户自定义action的调用,下方我们好好分析一下这个方法 resultCode = invokeActionOnly(); } // this is needed because the result will be executed, then control will return to the Interceptor, which will // return above and flow through again if (!executed) { if (preResultListeners != null) { for (Object preResultListener : preResultListeners) { PreResultListener listener = (PreResultListener) preResultListener; String _profileKey = "preResultListener: "; try { UtilTimerStack.push(_profileKey); listener.beforeResult(this, resultCode); } finally { UtilTimerStack.pop(_profileKey); } } } // 当执行完了用户的indexAction,开始对result进行处理 if (proxy.getExecuteResult()) { executeResult(); } executed = true; } return resultCode; } finally { UtilTimerStack.pop(profileKey); } }
我们看一下invokeActionOnly的实现:
public String invokeActionOnly() throws Exception { return invokeAction(getAction(), proxy.getConfig()); } public Object getAction() { // 拿到我们的indexAction对象 return action; } protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception { // 得到indexAction的执行方法名 String methodName = proxy.getMethod(); if (LOG.isDebugEnabled()) { LOG.debug("Executing action method = " + actionConfig.getMethodName()); } String timerKey = "invokeAction: " + proxy.getActionName(); try { UtilTimerStack.push(timerKey); boolean methodCalled = false; Object methodResult = null; Method method = null; try { // 得到method对象 method = getAction().getClass().getMethod(methodName, EMPTY_CLASS_ARRAY); } catch (NoSuchMethodException e) { // hmm -- OK, try doXxx instead try { String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1); // 额。。看懂了吧? 这又是一个风骚的操作,带有do的方法名 method = getAction().getClass().getMethod(altMethodName, EMPTY_CLASS_ARRAY); } catch (NoSuchMethodException e1) { // well, give the unknown handler a shot if (unknownHandlerManager.hasUnknownHandlers()) { try { methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName); methodCalled = true; } catch (NoSuchMethodException e2) { // throw the original one throw e; } } else { throw e; } } } if (!methodCalled) { // 用 java 的反射,调用indexAction的方法,得到result结果 methodResult = method.invoke(action, new Object[0]); } // 这里扩展了返回结果的类型,具体可以看struts-default.xml中的配置, 好多好多result扩展类型 if (methodResult instanceof Result) { this.explicitResult = (Result) methodResult; // Wire the result automatically container.inject(explicitResult); return null; } else { // 返回普通的string结果 return (String) methodResult; } } catch (NoSuchMethodException e) { throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + ""); } catch (InvocationTargetException e) { // We try to return the source exception. Throwable t = e.getTargetException(); if (actionEventListener != null) { String result = actionEventListener.handleException(t, getStack()); if (result != null) { return result; } } if (t instanceof Exception) { throw (Exception) t; } else { throw e; } } finally { UtilTimerStack.pop(timerKey); } }
好了,分析完了action的执行,我们看一下结果处理,回顾一下
resultCode = invokeActionOnly(); // 省略n行代码 if (proxy.getExecuteResult()) { executeResult(); }
看一下executeResult的实现:
/** * Uses getResult to get the final Result and executes it * * @throws ConfigurationException If not result can be found with the returned code */ private void executeResult() throws Exception { // 创建ServletDispchterResult对象, // 通过配置文件获取result配置信息,查找到location // 设置ServletDispchterResult对象的location为配置文件获得location result = createResult(); String timerKey = "executeResult: " + getResultCode(); try { UtilTimerStack.push(timerKey); if (result != null) { // 执行result // 最终还是调用的ServletDispchterResult的doExecute方法 // 下方我们看一下这个方法的具体实现 result.execute(this); } else if (resultCode != null && !Action.NONE.equals(resultCode)) { throw new ConfigurationException("No result defined for action " + getAction().getClass().getName() + " and result " + getResultCode(), proxy.getConfig()); } else { if (LOG.isDebugEnabled()) { LOG.debug("No result returned for action " + getAction().getClass().getName() + " at " + proxy.getConfig().getLocation()); } } } finally { UtilTimerStack.pop(timerKey); } }
ServletDispchterResult的doExecute方法的实现:
// 加入我们的配置文件里的location是:/WEB-INF/jsp/index.jsp public void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { if (LOG.isDebugEnabled()) { LOG.debug("Forwarding to location " + finalLocation); } PageContext pageContext = ServletActionContext.getPageContext(); if (pageContext != null) { pageContext.include(finalLocation); } else { HttpServletRequest request = ServletActionContext.getRequest(); HttpServletResponse response = ServletActionContext.getResponse(); // 将配置文件中的location传递给系统Servlet的getRequestDispatcher方法 // 关于Servlet的getRequestDispatcher方法,参考[3] // 这里仅是构造dispatcher,(这是java的servlet的编程, 非struts2) RequestDispatcher dispatcher = request.getRequestDispatcher(finalLocation); //add parameters passed on the location to #parameters // see WW-2120 if (invocation != null && finalLocation != null && finalLocation.length() > 0 && finalLocation.indexOf("?") > 0) { String queryString = finalLocation.substring(finalLocation.indexOf("?") + 1); Map parameters = (Map) invocation.getInvocationContext().getContextMap().get("parameters"); Map queryParams = UrlHelper.parseQueryString(queryString, true); if (queryParams != null && !queryParams.isEmpty()) parameters.putAll(queryParams); } // if the view doesn't exist, let's do a 404 if (dispatcher == null) { response.sendError(404, "result '" + finalLocation + "' not found"); return; } //if we are inside an action tag, we always need to do an include Boolean insideActionTag = (Boolean) ObjectUtils.defaultIfNull(request.getAttribute(StrutsStatics.STRUTS_ACTION_TAG_INVOCATION), Boolean.FALSE); // If we're included, then include the view // Otherwise do forward // This allow the page to, for example, set content type if (!insideActionTag && !response.isCommitted() && (request.getAttribute("javax.servlet.include.servlet_path") == null)) { request.setAttribute("struts.view_uri", finalLocation); request.setAttribute("struts.request_uri", request.getRequestURI()); // 转发到 /WEB-INF/jsp/index.jsp ,由jsp进行相应操作 dispatcher.forward(request, response); } else { dispatcher.include(request, response); } } }
目前我们已经分析完成Action的执行和Result处理的所有流程,最后可以看到Result结果处理使用的是Servlet的API,具体可以参考[4].
下一篇我们分析一下Struts2的标签库
参考
以上所述就是小编给大家介绍的《Struts2 运行流程分析之doFilter》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- MapReduce运行流程分析
- Goland使用初探以及运行流程浅析
- 一文读懂 Spark SQL 运行流程
- Apache Hadoop YARN 的架构与运行流程
- 从Zygote说到View(一)Zygote的启动流程及运行机制
- 宜信开源|分布式任务调度平台SIA-TASK的架构设计与运行流程
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
About Face 3
Alan Cooper、Robert Reimann、David Cronin / John Wiley & Sons / 2007-5-15 / GBP 28.99
* The return of the authoritative bestseller includes all new content relevant to the popularization of how About Face maintains its relevance to new Web technologies such as AJAX and mobile platforms......一起来看看 《About Face 3》 这本书的介绍吧!