最近 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 こんなアクセスをすると結果こうなりました。きちんと表示されますね。

0 件のコメント:
コメントを投稿