示例
此文档主要演示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