最近 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 件のコメント:
コメントを投稿