dRuby ではサーバ側のホスト指定をしないほうがいい
Java の RMI みたいな分散コンピューティング技術である dRuby。これを使おうとしてはまったポイントがあったのでメモ。
dRuby 自体は涙が出るほど使うのが簡単。
たとえば、同じマシンの上で、server.rb と client.rb と2つ実行ファイルを用意しよう。
# server.rb require 'drb/drb' server = "abc" DRb.start_service("druby://:5000", server) sleep # 待ちうけ状態にするため
# client.rb require 'drb/drb' server = DRbObject.new_with_uri("druby://localhost:5000") puts server.size
これで
% ruby server.rb
としてサーバを起動。シェルの別窓で、
% ruby client.rb
とすると、"3" という出力が見られるはずだ。詳しくは、dRuby 本家を見ていただきたい。
もちろん、dRuby の真骨頂は、マシンの境界を越えたリモートへの呼び出しなわけだが、私はここではまってしまった。結論から言うと、上の server.rb で
DRb.start_service("druby://localhost:5000", server)
とローカルホストを指定するとうまくいかない。つまりここでサーバが listen する IP アドレスを限定してしまうことになるからだ。ソケットプログラミングに詳しい方なら常識だろうが、私は、これを発見するまで小一時間 dRuby のソースコードの中を彷徨うことになってしまった。皆さんは注意されたい。
dRuby を使って Excel ファイルの中身を配信
趣旨
私は Windows (ホストOS) + VWMare 上の Linux(ゲストOS) という構成で開発を行っているのだが、どうしても Linux 側から Excel ファイルを読みたくなった。そこで、dRuby を使って Windows 側から Excel ファイルの中身を読み出す Ruby プログラムを書いてみた。拙いコードだが皆さんの参考になれば幸いである。
使い方
サーバ側
下のソースコードを excel_server.rb というファイル名で Windows マシン上に保存する。いま仮に C:\ に保存したとする。次のようにして実行。
C:\>ruby excel_server.rb druby://windows_pc_name:12345 Press any key to quit.
となるはずだ。これでサーバ側の準備 OK。
クライアント側
% irb --simple-prompt >> require 'drb/drb' => true >> server = DRbObject.new_with_uri('druby://windows_pc_name:12345') => #<DRb::DRbObject:0x401d07c8 @ref=nil, @uri="druby://windows_pc_name:12345"> >> server.read_worksheet('C:\test1.xls') => {"Sheet1"=>"d\r\nb\r\nd\r\n", "Sheet2"=>"a\tb\r\nc\td\r\n", "Sheet3"=>"a\r\nb\r\n"}
のように使う。ExcelServer#read_worksheet の第 1 引数に Excel ファイルへのパスを指定すると、「シート名 => シートの内容」というハッシュを UTF-8 で返してくれる。
その他の注意
サーバコマンド excel_server.rb は
C:\>ruby excel_server.rb my_password C:\>ruby excel_server.rb nil druby://:22222 C:\>ruby excel_server.rb my_password druby://:22222
のように(パスワード) (URI) という2つの引数を取ることができる。パスワードを指定した場合は、クライアント側で read_worksheet の第2引数にパスワードを指定しないと Access denied エラーとなる。
server.read_worksheet('C:\test1.xls', 'my_password')
という感じに使う。まあ、貧弱なセキュリティ措置で焼け石に水という感じではあるが、ないよりはましだろう。
ソースコード
require 'rubygems' require "win32/clipboard" require 'win32ole' require 'kconv' require 'drb/drb' require 'Win32API' class ExcelServer def initialize(options = nil) options = options ? options.dup : {} @password = options[:password] end def authorized?(password) @password.nil? || @password == password end # シート名 => シート内容というハッシュを得る。 def read_worksheet(file_path, password = nil, return_cache = false) raise "Access denied." unless authorized?(password) @workbook_content ||= {} return @workbook_content if return_cache && @workbook_content != {} @excel ||= WIN32OLE.new('Excel.Application') @excel.visible = false @excel.displayAlerts = false @excel.workbooks.open 'filename' => file_path i = 1 count = @excel.sheets.count puts "Requested: file_path = #{file_path}, sheets.count = #{count}" while i <= count sheet = @excel.sheets(i) sheet.select @excel.cells.select @excel.selection.copy s = Win32::Clipboard.data @workbook_content[sheet.name] = s.toutf8 i += 1 end @workbook_content end def shutdown @excel.quit end def self.start_service(options = nil) begin options = options ? options.dup : {} options[:uri] ||= "druby://:12345" server = ExcelServer.new(options) DRb.start_service(options[:uri], server) puts DRb.uri kbhit = Win32API.new('msvcrt','_kbhit',[],'l') print 'Press any key to quit.' while true sleep 0.5 if kbhit.call != 0 break end end puts "\nA key is pressed." ensure server.shutdown if server end end end password = ARGV.shift password = password == "nil" ? nil : password uri = ARGV.shift ExcelServer.start_service(:uri => uri, :password => password)