就像上回所讲,官方提供了一个样例JsgiServlet。虽然已经在其上stick矿建,但是就个人看来,这个东西确实类似一个样例。
如果使用serlvet作为js容器(ringo-engine)的载体,那么势必一个webapp可以配置多个servlet,也就是多个js容器。这种方式会导致开发人员在不熟悉环境的情况错误的开发判断。其实,如果要方便开发,那么就应该至少提供一个较为但单纯的环境。降低入门曲线。即,提供一个webapp默认对应的js容器。
另一方面,我后续的需要完成的仿制wordpress plugin的机制也需要用到直接获取默认引擎。
参考spring:
spring在这块做的比较好,其WebApplicationContext在开发时对于业务开发者来看其实就是被绑定在ServletContext(application)对象上的“单例”。虽然springContext并没有那么简单,其context可以有树形结构的。但至少提供一个默认入口。
ok,那我现在其实可以参考spring的context绑定方式对Jsgi的进行改造。另外,一方面其实有经验的开发者完全可以自己构造ringo的web-connector接口不一定要符合标准jsgi。完全可以构造一个类似nodejs-express的web框架。我这里是想尽量利用ringo自带的stick框架才这么干的。
架构:
下面是一个粗鲁(是“鲁”)的框线图:
整体结构大致由RingoListener,JsgiFilter,JsgiUtils,RingoEngineHolder(附加),RingoSpringBindingListener(附加)等若干类组成。
RingoListener:
其功能就是读取web.xml中的配置初始化ringo engine。然后将engine绑定到对servletContext。
主要代码是实现一个createRingoEngine方法,初始化engine。
protected RhinoEngine createRingoEngine(ServletContext servletContext){
if (engine == null) {
String ringoHome = getStringParameter(servletContext, "ringo-home", "/WEB-INF");
String modulePath = getStringParameter(servletContext, "ringo-module-path", "WEB-INF/usrmod"); //原来默认WEB-INF/app,可以多个逗号分割。
logger.info("modulePath:"+modulePath);
String bootScripts = getStringParameter(servletContext, "ringo-bootscript", null); //这个似乎不是基于module路径寻找的。
int optlevel = getIntParameter(servletContext, "ringo-optlevel", 0);
boolean debug = getBooleanParameter(servletContext, "ringo-debug", false);
boolean production = getBooleanParameter(servletContext, "ringo-production", false);
boolean verbose = getBooleanParameter(servletContext, "ringo-verbose", false);
boolean legacyMode = getBooleanParameter(servletContext, "ringo-legacy-mode", false);
// ServletContext context = servletContext;
Repository base = new WebappRepository(servletContext, "/");
Repository home = new WebappRepository(servletContext, ringoHome);
try {
if (!home.exists()) {
home = new FileRepository(ringoHome);
System.err.println("Resource \"" + ringoHome + "\" not found, "
+ "reverting to file repository " + home);
}
// Use ',' as platform agnostic path separator
String[] paths = StringUtils.split(modulePath, ",");
String[] systemPaths = {"modules", "packages"}; //如果ringo-home下有则ringo会直接用该目录作为系统模块,而取消从jar包中读取。所以如果没有必要不要建立。
RingoConfig ringoConfig =
new RingoConfig(home, base, paths, systemPaths);
ringoConfig.setDebug(debug);
ringoConfig.setVerbose(verbose);
ringoConfig.setParentProtoProperties(legacyMode);
ringoConfig.setStrictVars(!legacyMode && !production);
ringoConfig.setReloading(!production);
ringoConfig.setOptLevel(optlevel);
if (bootScripts != null) {
ringoConfig.setBootstrapScripts(Arrays.asList(
StringUtils.split(bootScripts, ",")));
}
engine = new RhinoEngine(ringoConfig, null);
return engine;
} catch (RuntimeException ex) {
logger.error("Ringo-engine initialization failed", ex);
servletContext.setAttribute(RINGO_ENGINE_ATTRIBUTE, ex);
throw ex;
} catch (Exception e) {
logger.error("Ringo-engine initialization failed", e);
servletContext.setAttribute(RINGO_ENGINE_ATTRIBUTE, e);
throw new java.lang.RuntimeException("Ringo-engine initialization failed",e);
} catch (Error err) {
logger.error("Ringo-engine initialization failed", err);
servletContext.setAttribute(RINGO_ENGINE_ATTRIBUTE, err);
throw err;
}
}
else{
return this.engine;
}
}
JsgiUtils:
这个类其实是一个工具类,通过它可以直接从servletContext中获取engine,非常简单。
JsgiFilter:
这个就是处理请求的filter。之所以用Filter主要是可以忽略一些处理。如果js无法处理,还能回到java版本的serlvet来完成。当然,一旦使用filter,js部分如果需要提供filterChain.doFilter()功能则需要二外提供扩展。
其主要部分当然是doFilter的实现。
/**
* 当前没有调用,基本只要filter-mapping url-pattern正确就会执行,并不会执行后续操作。?
* chain.doFilter(req, resp);
*/
@Override
public void doFilter(ServletRequest prequest, ServletResponse presponse,
FilterChain filterChain) throws IOException, ServletException {
//http
HttpServletRequest request = (HttpServletRequest) prequest;
HttpServletResponse response = (HttpServletResponse) presponse;
JsgiRequest req = new FilterJsgiRequest(request, response, requestProto,
engine.getScope(),this.servletContext ,this,this.filterConfig,filterChain); //如果底层不进行修改需要生成一个模拟的servlet
RingoWorker worker = engine.getWorker();
try {
//filterChain.doFilter也可以由js内部完成。帮助过滤真正需要的内容。
//区别于jsgiservlet一旦符合pattern(比如/XXX/*)必须进行处理。无法要求释放该请求过滤某个后缀。
//servlet无法取消某个特定url的拦截。二filter可以doFilter。
worker.invoke("ringo/jsgi/connector", "handleRequest", module,
function, req);
//
} catch (Exception x) {
List errors = worker.getErrors();
boolean verbose = engine.getConfig().isVerbose();
try {
renderError(x, response, errors);
RingoRunner.reportError(x, System.err, errors, verbose);
} catch (Exception failed) {
// custom error reporting failed, rethrow original exception
// for default handling
RingoRunner.reportError(x, System.err, errors, false);
throw new ServletException(x);
}
} finally {
worker.release();
}
}
需要注意的是,其实传递到js中时存在一个request的封装(按照jsgi要求)。
在其env属性下取消原有的servlet,添加filter相关内容。
/**
* 用来适配request对象原型类
* @author wfeng007
* @date 2013-10-1 下午08:43:49
*/
static class FilterJsgiRequest extends JsgiRequest{
.....
.....
public FilterJsgiRequest(HttpServletRequest request,
HttpServletResponse response, JsgiRequest prototype,
Scriptable scope,ServletContext servletContext,Filter filter,FilterConfig filterConfig,FilterChain filterChain) {
super(request, response, prototype, scope, new JsgiServlet()); //一个空的jsgiservlet之后需要删除。
deleteProperty((Scriptable)this.getProperty(this, "env"),"servlet"); //删除该对象引用,与servlet互相排斥?
//env-ext
defineProperty((Scriptable)this.getProperty(this, "env"),"servletContext", //filter.filterConfig可以得到config?
new NativeJavaObject(scope, servletContext, null), PERMANENT);
defineProperty((Scriptable)this.getProperty(this, "env"),"filter", //filter.filterConfig可以得到config?
new NativeJavaObject(scope, filter, null), PERMANENT);
defineProperty((Scriptable)this.getProperty(this, "env"),"filterConfig", //filterConfig
new NativeJavaObject(scope, filterConfig, null), PERMANENT);
defineProperty((Scriptable)this.getProperty(this, "env"),"filterChain",
new NativeJavaObject(scope, filterChain, null), PERMANENT);
}
}
RingoEngineHolder与RingoSpringBindingListener
这两个附加内容都是与engine绑定。通过这两个类可以讲engine绑定到一个静态区域,同时也可以绑定到默认的spring Context上。实现原理与参考Spring的listener即可。