Turbine是apache 项目中的server-side java技术,位于jakarta子项目,是基于servlet的web应用框架.它提供了很多基础服务:访问控制,页面个性化,服务调度,表单确认,xml-rpc格式的web服务等等.可以做为开发面向服务架构应用的基础,因为turbine很容易开发其它服务,并在其服务管理框架下运行.其下一个版本为2.4,它明确使用亚瑟王神剑项目(该项目实现了IOC模式)来管理服务组件。
本文描述的场景是:turbine2.31+velocity-1.4+torque-3.1。要解决在该场景下提交编码模式为multipart/form-data的表单不能正确处理中文的问题,虽然application/x-www-form-urlencoded编码下可以正确处理中文.
纠正本问题需要提供:Maven,Ant两个工具以及Turbine源码.
首先,看Turbine的入口servlet Turbine.java中的代码片段,是doGet方法: 目的是看原始的web服务器请求对象(request)是如何被传递入Turbine中的,以及Turbine如何处理request对象. public final void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { // set to true if the request is to be redirected by the page boolean requestRedirected = false;
// Placeholder for the RunData object. RunData data = null; try { // Check to make sure that we started up properly. if (initFailure != null) { throw initFailure; }
// Get general RunData here... // Perform turbine specific initialization below.
data = rundataService.getRunData(req, res, getServletConfig());
// If this is the first invocation, perform some // initialization. Certain services need RunData to initialize // themselves. if (firstDoGet) { init(data); }
// set the session timeout if specified in turbine's properties // file if this is a new session if (data.getSession().isNew()) { int timeout = configuration.getInt(SESSION_TIMEOUT_KEY, SESSION_TIMEOUT_DEFAULT);
if (timeout != SESSION_TIMEOUT_DEFAULT) { data.getSession().setMaxInactiveInterval(timeout); } }
// Fill in the screen and action variables. data.setScreen(data.getParameters().getString(URIConstants.CGI_SCREEN_PARAM)); data.setAction(data.getParameters().getString(URIConstants.CGI_ACTION_PARAM));
// Special case for login and logout, this must happen before the // session validator is executed in order either to allow a user to // even login, or to ensure that the session validator gets to // mandate its page selection policy for non-logged in users // after the logout has taken place. if (data.hasAction()) { String action = data.getAction(); log.debug("action = " + action);
if (action.equalsIgnoreCase( configuration.getString(ACTION_LOGIN_KEY, ACTION_LOGIN_DEFAULT))) { loginAction(data); } else if (action.equalsIgnoreCase( configuration.getString(ACTION_LOGOUT_KEY, ACTION_LOGOUT_DEFAULT))) { logoutAction(data); } }
// This is where the validation of the Session information // is performed if the user has not logged in yet, then // the screen is set to be Login. This also handles the // case of not having a screen defined by also setting the // screen to Login. If you want people to go to another // screen other than Login, you need to change that within // TurbineResources.properties...screen.homepage; or, you // can specify your own SessionValidator action. ActionLoader.getInstance().exec( data, configuration.getString(ACTION_SESSION_VALIDATOR_KEY, ACTION_SESSION_VALIDATOR_DEFAULT));
// Put the Access Control List into the RunData object, so // it is easily available to modules. It is also placed // into the session for serialization. Modules can null // out the ACL to force it to be rebuilt based on more // information. ActionLoader.getInstance().exec( data, configuration.getString(ACTION_ACCESS_CONTROLLER_KEY, ACTION_ACCESS_CONTROLLER_DEFAULT));
// Start the execution phase. DefaultPage will execute the // appropriate action as well as get the Layout from the // Screen and then execute that. The Layout is then // responsible for executing the Navigation and Screen // modules. // // Note that by default, this cannot be overridden from // parameters passed in via post/query data. This is for // security purposes. You should really never need more // than just the default page. If you do, add logic to // DefaultPage to do what you want.
String defaultPage = (templateService == null) ? null :templateService.getDefaultPageName(data);
if (defaultPage == null) { /* * In this case none of the template services are running. * The application may be using ECS for views, or a * decendent of RawScreen is trying to produce output. * If there is a 'page.default' property in the TR.props * then use that, otherwise return DefaultPage which will * handle ECS view scenerios and RawScreen scenerios. The * app developer can still specify the 'page.default' * if they wish but the DefaultPage should work in * most cases. */ defaultPage = configuration.getString(PAGE_DEFAULT_KEY, PAGE_DEFAULT_DEFAULT); }
PageLoader.getInstance().exec(data, defaultPage);
// If a module has set data.acl = null, remove acl from // the session. if (data.getACL() == null) { try { data.getSession().removeAttribute( AccessControlList.SESSION_KEY); } catch (IllegalStateException ignored) { } }
// handle a redirect request requestRedirected = ((data.getRedirectURI() != null) && (data.getRedirectURI().length() > 0)); if (requestRedirected) { if (data.getResponse().isCommitted()) { requestRedirected = false; log.warn("redirect requested, response already committed: " + data.getRedirectURI()); } else { data.getResponse().sendRedirect(data.getRedirectURI()); } }
if (!requestRedirected) { try { if (data.isPageSet() == false && data.isOutSet() == false) { throw new Exception("Nothing to output"); }
// We are all done! if isPageSet() output that way // otherwise, data.getOut() has already been written // to the data.getOut().close() happens below in the // finally. if (data.isPageSet() && data.isOutSet() == false) { // Modules can override these. data.getResponse().setLocale(data.getLocale()); data.getResponse().setContentType( data.getContentType());
// Set the status code. data.getResponse().setStatus(data.getStatusCode()); // Output the Page. data.getPage().output(data.getOut()); } } catch (Exception e) { // The output stream was probably closed by the client // end of things ie: the client clicked the Stop // button on the browser, so ignore any errors that // result. log.debug("Output stream closed? ", e); } } } catch (Exception e) { handleException(data, res, e); } catch (Throwable t) { handleException(data, res, t); } finally { // Return the used RunData to the factory for recycling. rundataService.putRunData(data); } }
以及org.apache.turbine.services.rundata.TurbineRunDataService中的: public RunData getRunData(HttpServletRequest req, HttpServletResponse res, ServletConfig config) throws TurbineException { return getRunData(DEFAULT_CONFIG, req, res, config); }
/** * Gets a RunData instance from a specific configuration. * * @param key a configuration key. * @param req a servlet request. * @param res a servlet response. * @param config a servlet config. * @return a new or recycled RunData object. * @throws TurbineException if the operation fails. * @throws IllegalArgumentException if any of the parameters are null. */ public RunData getRunData(String key, HttpServletRequest req, HttpServletResponse res, ServletConfig config) throws TurbineException, IllegalArgumentException { // The RunData object caches all the information that is needed for // the execution lifetime of a single request. A RunData object // is created/recycled for each and every request and is passed // to each and every module. Since each thread has its own RunData // object, it is not necessary to perform syncronization for // the data within this object. if ((req == null) || (res == null) || (config == null)) { throw new IllegalArgumentException("HttpServletRequest, " + "HttpServletResponse or ServletConfig was null."); }
// Get the specified configuration. String[] cfg = (String[]) configurations.get(key); if (cfg == null) { throw new TurbineException("RunTime configuration '" + key + "' is undefined"); }
TurbineRunData data; try { data = (TurbineRunData) pool.getInstance(cfg[0]); data.setParameterParser((ParameterParser) pool.getInstance(cfg[1])); data.setCookieParser((CookieParser) pool.getInstance(cfg[2])); } catch (ClassCastException x) { throw new TurbineException("RunData configuration '" + key + "' is illegal", x); }
// Set the request and response. data.setRequest(req); data.setResponse(res);
// Set the servlet configuration. data.setServletConfig(config);
// Set the ServerData. data.setServerData(new ServerData(req));
return data; }
在 org.apache.turbine.services.rundata.DefaultTurbineRunData中的: /** * Sets the servlet request. * * @param req a request. */ public void setRequest(HttpServletRequest req) { this.req = req; }
/** * Sets the servlet response. * * @param res a response. */ public void setResponse(HttpServletResponse res) { this.res = res; }
在该类中request对象被不加修改的传递到了ParameterParser: /** * Gets the parameters. * * @return a parameter parser. */ public ParameterParser getParameters() { // Parse the parameters first, if not yet done. if ((this.parameters != null) && (this.parameters.getRequest() != this.req)) { this.parameters.setRequest(this.req); } return this.parameters; }
/** * Gets the parameter parser without parsing the parameters. * * @return the parameter parser. */ public ParameterParser getParameterParser() { return parameters; }
/** * Sets the parameter parser. * * @param parser a parameter parser. */ public void setParameterParser(ParameterParser parser) { parameters = parser; }
以上类中展示的代码都涉及到了Request对象,从这些代码中看出在Turbine中,实际上并没有对Request对象做任何修改,这样就可以按照通常的方法,加入对Request对象的处理代码.最后, 这里是关键的地方:在类DefaultParameterParser中对Request对象的处理,也就是通过对该处的修改,问题得到了解决: /** * Sets the servlet request to be parser. This requires a * valid HttpServletRequest object. It will attempt to parse out * the GET/POST/PATH_INFO data and store the data into a Map. * There are convenience methods for retrieving the data as a * number of different datatypes. The PATH_INFO data must be a * URLEncoded() string. * <p> * To add name/value pairs to this set of parameters, use the * <code>add()</code> methods. * * @param request An HttpServletRequest. */ public void setRequest(HttpServletRequest request) { clear();
uploadData = null;
//String enc = request.getCharacterEncoding(); 原来是: enc = request.getCharacterEncoding(); setCharacterEncoding(enc != null ? enc : "US-ASCII"); 修改后: if(enc==null) enc=org.apache.turbine.Turbine.getConfiguration().getString(CharacterEncoding_Key,"US-ASCII"); setCharacterEncoding(enc); // String object re-use at its best. String tmp = null;
tmp = request.getHeader("Content-type");
if (uploadServiceIsAvailable && uploadService.getAutomatic() && tmp != null && tmp.startsWith("multipart/form-data")) { log.debug("Running the Turbine Upload Service"); try { TurbineUpload.parseRequest(request, this); } catch (TurbineException e) { log.error("File upload failed", e); } }
for (Enumeration names = request.getParameterNames(); names.hasMoreElements();) { tmp = (String) names.nextElement(); add(convert(tmp), request.getParameterValues(tmp)); }
|