Tuppari の Java クライアントを作った
Tuppari - WebSocket on Your Cloud - - Scalaとlift のはずだった ・・・
前回、Node.js で構築した Tuppari (コンセプトは Pusher クローン)を紹介しましたが、実は API 仕様が公開されていて、Node.js 以外の言語/環境でもクライアントを作ることができます。
で、これにもとづいて Java クライアントを作りました。他の言語でも書けると思うので、興味がある方は是非挑戦してみてください。そして、できたら教えて下さい。
Tuppari 公開記念ハッカソン
興味があればどうぞー。Node.js はちょっとわからないけど、Java なら任せろー、な方も大歓迎です。ご希望があれば、Tuppari の使い方、各自の環境へのインストール方法などサポートします。
使い方
maven から利用することを想定しています。pom.xml に以下の設定を記入してください。
<repositories> <repository> <id>tuppari-java</id> <name>tuppari-java repo</name> <url>http://hakobera.github.com/tuppari-java/releases</url> </repository> </repositories>
<dependencies> <dependency> <groupId>tuppari</groupId> <artifactId>tuppari-java</artifactId> <version>0.1.0</version> </dependency> </dependencies>
サンプルコード
以下の様な感じで、ほぼ Node.js のコード同じ感じで書けます。
package com.tuppari; import com.tuppari.TuppariChannel; import com.tuppari.TuppariClient; import java.net.URI; import java.util.Map; public class TuppariExample { /** * Usage: java com.tuppari.example.TuppariExample [applicationId] [accessKeyId] [accessSecretKey] */ public static void main(String[] args) { if (args.length != 3) { throw new IllegalArgumentException("Usage: java com.tuppari.example.TuppariExample [applicationId] [accessKeyId] [accessSecretKey]"); } String applicationId = args[0]; String accessKeyId = args[1]; String accessSecretKey = args[2]; TuppariClient client = new TuppariClient(applicationId, accessKeyId, accessSecretKey); // Run on you own cloud, specify the target URL. // TuppariClient client = new TuppariClient(applicationId, accessKeyId, accessSecretKey, URI.create("http://localhost:5100")); TuppariChannel channel = client.join("your_channel"); Map<String, String> result = channel.send("your_event", "hello"); System.out.print(result); } }
Tuppari - WebSocket on Your Cloud -
Tuppari とは
東京Node学園 6時限目で発表した Node.js で作られた Pusher クローンです。
簡単に言うと WebSocket を利用した大規模 Broadcast に特化したサービスです。
インフラとしては Amazon Web Services (以降、AWS)上で動かすことに最適化されていますが、AWS以外でも動かすことは可能です。つまり、クラウド上に自分自身の Pusher を構築することが可能になります。
現時点では Pusher の機能には追いついていませんが、今後も鋭意開発を進めていくので、是非使ってみてください。
あと、WebSocket を使うので、IE9 以下では動きません。
8/15 追記: v0.2.0 から IE もサポートしました。
詳細は以下の記事を参照して下さい。
Tuppari コミュニティ始めました & IE に対応しました - Scalaとlift のはずだった ・・・
Tuppari 公開記念ハッカソン
興味があればどうぞー。
ご希望があれば、Tuppari の使い方、各自の環境へのインストール方法などサポートします。
ソース
CLI & npm モジュール: https://github.com/tuppari/tuppari
デモ
- 通貨レートの Push 更新:
- GeoタグにもとづいてTweetをGoogleマップ上に表示:
試してみよう
Tuppari は各自のインフラで動作させることを想定していますが、インストール済みサンプルとして、 https://api.tuppari.com でサービスを稼働させてありますので、まずはお試しください。
公式ドキュメント: https://github.com/tuppari/tuppari/wiki
アカウントの作成
まずは、アカウントを作成します。
アカウント名は他人と同じものは使用できません。
tuppari register [YOUR_ACCOUNT_NAME] Password: [ENTER YOUR PASSWORD] Create new account name "[YOUR_ACCOUNT_NAME]" success.
ログイン
アカウントを作成したらログインします。
tuppari login Account Name: [ENTER_YOUR_ACCOUNT_NAME] Password: [ENTER_YOUR_PASSWORD] Login success.
アプリケーションの作成
tuppari create [YOUR_APPLICATION_NAME] Create a new application named "[YOUR_APPLICATION_NAME]" success. Following is the application Information to access from your code. { "applicationId": "[SHOW_YOUR_APPLICATION_ID]", "accessKeyId": "[SHOW_YOUR_ACCESS_KEY_ID]", "accessSercretKey": "[SHOW_YOUR_ACCESS_SECRET_KEY]" }
ここで出力される情報は後で使うので、メモしておいてください。
HTML ファイルの作成
<!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <!-- 1. Include the Tuppari client JavaScript libraries on your page from CDN --> <script src="http://cdn.tuppari.com/0.1.0/tuppari.js"></script> <script> // 2. Instantiate connection object to tuppari server. var client = tuppari.createClient({ applicationId: 'YOUR_APPLICATION_ID' // Replace this with your Application ID. }); // 3. Create channel you want to use. var channel = client.subscribe('your_channel'); // 4. Listen for some events on you channel channel.bind('your_event', function (data) { console.log('An event was triggered with message: %s', data); }); </script> </head> <body> </body> </html>
Node.js のコードからイベントを発行する
package.json と app.js を以下のように作成します。
package.json
{ "name": "helloworld", "version": "0.1.0", "dependencies": { "tuppari": "0.1.0" } }
app.js
var Tuppari = require('tuppari'); var tuppari = new Tuppari({ applicationId: 'YOUR APPLICATION ID', accessKeyId: 'YOUR ACCESS KEY ID', accessSecretKey: 'YOUR ACCESS SECRET KEY' }); var channel = tuppari.join('your_channel'); channel.send('your_event', 'hello world', function (err, res, body) { if (err) { console.error(err); } console.log(res.statusCode, body); });
ファイルを作成後、以下のコマンドで実際のイベント発行します。
cd [YOUR_WORKING_DIRECTORY] npm install node app.js
ブラウザのコンソールに以下の文字列が出力されていることを確認してください。
An event was triggered with message: hello world
東京Node学園 6時限目の発表資料
簡単にレーダーチャートが描けるライブラリ Raphael-Radar
最近は Google Chart Tools や Highcharts のように綺麗なグラフがかけるライブラリが増えてきましたが、レーダーチャートをサポートしているものがあまりなく、あっても用途にマッチしなかったので jQuery と Raphael を使って、レーダーチャートが描けるライブラリを作ってみました。
作ってみましたといっても、正確には fork して改造になります。
ソース: hakobera/raphael-radar · GitHub
デモ: Raphaël Radar Chart Plugin Sample
サンプル
こんな感じのチャートが書けます。
ソースは以下のような感じです。
var objects = [ { title: "Real Madrid C.F.", offense: 80, defense: 90, technique: 70, strategy: 90, physicality: 70, mentality: 60, draw_options: { lines: {'stroke-width':'2', 'stroke':'#39b549','stroke-dasharray':'- '}, points: {'fill':'#39b549','stroke-width':'0'}, text: {} } }, { title: "FC Barcelona", offense: 100, defense: 70, technique: 100, strategy: 70, physicality: 60, mentality: 80, draw_options: { lines: {'stroke-width':'4','stroke':'#0070bb','stroke-opacity':0.7,'fill':'#f7d2a8','fill-opacity':0.6}, points: {'fill':'#f05a23','stroke-width':'1.5','stroke':'#333', 'size': 6}, text: {} } } ]; var paper = Raphael( "id_of_some_div", 460, 360); paper.radarchart(221, 160, 120, labels, 50, 100, objects);
吹き出し部分はイベントハンドラを設定して書いています。標準機能として取り込むかは検討中です。
paper.scores.forEach(function(s) { s.points.forEach(function (p) { console.log(p.score); var side = "right"; var offset = p.attrs.r; var popupLabel = paper5.text(p.attrs.cx, p.attrs.cy, p.title + '\n' + p.score.label + ': ' + p.score.rawValue).hide(); if (p.attrs.cx + popupLabel.getBBox().width > paper5.width) { side = "left"; offset = -offset; } var popup = paper5.popup(p.attrs.cx + offset, p.attrs.cy, popupLabel, side).attr({fill: "#fff", stroke: "#666", "stroke-width": 2 }).hide(); p.mouseover(function () { popup.toFront().show(); popupLabel.toFront().show(); }); p.mouseout(function () { popup.hide(); popupLabel.hide(); }); p.toFront(); }); });
Router-Line はじめました
4/18 に開催された 東京Node学園 5時限目 で @KOBA789 さんが自作のルータである Router-Line を紹介していました。
その時から便利そうとは思っていたのですが、最近ちょっとした API サーバを書く時に実際に Router-Line を使ってみて、改めて良いなと思ったので、サンプルを見せながら、個人的に便利だと思う使い方を紹介します。
Router-Line とは
github で「A URL routing module for Node.js」と書いてある通り、Router-Line はシンプルな URL ルータで、それ以外の機能は持っていません。サーバを構築するには Node.js の http モジュールなどと組み合わせて使う必要があります。
個人的には API サーバのルーティングには、Express (Connect) を使うより、すっきりかけて、速度も速いので合っていると感じています。
致命的な欠点は、サンプルがないこと、ですかね、はい。ドキュメントがほぼ皆無で、最初どうやってサーバと組み合わせるか悩みました。自分はテストコード読んで理解しましたが、初心者にはお勧めできない、的な雰囲気になってしまっているのがとても勿体無い。(ので、この記事を書きました)
Router-Line 事始め
Router-Line を利用した API サーバの雛形を書くと以下のようになります。
var http = require('http'), url = require('url'), Router = require('router-line').Router; var router = new Router(); function send(res, status, contentType, body) { res.writeHead(status, { 'Content-Type': contentType }); res.end(body); } /* * ここからルーティングの設定 */ router.add('GET', '/', function (req, res) { res.end('hello world'); }); // ここまでルーティングの設定 /* * HTTP サーバと連携させる。以下は個人的に使っているテンプレートコード。 */ var app = http.createServer(function (req, res) { var uri, r, handler; uri = url.parse(req.url, true); r = router.route(req.method, uri.pathname); if (r) { req.param = function (key) { return r.params[key] ? r.params[key] : uri.query[key]; } handler = r.value; if (typeof handler === 'function') { handler(req, res, r.params); } else if (typeof handler === 'string') { send(res, 200, 'text/plain', handler); } else { send(res, 200, 'application/json', JSON.stringify(handler)); } } else { send(res, 404, 'application/json', JSON.stringify({ reason: 'This not the URL you are looking for' })); } }); app.listen(process.env.PORT || 3000); console.log('Server listen on port %d', app.address().port);
Router-Line の基本
Router の作成
Router = require('router-line').Router; var router = new Router();
普通ですね。今回は1つしか使いませんが、何個でも作れます。
Routing Handler の設定
// HTTP メソッドを指定する add メソッド router.add('HTTP メソッド名(GET|POST|PUT|DELETE)', 'マッチさせる URL', Routing Handler [function, string, object なんでも OK]); // 全HTTP メソッドを一括指定できる ANY メソッド router.ANY('マッチさせる URL', Routing Handler [function, string, object なんでも OK]);
基本的には add() しか使わない。というか、add() しか使ったことない。
router.add('GET', '/', function (req, res) { res.end('index'); });
これを取得するには Router.route() メソッドを使います。
var app = http.createServer(function (req, res) { var uri, r, handler; uri = url.parse(req.url, true); r = router.route(req.method, uri.pathname);
URLに一致する handler がない場合は undefined が返ります。
URLに一致する handler がみつかった場合、params, value というプロパティを持った Object が返ります。
{ params: { Path parameter の解析結果 object }, value: { add()/ANY() で設定した handler object } }
以上です。簡単ですね。
応用編
Path Parameter を使う
コロン (:) で始まる文字列は Path Parameter として解析されます。例えば、以下の様な感じです。
/* * pathParams には route().params の結果が入っている。 */ router.add('GET', '/users/:user/apps/:app/:id', function (req, res, pathParams) { send(res, 200, 'application/json', JSON.stringify(pathParams)); });
これに対して "/users/user1/apps/app2/3" でアクセスすると、
{"user":"user1","app":"app2","id":"3"}
が返ります。実際に動作するサンプルを heroku においておきましたので、色々と URL を変えて試してみてください。 サンプル。
Path Parameter だけど、一部 Option パラメータを指定
URL を括弧で囲むとその部分は省略可能なパラメータとして解析されます。例えば以下。
router.add('GET', '/items/:item(/type/:type)', function (req, res, pathParams) { send(res, 200, 'application/json', JSON.stringify(pathParams)); });
これに対して "/items/item1" でアクセスすると、
{"item":"item1"}
こうなって、"/items/item1/type/small" でアクセスすると、
{"item":"item1","type":"small"}
まとめ
なんとなく Node.js なら Express 的な風潮が見受けられますが、API サーバ(画面なし、form から POST なし) を書くなら Router-Line を検討してみても良いと思います、というか Router-Line お勧めです。
鹿駆動勉強会に参加してきました
4/29 に奈良の能楽ホール(重要文化財!)で開催された 鹿駆動勉強会 に参加して、LTしてきました。関東以外の勉強会に参加したのは初めてでしたが、懇親会で twitter でしか知らなかった方々と直接お話できたりして、とても楽しかったです。
当日の流れや各LTの資料、参加報告 Blog などは Togetter に まとまっているので、そちらを参照してみてください。
#shikadriven 鹿駆動勉強会 - Togetter
感想
会場はこんな感じで、ここで LT できたのはとても貴重な体験でした。
勉強会は特にテーマ不定で怒涛のLT20連発。内容も多岐に渡っていて、普段聞くことがない内容のものもあり、とても刺激になりました。最近は興味のあるテーマの勉強会にしか言っていませんでしたが、こういう広いテーマの勉強会もまた参加してみたいと思いました。
自分も勉強会運営をしたことがあるのでわかるのですが、LTが多いと時間通りに進行させるのが難しいのですが、当日はほぼスケジュール通りに進行されており、運営の方の手際の良さにとても感心しました。
LTに関しては、@skrb さんの JavaFX が個人的には面白かったです。スライドも JavaFX で組んであって、グリグリ動いていてすごいなと思いました。Swing で UI 組むのは大変だったので、JavaFX でこの辺は便利になって良い感じですね。
他の方の LT の内容も面白かったので、Togetter まとめからリンクを辿ってみてみると良いですよ。
LT の内容
今回はGWの休暇を兼ねて、参加することに意義があったので LT はあまり凝らずに「WebRTC、顔認識、画像合成、画面共有 を全部 JavaScript でやる」という内容を話してきました。
実際の動作画面は以下のようになります。(当日は能楽ホールということで、能面の画像を使いました)
左が Mac Book Aire で配信元、左が iPad で共有先です。
ソースは github においてあります。
WebSocket で PNG 画像をバイナリ転送して、JavaScript で展開して表示してみた
Node.js で WebSocket-Node を使って実装しました。
転送するめぼしい画像が見当たらなかったので、デスクトップをスクリーンキャプチャして転送してみました。
ブラウザはChrome 17以上か、Firefox 11以上が必要です。サーバ側は scrrencapture コマンドを利用している関係で Mac OS X限定です。
デモ
上半分が転送元のデスクトップ、下半分が転送された画像をブラウザで表示したものです。ニコ動のコメントの飛び具合を見るとわかると思いますが、800*600の解像度の画像を、横640に縮小して転送して、1FPSくらいです。(※ これはWebSocket の限界ではありません。速度は向上させる余地はかなりありますが、今回の本質ではないので気にしないことにします)
ソースは github に置いてあります。
hakobera/screencast · GitHub
解説みたいなもの
今回の実装の面白い点は表題の通り「バイナリ転送した PNG 画像を、JavaScript で展開して、Canvas に表示していること」です。
PNG画像の展開には png.js を利用しています。
※コメントで教えてもらった URL.createObjectURL() で描画する方法の方が、クライアントのCPU負荷が低く(Thinkpad X61(Core2Duo 2GHz) Windows 7 の Chrome 16 で 25% -> 5%)なりました。ブラウザがサポートしている形式を展開する場合は blob 形式でデータを受信して、URL.createObjectURL()で生成したURLをImageで描画するのが良いのではないでしょうか。なお、URL.createObjectURL版のソースコードは blob ブランチに置いてあります。
WebSocket で画像を転送する方法(正確には画像を連続で送信して動画のように表示させる方法)は、今回のも含めて自分が試しただけでも4つあります。
それぞれの現時点での利点と欠点は以下だと思っています。
実装方法 | 利点 | 欠点 | Chrome | FireFox | Safari | iOS | IE9 | IE8 | IE6 |
---|---|---|---|---|---|---|---|---|---|
バイナリ(圧縮形式)で転送して、展開して表示 | 送信データ量が小さい | arraybuffer/blog サポートが必要 | ○ | ○ | × | × | × | × | × |
バイナリ(RAW)で転送して、そのまま表示 | クライアント側の展開がいらないので、クライアント負荷が小さい | 送信データ量が極大/arraybuffer/blog サポートが必要 | ○ | ○ | × | × | × | × | × |
Data URI(文字列)で転送して、そのまま表示 | Safari、Mobile WebKitでも使える | 送信データ量が大きい(バイナリ比+33%) | ○ | ○ | ○ | ○ | △ | △ | × |
画像URLを送信して、ブラウザでロードして表示 | 送信データ量が極小/Safari、Mobile WebKitでも使える | 別途画像配信用のHTTPサーバが必要 | ○ | ○ | ○ | ○ | △ | △ | △ |
IEはWebSocketをサポートしていないので、Socket.IO を使えばできる箇所は△にしています。
ブラウザサポートは次第に解消されていくと思うので、将来的にはバイナリ転送、JSで展開という形に落ち着いていくんじゃないのかな、と思いました。場合によっては独自の圧縮プロトコルも使えるので、高圧縮、暗号化など、色々と応用範囲がありそうですし。
また、WebSocketが gzip 圧縮をサポートするらしいので、画像が転送できれば良いという用途では、RAW形式も生き残るかもしれないですね。
Node.js vs Play vs SAStruts
前置き
Experiences with Node.js: Porting a RESTful Service Written in Java - ZiggyTech
上記記事では、実験的にJava (Jersey + Hibernate on Tomcat) で実装された REST API サーバを Node.js で書きなおしてみたら、少ないリソース(CPU/メモリ使用量)でほぼ同等のパフォーマンスが出せたよ(ただし、O/Rマッパーを使用しない場合)、と書いてあります。この件に関して @koichik さんとやり取りしていた中で以下のような意見を頂いたので、実際にやってみましたという記事です。
さすがに素の Struts 1.x は書き方も忘れたので、日本では結構採用事例があると思われる SAStruts と、最近仕事で使っている Play framework 1.2 を比較対象にします。
比較内容
/orders/ へのアクセスがあると、データベースに50件分のデータを検索しにいき、JSON の配列データに変換して返す REST API のスループット、および、その際の CPUとメモリ使用量を計測します。
実験に使ったソースは github においてあります。
hakobera/node-vs-sastruts-vs-playframework-rest-api-bench · GitHub
計測マシンのスペック
MacBook Pro 2011 early
計測対象の組み合わせ
- データベース
- PostgreSQL 9.1
- Node.js
- Node.js v0.6.12
- express 2.5.8
- pg 0.6.13 (JavaScript Pure Driver / SQL 直接実行, O/Rマッパーなし)
- Play
- Play framework 1.2.4
Java でGC が極端に発生しないようにヒープは最大 512MB を設定、何回は試行後、ヒープサイズが安定してから計測。それ以外はスレッドプールなどはデフォルト設定。全てプロダクションモードでの実行結果。
計測方法
元の記事では JMeter を使っていますが、JMeter だとそれ自体が結構リソースを食うので、今回は Apache Bench を利用しました。
ab -c 64 -n 5000 http://127.0.0.1:8080/orders/
スループットは上記コマンドを5回実行して、最大値と最小値を除く3回の平均値を採用。CPU 使用率とメモリ使用量はアクティビティモニタによる目測。
結果
スループット | CPU 使用率 | メモリ使用量 | スレッド数 | ||||||
Node.js | 488 (req/sec) | 100.6 (%) | 55.6 (MB) | 2 | |||||
Node.js (2 cluster) | 739 (req/sec) | 200.5(100.5 + 100.0) (%) | 115.7 (57.6+58.1) (MB) | 12(6*2) | |||||
Node.js (3 cluster) | 981 (req/sec) | 300.7(100.4 + 100.2 + 100.1) (%) | 167.1 (56.4+55.4+55.3) (MB) | 18(6*3) | |||||
Node.js (4 cluster) | 1129 (req/sec) | 400.8(100.0 + 100.3 + 100.3 + 100.2) (%) | 222.6 (55.6 + 55.4 + 55.7 + 55.9) (MB) | 24 (6*4) | |||||
SAStruts | 2237 (req/sec) | 388.6 (%) | 392.6 (MB) | 98 | |||||
Play | 906 (req/sec) | 557.8 (%) | 365.9 (MB) | 53 |
使っているものが色々と違うので直接は比較できないのですが、なんか元の記事とは傾向が異なる感じなりました。
【追記】github で cluster 化の プルリクエスト をもらったので、cluster のベンチを追加しました。4 cluster 以上は DBアクセスがネックになって、スループットは速くなったり、遅くなったりしてました。環境によっては 3,000 req/sec くらいまで伸びるようです。
まとめ
- Node.js
- Play
- 補足
- CPUコア数の多いマシンでは、シングルインスタンスの Node.js が、複数コアを利用できる SAStruts, Play に対しては圧倒的に遅い
- 2コアの MacBook Air で試してみると、SAStruts >> Play >= Node.js くらいにはなる
- Node.js は cluster での絶対性能の向上は図れると思うが、他の2つがデフォルトで複数コアを利用できるのに比べて、運用負荷がかかる