第13回北海道開発オフに参加

第13回北海道開発オフに参加してきました。
「大量ドロリッチ襲来」「変なキャンディー」「小さなすもけさんと大きなすもけさん」「ランチ大分裂時代」「@shuji_w6e&@hadzimmeサシで定食屋事変」「リアルハヂモiPhoneから参加」などなど、今回も盛りだくさんでお送りいたしました。終わってみれば19人(20人?)の大所帯。ホントすごいですね開発オフ。次回は寒そうなので、会場サイドからお許しいただけそうなら出前でも取りましょうそうしましょう。
さて、今回取り組んだことを紹介します。
現在、ブラウザ上で動くチャットアプリにボットを繋げることを考えています。チャットアプリには、ShootingStarという、Cometサーバを用いたウェブアプリを作れるRailsプラグインを使用しています。
Cometの特長は、こちらから何度もサーバにリクエストを送らなくても、更新があったタイミングでサーバから情報が送られる、Push型のアプリが実現できることです。Lingrなどでよく知られていると思います。
ブラウザ上では、その通信を行うために、Ajaxを使用しています。ボットがこの通信を行うために、JavaScriptでやっているのと同じやり方で、非同期通信を行うためのプログラムを書きます。
普段使っているRubyで、Cometサーバと通信するプログラムをガーッと書きました。以下にそのまま載せます。

require "rubygems"
require "httpclient"
require "json"

module Longpoll
  @connection = "connect"
  def self.start
    strike = "http://localhost:3000/meteor/strike"
    c = HTTPClient.new
    c.receive_timeout = 86400
    res = c.post_content(
      "http://localhost:8080/chat",
      "__p__" => @connection,
      "__t__" => "xhr",
      "execute" => strike,
      "sig" => 12345678,
      "uid" => "hadzimme")
    args = *res.match(/meteorStrike.execute\("*([^"]+?)"*,"([^"]+?)"\)/)
    res = c.get_content(
      strike + "/" + args[1] + "?" + args[2],
      "format" => "json")
    chat = JSON.parse(res)
    if chat.size > 0
      puts "#{chat["chat"]["name"]}> #{chat["chat"]["message"]}"
    end

    if @connection == "connect"
      @connection = "reconnect"
    end
    start
  end
end

Longpoll.start

Cometサーバとの通信では、クライアントが最初にサーバにリクエストを送るところは通常と同じですが、サーバはすぐにレスポンスを返しません。Webサーバ上で何らかの更新があったタイミングで、リクエストを預かっているクライアント全部にレスポンスを返します。クライアントはレスポンスを受け取って、すぐに次のリクエストを送ります。これを何度も繰り返します。つまり、ほとんどの時間は、サーバがリクエストを預かったままになっているわけです*1
RubyのコードではHTTPClientを使っていますが、receive_timeoutがデフォルトで60になっており、サーバから60秒間レスポンスがないとタイムアウトになってしまいます。なので、このタイムアウトを長めに設定しておくのがポイントです。レスポンスを受け取ったら、自身を呼び出して次のリクエストを送ります。
このアプリでは、Cometサーバからのレスポンスが、「meteorStrike.execute()」というJavaScriptインスタンスメソッドになっています。このメソッドにより、ブラウザからRails側のmeteor/strikeメソッドを呼び出して、発言内容やイベントを取得しています。今回のコードでも、それと同じ手順を踏みます。まずpost_contentでCometサーバからの通知を受け取り、次のget_contentでWebサーバ上の更新を取得します。meteor/strikeメソッドは、発言者名と発言内容をJSONを用いたレスポンスとして返すことができるように書き加えてあります。
ボット自体の発言は、単にPOSTメソッドで送ってやればよいです。これらを組み合わせることで、ブラウザ上の非同期チャットアプリでボットとおしゃべりをすることができるようになります。

*1:そのプロセスをサーバがどう処理するのか、というのが問題になりますが、そのへんはうまくゴニョゴニョされているらしいです