今回は、JBOSS Nettyを使って、HTTPストリーミングを実現させる方法を紹介します。
HTTPストリーミングとはなんぞや?
ということですが、要はレスポンスが何度も分割されて出力されるだけです。
chunkedという形式で結果をかえせば、それがストリーミングという訳です。
たとえば、どんなところで使えるかと言えば、
チャットシステムなんかを構築する際に、受けるデータ(他人のコメント)を、
HTTPストリーミングという形で受ければ、つまり、PUSH配信ということが実現できるという事です。
どうやら、私はやらないし、つかわないのでよくわかりませんが、
twitterのStreaming APIみたいな感じ(まちがっていたらすいません。)のPUSH配信を実現する感じです。
では、nettyでのhandlerのサンプルを乗せますと、
package com.coltware.chat;
import java.util.Set;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.handler.codec.http.DefaultHttpChunk;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.http.HttpHeaders;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ChatHandler extends SimpleChannelUpstreamHandler {
private static final Logger log = LoggerFactory.getLogger(ChatHandler.class);
public ChatHandler(){
}
@Override
public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception {
log.debug("channelOpen");
super.channelOpen(ctx, e);
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
throws Exception {
Object ret = e.getMessage();
if(ret instanceof HttpRequest){
final HttpRequest request = (HttpRequest)ret;
Set<string> headers = request.getHeaderNames();
for(String name:headers){
log.debug("header[" + name + "]=>[" + request.getHeader(name) + "]");
}
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
response.setChunked(true);
response.setHeader(HttpHeaders.Names.CONTENT_TYPE, "application/octet-stream");
response.setHeader(HttpHeaders.Names.TRANSFER_ENCODING,HttpHeaders.Values.CHUNKED);
log.debug("response" + response);
e.getChannel().write(response);
this.writeStringChunk(e.getChannel(), "this is 1st chunk data");
this.writeStringChunk(e.getChannel(), "this is 2nd chunk data");
e.getChannel().write(HttpChunk.LAST_CHUNK).addListener(ChannelFutureListener.CLOSE);
}
else{
super.messageReceived(ctx, e);
}
}
private void writeStringChunk(Channel channel,String data){
ChannelBuffer chunkContent = ChannelBuffers.dynamicBuffer(channel.getConfig().getBufferFactory());
chunkContent.writeBytes(data.getBytes());
HttpChunk chunk = new DefaultHttpChunk(chunkContent);
channel.write(chunk);
}
}
これをサーバとして動かし、実際にHTTPを使ってアクセスすると
HTTP/1.1 200 OK Content-Type: application/octet-stream Transfer-Encoding: chunked 16 this is 1st chunk data 16 this is 2nd chunk data 0
とこんな感じの結果になります。
この例では、1つの処理の中で2つのHttpChunkを返して、そのまま閉じてしまいましたが、
たとえば、Chatなどのシステムを作る場合には、
接続を閉じず(63行目を実行せず)、そのまま、全体の接続を他のところでとっておき、
それぞれに、このChunkでくぎられたデータを出力していけば、チャットシステムの機能が簡単に作れるという訳です。
ただし、これをActionScriptのURLStreamをつかってChunkごとにデータをキチンととれないかと、
調べてみましたが、どうやらそれができないようです。
chunkedの形式ではなく、
this is 1st chunk datathis is 2nd chunk data
のようにしかデータが取得できないので、データ自体に1つのデータの区切りを作るための仕掛けを作っておいた方が良さそうです。


