ラベル mustache の投稿を表示しています。 すべての投稿を表示
ラベル mustache の投稿を表示しています。 すべての投稿を表示

2013年5月3日金曜日

Template Engine - mustache : How to import another template.

以前にメモした {{mustache}} テンプレートについて書きましたが、mustache を使って対象のテンプレートに共通のヘッダやらフッターやらをimportしたいって思いますよね。
以下のようにできます。まぁ本家のマニュアルにも記載してありましたが,,

まずは、元になるテンプレートを用意します。
import したいテンプレートの部分は {{ > 対象となるテンプレートのパス }} のように設定します。

{{> common/common_header }}
  {{!This is comment of Mustache!!}}
  <h1>pojo.str</h1>
  <h2>{{str}}</h2>
  
  <h1>pojo.num</h1>
  <h2>{{num}}</h2>
  
  <h1>pojo.flag</h1>
  {{#flag}}flag = true {{/flag}}
  {{^flag}}flag = false {{/flag}}
  
  <h1>pojo.array</h1>
  {{#array}}
  {{.}}</br>
  {{/array}}
  
  <h1>pojo.data</h1>
  {{#data}}
  <p>upper: {{a}} , {{b}}</p></br>
  {{/data}}
  
  <h1>Escaped Characters</h1>
  {{escape}}
{{> common/common_footer }}

で import されるテンプレートは以下のように定義

<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
<meta charset="UTF-8">
</head><body>

<p>This is footer</p>
</body>
</html>

プログラム側は、以下のように ※前回のとほぼ変わりません。テンプレートに渡すオブジェクトを2つ渡しているだけです

package com.blogspot.agiroh.netty.template.html;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;

public class ExampleMustache {

 final static String template_path = "template/mustache/example.mustache";

 public static class ExamplePojo {
  String str;
  long num;
  boolean flag;
  Map<String, Object> data;
  List<String> array;
  String escape;
 }

 static Map<String, Mustache> templates = new HashMap<>();

 public void run() throws Exception {

  // default is classpath.
  MustacheFactory factory = new DefaultMustacheFactory(); 
  
  Mustache mustache = null;
  if (!templates.containsKey(template_path)) {
   mustache = factory.compile(template_path);
   templates.put(template_path, mustache);
  } else {
   System.out.println("templates from cache");
   mustache = templates.get(template_path);
  }

  ExamplePojo pojo = new ExamplePojo();
  pojo.str = UUID.randomUUID().toString();
  pojo.num = new Date().getTime() / 1000;
  pojo.flag = true;
  pojo.data = new HashMap<String, Object>();
  pojo.data.put("a", "A");
  pojo.data.put("b", "B");
  pojo.array = new ArrayList<>();
  pojo.array.add("hoge");
  pojo.array.add("fuga");
  pojo.escape = "<p>\"te&st\"</p>";

  Map<String, String> headerPojo = new HashMap<>();
  headerPojo.put("title", "Import another templtes");

  mustache.execute(new PrintWriter(System.out), new Object[] { pojo, headerPojo }).flush();
 }

 public static void main(String[] args) {
  try {
   new ExampleMustache().run();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}

これで思ったとおり動作できました。

2013年3月10日日曜日

Netty with html template engine.


最近 playframework やら、twitter の Finagle などが利用されていることで知られる nettyをいろいろ個人的に使っているのですが、netty は基本的に network framework なので基本的には html を生成するような昨日はありません。こういったことはアプリケーションの実装に任されています。playframework などは基盤のevent-drivenのnetworkフレームワークにnettyを利用しながら、httpのframeworkとしてよく考えられていることで人気があります。
playframework を使っても良かったのですが、playframework も設定などはplayframeworkの作法があります。netty だけを使ってアプリケーションは自由に実装したいと思いまずは、html をレンダリングすることからとおもいます。html のテンプレートエンジンは前回のブログで書いた mustache を使おうとおもいます。

まずは、pom.xml
<project 
  xmlns="http://maven.apache.org/POM/4.0.0" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>

 <groupId>com.blogspot.3agiroh.netty.template.html</groupId>
 <artifactId>mastache-template-test</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>jar</packaging>

 <name>mastache-template-test</name>
 <url>http://maven.apache.org</url>

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 </properties>

 <build>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
     <source>1.7</source>
     <target>1.7</target>
     <encoding>UTF-8</encoding>
    </configuration>
   </plugin>
   <plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <version>2.3.7</version>
    <extensions>true</extensions>
    <configuration>
     <instructions>
      <!--<Export-Package>*</Export-Package> <Import-Package>*</Import-Package> -->
     </instructions>
    </configuration>
   </plugin>
  </plugins>
 </build>

 <dependencies>
  <!-- mustache template engine. 
  @see https://github.com/spullara/mustache.java -->
  <dependency>
   <groupId>com.github.spullara.mustache.java</groupId>
   <artifactId>compiler</artifactId>
   <version>0.8.10</version>
  </dependency>

  <dependency>
   <groupId>io.netty</groupId>
   <artifactId>netty</artifactId>
   <version>3.6.2.Final</version>
  </dependency>
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-api</artifactId>
   <version>1.7.2</version>
  </dependency>
  <dependency>
   <groupId>ch.qos.logback</groupId>
   <artifactId>logback-classic</artifactId>
   <version>1.0.9</version>
  </dependency>
  <dependency>
   <groupId>commons-configuration</groupId>
   <artifactId>commons-configuration</artifactId>
   <version>1.9</version>
  </dependency>
  <dependency>
   <groupId>commons-lang</groupId>
   <artifactId>commons-lang</artifactId>
   <version>2.6</version>
  </dependency>

  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>3.8.1</version>
   <scope>test</scope>
  </dependency>
 </dependencies>
</project>

起動のクラスはこんな感じで

package com.blogspot.agiroh.netty.template.html;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpContentCompressor;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;

public class SimpleHttpServer {

 final int port;
 
 public SimpleHttpServer( int port) {
  this.port = port;
 }
 
 public void run() {
  
  ServerBootstrap bootstrap = new ServerBootstrap(
      new NioServerSocketChannelFactory(
        Executors.newCachedThreadPool(), 
        Executors.newCachedThreadPool()));
  
  bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
   
   @Override
   public ChannelPipeline getPipeline() throws Exception {
    
    ChannelPipeline pipeline = Channels.pipeline();
    pipeline.addLast("decoder", new HttpRequestDecoder());
    pipeline.addLast("aggregator", new HttpChunkAggregator(1048576));
    pipeline.addLast("encoder", new HttpResponseEncoder());
    pipeline.addLast("deflater", new HttpContentCompressor());
    pipeline.addLast("htmlrender", new HtmlRenderHandler());
    
    return pipeline;
   }
  });
  
  bootstrap.bind(new InetSocketAddress(port));
  
 }
 
 public static void main(String[] args) {
  int port = 9000;
  new SimpleHttpServer(port).run();
 }
}
で、HTMLを生成するところは mustache java を利用してこんな感じ
package com.blogspot.agiroh.netty.template.html;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBufferOutputStream;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpMessage;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.jboss.netty.handler.codec.http.QueryStringDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;

public class HtmlRenderHandler
  extends SimpleChannelUpstreamHandler {

 static Logger log = LoggerFactory.getLogger(HtmlRenderHandler.class);

 public static final int RES_BUFFER_CAPACITY = 65535;

 public static class ExamplePojo {
  String str;
  long num;
  boolean flag;
  Map<String, Object> data;
  List<String> array;
  String escape;
 }

 @Override
 public void messageReceived(
   ChannelHandlerContext ctx, MessageEvent e) throws Exception {
  
  Object msg = e.getMessage();
  
  if (msg instanceof HttpRequest) {

   HttpRequest request = (HttpRequest) msg;

   if (HttpHeaders.is100ContinueExpected(request)) {
    send100Continue(e);
   }

   ExamplePojo pojo = new ExamplePojo();
   pojo.str = "テスト";
   pojo.num = 30;
   pojo.flag = true;

   QueryStringDecoder qsd = new QueryStringDecoder(request.getUri());
   Map<String, List<String>> params = qsd.getParameters();
   pojo.data = new HashMap<String, Object>();
   if (!params.isEmpty()) {
    for (Map.Entry<String, List<String>> entry : params.entrySet())
     pojo.data.put(entry.getKey(), StringUtils.join(entry.getValue(), ","));
   } else {
    pojo.data.put("key1", "value1");
   }

   pojo.array = Arrays.asList("hoge", "fuga");
   pojo.escape = "<p>hogehogehoge</p>";

   render(e, "html/ja/test.mustache", pojo);
   return;
  }
  
  log.debug("[unknown]");
 }

 protected void render(
   MessageEvent e,  // HTTP request event.
   String resource, // mustache template. 
   Object data)   // mustache template bindings.
     throws IOException {
  
  log.debug("[{}] start rendering.", resource);
  
  HttpMessage req = (HttpMessage) e.getMessage();
  HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);

  MustacheFactory mf = new DefaultMustacheFactory();
  Mustache mustache = mf.compile(resource);

  ChannelBuffer buffer = ChannelBuffers.buffer(RES_BUFFER_CAPACITY);
  ChannelBufferOutputStream out = new ChannelBufferOutputStream(buffer);
  mustache.execute(new PrintWriter(out), data).flush();
  res.setContent(out.buffer());
  try {
   out.close();
  } catch (Exception ee) {}

  res.setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/html; charset=utf-8;");

  final boolean keepalive = HttpHeaders.isKeepAlive(req);
  if (keepalive) {
   res.setHeader(HttpHeaders.Names.CONTENT_LENGTH, res.getContent().readableBytes());
   res.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
  }

  // Unsupported cookie.

  ChannelFuture future = e.getChannel().write(res);
  if (!keepalive) {
   future.addListener(ChannelFutureListener.CLOSE);
  }

 }

 private static void send100Continue(MessageEvent e) {
  log.info("send 100 continue.");
  HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
  e.getChannel().write(response);
 }
 
 @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
            throws Exception {
  log.error("Error!! : {}", e.getCause().getMessage());
        e.getCause().printStackTrace();
        e.getChannel().close();
    }
}

そしてテンプレートは以下のようにして実行する。

<!DOCTYPE html>
<html>
<head>
<title>Mustache</title>
<meta charset="UTF-8">
</head>
<body>
 {{!This is comment of Mustache!!}}
 <h1>pojo.str</h1>
 <h2>{{str}}</h2>
 
 <h1>pojo.num</h1>
 <h2>{{num}}</h2>
 
 <h1>pojo.flag</h1>
 {{#flag}}flag = true {{/flag}}
 {{^flag}}flag = false {{/flag}}
 
 <h1>pojo.array</h1>
 {{#array}}
 {{.}}</br>
 {{/array}}
 
 <h1>pojo.data</h1>
 {{#data}}
 <p>upper: {{a}} , {{b}}</p></br>
 {{/data}}
 
 <h1>Escaped Characters</h1>
 {{escape}}
</body>
</html>

http://localhost:9000?a=hogehoge&b=fugafuga こんなアクセスをすると結果こうなりました。きちんと表示されますね。


Template Engine - mustache

テンプレートエンジンはみなさんご存知かと思います。有名なのですと以下のようなものがあります。
  • JSP
  • Velocity
  • FreeMarker
「テンプレートエンジンはテンプレートと呼ばれる雛形と、あるデータモデルで表現される入力データを合成し、成果ドキュメントを出力するソフトウェアまたはソフトウェアコンポーネントである」とあります。

最近だと以下の様なのあるようです。

  • Thymeleaf
    • 所見:コーダーさんが用意してくれるhtmlをほぼ変更せずにテンプレートエンジンを適用できるような感じ。
  • Mustache
    • 所見:「ますたっしゅ」って呼ぶそうです。Ruby、Java、Python、Javascript、node.js、... と多くの言語をサポート。使い方がシンプル。
今回は mustache についてJavaを使って簡単に試してみた結果をまとめます。
まずはテンプレートは以下の様なものを用意しました。

<!DOCTYPE html>
<html>
<head>
<title>Mustache</title>
<meta charset="UTF-8">
</head>
<body>
 {{!This is comment of Mustache!!}}
 <h1>pojo.str</h1>
 <h2>{{str}}</h2>
 
 <h1>pojo.num</h1>
 <h2>{{num}}</h2>
 
 <h1>pojo.flag</h1>
 {{#flag}}flag = true {{/flag}}
 {{^flag}}flag = false {{/flag}}
 
 <h1>pojo.array</h1>
 {{#array}}
 {{.}}</br>
 {{/array}}
 
 <h1>pojo.data</h1>
 {{#data}}
 <p>upper: {{a}} , {{b}}</p></br>
 {{/data}}
 
 <h1>Escaped Characters</h1>
 {{escape}}
</body>
</html>
</html>

{{ }} で囲われたところにデータで埋め込まれます。{{#xxx}}〜{{/xxx}}が配列データの繰り返しです。囲われたデータにアクセスする際は、{{.}}とドットでアクセスします。またMapデータのようなkey-valueは、{{key}}でvalue要素にアクセスします。


でJavaは以下のように用意してみました。

package com.blogspot.agiroh.netty.template.html;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;

public class ExampleMustache {
 
 final static String template_path = "template/mustache/example.mustache";
 
 public static class ExamplePojo {
  String str;
  long num;
  boolean flag;
  Map<String, Object> data;
  List<String> array;
  String escape;
 }
 
 static Map<String, Mustache> templates = new HashMap<>();
 
 public void run() throws Exception {
  
  MustacheFactory factory = new DefaultMustacheFactory(); // default is classpath root
  
  Mustache mustache = null;
  if ( !templates.containsKey(template_path)) {
   
   mustache = factory.compile(template_path);
   templates.put(template_path, mustache);
  } else {
   System.out.println("templates from cache");
   mustache = templates.get(template_path);
  }
  
  ExamplePojo pojo = new ExamplePojo();
  pojo.str = UUID.randomUUID().toString();
  pojo.num = new Date().getTime()/1000;
  pojo.flag = true;
  pojo.data = new HashMap<String, Object>();
  pojo.data.put("a", "A");
  pojo.data.put("b", "B");
  pojo.array = new ArrayList<>();
  pojo.array.add("hoge");
  pojo.array.add("fuga");
  pojo.escape = "<p>\"te&st\"</p>";
  
  mustache.execute( new PrintWriter(System.out), pojo).flush();
 }
 
 public static void main(String[] args) {
  try {
    new ExampleMustache().run();
  } catch (Exception e) {
   e.printStackTrace();
  }
 } 
}

結果は以下になります。きちんとhtmlのエスケープ処理もされています。なかなか使いやすいとおもいますがいかがでしょうか。

<!DOCTYPE html>
<html>
<head>
<title>Mustache</title>
<meta charset="UTF-8">
</head>
<body>
 
 <h1>pojo.str</h1>
 <h2>5b954508-cf30-4501-b173-75a33446e4ba</h2>
 
 <h1>pojo.num</h1>
 <h2>1362906356</h2>
 
 <h1>pojo.flag</h1>
 flag = true 
 
 
 <h1>pojo.array</h1>
 hoge</br>
 fuga</br>
 
 <h1>pojo.data</h1>
 <p>upper: A , B</p></br>
 
 <h1>Escaped Characters</h1>
 &lt;p&gt;&quot;te&amp;st&quot;&lt;/p&gt;
</body>
</html>
</html>