[OpenSocial][Rack] Rack で JSON-RPC を処理する。

OpenSocial の、ということに関していえば、それほど大変ではなかった。


config.ru にディスパッチルーチンをかく。

require 'rubygems'
require 'json'
require File.join(File.dirname(__FILE__), 'lib/service/system')

# dispatch the rpc request
def dispatch(rpc, req)
  service, method = rpc["method"].split(".")
  result = Service::System.apply(service, method, rpc["params"], req)
  # TODO
  #   Currently shindig implementation required 'data' field, not 'result' field.
  #   This should be changed.
  {
    "id"     => rpc["id"],
    "data"   => result
  }
end

run proc{|env|
  req = Rack::Request.new(env)
  begin
    if req.post?
      json = JSON.parse(req.body.read)
      if json.is_a?(Array)
        [200,
         {'Content-Type'=>'application/json'},
         json.map { |rpc|
           dispatch(rpc, req)
         }.to_json
        ]
      else
        [200,
         {'Content-Type'=>'application/json'},
         dispatch(json, req).to_json
        ]
      end
    else
      [405,
       {'Content-Type'=>'application/json'},
       nil]
    end
  rescue => e
    [500,
     {'Content-Type'=>'application/json'},
     {"error" => e.message, "trace" => e.backtrace}.to_json]
  end
}

OpenSocial では system という名前のサービスで、対応するメソッド名などをリストしなければならないので、Service::System クラスにメソッドのsignatureを定義しておいて、Method#call で呼び出す方針にしたのであります。

具体的には Service::System#apply(service, method, params, request) で、"service.method" が提供されているかどうかをチェックし、params のHashを渡して呼び出す。ついでにoAuthのトークンがrequestオブジェクトに入っているはず(OpenSocial的には Authentication ヘッダー使えってかいてあるように使用が読めるんだが、Shindigだとst=xxxx でクエリにいれているような気がする)なので、Requestオブジェクトも渡してあげます。

#
# OpenSocial system service
#
module Service
  class System
    # key valie pairs for available methods,
    # {service name => array of available methods}
    AVAILABLE_SERVICES = {
      :system => [:listMethods, :methodSignatures, :methodHelp],
      :people => [:get]
    }

    class << self
      #
      # A proxy method of available methods. This method should be for the dispach routine.
      #
      def apply(service, method, params, request)
        k = service.to_sym
        m = method.to_sym
        if AVAILABLE_SERVICES.has_key?(k) &&
            AVAILABLE_SERVICES[k].include?(m)
          require File.join(File.dirname(__FILE__), service)
          klass_name = service[0,1].upcase + service[1..-1]
          klass = Service.const_get(klass_name)
          klass.method(m).call(params, request)
        else
          # TODO
          raise "not supported"
        end
      end
    end
  end
end

これで、例えば、"people.get" であれば、Service::People.get にマップされるので、

require 'rubygems'
require 'restclient'
#
# OpenSocial system service
#
module Service
  class People
    class << self
      def get(params={}, req = nil)
        # ここにCouchDBのView呼び出しの実装をかく
        {
          "name" => "Jane Doe",
          "displayName" => "Jone Doe",
          "gender" => "female",
          "id" => "example.org:34KJDCSKJN2HHF0DW20394"
        }
      end
    end
  end
end

ということになります。

それはともかく、ここから先はoAuthしらないと実装できなさそうなので、しばし勉強モードです。