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 を削除するのはなぜなんだ。