Fork me on GitHub

模板引擎

  • 简洁友好的模板语法
  • 精益求精的性能优化
  • 高质量的设计与实现

示例

此文档主要演示HTTL比较重要的方面,可以做为入门参考。

模板示例

books.httl: (HTTL只有六个指令:#set, #if, #else, #for, #break, #macro,并且不会增加)

<!--## 定义变量类型,未定义的变量以Object类型处理 -->
<!--#set(User user, List<Book> books)-->
<html>
    <body>
    
    	<!--## 定义宏,可当变量或方法执行 -->
    	<!--#macro(cover(Book book))-->
            <img alt="${book.title}" src="${book.cover}" />
        <!--#end-->
        
        <!--## 条件判断 -->
        <!--#if(user.role =="admin")-->
        <table>
        
            <!--## 循环输出,类型可省,将基于泛型推导 -->
            <!--#for(Book book : books)-->
            <tr>
                
                <!--## 变量输出 -->
                <td>${book.title}</td>
                
                <!--## 执行宏输出,也可以用${cover} -->
                <td>$!{cover(book)}</td>
                
                <!--## 变量赋值,类型可省,将基于表达式推导 -->
                <!--#set(int price = book.price * book.discount)-->
                <td>${price}</td>
            </tr>
            
            <!--## 条件中断循环 -->
            <!--#break(for.index == 10)-->
            
            <!--## 当循环数据为空时执行 -->
            <!--#else-->
                没有数据。
            <!--#end-->
        
        </table>
        <!--#else(user)-->
            没有权限。
        <!--#else-->
            没有登录。
        <!--#end-->
    
    </body>
</html>

如果你使用Eclipse开发,可以通过下面的设置,让Eclipse用HTML编辑器打开httl文件:

Menus > Window > Preferences > General > ContentTypes > Text > HTML > Add > *.httl

配置示例

httl.properties:

import.packages+=com.xxx
template.directory=
message.basename=messages
input.encoding=UTF-8
output.encoding=UTF-8
reloadable=false
precompiled=false

其中,+=表示在缺省配置上追加配置,多个值用逗号分隔。

注意,如果使用缺省值,可以不配,缺省值参见: httl-default.properties

API示例

BooksServlet.java:

import httl.*;
import java.util.*;

Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put("user", user);
parameters.put("books", books);

Engine engine = Engine.getEngine();
Template template = engine.getTemplate("/books.httl");
template.render(parameters, response.getOutputStream());

注:缺省配置下,HTTL不依赖任何三方库,只需JDK1.5+即可。

注:缺省必须要用JDK运行,如果只有JRE,请配置为JavassistCompiler。

扩展示例

HTTL不允许扩展指令,以保持指令集最小化,防止模板语义复杂,所有扩展功能,以方法扩展实现。

比如JSP的自定义标签:<hasPrivilege resource="foo"> ... </hasPrivilege>

在HTTL中用方法扩展实现:#if(hasPrivilege("foo")) ... #end

只需定义静态方法:

package com.xxx;

public class PrivilegeMethod {

    public static boolean hasPrivilege(String resource) {
        return Context.getContext().get("loginUserId") != null;
    }

}

并配置:import.methods+=com.xxx.PrivilegeMethod

另外,HTTL支持用普通对象方法的格式,调用静态扩展方法,如:${date.format("yyyy-MM-dd")}

实际调用下面的静态方法:(被调对象作为第一个参数传入,后面的参数原样传入)

public static String format(Date date, String format) {
    return new SimpleDateFormat(format).format(date);
}

继承示例

你可以把模板继承的宏覆盖,理解成Java类继承的方法覆盖。

layout.httl:

<html>
    <body>
         <table>
            <tr>
                <td>
                    <!--#macro($crumbs)-->
                        <a href="./">Home</a>
                    <!--#end-->
                </td>
            </tr>
            <tr>
                <td>
                    <!--#macro($menus)-->
                        <a href="/users">Users</a><br/>
                        <a href="/books">Books</a><br/>
                    <!--#end-->
                </td>
                <td>
                    <!--#macro($main)-->
                        Building...
                    <!--#end-->
                </td>
            </tr>
        </table>
    </body>
</html>

其中,#macro($crumbs)表示在宏定义的位置上同时输出,相当于#macro(crumbs)加${crumbs}。

books.httl:

${extends("/layout.httl")}

<!--#macro(crumbs)-->
    ${super.crumbs} &gt; <a href="/books">Books</a>
<!--#end-->

<!--#macro(main)-->
    <table>
        <!--#for(Book book : books)-->
        <tr>
            <td>${book.title}</td>
        </tr>
        <!--#end-->
    </table>
<!--#end-->

其中,宏名称相同的覆盖父模板中的宏,宏参数可以和父模板中的宏不同。

没有覆盖的宏,将使用父模板中的宏直接输出。

注意,这里的宏名称前不要带$!符,否则会输出两次。

你可继承多个模板,子模板本身在继承指令前后也可以带内容。

crumbs宏里面的${super.crumbs}表示输出父模板中的crumbs宏。

你也可以基于CoC(Convention over Configuration)规则,自动继承模板:

# 自动继承模板名变量,如果context.get(extends.variable)变量存在,则继承该模板。
# 注意:此模板是从继承模板目录中查找的,即实际为:template.directory  + context.get(extends.variable)
extends.variable=layout

# 如果默认模板存在,则继承默认模板。
# 注意:默认模板是从继承模板目录中查找的,即实际为:template.directory + extends.default
extends.default=default.httl

# 父模板引用子模板的名称,如在父模板中调用:${nested},将输出子模板的内容。
# 或者父模板中如果有<!--#macro($nested)-->宏,将被子模板替换。
extends.nested=nested

安全示例

HTTL能根椐变量所处的位置,智能使用不同的安全过滤器,比如:

<htmL>

<!-- 这里会将html变量中的引号转成&quot; -->
<input value="${html}" />

<script type="text/javascript">
// 这里会将js变量中的引号转成\",而不是&quot;
var value ="${js}";
</script>

<style type="text/css">
.div {
    // 这里同样会将css变量中的引号转成\",而不是&quot;
    font-family:"${css}"
}
</style>

</htmL>

你可以通过httl.properties配置不同Filter实现,并且可以通过httl.spi.Switcher扩展点, 增加变量位置和Filter切换方式:(下面是缺省值,不用配置)

value.switchers=httl.spi.switchers.ScriptValueFilterSwitcher,httl.spi.switchers.StyleValueFilterSwitcher
value.filters=httl.spi.filters.EscapeXmlFilter
script.value.filters=httl.spi.filters.EscapeStringFilter
style.value.filters=httl.spi.filters.EscapeStringFilter

HTTL缺省开启了HTML,JS,CSS过滤,以防止用户忘记配置,而导致HTML注入攻击。

HTML注入攻击示例:

(1) 属性注入:

<input value="$!{foo}" />

如果foo变量的值为:
" onload="alert('HACK');
前面第一个引号会把value属性结束,中间的onload就会变成一个合法属性,从而执行注入的alert语句。

(2) 脚本注入:

<script type="text/javascript">
var value ="$!{foo}";
</script>

如果foo变量的值为:
";alert('HACK');//
前面的引号和分号会把value赋值结束,后面的双斜杠后会注释掉同行语句,从而执行中间注入的alert语句。

(3) 样式注入:

<style type="text/css">
.div {
    font-family:"$!{foo}"
}
</style>

如果foo变量的值为:
";star:expression_r(onload=function(){alert('HACK');});
前面的引号和分号会把font-family属性结束,从而通过expression_r执行注入的alert语句。

(4) 标签注入:

<td>$!{foo}</td>

如果foo变量的值为:
<script>alert('HACK');</script>
变量内容直接作为html运行,从而执行注入的alert语句。

异常示例

HTTL的异常信息通常都包含:出错原因,出错位置,解决办法,上下文信息。

比如变量未声明异常:

java.text.ParseException: Undefined variable"user". 
Please add variable type definition <!--#set(Xxx user)--> in your tempalte.
Occur to offset: 6, line: 2, column: 3, char: u, in: 
/WEB-INF/templates/user.httl
========================================
...${user.name}...
     ^-here
========================================
, stack: java.text.ParseException:
	at httl.spi.translators.DfaParser.parse(DfaParser.java:401)
	at httl.spi.translators.DefaultTranslator.translate(DefaultTranslator.java:109)
  • 出错原因:Undefined variable"user"
  • 出错位置:occur to offset: 6, line: 2, column: 3, char: u
  • 解决办法:Please add variable type definition <!--#set(Xxx user)-->
  • 上下文信息:/WEB-INF/templates/user.httl