Hatena::Grouporera

もし高校野球の女子マネージャーがOpera Browserを使ったら

logo
 | 

2009-12-31

Opera Uniteで、同期処理だけでCGIを書きたい

06:58

↓前回。


再度引用。

Rackの思想

「Webアプリって要するにリクエストをレスポンスに変換するだけの関数だよねー」

ってのがRackの基本っぽい。

なので、

env(環境変数のハッシュ)を受け取って

ステータスコードとHTTPヘッダとHTTPボディを返す

ような関数を書けばWebアプリになります。

Route 477 - 5分でわかるRack , シュレーディンガーの猫たち

こういうふうに、単純な関数で CGI 処理を書きたいわけ。


window.onload = function () {
  var webserver = opera.io.webserver;
  if (webserver){
    webserver.addEventListener('_index', index, false);
  }
}

Opera Unite ではこういうふうにリクエストを受けるんだけど、index という関数がもし、

function index(e) {
  var req = e.connection.request;

  var xhr = new XMLHttpRequest;
  xhr.open('GET', 'レスポンスの遅いURL?timestamp='+t, false); // ←同期
  xhr.send();

  var res = e.connection.response;
  res.write(xhr.responseText);
  res.close();
}

という形をしていたら、CGI は一度に1本のリクエストしか処理できない。これは JavaScript のスレッドモデルからは明白なんだけど、一応自分で試して確認した。

これでは問題なので、こんなふうにしたらどうか。

function index(e) {
  var req = e.connection.request;

  var xhr = new XMLHttpRequest;
  xhr.open('GET', 'レスポンスの遅いURL?timestamp='+t, true); // ←非同期
  xhr.onload = function() {
    var res = e.connection.response;
    res.write(xhr.responseText);
    res.close();
  };
  xhr.send();
}

これだと一度に何本も同時にリクエストを処理できる。

しかし、今度は CGI が単純な関数ではなくなってしまった。


この悩みを同時に解決できる方法、つまり、同時に何本ものリクエストを処理しつつ、CGI 処理は同期だけの関数で書く方法は無いか。


と考えて思いついたのがこれ。

var count = 0;
var connections = {};

function index(e) {
  var id = count++;

  // iframe を動的に作って、そこに postMessage で処理を投げる。
  var frame = document.createElement('iframe');
  frame.src = 'frame.html';
  frame.id = id;
  frame.onload = function() {
    frame.contentWindow.postMessage(JSON.stringify({id: id, body: ''}));
  }
  document.body.appendChild(frame);

  connections[id] = e.connection;
}

// iframe からの callback を受け取る。
window.addEventListener('message', function(e) {
  var message = JSON.parse(e.data);
  var id = message.id;
  var connection = connections[id];
  var res = connection.response;

  res.write(message.body);
  res.close();

  connections[id] = null;
  document.body.removeChild( document.getElementById(id) );
}, false);

iframe に読み込んでいる frame.html というページでは、ゆっくり同期処理をすればいい。

<script src="json2.js"></script>
<script>

addEventListener('message', function(e) { // e : {data: any string, origin: URL, source: pointer to other window}
  var message = JSON.parse(e.data); // message : {id: integer, body: any string}
  var t = +new Date;

  var xhr = new XMLHttpRequest;
  xhr.open('GET', 'レスポンスの遅いURL?timestamp='+t, false); // ←同期
  xhr.send();

  e.source.postMessage(JSON.stringify({id: message.id, body: xhr.responseText }));
}, false);

</script>

やってることは WebWorkers のアイデアと同じことなんだけど。(というか WebWorkers というのは、こういうことをもうちょっと簡単にやりたかったために生まれた)


コードが長くなってファイルも2つになったけど、CGI のための処理は結局同じで、そこの部分だけを「リクエストをレスポンスに変換するだけの関数」に置き換えてしまえばいいというわけ。


まー問題が無いわけじゃないんだけど。この方法だと、全リクエストハンドラーで共有するオブジェクトというのは持てなくなるし、File API を使おうと思っても File API には transaction のようなものは無いので、スレッドセーフではない。

やっぱり Jack は諦めて JSDeferred を使うのが現実的かもしれない。

 |