ネットワーク入出力
この節ではRubyでのネットワーク入出力について解説します。
POPd#_sessionメソッドの定義
前節ではスタンドアローンモード、inetdモードいずれの場合もsessionメソッドの呼び出しに帰着したところで終わりました。sessionメソッドではエラーに備えていくつか準備をしたあと、_sessionメソッドを呼び出します。この_sessionメソッドがPOP3サーバの処理の中核となっています。
以下にPOPd#_sessionメソッドのコードを示します。
def _session stamp = apop_stamp() print_line "+OK popd #{stamp}" mailbox = auth(stamp) while cmd = read_cmd() mid = "cmd_#{cmd.name}" unless respond_to?(mid, true) print_line '-ERR unknown command' next end __send__(mid, mailbox, cmd.args) end end
_sessionメソッドの最初の3行、つまりapop_stampからauthまではAPOP認証のコードです。そして、そのあとのwhileループで認証後のPOPコマンドを1つずつ処理しています。
しかし、今はまずそのような処理の流れを無視して、入出力にだけ注目しましょう。POPdの入出力は、print_lineメソッドとread_cmdメソッドでほぼすべてです。
以下、順番に説明しましょう。
POPd#print_lineメソッドの定義
POPd#print_lineは@outputに1行書き込むメソッドです。POPdでは、ネットワークへの出力にはPOPd#print_lineメソッドを使います。
POPd#print_lineメソッドのコードを以下に示します。
def print_line(str) @output.print "#{str}\r\n" end
このとおり、@output(ソケットまたはSTDOUT)に対してprintメソッドを呼ぶだけです。普段ファイルに出力する場合とほとんど変わりませんね。
あえて違うところを探すと、改行コードを明示的に\r\nにしている点が挙げられます。POP3では改行コードとして\r\n(CR+LF)を使うと決まっているので、このように明示的に改行コードを指定しているのです。ここでputsメソッドを使うとLinuxなどでは改行コードが\nになってしまいますから、不適切です。
POPd#read_cmdメソッドの定義
POPd#read_cmdは、POPコマンド1つ(1行)を入力から読み込んで、POPd::Commandオブジェクトを返すメソッドです。POPdでは、ネットワークからの入力にはPOPd#read_cmdメソッドを使います。
POPd#read_cmdメソッドのコードを以下に示します。
def read_cmd line = read_line() or return nil cmd, *args = line.split Command.new(cmd, args) end
本体1行目のPOPd#read_lineは@inputから1行読み込んで返すメソッドです。定義はこの後すぐに紹介します。
read_lineメソッドの返り値がnilだった場合はTCP接続が切れたということなので、read_cmdメソッドもnilを返します。きちんとlineが読み込めた場合は、次の行に移ってPOP3コマンドを解析します。
POP3のコマンドはどれも空白で単語が区切られた単純な構造なので、String#splitコマンド一発で解析できます。例えば、RETRコマンドであれば、lineは「"RETR 13"\r\n」という文字列です。この文字列に対してsplitメソッドを呼ぶと返り値は「["RETR", "13"]」という配列です。その配列を次のように多重代入すると、
cmd, *args = ["RETR", "13"]
cmdが"RETR"、argsが["13"]となります。あとはPOPd::Commandオブジェクトを作るだけです。POPd::Commandオブジェクトは、ほぼ単なる構造体なので、実装は省略します。
POPd#read_lineメソッドの定義
POPd#read_lineメソッドのコードを以下に示します。
READ_TIMEOUT = 90 def read_line begin timeout($DEBUG ? nil : READ_TIMEOUT) { return @input.gets } rescue TimeoutError terminate end end
このメソッドで実際に入力を行っている部分は@input.getsだけです。あとはタイムアウトのためのコードと、その後処理にすぎません。
タイムアウトの実装も、timeoutライブラリが提供するtimeoutメソッドを使うだけです。上記のコードではREAD_TIMEOUT秒(90秒)のあいだにgetsメソッドが完了しなかったらタイムアウトするようにしています。
ただし、デバッグ時($DEBUG == true)にはtimeoutメソッドにnilを渡して、タイムアウト機能を無効にします。これは、デバッグ時には手でPOPコマンドを打ち込む場合がありますから、一定時間内に急いで入力しなければならない仕様だと不便だからです。
timeoutメソッドの詳細についてはリファレンスマニュアルを見てください。
