失敗、cometを使ってtail -fの内容を送りたい。 2008/02/03

クライアントでサーバー側のログをみたいということで、tomcat6に入っているcometさんの機能を使えないかなというわけで、頭をひねってみたのでした。

安直にtail -fをコマンド実行して標準出力の内容をせっせっとcomet使って、クライアントに送ればいいじゃんと思ったわけです。

方々を参考にして簡単に実装してみましたが、結果は、「もっと工夫しないとだめ」です。

工夫する必要がある箇所は、ずばりログのとりこぼしがないようにしないといけないところです。

ログのとりこぼしがないようにするためには、どこまでのログをクライアントは受信されたのかという情報をもたせないとできない。それで、その情報をもとにして、サーバーがログを送ってあげる。

あと、CometサーバーとCometクライアントの仕組みがよくわからないです。
基本はLong Pollingなんだというキーワード的にはわかってるんですが....

サーバー側がずーと書き込めるストリームをもちつづけて、だらだら書けば、クライアントに表示できるのであれば簡単だなぁと思ったのですが、その場合のクライアントの実装が思いつかないので、XmlHttpRequest方式(たぶん)で。

しかし、サーバー側の書き込みが終了したということを通知する方法がわからなかったので、BEGINで受け取ったイベントに対してTIMEOUTを設定して、必ずその時間がたったらクライアントはレスポンスを受け取り、処理をして再接続という実装(のつもり)にしてみました。

すぐにクライアントに反映されないので、メリットが感じられないような...

ログのように常に変化し続けるものを表示する場合ってiframe方式(たぶん)がいいのかもしれない。



理解不足、曲解、だめだめですが、とりあえず晒しておくことにします。

<html>
<head>
<script type="text/javascript" src="jquery-1.2.2.min.js"></script>
<script type="text/javascript" >
var isStop = false;
var cash_log1 ="";
var cash_log2 ="";
var cnt =0;
function oo(){
$.ajax({
url: 'http://localhost:8090/examples/tail',
type: 'POST',
dataType: "text",
timeout: 60000,
error: function(){
if(isStop) return;
alert('Error loading text document');
},
success: function(data){

if(isStop) return;
if(!(0 < data.length)){
setTimeout(oo, 1);
return;
}

cash_log1 = cash_log2;
cash_log2 = data;

$('#pp').val(cash_log1 + cash_log2);
setTimeout(oo, 1);
}
});


}
</script>
</head>
<body onload="oo();">
<input type="button" onclick="javascript:isStop=true;" value="stop"/>
<input type="button" onclick="javascript:isStop=false;oo();" value="start"/>
<div id="p"></div>
<textarea id="pp" cols=100 rows=10>
</textarea>
</body>
</html>


import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Scanner;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;

import org.apache.catalina.CometEvent;
import org.apache.catalina.CometProcessor;

public class TailServlet extends HttpServlet implements CometProcessor {

private static final long serialVersionUID = 1L;
protected ArrayList<CometEvent> connections = new ArrayList<CometEvent>();
protected MessageSender messageSender = null;

public void init() throws ServletException {
messageSender = new MessageSender(getInitParameter("targetFile"));
Thread messageSenderThread = new Thread(messageSender, "MessageSender["
+ getServletContext().getContextPath() + "]");
messageSenderThread.setDaemon(true);
messageSenderThread.start();
}

public void destroy() {
connections.clear();
messageSender.stop();
messageSender = null;
}

public void event(CometEvent event) throws IOException, ServletException {

HttpServletRequest request = event.getHttpServletRequest();
if (event.getEventType() == CometEvent.EventType.BEGIN) {

// timeoutを起こしてクライアントにいったんデータを返す。
event.setTimeout(new Integer(5000));
log("Begin for session: " + request.getSession(true).getId());
log(connections.toString());
synchronized (connections) {
connections.add(event);
}

} else if (event.getEventType() == CometEvent.EventType.ERROR) {
log("Error for session: " + request.getSession(true).getId());
log("EventSubType: " + event.getEventSubType());
synchronized (connections) {
boolean isRemoved = connections.remove(event);
log(" isRemoved:" + isRemoved);
}
event.close();
} else if (event.getEventType() == CometEvent.EventType.END) {
log("End for session: " + request.getSession(true).getId());
log("EventSubType: " + event.getEventSubType());
synchronized (connections) {
boolean isRemoved = connections.remove(event);
log(" isRemoved:" + isRemoved);
}
event.close();
} else if (event.getEventType() == CometEvent.EventType.READ) {
}
}

public class MessageSender implements Runnable {
protected boolean running = true;
String targetFile;

public MessageSender(String targetFile) {
this.targetFile = targetFile;
}

public void stop() {
running = false;
}

public void run() {
String command = "/usr/bin/tail -f " + targetFile;
Runtime runtime = Runtime.getRuntime();
Process process = null;
try {
process = runtime.exec(command);
InputStream in = process.getInputStream();
Scanner scanner = new Scanner(in);

while (scanner.hasNext() && running) {
String s = scanner.nextLine();
// log("o_o!==>" + s);
synchronized (connections) {
Iterator<CometEvent> iterator = connections.iterator();
while (iterator.hasNext()) {
CometEvent event = (CometEvent) iterator.next();
try {

PrintWriter writer = event
.getHttpServletResponse().getWriter();
writer.println(s);
writer.flush();

} catch (IOException e) {
log("IOExeption sending message", e);
} catch (UnsupportedOperationException e) {
e.printStackTrace();
}

}

}
}

} catch (IOException e1) {
e1.printStackTrace();
} finally {
if (process != null) {
try {
process.getErrorStream().close();
} catch (IOException e) {
}
try {
process.getInputStream().close();
} catch (IOException e) {
}
try {
process.getOutputStream().close();
} catch (IOException e) {
}
}
running = false;
}

}
}

}

: