示例
此文档主要演示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} > <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变量中的引号转成" -->
<input value="${html}" />
<script type="text/javascript">
// 这里会将js变量中的引号转成\",而不是"
var value ="${js}";
</script>
<style type="text/css">
.div {
// 这里同样会将css变量中的引号转成\",而不是"
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
