3.16. 定制模板引擎
Beetl在线体验(http://ibeetl.com/beetlonline/)面临一个挑战,允许用户输入任何脚本做练习或者分享代码。但又需要防止用户输入恶意的代码,如
<%
for(var i=0;i<10000000;i++){
//其他代码
}
%>
此时,需要定制模板引擎,遇到for循环的时候,应该限制循环次数,譬如,在线体验限制最多循环5次,这是通过定义替换GeneralForStatement类来完成的,这个类对应了for(exp;exp;exp) ,我们需要改成如下样子:
class RestrictForStatement extends GeneralForStatement{
public RestrictForStatement(GeneralForStatement gf){
super(gf.varAssignSeq, gf.expInit, gf.condtion, gf.expUpdate, gf.forPart, gf.elseforPart, gf.token);
}
public void execute(Context ctx){
if (expInit != null){
for (Expression exp : expInit){
exp.evaluate(ctx);
}
}
if (varAssignSeq != null){
varAssignSeq.execute(ctx);
}
boolean hasLooped = false;
int i = 0;
for (; i < 5; i++){
boolean bool = (Boolean) condtion.evaluate(ctx);
if (bool){
hasLooped = true;
forPart.execute(ctx);
switch (ctx.gotoFlag){
case IGoto.NORMAL:
break;
case IGoto.CONTINUE:
ctx.gotoFlag = IGoto.NORMAL;
continue;
case IGoto.RETURN:
return;
case IGoto.BREAK:
ctx.gotoFlag = IGoto.NORMAL;
return;
}
}else{
break;
}
if (this.expUpdate != null){
for (Expression exp : expUpdate){
exp.evaluate(ctx);
}
}
}
if (i >= 5){
try{
ctx.byteWriter.writeString("--Too may Data in loop,Ignore the left Data for Online Engine--");
ctx.byteWriter.flush();
} catch (IOException e){
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
@Override
public void infer(InferContext inferCtx){
super.infer(inferCtx);
}
}
尽管上面代码很复杂,但实际上是改写了原来的GeneralForStatement,将原来的24行while(true) 替换成for (; i < 5; i++) 用来控制最大循环,并且62行检测如果循环退出后,i等于5,则提示Too Many Data in Loop.
现在需要将此类替换原有的GeneralForStatement,
public class OnlineTemplateEngine extends DefaultTemplateEngine{
public Program createProgram(Resource resource, Reader reader, Map<Integer, String> textMap, String cr,GroupTemplate gt){
Program program = super.createProgram(resource, reader, textMap, cr, gt);
modifyStatemetn(resource,program,gt);
return program;
}
private void modifyStatemetn(Resource resource,Program program,GroupTemplate gt){
Statement[] sts = program.metaData.statements;
StatementParser parser = new StatementParser(sts, gt, resource.getId());
parser.addListener(WhileStatement.class, new RestrictLoopNodeListener());
parser.addListener(GeneralForStatement.class, new RestrictLoopNodeListener());
parser.parse();
}
}
继承FastRuntimeEngine有所不同,因为改引擎会copy出一个脚本做分析优化,因此,俩个脚本都需要做修改
public class OnlineTemplateEngine extends FastRuntimeEngine{
public Program createProgram(Resource resource, Reader reader, Map<Integer, String> textMap, String cr,GroupTemplate gt){
FilterProgram program = (FilterProgram)super.createProgram(resource, reader, textMap, cr, gt);
modifyStatemetn(resource,program,gt);
modifyStatemetn(resource,program.getCopy(),gt);
return program;
}
}
- StatementParser 是关键类,他允许对模板的Program进行解析,并替换其中的Statement。parser.addListener 方法接受俩个参数,第一个是需要找的类,第二个是执行的监听器。
- 可以参考在线体验的源码:http://git.oschina.net/xiandafu/beetlonline/blob/master/src/org/bee/tl/online/OnlineTemplateEngine.java
class RestrictLoopNodeListener implements Listener{
@Override
public Object onEvent(Event e){
Stack stack = (Stack) e.getEventTaget();
Object o = stack.peek();
if (o instanceof GeneralForStatement){
GeneralForStatement gf = (GeneralForStatement) o;
RestrictForStatement rf = new RestrictForStatement(gf);
return rf;
}else{
return null;
}
}
}
该监听器返回一个新的RestrictForStatement 类,用来替换来的GeneralForStatement。如果返回null,则不需替换。这通常发生在你仅仅通过修改该类的某些属性就可以的场景
完成这些代码后,在配置文件中申明使用新的引擎
ENGINE=org.bee.tl.online.VarRefTemplateEngine
这样就完成了模板引擎定制。
另外一种定制模板引擎方法(2.7.22)
在2.7.21 版本后,提供了另外一种定制模板引擎的方法,可以在Beetl语法树生成的时候提供定制(上面那种是在生成后),这种方法更灵活。但需要对语法树有所了解。
首先需要创建一个引擎
ENGINE=org.bee.tl.online.VarRefTemplateEngine
OnlineTemplateEngine 代码如下,
public class VarRefTemplateEngine extends DefaultTemplateEngine {
protected AntlrProgramBuilder getAntlrBuilder(GroupTemplate gt){
AntlrProgramBuilder pb = new AntlrProgramBuilder(gt);
return pb;
}
class VarRefAntlrProgramBuilder extends AntlrProgramBuilder{
public VarRefAntlrProgramBuilder(GroupTemplate gt) {
super(gt);
}
}
}
AntlrProgramBuilder 方法用于构造语法树,有多个Protected方法可以重载,以实现新的实现。