Java Web Start版 Groovy Console をちょっとだけ変えてみた その3
残念ながら 参加はできませんでしたが...
12/20 の JavaFX & GlassFish 合同勉強会 で id:nobeans さんが FxBuilder を使って Groovy から JavaFX を利用する方法を発表されていたのを受けて、Java Web Start 版 Groovy Console でも FxBuilder を利用できるようにしてみました。
id:nobeans さんの発表内容は こちら。
今回の変更点は JNLP ファイルのみ...
Groovy Console 用 JNLP ファイルの resources に JavaFX Runtion の JNLP ファイルと
<extension name="JavaFX Runtime" href="http://dl.javafx.com/1.2/javafx-rt.jnlp" />FxBuilder で必要な JAR ファイル
<jar href="fxbuilder-0.2.jar" /> <jar href="JFXtras-0.5.jar" /> <jar href="miglayout-3.6.3-swing.jar" />を追加しただけ...
※ 実際には他の修正も一緒に行っていますが 今回は省略。詳細は次回以降で...
しかし JNLP の extension 機能はとっても便利ですね。
JavaFX Runtime のように既に公開された JNLP コンポーネントがあれば、何も考えなくても自分のアプリケーションに必要なライブラリを全て取り込んでくれるのです。
Groovy や Griffon も JNLP のコンポーネントとして公開されないのだろうか。
もちろん、この Java Web Start 版 Groovy Console を利用すれば...
面倒な インストール作業なしに
すぐに JavaFX の世界を体験することができます。
「JavaFX を試してみたいけど インストール面倒だし やめとこ...」なんて方には とってもおススメ!!
興味のある方は右下のメニューの Groovy Console の [Launch] ボタンをクリックしてみてください。
注意!!
Groovy Console はオレオレ証明書で署名されており、全ての権限が与えられています。
利用される方はあくまで自己責任で...
あとは こんな感じでコーディングして実行するだけ...
import griffon.builder.fx.*; import javafx.lang.*; import javafx.animation.*; def fx = new griffon.builder.fx.FxBuilder(); def rt = fx.parallelTransition { fx.rotateTransition( duration: Duration.valueOf(3000), interpolator: Interpolator.$LINEAR, byAngle: 360, repeatCount:Timeline.$INDEFINITE ) fx.scaleTransition( duration: Duration.valueOf(3000), interpolator: Interpolator.$LINEAR, fromX: 0.1, fromY: 0.1, toX: 2.0, toY: 2.0, autoReverse: true, repeatCount:Timeline.$INDEFINITE ) }; fx.edt { stage(width: 200, height: 200) { scene { stack (width: 200, height: 200) { text = text(content: "Hello, World.") } button = button( text: "PAUSE", layoutX: 10, layoutY: 10, action: { if (button.text == "PAUSE") { rt.pause(); button.text = "PLAY"; } else { rt.play(); button.text = "PAUSE"; } } ) } } rt.node = text; rt.play(); }
ただ、残念なのは FxBuilder が 最新の JavaFX 1.3 に対応していないってことかな...
JavaFX Script 再び...
JavaFX 2.0 から 単なる Java ライブラリ になり JavaFX Script がサポートされなくなるということで、
Groovy で JavaFX しようと考えていたのも束の間...
なんでも JavaFX Script が オープンソース となって帰ってくるらしい...
しかも名前を変えて!!
その名も Visage...
詳細は プロジェクトオーナーでもある Stephen Chin さんが
『Announcing Visage – The DSL for Writing UIs』
でアナウンスされています。
プロジェクトページは http://code.google.com/p/visage/
既に一部のコードが JavaFX Script Compiler から fork されているみたい。
まだこれから活動を始めるようなので どうなるかわからないが
『Visage - A Refreshing Change』
によると 最初のターゲットは Android らしく、
Google を開発に巻き込もうという計画らしい。
もし、本当にこの計画がうまく行くようなら かなり面白いことになりそうだ。
JavaFX 2.0 roadmap and features overview
先月開催された JavaOne 2010 で
次期リリース JavaFX 2.0 についての発表がありました。
JavaFX 2.0 のロードマップについては
http://javafx.com/roadmap/で公開されています。
全体的には機能拡張されるようでよいのですが...
なんと 次期 JavaFX 2.0 からは単なる Java のライブラリになってしまうらしい...
と言うことで...
あえなく JavaFX Script の歴史に終止符がうたれることに...
過去 JavaFX には何度も裏切られてきたが、今回のはちょっと半端ない。
それでも Java や Groovy、JRuby と言ったJVM で実行可能な あらゆる言語で
利用できるようになるのは ちょっとうれしかったりするのがちょっと悲しい...
でも それって Swing と何が違うの...
と言ってても 仕方がないので、今から JavaFX 2.0 に移行できるように準備しておくとしよう。
JavaFX 2.0 を利用するには、まず使用する言語を決めないと...
今更 Scala に手を出すのも厳しいし、Java という選択肢もあり得ない。
となると Groovy か JRuby なのだが JavaOne のプレゼン資料
を見ると どちらかと言うと Groovy の方がしっくりくるかな...
JavaFX Script だと こんな感じが...
Stage { title: "Hello Rectangle (Groovy FxBuilder 2)" width: 600 height: 450 scene: Scene { fill: Color.LIGHTSKYBLUE content: Rectangle { x: 25, y: 40 width: 100, height: 50 fill: Color.RED } } }
Groovy だと こんな感じになるらしい...
FxBuilder.build { stage = stage( title: "Hello Rectangle (Groovy FxBuilder 2)", width: 600, height: 450, scene: scene(fill: Color.LIGHTSKYBLUE) { rectangle( x: 25, y: 40, width: 100, height: 50, fill: Color.RED ) } ) stage.visible = true; }と言うことで Groovy で行ってみよう!!
Wake on LAN してみる Part 4 (SOCKS5 を越えて)
『Wake on LAN をしてみる Part 2』 で Wake on LAN を JavaFX で実装する方法について書いた。
未だ完成には いたらないのものの、とりあえず、居間にいながらにして
別の部屋にある Linux サーバの電源の ON/OFF を行えるぐらいにはなった。
とりあえず、当初の目的は十分達成できたので 後は適当に画面を作れば 何となくアプリケーションは完成するはずだったのだが...
人間の欲望というのは恐ろしいもので、これだけ便利だと 居間からだけではなく、
会社からも ON/OFF できるようにしたくなる。
ネットワークにつながっていれば どこからでも一緒でしょ!! なんて言う甘い考えで試してみることに...
会社のデスクから 自宅の PC を起動するためには 会社の PC から自宅のプライベートネットワークに
マジックパケットをブロードキャストしなくてはならない。
しかも
- 自宅のルータ
- 会社のプロキシ
こんな感じで...
会社PC ----> [会社のプロキシ] ---- インターネット ----> [自宅のルータ] ----> 自宅PC巷で ルータ越え とか プロキシ越え とか言われているやつ。
前者は利用しているルータにもよるが、大抵の場合 ルータの設定を変更するだけでなんとかなるはず...
もし、不幸にも なんともならなかったら、そんな ルータ は買い替えてしまえばよい。
で、実際何を設定すれば良いかと言うと
任意のポートに送信されてきた UDP のパケットを プライベートネットワークのブロードキャストアドレス宛に転送するように設定するだけで良い。
例えば、
5555 -- 転送 --> 192.168.0.255:5555のように設定すると...
ルータの 5555 ポート (5555 でなくてもよい) 宛に マジックパケットを送信するだけで ルータが勝手にプライベートネットワークに ブロードキャストしてくれる。
早速、自宅のルータでも設定してみたのだが、なんと!!
『IP アドレス は 255 より小さな値でなければなりません。』とか言われて 255 への転送を許可してくれない。
確かに、下手に ブロードキャストアドレスに転送できてしまったら Smurf 攻撃の踏み台にされかねないので、しかたがないのだが...
とは言え、簡単にはあきらめられないので、試しに サブネットマスクを 255.255.255.128 に変更してみた。
これは、単にブロードキャストアドレスの4バイト目を 255 以外にしてみれば 良いんじゃない という安易な考えだったのだが...
なんと!!
5555 -- 転送 --> 192.168.0.127:5555という感じで ブロードキャストアドレス 192.168.0.127 に転送するように設定できた。
家庭用ルータなんてこんなもんなんだね。^^;)
と言うことで...
早速 Linux サーバが起動されるかテストしてみたが、
もちろん問題なく起動してくれた。
ちなみに ルータのグローバルアドレスは 大抵 DHCP で割り当てられているので、
固定のドメイン名でアクセスできるように ダイナミックDNS に登録しておけば完璧だ。
これで 公衆無線LANからなら どこからでも 自宅のサーバを起動することができてとっても便利だ。
これで1つ目の壁は無事乗り越えることができたのだが...
会社から 自宅の Linux サーバを立ち上げるには まだもう一つ超えなくてはならない 『プロキシ』 という壁がある。
通常、JavaFX は Java Web Start や Applet として起動するため アプリケーション側で プロキシ のことを気にする必要はない。
Java Preferences でプロキシの設定ができるからである。ちなみに 通常は システムの設定がそのまま利用される。
しかし、これは HTTP 等の TCP に限った話だ。
TCP については J2SE 5.0 で プロキシをサポートするようになったのだが、UDP については Java 6 でもサポートされていない。(詳細は こちら)
プロキシには HTTP や SOCKS など いくつかの種類があるが UDP で利用できるプロキシは SOCKS5 以外にない。
SOCKS5 は rfc1928 でちゃんとスペックが決まっているので、自前で実装することもできるのだが...
google で検索したら jsocks という Java のライブラリが見つかったので それを使うことにした。
JSch もそうだが 既存の Java のライブラリがそのまま利用できるのは JavaFX のメリットの一つでもある。
このライブラリには SOCKS5 のプロキシ経由で UDP パケットを送信するための Socks5DatagramSocket というクラスが用意されている。
使い方は いたって簡単だ。以下のように 通常の DatagramSocket の代わりに使うだけ...
import java.lang.Integer.parseInt; import java.net.DatagramSocket; import socks.Socks5DatagramSocket; import socks.Socks5Proxy; // 起動する PC の NIC の MAC アドレス public var macAddress = "aa:bb:cc:dd:ee:ff" // マジックパケットの送信先ホスト名 (ルータのダイナミックDNSのドメイン名) public var host = "host.to.wake.on.lan"; // マジックパケットの送信先ポート (ルータのポート) public var port = 5555; // SOCKS5 プロキシサーバのホスト名 又は IPアドレス def proxyHost = "socks5.proxy"; // SOCKS5 プロキシサーバのポート番号 def proxyPort = 1080; // マジックパケット def magicPacket = [ for (i in [1..6]) { 0xff } for (i in [1..16], b in macAddress.split("-|:")) { parseInt(b, 16) } ]; // プロキシサーバ def proxy = new Socks5Proxy(proxyHost, proxyPort); proxy.resolveAddrLocally(false); // マジックパケットを任意のポートにブロードキャストするための UDPデータグラムパケット // ここでは送信先のホスト名は指定してはいけない。 // プロキシの内側のネットワークでは外部のネットワークの名前解決ができない場合があるため... def packet = new DatagramPacket(magicPacket, sizeof magicPacket, null, port); // プロキシサーバ経由で マジックパケット 送信 def socket = new Socks5DatagramSocket(proxy, 0, null); socket.send(packet, host); // ここでホスト名を指定する。
『Wake on LAN をしてみる Part 2』 のサンプルコードと見比べても そんなに違いはない。
たったこれだけのコードでプロキシを越えられるなんて少し拍子抜けしてしまったが...
これで いざというときに 会社から自宅の PC を起動することができてしまう。
なんて幸せなんだろう...
プロキシを越えられる WOL のアプリケーションは見たことがないので これはなかなかおもしろいかもしれない。
今回は起動だけしか試せなかったが 同様にサーバを停止することだってできるはずである。
これについては またの機会ということで...
SSH してみる Part 3 (コマンド実行編)
今回は SSH 経由で リモートサーバ上のコマンドを実行する方法です。
SSH してみる Part2 でも
書いている通り 最終的な目標はサーバのモニタリングツールを作ることですが、
まずは vmstat を実行して その結果をそのまま画面に表示することからはじめてみましょう。
今回もサーバには Ubuntu 10.04 LTS を使っています。
サーバ にログインして vmstat をオプションなしで実行してみると
以下のような情報がコンソールに標準出力されます。
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu---- r b swpd free buff cache si so bi bo in cs us sy id wa 0 0 0 235708 24976 100720 0 0 6 3 13 31 0 0 100 0
これと同じことを JavaFX で実現してみましょう。
まずは表示する画面の作成から...
画面は 『SSH してみる (パスワード認証編)』 で作成したものをそのまま利用します。
もちろん パスワード認証部分も...
init 状態 (初期状態) の 真っ黒な画面に 識別子 "foreground" で ラベル を一つ追加します。
後は 『SSH してみる (パスワード認証編)』 で作成したコード中の
Alert.inform("Auth success");を以下のように変更するだけです。
もちろん、今回もエラー処理等は考慮していません。
// コマンドを実行するためのチャネルを開く def channel = session.openChannel("exec") as ChannelExec; channel.setCommand("vmstat"); channel.setErrStream(System.err); // TODO エラー処理 def in = channel.getInputStream(); // コマンドを送信する channel.connect(); // アプリケーション終了時にチャネルを閉じるためのフック FX.addShutdownAction(function(): Void { channel.disconnect(); }); try { def r = new BufferedReader(new InputStreamReader(in, "UTF-8")); // チャネルが開いている間 処理を続ける while (true) { // ブロックせずに読み込めるか確認 while (r.ready()) { // 一行分のデータを読み込み画面に表示 foreground.text = "{foreground.text}\n{r.readLine()}" } // チャネルが閉じていたら終了 if (channel.isClosed()) { break; } // 暫く待ってもう一度データを読み込む Thread.sleep(500); } } catch (e: InterruptedException) { // ignore }
ポイントは
- コマンドを送信する前に InputStream を取得しておく
- Reader#ready() で読み込み可能か確認する
1つ目ですが Channel#connect() を呼び出す前に InputStream を取得しておかないと
タイミングによっては サーバからの出力が受け取れないことがあるからです。というのも...
サーバからの出力を受け取るための InputStream は Channel#getInputStream() を実行したときに初めて生成されるのです。
その為 Channel#connect() して サーバ上で コマンドを実行する前に準備しておかないとダメなのです。
2つ目ですが BufferedReader で いつもの
var line: Object; while ((line = r.readLine()) != null) { ... }のように readLine() をいきなり呼んでしまっては readLine() でブロックされてしまいます。
その為 ready() を実行して ブロックされずに読み込めるか 確認してから読み込まないとダメなのです。
でないと サーバからの出力が返ってこない場合、永遠に readLine() で待たされてしまうことに...
今回はサンプルなので問題ないですが、通常 タイムアウト処理 や 再実行処理等 を ちゃんと実装しないといけないのでこれは重要です。
sudo をプログラムで使う際の注意点
『Wake on LAN してみる Part 3 (shutdown)』 で Linux サーバを停止する際の sudo の使い方に問題があったので まとめておくことに...
問題1
sudo のバージョンによって標準入力からパスワードを渡す方法が異なるらしい。
Ubuntu 8.04 LTS の sudo (1.6.9p10) では
echo "pass" | ssh hoge@192.168.1.1 "sudo /sbin/shutdown -h now"で問題がなかったが 最新の Ubuntu 10.04 LTS の sudo (1.7.2p1) では
sudo: no tty present and no askpass program specifiedのようなエラーになってしまう。どうやら
echo "pass" | ssh hoge@192.168.1.1 "sudo -S /sbin/shutdown -h now"のように "-S" オプションをつけて実行しないとダメらしい。
問題2
sudo に "-S" オプションをつけて実行すると プログラムから標準入力経由でパスワードを渡しても
[sudo] password for hoge: Sorry, try again. [sudo] password for hoge: Sorry, try again. [sudo] password for hoge: Sorry, try again. sudo: 3 incorrect password attemptsのように認証エラーになる。
どうやらパスワードの最後に 改行文字 "\n" をつけて
channel.setInputStream(new ByteArrayInputStream("{password}\n".getBytes()));としないとダメらしい。考えてみれば、当然だけど...
改行コードが無いと どこまでがパスワードなのか分からないからね。
無理して InputStream でパスワードを渡すより、ssh で実行するコマンド自体を
"echo {password} | sudo -S -p '' /sbin/shutdown -h now";としてしまった方が簡単で良いかもしれない。
echo なら何も考えなくても改行コードつけてくれるから...
問題3
sudo を実行すると
[sudo] password for hoge:のように パスワード要求プロンプトが標準エラーに出力されてしまう。
パスワードを要求するプロンプトは "-p" オプションで指定できるので
sudo -p '' ...のように空文字を指定すれば プロンプトの出力を消すことができます。
これらを踏まえると 最終的なコードは
import java.io.ByteArrayInputStream; import java.lang.*; import com.jcraft.jsch.ChannelExec; import com.jcraft.jsch.JSch; // サーバ情報 def host = "192.168.1.1"; def port = 22; // ユーザ情報 def username = "hoge"; def password = "pass"; // shutdown コマンド def command = "sudo -S -p '' /sbin/shutdown -h now"; def jsch = new JSch(); def session = jsch.getSession(username, host, port); try { // SchException: UnknownHostKey を防ぐための設定 session.setConfig("StrictHostKeyChecking", "no"); session.setPassword(password); session.connect(5000); var channel = session.openChannel("exec") as ChannelExec; try { channel.setCommand(command); channel.setInputStream(new ByteArrayInputStream("{password}\n".getBytes())); channel.setOutputStream(System.out); channel.setErrStream(System.err); channel.connect(); // コマンドが終了するまで待機 while (not channel.isClosed()) { Thread.sleep(1000); } } finally { channel.disconnect(); } } finally { session.disconnect(); }のようになる。
もちろん 『Wake on LAN してみる Part 3 (shutdown)』 についても上記の内容でこっそり修正してあります。