@m_seki の

I like ruby tooから引っ越し

send_io/recv_io

親子関係のないプロセス間でファイルディスクリプタを共有する - @tmtms のメモ

id:tmtms さんの日記に出てたFDを飛ばすやつでCGI書いてみました。
元ネタはいつものdRubyを使ったCGIで、オリジナルはdRubyで$stdin, $stdoutを公開してました。
このバージョンは$stdin, $stdoutを分散オブジェクトにする代わりに、FDを送ります。

#!/usr/local/bin/ruby
require 'socket'

def to_cgi(path)
  soc = UNIXSocket.new(path)
  env = Marshal.dump(ENV.to_hash)
  soc.write([env.size].pack('N'))
  soc.write(env)
  soc.send_io($stdin)
  soc.send_io($stdout)
  soc.read rescue nil
end

if __FILE__ == $0
  path = "/tmp/#{File.basename($0, '.rb')}.soc"
  to_cgi(path)
end

サーバ側の受け口はこれ。ちょっと長いよ。

require 'socket'

class ToCGIServer
  def initialize(path, cgi, config={})
    File.unlink(path) rescue nil
    @server = UNIXServer.new(path)
    owner = config[:owner]
    group = config[:group]
    if owner || group
      require 'etc'
      owner = Etc.getpwnam(owner).uid if owner
      group = Etc.getgrnam(group).uid if group
      File.chown(owner, group, path)
    end
    mode = config[:mode]
    File.chmod(mode, path) if mode
    @cgi = cgi
  end

  def run
    while true
      Thread.new(@server.accept) do |soc|
        begin
          on_client(soc)
        rescue
        ensure
          soc.close
        end
      end
    end
  end

  def on_client(soc)
    sz = soc.read(4)
    buf = soc.read(sz.unpack('N')[0])
    env = Marshal.load(buf)
    sin = soc.recv_io
    sout = soc.recv_io
    @cgi.start(env, sin, sout)
  ensure
    sin.close if sin
    sout.close if sout
  end
end

FDを送ることで、サーバからCGIプロセスへのコネクションとデータの転送が減るので効率が良くなります。
ENVをMarshalでない形式で送れば、CGIプロセス側をCで書くこともできるなー。mod_なんとか を作るのもありか。

dRubyを使ったCGIのサーバの形式だったらこんな風に直すだけ。

if __FILE__ == $0
  book = WikiR::Book.new
  ui = WikiR::UI.new(book)
  DRb.start_service('druby://localhost:50830', ui)
  ToCGIServer.new('/tmp/to_cgi.soc', ui, {:mode => 0666}).run
end

https://github.com/seki/ToCGIのappの下に感想戦で使ったWiki_Rを改造したものがあります。

100行もあるけど貼っちゃおう。

require 'kramdown'
require 'webrick'
require 'webrick/cgi'
require 'drb/drb'
require 'erb'
require 'monitor'
require 'to_cgi_server'
 
class WikiR
  class Book
    include MonitorMixin
    def initialize
      super()
      @page = {}
    end
 
    def [](name)
      @page[name] || Page.new(name)
    end
 
    def []=(name, src)
      synchronize do
        page = self[name]
        @page[name] = page
        page.set_src(src)
      end
    end
  end
 
  class Page
    def initialize(name)
      @name = name
      set_src("# #{name}\n\nan empty page. edit me.")
    end
    attr_reader :name, :src, :html, :warnings
 
    def set_src(text)
      @src = text
      km = Kramdown::Document.new(text)
      @html = km.to_html
      @warnings = km.warnings
    end
  end
 
  class UI < WEBrick::CGI
    include ERB::Util
    extend ERB::DefMethod
    def_erb_method('to_html(page)', ERB.new(<<EOS))
<html>
 <head>
  <title>Kramdown</title>
  <script language="JavaScript">
function open_edit(){
document.getElementById('edit').style.display = "block";
}
  </script>
 </head>
 <body>
  <%= page.html %>
  <a href='javascript:open_edit()'>[edit]</a>
  <div id='edit' style='display:none;'>
   <form method='post'>
    <textarea name='text' rows="40" cols="50"><%=h page.src %></textarea>
   <input type='submit' name='ok' value='ok'/>
   </form>
  </div>
 </body>
</html>
EOS
 
    def initialize(book, *args)
      super(*args)
      @book = book
    end
 
    def do_GET(req, res)
      do_request(req, res)
      build_page(req, res)
    end
    alias :do_POST :do_GET
 
    def do_request(req, res)
      text ,= req.query['text']
      return if text.nil? || text.empty?
      text = text.force_encoding('utf-8')
      @book[req.path_info] = text
    rescue
    end
 
    def build_page(req, res)
      res['content-type'] = 'text/html; charset=utf-8'
      res.body = to_html(@book[req.path_info])
    end
  end
end
 
if __FILE__ == $0
  book = WikiR::Book.new
  ui = WikiR::UI.new(book)
  DRb.start_service('druby://localhost:50830', ui)
  ToCGIServer.new('/tmp/to_cgi.soc', ui, {:mode => 0666}).run
end

あわせて読みたい

The dRuby Book: Distributed and Parallel Computing with Ruby

The dRuby Book: Distributed and Parallel Computing with Ruby