OFBiz:解析doRequest()

时间:2023-03-09 00:32:22
OFBiz:解析doRequest()

这里的doRequest()是指RequestHandler中的同名函数:

public void doRequest(HttpServletRequest request, HttpServletResponse response, String chain,
GenericValue userLogin, Delegator delegator) throws RequestHandlerException { ... }

下面以http://localhost:8080/practice/control/main?foo=xxx&bar=yyy为例演示说明一下整个doRequest()的处理过程。对应的controller.xml内容如下:

<?xml version="1.0" encoding="UTF-8"?>

<site-conf xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ofbiz.apache.org/dtds/site-conf.xsd">
<include location="component://common/webcommon/WEB-INF/common-controller.xml"/>
<include location="component://common/webcommon/WEB-INF/portal-controller.xml"/> <request-map uri="main">
<response name="success" type="view" value="main"/>
</request-map> <view-map name="main" type="screen" page="component://practice/widget/PracticeScreens.xml#main"/>
</site-conf>

步骤1: 获取controller.xml相关配置信息。

// get the controllerConfig once for this method so we don't have to get it over and over inside the method
ConfigXMLReader.ControllerConfig controllerConfig = this.getControllerConfig();
Map<String, ConfigXMLReader.RequestMap> requestMapMap = controllerConfig.getRequestMapMap();

步骤2: 获取请求的应用名称,当前是practice。getContextPath()结果是/practice,getApplicationName()会去掉第一个字符,返回的是practice。

// workaround if we are in the root webapp
String cname = UtilHttp.getApplicationName(request); // public static String getApplicationName(HttpServletRequest request) {
// String appName = "root";
// if (request.getContextPath().length() > 1) {
// appName = request.getContextPath().substring(1);
// }
// return appName;
// }

步骤3: 获取请求的URI,当前是main。getPathInfo()的结果是/main,getRequestUri()会按照/做分割,然后返回第一项。分割的目的是因为可能处理现/main/sports/news/nba这样的状况。

String defaultRequestUri = RequestHandler.getRequestUri(request.getPathInfo());

// public static String getRequestUri(String path) {
// List<String> pathInfo = StringUtil.split(path, "/");
// if (UtilValidate.isEmpty(pathInfo)) {
// Debug.logWarning("Got nothing when splitting URI: " + path, module);
// return null;
// }
// if (pathInfo.get(0).indexOf('?') > -1) {
// return pathInfo.get(0).substring(0, pathInfo.get(0).indexOf('?'));
// } else {
// return pathInfo.get(0);
// }
// }

将请求URI保存到request。这里为什么在session中会有_PREVIOUS_REQUEST_,而且还优先request?

if (request.getAttribute("targetRequestUri") == null) {
if (request.getSession().getAttribute("_PREVIOUS_REQUEST_") != null) {
request.setAttribute("targetRequestUri", request.getSession().getAttribute("_PREVIOUS_REQUEST_"));
} else {
request.setAttribute("targetRequestUri", "/" + defaultRequestUri);
}
}

步骤4: 获取overrideViewUri,可以理解为URI后面一段。例如http://localhost:8080/practice/main/sports/news/nba?foo=xxx&bar=yyy,返回的defaultRequestUri是main,overrideViewUri是sports/news/nba。

String overrideViewUri = RequestHandler.getOverrideViewUri(request.getPathInfo());

// public static String getOverrideViewUri(String path) {
// List<String> pathItemList = StringUtil.split(path, "/");
// if (pathItemList == null) {
// return null;
// }
// pathItemList = pathItemList.subList(1, pathItemList.size());
//
// String nextPage = null;
// for (String pathItem: pathItemList) {
// if (pathItem.indexOf('~') != 0) {
// if (pathItem.indexOf('?') > -1) {
// pathItem = pathItem.substring(0, pathItem.indexOf('?'));
// }
// nextPage = (nextPage == null ? pathItem : nextPage + "/" + pathItem);
// }
// }
// return nextPage;
// }

步骤5: 根据defaultRequestUri获取requestMap。如果defaultRequestUri没有对应的RequestMap,则用defaultRequest试试看。

ConfigXMLReader.RequestMap requestMap = null;
if (defaultRequestUri != null) {
requestMap = requestMapMap.get(defaultRequestUri);
}
// check for default request
if (requestMap == null) {
String defaultRequest = controllerConfig.getDefaultRequest();
if (defaultRequest != null) {
requestMap = requestMapMap.get(defaultRequest);
}
}

defaultRequest也可能为空,因此不排除执行到这里requestMap还是为空的情况。

步骤6: 检查overrideViewUri。

// check for override view
if (overrideViewUri != null) {
ConfigXMLReader.ViewMap viewMap = getControllerConfig().getViewMapMap().get(overrideViewUri);
if (viewMap == null) {
String defaultRequest = controllerConfig.getDefaultRequest();
if (defaultRequest != null) {
requestMap = requestMapMap.get(defaultRequest);
}
}
}

看到这里突然明白了,针对/practice/control/main/sports/news/nba这种情况,defaultRequestUri是main,overrideViewUri是sports/news/nbs,doRequest()用defaultRequestUri去获取request-map,用overrideViewUri去获取view-map。OFBiz不允许request-uri包含斜杠,但是允许view-uri包含斜杠。

到这里为止,requestMap如果还是为空,则认为是异常。

// still not found so stop
if (requestMap == null) {
throw new RequestHandlerException(requestMissingErrorMessage);
}

步骤7: 检查是否是request-chain。chain是doRequest()的一个参数变量,默认地从ControlServlet过来的请求该变量为null,当转到request-chain的下一个请求时该变量为要执行的请求。getRequestUri()和getOverrideViewUri()都有处理Uri后面的QueryString,因此,这里的chain也应该是可以带上QueryString的。

// <request-map uri="main">
// <response name="success" type="request" value="second"/>
// </request-map>
//
// doRequest()会在后面的代码中再次调用doRequest()自身:
// if ("request".equals(nextRequestResponse.type)) {
// // chained request
// Debug.logInfo("[RequestHandler.doRequest]: Response is a chained request.", module);
// doRequest(request, response, nextRequestResponse.value /* second */, userLogin, delegator);
// } // Check for chained request.
if (chain != null) {
// chain也可以是/main/sports/news/nba这种形式 String chainRequestUri = RequestHandler.getRequestUri(chain);
requestMap = requestMapMap.get(chainRequestUri);
if (requestMap == null) {
throw new RequestHandlerException("Unknown chained request [" + chainRequestUri + "]; this request does not exist");
} if (request.getAttribute("_POST_CHAIN_VIEW_") != null) {
overrideViewUri = (String) request.getAttribute("_POST_CHAIN_VIEW_");
} else {
overrideViewUri = RequestHandler.getOverrideViewUri(chain);
} if (overrideViewUri != null) {
// put this in a request attribute early in case an event needs to access it
// not using _POST_CHAIN_VIEW_ because it shouldn't be set unless the event execution is successful
request.setAttribute("_CURRENT_CHAIN_VIEW_", overrideViewUri);
} if (Debug.infoOn())
Debug.logInfo("[RequestHandler]: Chain in place: requestUri=" + chainRequestUri + " overrideViewUri=" + overrideViewUri, module);
}

步骤8: 检查是否有在request-map中设置这是一个需要X509证书的请求,如果有设置,则必须是HTTPS协议才行。默认cert是false。

// <request-map uri="main">
// <security cert="true"/>
// <response name="success" type="view" value="main"/>
// </request-map> // Check if X509 is required and we are not secure; throw exception
if (!request.isSecure() && requestMap.securityCert) {
throw new RequestHandlerException(requestMissingErrorMessage);
}

步骤9: 检查是否有在request-map中设置这是一个可以直接请求direct-request的请求。默认direct-request是true。direct-request为false的请求不能在这里执行,改用default-request替换。

// <request-map uri="main">
// <security direct-request="true"/>
// <response name="success" type="view" value="main"/>
// </request-map> // Check to make sure we are allowed to access this request directly. (Also checks if this request is defined.)
// If the request cannot be called, or is not defined, check and see if there is a default-request we can process
if (!requestMap.securityDirectRequest) {
String defaultRequest = controllerConfig.getDefaultRequest();
if (defaultRequest == null || !requestMapMap.get(defaultRequest).securityDirectRequest) {
// use the same message as if it was missing for security reasons, ie so can't tell if it is missing or direct request is not allowed
throw new RequestHandlerException(requestMissingErrorMessage);
} else {
requestMap = requestMapMap.get(defaultRequest);
}
}

步骤10:  检查是否有在request-map中设置这是一个可以必须使用HTTPS的请求。默认https是false。OFBiz会自动在HTTP和HTTPS之间跳转。

// <request-map uri="main">
// <security https="true"/>
// <response name="success" type="view" value="main"/>
// </request-map> boolean forceHttpSession = "true".equals(context.getInitParameter("forceHttpSession")); // Check if we SHOULD be secure and are not.
String forwardedProto = request.getHeader("X-Forwarded-Proto");
boolean isForwardedSecure = UtilValidate.isNotEmpty(forwardedProto) && "HTTPS".equals(forwardedProto.toUpperCase()); // isForwardedSecure指上一个请求,request指当前请求,requestMap指下一个请求。
// 下面这个IF应该可以理解为:上一个请求是HTTP,当前请求是HTTP,下一个请求是HTTPS。
if ((!request.isSecure() && !isForwardedSecure) && requestMap.securityHttps) {
// If the request method was POST then return an error to avoid problems with XSRF where the request may have come from another machine/program and had the same session ID but was not encrypted as it should have been (we used to let it pass to not lose data since it was too late to protect that data anyway)
// 从一个HTTP发送过来的POST请求到HTTPS,为了避免XSRF/CSRF的嫌疑,返回一个错误或异常。
if (request.getMethod().equalsIgnoreCase("POST")) {
// we can't redirect with the body parameters, and for better security from XSRF, just return an error message
Locale locale = UtilHttp.getLocale(request);
String errMsg = UtilProperties.getMessage("WebappUiLabels", "requestHandler.InsecureFormPostToSecureRequest", locale);
Debug.logError("Got a insecure (non-https) form POST to a secure (http) request [" + requestMap.uri + "], returning error", module); // see if HTTPS is enabled, if not then log a warning instead of throwing an exception
Boolean enableHttps = null;
String webSiteId = WebSiteWorker.getWebSiteId(request);
if (webSiteId != null) {
try {
GenericValue webSite = delegator.findByPrimaryKeyCache("WebSite", UtilMisc.toMap("webSiteId", webSiteId));
if (webSite != null) enableHttps = webSite.getBoolean("enableHttps");
} catch (GenericEntityException e) {
Debug.logWarning(e, "Problems with WebSite entity; using global defaults", module);
}
}
if (enableHttps == null) {
enableHttps = UtilProperties.propertyValueEqualsIgnoreCase("url.properties", "port.https.enabled", "Y");
} // 如果网站允许HTTPS(enableHttps=true),但是当前又是一个HTTP POST,这种情况是不允许的,返回一个异常。
// 如果网站不允许HTTPS,则提示一条警告信息。 if (Boolean.FALSE.equals(enableHttps)) {
Debug.logWarning("HTTPS is disabled for this site, so we can't tell if this was encrypted or not which means if a form was POSTed and it was not over HTTPS we don't know, but it would be vulnerable to an XSRF and other attacks: " + errMsg, module);
} else {
throw new RequestHandlerException(errMsg);
}
} else {
// 不是POST的处理。如果当前应用允许HTTPS,则调用callRedirect()实现HTTP到HTTPS的跳转。
// 如果当前应用不允许HTTPS,但是requestMap中又设置了https,有可能是requestMap设置不对,则继续执行。
StringBuilder urlBuf = new StringBuilder();
urlBuf.append(request.getPathInfo());
if (request.getQueryString() != null) {
urlBuf.append("?").append(request.getQueryString());
}
String newUrl = RequestHandler.makeUrl(request, response, urlBuf.toString());
if (newUrl.toUpperCase().startsWith("HTTPS")) {
// if we are supposed to be secure, redirect secure.
callRedirect(newUrl, response, request);
return;
}
} // if this is a new session and forceHttpSession is true and the request is secure but does not
// need to be then we need the session cookie to be created via an http response (rather than https)
// so we'll redirect to an unsecure request
} else if (forceHttpSession && request.isSecure() && session.isNew() && !requestMap.securityHttps) {
StringBuilder urlBuf = new StringBuilder();
urlBuf.append(request.getPathInfo());
if (request.getQueryString() != null) {
urlBuf.append("?").append(request.getQueryString());
}
String newUrl = RequestHandler.makeUrl(request, response, urlBuf.toString(), true, false, false);
if (newUrl.toUpperCase().startsWith("HTTP")) {
callRedirect(newUrl, response, request);
return;
}
}

步骤11: 检查客户端传过来的X509数字证书,这一过程与步骤8相对应。

// Check for HTTPS client (x.509) security
if (request.isSecure() && requestMap.securityCert) {
X509Certificate[] clientCerts = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); // 2.2 spec
if (clientCerts == null) {
clientCerts = (X509Certificate[]) request.getAttribute("javax.net.ssl.peer_certificates"); // 2.1 spec
}
if (clientCerts == null) {
Debug.logWarning("Received no client certificates from browser", module);
} // check if the client has a valid certificate (in our db store)
boolean foundTrustedCert = false; if (clientCerts == null) {
throw new RequestHandlerException(requestMissingErrorMessage);
} else {
if (Debug.infoOn()) {
for (int i = 0; i < clientCerts.length; i++) {
Debug.logInfo(clientCerts[i].getSubjectX500Principal().getName(), module);
}
} // check if this is a trusted cert
if (SSLUtil.isClientTrusted(clientCerts, null)) {
foundTrustedCert = true;
}
} if (!foundTrustedCert) {
Debug.logWarning(requestMissingErrorMessage, module);
throw new RequestHandlerException(requestMissingErrorMessage);
}
}

关于SSL的更多信息可以看一下这里《Tomcat6.X SSL的配置》。

步骤12: 执行FirstVisit事件。session中的_FIRST_VISIT_EVENTS_用来区分当前请求是否是会话中的第一个请求。

// If its the first visit run the first visit events.
if (this.trackVisit(request) && session.getAttribute("_FIRST_VISIT_EVENTS_") == null) {
if (Debug.infoOn())
Debug.logInfo("This is the first request in this visit." + " sessionId=" + UtilHttp.getSessionId(request), module);
session.setAttribute("_FIRST_VISIT_EVENTS_", "complete");
for (ConfigXMLReader.Event event: controllerConfig.getFirstVisitEventList().values()) {
try {
String returnString = this.runEvent(request, response, event, null, "firstvisit");
if (returnString != null && !returnString.equalsIgnoreCase("success")) {
throw new EventHandlerException("First-Visit event did not return 'success'.");
} else if (returnString == null) {
interruptRequest = true;
}
} catch (EventHandlerException e) {
Debug.logError(e, module);
}
}
}

trackVisit()判断是否需要跟踪用户的访问。首先检查web.xml是否有设置tract-visit,然后再检查request-map是否也有设置track-visit,只有两个地方都设置了true才会执行FirstVisit事件。

public boolean trackVisit(HttpServletRequest request) {
if (!"false".equalsIgnoreCase(context.getInitParameter("track-visit"))) {
String uriString = RequestHandler.getRequestUri(request.getPathInfo());
if (uriString == null) {
uriString="";
}
ConfigXMLReader.RequestMap requestMap = getControllerConfig().getRequestMapMap().get(uriString);
if (requestMap == null) return false;
return requestMap.trackVisit;
} else {
return false;
}
}

步骤13:执行Preprocessor事件。

// Invoke the pre-processor (but NOT in a chain)
for (ConfigXMLReader.Event event: controllerConfig.getPreprocessorEventList().values()) {
try {
String returnString = this.runEvent(request, response, event, null, "preprocessor");
if (returnString != null && !returnString.equalsIgnoreCase("success")) {
if (!returnString.contains(":_protect_:")) {
throw new EventHandlerException("Pre-Processor event [" + event.invoke + "] did not return 'success'.");
} else { // protect the view normally rendered and redirect to error response view // 如果returnString包含":_protect_:", 表明这是一段错误信息内容
returnString = returnString.replace(":_protect_:", "");
if (returnString.length() > 0) {
request.setAttribute("_ERROR_MESSAGE_", returnString);
} eventReturn = null; // check to see if there is a "protect" response, if so it's ok else show the default_error_response_view
// 首先找request-map下面是否有设置"protect" response
// 然后再找controller.xml是否有设置protect view
// 最后找security.properties中的default.error.response.view
if (!requestMap.requestResponseMap.containsKey("protect")) {
String protectView = controllerConfig.getProtectView();
if (protectView != null) {
overrideViewUri = protectView;
} else {
overrideViewUri = UtilProperties.getPropertyValue("security.properties", "default.error.response.view");
overrideViewUri = overrideViewUri.replace("view:", "");
if ("none:".equals(overrideViewUri)) {
interruptRequest = true;
}
}
}
}
} else if (returnString == null) {
interruptRequest = true;
}
} catch (EventHandlerException e) {
Debug.logError(e, module);
}
}

注意,步骤8-13只有在请求链中的第一个请求才会被执行,后面的请求都不会被执行,避免了重复检查和执行的情况。

步骤14: 执行完FirstVisit/Preprocessor后,检查请求有没有被这两个事件终止。

// Pre-Processor/First-Visit event(s) can interrupt the flow by returning null.
// Warning: this could cause problems if more then one event attempts to return a response.
if (interruptRequest) {
if (Debug.infoOn()) Debug.logInfo("[Pre-Processor Interrupted Request, not running: [" + requestMap.uri + "], sessionId=" + UtilHttp.getSessionId(request), module);
return;
}

步骤15: 保存thisRequestUri。步骤3有保存targetRequestUri=getRequestUri(request.getPathInfo()),此处与此对应。

if (Debug.verboseOn())
Debug.logVerbose("[Processing Request]: " + requestMap.uri, module);
request.setAttribute("thisRequestUri", requestMap.uri); // store the actual request URI

步骤16: 执行安全检查。所谓安全检查,实质是指checkLogin请求中的事件。

// <request-map uri="main">
// <security auth="true"/>
// <response name="success" type="view" value="main"/>
// </request-map> // Perform security check.
if (requestMap.securityAuth) {
// Invoke the security handler
// catch exceptions and throw RequestHandlerException if failed.
if (Debug.verboseOn())
Debug.logVerbose("[RequestHandler]: AuthRequired. Running security check.", module);
ConfigXMLReader.Event checkLoginEvent = requestMapMap.get("checkLogin").event;
String checkLoginReturnString = null; try {
checkLoginReturnString = this.runEvent(request, response, checkLoginEvent, null, "security-auth");
} catch (EventHandlerException e) {
throw new RequestHandlerException(e.getMessage(), e);
}
if (!"success".equalsIgnoreCase(checkLoginReturnString)) {
// previous URL already saved by event, so just do as the return says...
eventReturn = checkLoginReturnString; // 安全检查失败, 则请求被转移到登录页面
// if the request is an ajax request we don't want to return the default login check
if (!"XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
requestMap = requestMapMap.get("checkLogin");
} else {
requestMap = requestMapMap.get("ajaxCheckLogin");
}
}
}

这里要说明一下checkLogin。checkLogin检查用户是否已通过验证,返回结果success或error。如果验证失败,checkLogin会在session里面记录下当前请求Uri和参数,然后转到登录页面,待验证通过后可以再恢复到先前请求的Uri和参数。

package org.ofbiz.webapp.control;

public class LoginWorker {
public static String checkLogin(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession();
GenericValue userLogin = (GenericValue) session.getAttribute("userLogin"); // anonymous shoppers are not logged in
if (userLogin != null && "anonymous".equals(userLogin.getString("userLoginId"))) {
userLogin = null;
} // user is logged in; check to see if they have globally logged out if not
// check if they have permission for this login attempt; if not log them out
if (userLogin != null) {
if (!hasBasePermission(userLogin, request) || isFlaggedLoggedOut(userLogin)) {
Debug.logInfo("User does not have permission or is flagged as logged out", module);
doBasicLogout(userLogin, request, response);
userLogin = null; // have to reget this because the old session object will be invalid
session = request.getSession();
}
} String username = null;
String password = null; if (userLogin == null) {
// check parameters
username = request.getParameter("USERNAME");
password = request.getParameter("PASSWORD");
// check session attributes
if (username == null) username = (String) session.getAttribute("USERNAME");
if (password == null) password = (String) session.getAttribute("PASSWORD"); // in this condition log them in if not already; if not logged in or can't log in, save parameters and return error
if ((username == null) || (password == null) || ("error".equals(login(request, response)))) { // make sure this attribute is not in the request; this avoids infinite recursion when a login by less stringent criteria (like not checkout the hasLoggedOut field) passes; this is not a normal circumstance but can happen with custom code or in funny error situations when the userLogin service gets the userLogin object but runs into another problem and fails to return an error
request.removeAttribute("_LOGIN_PASSED_"); // keep the previous request name in the session
session.setAttribute("_PREVIOUS_REQUEST_", request.getPathInfo()); // NOTE: not using the old _PREVIOUS_PARAMS_ attribute at all because it was a security hole as it was used to put data in the URL (never encrypted) that was originally in a form field that may have been encrypted
// keep 2 maps: one for URL parameters and one for form parameters
Map<String, Object> urlParams = UtilHttp.getUrlOnlyParameterMap(request);
if (UtilValidate.isNotEmpty(urlParams)) {
session.setAttribute("_PREVIOUS_PARAM_MAP_URL_", urlParams);
}
Map<String, Object> formParams = UtilHttp.getParameterMap(request, urlParams.keySet(), false);
if (UtilValidate.isNotEmpty(formParams)) {
session.setAttribute("_PREVIOUS_PARAM_MAP_FORM_", formParams);
} //if (Debug.infoOn()) Debug.logInfo("checkLogin: PathInfo=" + request.getPathInfo(), module); return "error";
}
} return "success";
}
}

步骤17: 检查后_PREVIOUS_PARAM_MAP_FORM_。这里的代码不是很明白:为什么要检查后session.getAttribute("_PREVIOUS_REQUES_")为空呢?see if a post-login redirect has completed and we have data from the pre-login request form to use now在post-login重定向完成后检查是否有pre-login之前的数据需要带过来。

// after security check but before running the event, see if a post-login redirect has completed and we have data from the pre-login request form to use now
// we know this is the case if the _PREVIOUS_PARAM_MAP_ attribute is there, but the _PREVIOUS_REQUEST_ attribute has already been removed
if (request.getSession().getAttribute("_PREVIOUS_PARAM_MAP_FORM_") != null && request.getSession().getAttribute("_PREVIOUS_REQUEST_") == null) {
Map<String, Object> previousParamMap = UtilGenerics.checkMap(request.getSession().getAttribute("_PREVIOUS_PARAM_MAP_FORM_"), String.class, Object.class);
for (Map.Entry<String, Object> previousParamEntry: previousParamMap.entrySet()) {
request.setAttribute(previousParamEntry.getKey(), previousParamEntry.getValue());
} // to avoid this data being included again, now remove the _PREVIOUS_PARAM_MAP_ attribute
request.getSession().removeAttribute("_PREVIOUS_PARAM_MAP_FORM_");
}

步骤18: 执行request-map中定义的事件。

// Invoke the defined event (unless login failed)
// eventReturn表示checkLogin失败,eventReturn = checkLoginReturnString
// requestMap.event表示request-map有定义事件
if (eventReturn == null && requestMap.event != null) {
if (requestMap.event.type != null && requestMap.event.path != null && requestMap.event.invoke != null) {
try {
long eventStartTime = System.currentTimeMillis(); // run the request event
eventReturn = this.runEvent(request, response, requestMap.event, requestMap, "request"); // save the server hit for the request event
if (this.trackStats(request)) {
ServerHitBin.countEvent(cname + "." + requestMap.event.invoke, request, eventStartTime,
System.currentTimeMillis() - eventStartTime, userLogin);
} // set the default event return
if (eventReturn == null) {
nextRequestResponse = ConfigXMLReader.emptyNoneRequestResponse;
}
} catch (EventHandlerException e) {
// check to see if there is an "error" response, if so go there and make an request error message
// 有异常发生,显示error response
if (requestMap.requestResponseMap.containsKey("error")) {
eventReturn = "error";
Locale locale = UtilHttp.getLocale(request);
String errMsg = UtilProperties.getMessage("WebappUiLabels", "requestHandler.error_call_event", locale);
request.setAttribute("_ERROR_MESSAGE_", errMsg + ": " + e.toString());
} else {
throw new RequestHandlerException("Error calling event and no error response was specified", e);
}
}
}
}

trackStats()判断是否需要跟踪服务器点击。首先检查web.xml是否有设置tract-serverhit,然后再检查request-map是否也有设置track-serverhit,只有两个地方都设置了true才会执行。

public boolean trackStats(HttpServletRequest request) {
if (!"false".equalsIgnoreCase(context.getInitParameter("track-serverhit"))) {
String uriString = RequestHandler.getRequestUri(request.getPathInfo());
if (uriString == null) {
uriString="";
}
ConfigXMLReader.RequestMap requestMap = getControllerConfig().getRequestMapMap().get(uriString);
if (requestMap == null) return false;
return requestMap.trackServerHit;
} else {
return false;
}
}

步骤19: 检查eventReturn对应的response是否存在。

// eventReturn表示checkLogin返回的错误结果error, 或者是requestMap.event返回的结果(成功success和错误都在里面),也可能就是null。// 检查eventReturn response存不存在?

// Process the eventReturn
// at this point eventReturnString is finalized, so get the RequestResponse
ConfigXMLReader.RequestResponse eventReturnBasedRequestResponse = eventReturn == null ? null : requestMap.requestResponseMap.get(eventReturn);
if (eventReturnBasedRequestResponse != null) {
//String eventReturnBasedResponse = requestResponse.value;
if (Debug.verboseOn())
Debug.logVerbose("[Response Qualified]: " + eventReturnBasedRequestResponse.name + ", " + eventReturnBasedRequestResponse.type + ":" + eventReturnBasedRequestResponse.value, module); // If error, then display more error messages:
if ("error".equals(eventReturnBasedRequestResponse.name)) {
if (Debug.errorOn()) {
String errorMessageHeader = "Request " + requestMap.uri + " caused an error with the following message: ";
if (request.getAttribute("_ERROR_MESSAGE_") != null) {
Debug.logError(errorMessageHeader + request.getAttribute("_ERROR_MESSAGE_"), module);
}
if (request.getAttribute("_ERROR_MESSAGE_LIST_") != null) {
Debug.logError(errorMessageHeader + request.getAttribute("_ERROR_MESSAGE_LIST_"), module);
}
}
}
} else if (eventReturn != null) {
// only log this warning if there is an eventReturn (ie skip if no event, etc)
Debug.logWarning("Could not find response in request [" + requestMap.uri + "] for event return [" + eventReturn + "]", module);
} // Set the next view (don't use event return if success, default to nextView (which is set to eventReturn later if null); also even if success if it is a type "none" response ignore the nextView, ie use the eventReturn)
// 当requestMap.event时,eventReturn有可能是success,也有可能是null。
if (eventReturnBasedRequestResponse != null &&
(!"success".equals(eventReturnBasedRequestResponse.name) || "none".equals(eventReturnBasedRequestResponse.type)))
nextRequestResponse = eventReturnBasedRequestResponse;

步骤20: 这一段代码不是很懂,大致意思应该是找回先前的请求参数。_REQ_ATTR_MAP_是在callRedirect()设置的,重定向之前,callRedirect()会将当前请求保存到_REQ_ATTR_MAP_。

// get the previous request info
// _PREVIOUS_REQUES_和_LOGIN_PASSED_会在checkLogin中被设置
String previousRequest = (String) request.getSession().getAttribute("_PREVIOUS_REQUEST_");
String loginPass = (String) request.getAttribute("_LOGIN_PASSED_"); // restore previous redirected request's attribute, so redirected page can display previous request's error msg etc.
String preReqAttStr = (String) request.getSession().getAttribute("_REQ_ATTR_MAP_");
Map<String, Object> previousRequestAttrMap = null;
if (preReqAttStr != null) {
previousRequestAttrMap = FastMap.newInstance();
request.getSession().removeAttribute("_REQ_ATTR_MAP_");
byte[] reqAttrMapBytes = StringUtil.fromHexString(preReqAttStr);
Map<String, Object> preRequestMap = checkMap(UtilObject.getObject(reqAttrMapBytes), String.class, Object.class);
if (UtilValidate.isNotEmpty(preRequestMap)) {
for (Map.Entry<String, Object> entry: preRequestMap.entrySet()) {
String key = entry.getKey();
if ("_ERROR_MESSAGE_LIST_".equals(key) || "_ERROR_MESSAGE_MAP_".equals(key) || "_ERROR_MESSAGE_".equals(key) ||
"_EVENT_MESSAGE_LIST_".equals(key) || "_EVENT_MESSAGE_".equals(key)) {
request.setAttribute(key, entry.getValue());
previousRequestAttrMap.put(key, entry.getValue());
}
}
}
} if (Debug.verboseOn())
Debug.logVerbose("[RequestHandler]: previousRequest - " + previousRequest + " (" + loginPass + ")", module);

步骤21: 如果checkLogin之前有请求previousRequest,且本次checkLogin登录成功,则重定向到previousRequest。checkLogin成功,会设置loginPass=TRUE。

// if previous request exists, and a login just succeeded, do that now.
if (previousRequest != null && loginPass != null && loginPass.equalsIgnoreCase("TRUE")) {
request.getSession().removeAttribute("_PREVIOUS_REQUEST_");
// special case to avoid login/logout looping: if request was "logout" before the login,
// change to null for default success view; do the same for "login" to avoid going back to the same page
if ("logout".equals(previousRequest) || "/logout".equals(previousRequest) ||
"login".equals(previousRequest) || "/login".equals(previousRequest) ||
"checkLogin".equals(previousRequest) || "/checkLogin".equals(previousRequest) ||
"/checkLogin/login".equals(previousRequest)) {
Debug.logWarning("Found special _PREVIOUS_REQUEST_ of [" + previousRequest + "], setting to null to avoid problems, not running request again", module);
} else {
if (Debug.infoOn())
Debug.logInfo("[Doing Previous Request]: " + previousRequest, module); // note that the previous form parameters are not setup (only the URL ones here),
// they will be found in the session later and handled when the old request redirect comes back
Map<String, Object> previousParamMap = UtilGenerics.checkMap(request.getSession().getAttribute("_PREVIOUS_PARAM_MAP_URL_"), String.class, Object.class);
String queryString = UtilHttp.urlEncodeArgs(previousParamMap, false);
String redirectTarget = previousRequest;
if (UtilValidate.isNotEmpty(queryString)) {
redirectTarget += "?" + queryString;
}
callRedirect(makeLink(request, response, redirectTarget), response, request); // the old/uglier way: doRequest(request, response, previousRequest, userLogin, delegator); // this is needed as the request handled will be taking care of the view, etc
return;
}
}

步骤22: 检查success response,设置nextRequestResponse。

ConfigXMLReader.RequestResponse successResponse = requestMap.requestResponseMap.get("success");
if ((eventReturn == null || "success".equals(eventReturn)) &&
successResponse != null && "request".equals(successResponse.type)) {
// chains will override any url defined views; but we will save the view for the very end
if (UtilValidate.isNotEmpty(overrideViewUri)) {
request.setAttribute("_POST_CHAIN_VIEW_", overrideViewUri);
}
nextRequestResponse = successResponse;
} // Make sure we have some sort of response to go to
if (nextRequestResponse == null)
nextRequestResponse = successResponse; if (nextRequestResponse == null) {
throw new RequestHandlerException("Illegal response; handler could not process request [" + requestMap.uri + "] and event return [" + eventReturn + "].");
} if (Debug.verboseOn())
Debug.logVerbose("[Event Response Selected] type=" + nextRequestResponse.type + ", value=" + nextRequestResponse.value, module);

步骤23: 后面的代码都是处理nextRequestResponse的,在文章《OFBiz:处理nextRequestResponse》详细说明。