=begin = net/protocol.rb Copyright (c) 1999-2003 Yukihiro Matsumoto Copyright (c) 1999-2003 Minero Aoki written and maintained by Minero Aoki This program is free software. You can re-distribute and/or modify this program under the same terms as Ruby itself, Ruby Distribute License or GNU General Public License. $Id$ WARNING: This file is going to remove. Do not rely on the implementation written in this file. =end require 'socket' require 'timeout' module Net class Protocol private def Protocol.protocol_param( name, val ) module_eval(<<-End, __FILE__, __LINE__ + 1) def #{name} #{val} end End end end class ProtocolError < StandardError; end class ProtoSyntaxError < ProtocolError; end class ProtoFatalError < ProtocolError; end class ProtoUnknownError < ProtocolError; end class ProtoServerError < ProtocolError; end class ProtoAuthError < ProtocolError; end class ProtoCommandError < ProtocolError; end class ProtoRetriableError < ProtocolError; end ProtocRetryError = ProtoRetriableError class InternetMessageIO class << self alias open new end def initialize( addr, port, open_timeout = nil, read_timeout = nil, debug_output = nil ) @address = addr @port = port @read_timeout = read_timeout @debug_output = debug_output @socket = nil @rbuf = nil # read buffer @wbuf = nil # write buffer connect open_timeout LOG 'opened' end attr_reader :address attr_reader :port def ip_address return '' unless @socket @socket.addr[3] end attr_accessor :read_timeout attr_reader :socket def connect( open_timeout ) LOG "opening connection to #{@address}..." timeout(open_timeout) { @socket = TCPsocket.new(@address, @port) } @rbuf = '' end private :connect def close if @socket @socket.close LOG 'closed' else LOG 'close call for already closed socket' end @socket = nil @rbuf = '' end def reopen( open_timeout = nil ) LOG 'reopening...' close connect open_timeout LOG 'reopened' end def closed? not @socket end def inspect "#<#{self.class} #{closed?() ? 'closed' : 'opened'}>" end ### ### READ ### public def read( len, dest = '', ignore_eof = false ) LOG "reading #{len} bytes..." LOG_off() read_bytes = 0 begin while read_bytes + @rbuf.size < len read_bytes += rbuf_moveto(dest, @rbuf.size) rbuf_fill end rbuf_moveto dest, len - read_bytes rescue EOFError raise unless ignore_eof end LOG_on() LOG "read #{read_bytes} bytes" dest end def read_all( dest = '' ) LOG 'reading all...' LOG_off() read_bytes = 0 begin while true read_bytes += rbuf_moveto(dest, @rbuf.size) rbuf_fill end rescue EOFError ; end LOG_on() LOG "read #{read_bytes} bytes" dest end def readuntil( terminator, ignore_eof = false ) dest = '' begin until idx = @rbuf.index(terminator) rbuf_fill end rbuf_moveto dest, idx + terminator.size rescue EOFError raise unless ignore_eof rbuf_moveto dest, @rbuf.size end dest end def readline readuntil("\n").chop end def each_message_chunk LOG 'reading message...' LOG_off() read_bytes = 0 while (line = readuntil("\r\n")) != ".\r\n" read_bytes += line.size yield line.sub(/\A\./, '') end LOG_on() LOG "read message (#{read_bytes} bytes)" end # *library private* (cannot handle 'break') def each_list_item while (str = readuntil("\r\n")) != ".\r\n" yield str.chop end end private def rbuf_fill until IO.select([@socket], nil, nil, @read_timeout) raise TimeoutError, "socket read timeout (#{@read_timeout} sec)" end @rbuf << @socket.sysread(1024) end def rbuf_moveto( dest, len ) dest << (s = @rbuf.slice!(0, len)) @debug_output << %Q[-> #{s.dump}\n] if @debug_output len end ### ### WRITE ### public def write( str ) writing { write0 str } end def writeline( str ) writing { write0 str + "\r\n" } end def write_message( src ) LOG "writing message from #{src.class}" LOG_off() len = using_each_crlf_line { write_message_0 src } LOG_on() LOG "wrote #{len} bytes" len end def write_message_by_block( &block ) LOG 'writing message from block' LOG_off() len = using_each_crlf_line { begin block.call(WriteAdapter.new(self, :write_message_0)) rescue LocalJumpError # allow `break' from writer block end } LOG_on() LOG "wrote #{len} bytes" len end private def writing @written_bytes = 0 @debug_output << '<- ' if @debug_output yield @socket.flush @debug_output << "\n" if @debug_output bytes = @written_bytes @written_bytes = nil bytes end def write0( str ) @debug_output << str.dump if @debug_output len = @socket.write(str) @written_bytes += len len end # # Reads string from src calling :each, and write to @socket. # Escapes '.' on the each line head. # def write_message_0( src ) prev = @written_bytes each_crlf_line(src) do |line| if line[0] == ?. then write0 '.' + line else write0 line end end @written_bytes - prev end # # setup @wbuf for each_crlf_line. # def using_each_crlf_line writing { @wbuf = '' yield if not @wbuf.empty? # unterminated last line if @wbuf[-1] == ?\r @wbuf.chop! end @wbuf.concat "\r\n" write0 @wbuf elsif @written_bytes == 0 # empty src write0 "\r\n" end write0 ".\r\n" @wbuf = nil } end # # extract a CR-LF-terminating-line from @wbuf and yield it. # def each_crlf_line( src ) adding(src) do beg = 0 buf = @wbuf while buf.index(/\n|\r\n|\r/, beg) m = Regexp.last_match if (m.begin(0) == buf.length - 1) and buf[-1] == ?\r # "...\r" : can follow "\n..." break end str = buf[beg ... m.begin(0)] str.concat "\r\n" yield str beg = m.end(0) end @wbuf = buf[beg ... buf.length] end end # # Reads strings from SRC and add to @wbuf, then yield. # def adding( src ) case src when String # for speeding up. 0.step(src.size - 1, 2048) do |i| @wbuf << src[i,2048] yield end when File # for speeding up. while s = src.read(2048) s[0,0] = @wbuf @wbuf = s yield end else # generic reader src.each do |s| @wbuf << s yield if @wbuf.size > 2048 end yield unless @wbuf.empty? end end ### ### DEBUG ### private def LOG_off @save_debug_out = @debug_output @debug_output = nil end def LOG_on @debug_output = @save_debug_out end def LOG( msg ) return unless @debug_output @debug_output << msg @debug_output << "\n" end end # # The writer adapter class # class WriteAdapter def initialize( sock, mid ) @socket = sock @method_id = mid end def inspect "#<#{self.class} socket=#{@socket.inspect}>" end def write( str ) @socket.__send__(@method_id, str) end alias print write def <<( str ) write str self end def puts( str = '' ) write str.sub(/\n?/, "\n") end def printf( *args ) write sprintf(*args) end end # # The reader adapter class for internal use only. # class ReadAdapter def initialize( block ) @block = block end def inspect "#<#{self.class}>" end def <<( str ) call_block(str, &@block) if @block end private # # This method is needed because @block must be called by yield, # not Proc#call. You can see difference when using `break' in # the block. # def call_block( str ) yield str end end # For backward compatibility module NetPrivate Socket = ::Net::InternetMessageIO end end # module Net