2010-08-12 :-)
_ 朝ッ
0400 起床
_ [QuickML][コードリーディング][Ruby]QuickML を読む - server 起動
あとまわしにしていた server の処理を読む。起動のところだけ。
ファイルは lib/quickml/server.rb
Server クラスの処理。
   def initialize (config)
     @config = config
     @status = :stop
     @logger = @config.logger
     @server = TCPServer.new(@config.bind_address, @config.port)
   end
initialize() で内部の状態を設定などしている。server は Ruby の TCPServer をラップしている。
initialize() したら start() が呼ばれる。start() はこう。
   def start
     raise "server already started" if @status != :stop
     write_pid_file
     @logger.log sprintf("Server started at %s:%d [%d]",
                         "localhost", @config.port, Process.pid)
     accept
     @logger.log "Server exited [#{Process.pid}]"
     remove_pid_file
   end
状態をチェックし、起動済みならば例外を発生させる。
write_pid_file() では QuickML のプロセスID が書かれたファイルを作成している。Unix では伝統的な pid ファイルである。このファイルは quickml-ctl の stop() で以下のように使われる。
stop() {
        echo -n "Stopping QuickML services: "
        kill `cat /var/run/quickml.pid`
        echo
}
次に accept() する。名前の通り接続待ちになる。accept() はこう。
   def accept
     running_sessions = []
     @status = :running
     while @status == :running
       begin
         t = Thread.new(@server.accept) {|s|
           process_session(s)
         }
         t.abort_on_exception = true
         running_sessions.push(t)
       rescue Errno::ECONNABORTED # caused by @server.shutdown
       rescue Errno::EINVAL
       end
       running_sessions.delete_if {|t| t.status == false }
       if running_sessions.length >= @config.max_threads
         ThreadsWait.new(running_sessions).next_wait
       end
     end
     running_sessions.each {|t| t.join }
   end
実際の受け入れ処理は TCPServer の @server.accept がおこなっている。ブロックに渡される s は TCPSocket である。つまりソケット。
そして running_sessions には確立したセッションのスレッドが格納されていく。
セッションを処理している process_session() を見てみる。
   def process_session (socket)
     begin
       session = Session.new(@config, socket)
       session.start
     rescue Exception => e
       @logger.log "Unknown Session Error: #{e.class}: #{e.message}"
       @logger.log e.backtrace
     end
   end
Session を生成し、開始している。
Session はこう。
class Session include GetText::GetText
   def initialize (config, socket)
     @socket = socket
     @config = config
     @command_table = [:helo, :ehlo, :noop, :quit, :rset, :rcpt, :mail, :data]
     @hello_host = "hello.host.invalid"
     @protocol = nil
     @peer_hostname = @socket.hostname
     @peer_address = @socket.address
     @remote_host = (@peer_hostname or @peer_address)
     @logger = @config.logger
     @catalog = @config.catalog
     @data_finished = false
     @my_hostname = if @config.port == 25 then
                      Socket.gethostname
                    else
                      "localhost"
                    end
     @message_charset = nil
   end
名前のとおり、接続ごとの処理を請け負う。
command_table に格納しているのは SMTP のコマンドである。
initialize() の次は start() である。
class Session
 :
    public
   def start
     start_time = Time.now
     _start
     elapsed = Time.now - start_time
     @logger.vlog "Session finished: #{elapsed} sec."
   end
_start() を見る。
class Session
 :
   def _start
     begin
       connect
       timeout(@config.timeout) {
         process
       }
     rescue TimeoutError
       @logger.vlog "Timeout: #{@remote_host}"
     ensure
       close
     end
   end
connect() して process() している。connect() を見る。
   def connect
     def @socket.puts(*objs)
       objs.each {|x|
         begin
           self.print x.xchomp, "\r\n"
         rescue Errno::EPIPE
         end
       }
     end
     @socket.puts "220 #{@my_hostname} ESMTP QuickML"
     @logger.vlog "Connect: #{@remote_host}"
   end
ここで、def @socket.puts(*objs) することによりこのスコープでのみ @socket の puts() をオーバーライドしている。*objs は渡された引数を表す。self.print で @socket.print を呼び出している。x には渡された引数、つまり文字列 String になる。xchomp() は lib/quickml/util.rb で定義している。
class String
 :
 def xchomp
    self.chomp("\n").chomp("\r")
  end
end
そしてさらに "\r\n" を追加している。つまり渡された文字列から \n \r を削除し、そのあとに \r\n を追加している。
のだと思う。
@socket がリモートから受け取った文字列に対して処理するならば分かるんだが、@socket.puts() はすぐ下の @socket.puts "220.... で使っているだけなのだろうけど、\r \n を削除するのはなぜなんだ。














