eSEAT Webアダプタ
eSEATは、Socketアダプタを作成できますので、通常のTCP/IPのソケット通信はすでにサポート済みですが、HTTPでも色々なPUSH配信機構が提案され、HTTP/2.0もそろそろ広く使えるようになってきました。また、ユーザインターフェースとして、Webブラウザは、Javascriptを使った動的なUIを作ることができますので、eSEATにも、Webサーバー機能とCometによるPUSH配信機能を追加することにしました。
この機能の実装のきっかけになったのは、RTM-REST-APIの実装からなのですが、まだ、互換性は保てていませんので、その内修正するつもりです。
Webアダプタでできることできないこと
まず、現在のeSEATの実装では、1つのseatmlの中に1つのWebアダプタしか記述することができません。2つ目は、エラーとなりWebアダプタを生成しません。
複数のWebアダプタを生成しなければ実現できないアプリケーションが出てきたら、その時に考えることにしていますので、使った後のご意見などは遠慮なく、OpenRTM-aistのMLにお願いいたします。
しかしながら、Webアダプタ自体が非常に簡単なWebサーバーであるため、htmlファイルを複数用意しておけば、RTCと連携するWebアプリを実現することができます。
Webアダプタで実現できている基本的な機能は、以下のようになります。
- 簡易Webサーバー
- eSEATからのデータPUSH (あらかじめCallback用のXHRをeSEATへ登録する必要がありますが、Webページに組込むこともできます)
- Webブラウザのフォーム、JavascriptからeSEATへのデータ入力
基本的には、この3つの機能しかないのですが使い方によっては、WebブラウザからのRTCのコントロール、リンクの生成などの可能だと思います。また、簡単なプロキシも作れるとは思うのですが、使い方次第というところです。
(2015/08/17 追加)Webアダプタの機能として、Webブラウザから送信したスクリプトをeSEAT内で実行できるような機能を追加しました。ソースコードを見ればわかりますが、Pythonのexec関数を使っていますので、使い方によっては非常に危険です。一応、seatmlの方でこの機能が使えるクライアントアドレスを指定するようにし、eSEATを経由してダウンロードされたHTMLドキュメントからでないと実行できないようにはなっています。(本来は、SSL通信にして情報の隠ぺいやキーの有効期限が設定できるようにすべきですが、現状ではそのような機能の実装予定はありません)
Webアダプタの使用方法
Webアダプタは、eSEATのRaw socketやRTCのData portと同じように、seatmlで定義します。現在の実装では、1つのWebアダプタしか作ることができません。理由はいくつかあるのですが、一番の理由は私の頭の中でうまい実装モデルができなかったためです。eSEATは、基本的にRTCなのですから、Webブラウザの連携は1つで十分かなと思ったのも理由の1つです。
Webアダプタの定義
eSEATで、Webアダプタを定義したい場合には、<general>タグの中で、
<adaptor name="web_io" type="web" port="8080" />
という感じで定義します。この定義では、ポート番号8080に web_ioという名前のWebアダプタを生成すると意味になります。ここで少し気を付けていただきたいのは、Webアダプタは、eSEATの生成直後からアクティブになり、RTCの状態とは無関係に終了時までの間有効になっています。この実装では、RTCのコンポーネントモデルに対してちょっと問題があるとは思ったのですが、GUIと同じように実装してしまっていたので、このままにしています。
これはこれで、何かと便利に使うことができます。
ここでWebアダプタを1つだけ持っているeSEATコンポーネントを下のようにseatmlファイルを作成してみます。
<?xml version="1.0" encoding="UTF-8"?>
<seatml>
<general name="test1">
<adaptor name="web" type="web" port="8080" />
</general>
<state name="main">
</state>
</seatml>
このファイルでeSEATを起動すると、ポート番号8080の簡易Webサーバーとなります。ここで通常のWebサーバーと異なるのは、ドキュメントルートがeSEATの起動ディレクトリの直下に置かれたhtmlというディレクトリに固定されていることと、ファイル名を指定しないURL (例えば、 http://localhost:8080/ ) の場合には、html/index.html というファイルではなく、html/test1.html となるというところです。
これは、上のseatmlファイルの <general>タグの name属性値をデフォルトのファイル名にしてしまっているからです。これによって、異なるseatmlで起動されたeSEATのWebアダプタで異なるデフォルトファイルを指定できるようにできます。ただし、http://localhost:8080/index.html と書いてしまえば、 html/index.html が配信されます。
Webアダプタを利用するHTMファイルの作成
上述のように、eSEATでWebアダプタを定義すると、eSEATは簡易Webサーバーとしての動作します。単にHTMLファイルを配信するだけでは、RTCに関して何もできませんので(RTCがHTMLファイルを出力していれば別ですが…)、コントロールようのHTMLファイルを作成する必要があります。
これは、Webアダプタでは、Cometというサーバープッシュ機能を実装しており、XHR(XMLHttpRequest)を利用することでeSEATからのPUSH型配信機能を提供しています。また、POST命令を用いることでRTCに対してデータ送信を行うことができます。POST命令によるコマンド発行は、Dataポートからの入力とよく似た振舞をするように実装していますので、WebブラウザからのXHR通信を介して様々なデータを送ることができます。
XHRを用いた通信機能は、comet.jsの中にまとめてありますが、この機能はJQueryも使っていますので、配信するHTMLファイルには、
<script src="jquery-1.11.3.min.js"></script>
<script src="jquery-migrate-1.2.1.min.js"></script>
<script src="comet.js"></script>
の3つを記述しておく必要があります。
eSEATにデータを送信する
上述のようにeSEATにデータを送信する関数は、comet.jsの中に定義しています。ここで定義している関数は、
- sendMessageToRtc(message, func)
- sendValueToRtc(value, func)
の2つです。
sendMessageToRtc関数は、引数messageで与えれた文字列をeSEATに送信します。eSEATの方は、この関数で送信されてきた文字列を<rule>タグで定義した<key>タグの文字列とのマッチングを行って、定義された動作を行います。内部では、processResultメソッドを直接呼び出していますので、現在のところeSEATがアクティブの状態でなくても動作してしまいます。これは、仕様としてはちょっと問題があるので、変更する可能性もあります。
次にsendValueToRtc関数ですが、こちらは引数valueで与えられた文字列またはJSONオブジェクトをeSEATに送信します。この関数で送信されたvalueが文字列の場合には、ほぼsendMessageToRtcと同様の振舞をしますが、JSONオブジェクトの場合は、送信時に文字列にダンプされ、eSEATで受信時には、内部では配列データに変換されます。この関数は、eSEAT内部では、onDataメソッドを呼び出していますので、eSEATがアクティブの状態でないと実行されません。
両方の関数の第2引数のfuncに関してなのですが、こちらは共通しており、eSEATでの処理後の返事をフックする関数を定義することができます。funcを省略した場合には、showReply関数が呼び出されますので、HTMLファイルの"response"というIDを持つ要素へ返り値が表示されます。
例:ボタンを押下すると文字列を送る
では、簡単な例として、ボタンを押下するとeSEATに文字列を送信するHTMLファイルを作成してみましょう。前述の関数として、まず、sendMessageToRtc関数を使って”Hello"という文字列を送信するようにしてみます。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Send Message</title>
<script src="jquery-1.11.3.min.js"></script>
<script src="jquery-migrate-1.2.1.min.js"></script>
<script type="text/javascript" src="comet.js"></script>
</head>
<body>
<button onClick="sendMessageToRtc('Hello');">Push</button>
<div id="response"></div>
</body>
</html>
これをtest1.htmlというファイルに保存して、htmlの下に配置してください。また、eSEAT側の振舞としては、'Hello'という文字列が入力されは、入力用のGUIに 'Hello World' と出力されるようにseatmlを下のように定義しましょう。
<?xml version="1.0" encoding="UTF-8"?>
<seatml>
<general name="test1">
<adaptor name="web" type="web" port="8080" />
</general>
<state name="main_mode">
<input id="textOut" width="50" ></input>
<rule>
<key>Hello</key>
<script>
seat.setEntry("main_mode:textOut", rtc_in_data+" World")
</script>
</rule>
</state>
</seatml>
これをtest1.seatml として保存して、
python eSEAT.py test1.seatml
で実行してみましょう。すると
と表示されます。
次に、Webブラウザで、http://localhost:8080/ にアクセスします。すると 'Push'というラベルのボタンが表示されますので、そのボタンを押下してください。すると、上図の入力枠に 'Hello World' という文字列が表示されると思います。
このように、eSEATにWebブラウザからデータを送信することができました。上述のtest1.htmlファイルのsendMessageToRtc関数をsendValueToRtc関数に置き換えると、今度は、eSEATがアクティブの状態のときのみ同じ動作をするはずです。
eSEATからデータを受け取る
上述のサンプルを動作させた場合にも、返り値が'response'というidを持つdviタグに表示されたと思いますが、eSEATからPUSH配信でデータを受信する方法を説明します。本来HTTPでは、クライアント側(Webブラウザ)からリクエストを送らないとサーバーから何もデータを受信できないのですが、Cometという技術を使うと疑似的にサーバーからのPUSH配信を実現できます。
最近は、HTTP/2.0が制定され、サポートしているサーバーとブラウザがあれば、簡単に非同期通信できるのですが、今までのHTTPの枠組みでPUSH配信を行うとすると、Cometを使う方が、汎用性があります。
そこで、eSEATのWebアダプタでは、Comet機能を付加しています。
Cometの基本的な動作は、
- WebブラウザからXHRリクエストを送り、待ち状態になる
- サーバー側は、送信すべきデータがあれば、現在待ち状態になっているXHRに対して返事をする
- Webブラウザは、XHRに対する返事(サーバーからのデータ)を受信後、あらかじめ設定した関数を実行して、同じリクエストをサーバーに送信して待ち状態になる
- 以降、2,3を繰り返し実行する
というものです。この動作を実現するために、comet.jsの中に
- requestComet(id, force)という関数を定義しています。
eSEATからデータを受信したい場合には、このrequestComet関数を使って、あらかじめeSEATにComet用の通信を確立しておく必要があります。
この関数は、Webブラウザから明示的に実行してもよいですが、<body>タグのonLoad属性に記述しておくこともできます。
eSEATから受信したデータの処理は、デフォルトでは、processEvenst_defaultという関数を呼び出すようになっていますが、HTMLファイルの中で、 processEventsという関数を定義しておけば、その関数を呼び出すことになります。
例:単純な文字列の受信
では、簡単な文字列の受信の例を作っていきます。ここで作成する例は、eSEATに2つのブラウザからアクセスし、前に作った文字列を送信するtest1.htmlと文字列受信のHTMLファイル(test2.htmlとします)間でブラウザ間で文字列送信をするような例を作っていきます。
まず、test1.html の方ですが、下のように sendValueToRtc関数を使ったものに書き換えてください。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Send Message</title>
<script src="jquery-1.11.3.min.js"></script>
<script src="jquery-migrate-1.2.1.min.js"></script>
<script type="text/javascript" src="comet.js"></script>
</head>
<body>
<button onClick="sendValueToRtc('Hello');">Push</button>
<div id="response"></div>
</body>
</html>
そして、test2.htmlとして、下のようなファイルを作成して htmlの下に配置してください。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Send Message</title>
<script src="jquery-1.11.3.min.js"></script>
<script src="jquery-migrate-1.2.1.min.js"></script>
<script type="text/javascript" src="comet.js"></script>
<script>
function processEvents(evt){
$("#result").html($("#result").html() + "<br>"+evt.message+" on "+evt.date);
}
</script>
</head>
<body onLoad="requestComet('test');">
<div id="result"></div>
</body>
</html>
そして、最後に、文字列をPUSH配信するためのseatmlファイル(test2.seatml)を下のように作成します。
<?xml version="1.0" encoding="UTF-8"?>
<seatml>
<general name="test2">
<adaptor name="web" type="web" port="8080" />
</general>
<state name="main_mode">
<rule source="web">
<script sendto="web">
rtc_result = "Hello!!"
</script>
</rule>
</state>
</seatml>
ここまで準備ができましたら、下のようにeSEATを起動してください。
python eSEAT.py test2.seatml
次に、RTSystemEditorを用いて、eSEATをアクティブ状態にします。
次に、Webブラウザの画面を2つ用意し、http://localhost:8080/ と http://localhost:8080/test1.html にアクセスしてください。
すると下図のように、何も表示されない画面と、test1.htmlのようにボタンが1つある画面が得られます。
次に、'Push'ボタンを押下すると下図のように、メッセージが表示されるはずです。(下図は、Pushを3回押下しています)
その他の機能
WebAdaptorでは、もう1つWebブラウザ経由でPythonスクリプトを送信し、eSEAT内部で実行させる機能があります。この機能は、exec関数を呼び出していますのでセキュリティホールになりやすい機能なのですが、
- eSEAT経由で送信したページを受け取ったブラウザからのアクセスを禁止ている
- seatmlで指定されたPCからでないとexec関数を処理しない
のような対策をしています。
本来は、httpsのみで通信すべきところですが、現在はそのような対応はしておりません。
使い方としては、commet.jsの中に
- sendScriptToRtc(script, func)
という関数が用意されていますので、この関数を使ってeSEATへpython scriptを送信し、実行させることができます。使い方は、sendValueToRtc関数とほぼ同じです。