内容简介:注意: 本文基于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的架构设计与运行流程
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
数据密集型应用系统设计
Martin Kleppmann / 赵军平、李三平、吕云松、耿煜 / 中国电力出版社 / 2018-9-1 / 128
全书分为三大部分: 第一部分,主要讨论有关增强数据密集型应用系统所需的若干基本原则。首先开篇第1章即瞄准目标:可靠性、可扩展性与可维护性,如何认识这些问题以及如何达成目标。第2章我们比较了多种不同的数据模型和查询语言,讨论各自的适用场景。接下来第3章主要针对存储引擎,即数据库是如何安排磁盘结构从而提高检索效率。第4章转向数据编码(序列化)方面,包括常见模式的演化历程。 第二部分,我们将......一起来看看 《数据密集型应用系统设计》 这本书的介绍吧!
RGB CMYK 转换工具
RGB CMYK 互转工具
HEX CMYK 转换工具
HEX CMYK 互转工具