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














