「緊急地震速報 by Extension」を作って経験したことなどをまとめました。今回は後編です。前編は会社のブログに書きました。
僕は生産設備を持っていませんので食料をはじめとした物流における支援は難しいのかもしれません。しかしながらシステムエンジニアとして間接的な支援や情報における支援はできます。僕は、天災における「破壊」を修復するのは人々の「生産」と考えるとともに、その「生産」の一部分を担う者として頑張っていきたいと思います。
東北地方太平洋沖地震の後、余震が頻発し緊急地震速報の警告音も頻繁に耳にするようになりました。警告音に焦らされる一方で、本当は落ち着いて揺れに対処しなければならないと思っていました。そこで緊迫感を与えないような緊急地震速報を出せないものかと今回の制作にいたりました。(なので初期バージョンには音がなかったのです)
また、本当は「緊急地震速報 by Chrome Extension」という名前で登録しようとしたのですが「Chrome」というワードは使用ができないので「緊急地震速報 by Extension」という名前になりました。
■プロキシ経由での接続
大きな会社などではプロキシ経由でインターネットに接続するようになっていて配信サーバと接続ができないようでした。
変更前
ポート12001番を使用していました。
var hosts = [
'123.123.123.123:12001'
];
変更後
ポート443番を使用するように変更しました。
var hosts = [
'123.123.123.123:443'
];
HTTPSで使用するポートを用いることにより一部の方の接続が可能になりました。
さらに変更後
ポート443番、80、8080番、12001番を使用するように変更しました。
var hosts = [
'123.123.123.123:443',
'123.123.123.123:80',
'123.123.123.123:8080',
'123.123.123.123:12001'
];
これによって判明したのは1/3〜1/4の利用者の方は443にしか接続できなかった事です。
■マルチバイト文字列のbroadcast
今回、Websocketサーバからbroadcastする際、node-websocket-serverを使用したのですが日本語を正しく送信できませんでした。
解決策
wsServer.broadcast('\\u3092');
こうすることでクライアント側には\u3092という文字列が送信されるようになります。
ちょっとライブラリの中をのぞいてみます。
server.js
broadcastメソッドです。
this.broadcast = function(data) {
manager.forEach(function(client) {
clientWrite(client, data);
});
};
上述のとおりforEachで回してます。clientWriteメソッドも確認します。
function clientWrite(client, data) {
if (client && client._state === 4) {
client.write(data, 'utf8');
}
}
utf8としていされてwriteされています。
manager.js
Manager.prototype.forEach = function(callback, thisArg) {
var current = this._head;
while (current !== null) {
callback.call(thisArg, current.connection);
current = current._next;
}
};
this._headは連結リストの先頭の要素のようです。
this._head = client;
clientオブジェクトが入っているようです。
var client = {
id: connection.id,
_next: null,
connection: connection
};
接続IDと接続、次の要素を保持しています。
connection.js
function write(connection, data) {
debug(connection.id, 'write: ', (new Buffer(data)).inspect());
if (connection._socket.writable) {
return connection._socket.write(data, 'binary');
}
return false;
}
binaryと指定されていますね。完全に読み切るには少々時間がかかるので適宜更新します。
参考
■正規表現
情報はtwitterのUser Streams API経由で取得していたのですが、ご利用者の方から震度やマグニチュードだけを簡略化して欲しいとの要望がありました。そこで正規表現を使用し文字列を整形して表示することにしました。ところが一部のご利用者には元の文章がそのまま表示されていました。
解決策
以下のようにして日本語をマッチさせるときに16進表現を使用することで解決しました。
var reg = new RegExp('\u9707\u5EA6([3-9]\u5F37*\u5F31*)', 'ig');
text.match(reg);
var scale = RegExp.$1;
// filter : simplify
if(localStorage["simplify"] !== "false"){
var reg = new RegExp('([0-9]*/[0-9]*/[0-9]*)', 'ig');
text.match(reg);
var date = RegExp.$1;
var reg = new RegExp('([0-9]*\:[0-9]*)', 'ig');
text.match(reg);
var time = RegExp.$1;
var reg = new RegExp('\u30DE\u30B0\u30CB\u30C1\u30E5\u30FC\u30C9([0-9\.]*)', 'ig');
text.match(reg);
var magnitude = RegExp.$1;
var reg = new RegExp('\u3001(.*?)\u306E', 'ig');
text.match(reg);
var place = RegExp.$1;
if(date != '' && time != '' && place != '' && scale != '' && magnitude != ''){
text = date + " " + time + " " + place + "\n\u9707\u5EA6" + scale + "\n\u30DE\u30B0\u30CB\u30C1\u30E5\u30FC\u30C9" + magnitude;
}
}
// filter : scale
switch(localStorage["scale"]){
case "7":
if(parseInt(scale, 10) < 7){return false;}
break;
case "6":
if(parseInt(scale, 10) < 6){return false;}
break;
case "5":
if(parseInt(scale, 10) < 5){return false;}
break;
case "4":
if(parseInt(scale, 10) < 4){return false;}
break;
}
ブラウザ側の言語設定は各々異なります。通信時の文字コードを指定しても解決するかもしれません。
■twitterとの接続
node.jsを使い、以下のようなコードでtwitterと接続してます。
var client = http.createClient(
80,
"stream.twitter.com",
false,
false,
{
"username" : "hogehoge",
"password" : "fugafuga"
}
);
var request = client.request(
"GET",
"/1/statuses/filter.json?follow=123456789",
{
"host" : "stream.twitter.com"
}
);
request.end();
request.on(
"response",
function(response){
//sys.puts("response");
var chunk = '';
response.on(
"data",
function(data){
// データ受信処理
}
);
response.on(
'end',
function(){
// twitterから接続を切られた時
}
);
}
);
twitter切断時
実はたまに接続を切られます。切断される可能性を考慮し以下のようなコードを使用してます。
var getRequest = function(){
var request = client.request(
"GET",
"/1/statuses/filter.json?follow=123456789",
{
"host" : "stream.twitter.com"
}
);
request.end();
request.on(
"response",
function(response){
response.on(
"data",
function(data){
// データ受信処理
}
);
response.on(
'end',
function(){
// twitterから接続を切られた時
r = getRequest();// 再接続
}
);
}
);
return request;
}
var r = getRequest();
通信は必ず切れるものと考えてコードを書かなくてはいけませんね。さらに実は以下のようなスクリプトも実行してます。
request.on(
"response",
function(response){
//sys.puts("response");
var chunk = '';
response.on(
"data",
function(data){
// データ受信処理
}
);
response.on(
'end',
function(){
// twitterから接続を切られた時
throw new Error('twitter disconnected me!!');
}
);
}
);
node.jsにおいてcatchできなかったErrorはプロセスを終了させます。従って以下のようなシェルスクリプトと組み合わせてプロセスが終了してないか定期的にチェックし、終了していた場合は再起動することで接続を維持しています。
#!/bin/sh
while true
do
isAlive=`ps -ef | grep "my-websocket-server.js" | grep -v grep | wc -l`
if [ $isAlive -ge 1 ]; then
echo "process is alive"
else
node my-websocket-server.js.js
echo "process is dead"
fi
sleep 3
done
■アップデート
拡張機能のアップデートは自動で行われますが、新バージョンを登録してすぐに全ユーザのアップデートが完了するわけではありません。意外にも早く直後にアップデートされる方もいれば、時間がかかる方もいます。1時間で10%程度の方がアップデートされるようです。一方、90%のユーザはアップデート前のクライアントでサーバに接続しますので考慮して更新しなければなりません。
■起動
ちなみにですが以下のコマンドでWebSocketサーバを起動してます。
nohup ./check.sh > log.txt &
接続数を調べるには以下のようなコマンドを使ってます。
lsof -i:12345 | grep "node" | wc -l
■twitterからの文字列
システムの設計の問題にもなりますが、twitterからの文字列が正しくパースできないようなケースが稀にあります。入力は受け手側が意図する形式になるとは限らないということです。
- 文字列をパースできるように条件分岐する
- 但し、ブラックリスト方式に近いので未知の正しくない文字列はパース出来ない
- データストアを作って保存に対してbroadcastのトリガを引かせる
- 全てのサーバが正しくパースできなかった事はないので非常に有用だが、失敗する確率はわずかながらある
■サーバ負荷
node.jsの素晴らしさを実感する毎日ですが、接続数が1万数千を超えるとさすがに0.5〜1秒程度遅延します。早く多くの方に届けるべき情報ではあるものの、一方で多くの方が接続すると遅延が生じやすくなります。サーバは個人で賄っているので限度があります。非常に難しい問題です。財閥の末裔だったらデータセンターごと買うのになと思う毎日です。
■まとめ
僕は水や電気の節約には賛成です。しかしながら根拠が不明瞭な自粛ムードには反対です。冒頭で述べました通り、破壊を修復するのは生産であり、手を止めて遠慮する事では無いと思います。何もできることがないと考えた結論の自粛より、いつも通り働き、学び、生活する方が生産的だと僕は考えます。まだまだ対応できていない事ばかりで完璧ではありませんが今後とも宜しくお願いいたします。早く余震が収まるといいですね。(●´⌓`●)
拡張機能への要望
今まで通り@mi_eqbotまでお願いいたします。自分のブログよりも確実にチェックしてます。w
その他開発などへの興味
@mitsuaki_iまでお願いいたします。