1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/lib/net/protocol.rb
aamine 4dae731bbd aamine
* lib/net/protocol.rb: one write(2) per one line.


git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@1237 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2001-03-08 08:39:40 +00:00

874 lines
15 KiB
Ruby

=begin
= net/protocol.rb version 1.2.0
Copyright (C) 1999-2001 Yukihiro Matsumoto
written & maintained by Minero Aoki <aamine@dp.u-netsurf.ne.jp>
This program is free software. You can re-distribute and/or
modify this program under the same terms as Ruby itself,
GNU General Public License or Ruby License.
Japanese version of this document is in "net" full package.
You can get it from RAA (Ruby Application Archive). RAA is:
http://www.ruby-lang.org/en/raa.html
== Net::Protocol
the abstract class for some internet protocols
=== Super Class
Object
=== Class Methods
: new( address = 'localhost', port = nil )
This method Creates a new protocol object.
: start( address = 'localhost', port = nil, *protoargs )
: start( address = 'localhost', port = nil, *protoargs ) {|proto| .... }
This method creates a new Protocol object and opens a session.
equals to Net::Protocol.new( address, port ).start( *protoargs )
=== Methods
: address
the address of connecting server (FQDN).
: port
connecting port number
: start( *args )
: start( *args ) {|proto| .... }
This method starts protocol. If protocol was already started,
do nothing and returns false.
'*args' are specified in subclasses.
When is called with block, gives Protocol object to block and
close session when block finished.
: finish
This method ends protocol. If you call this method before protocol starts,
it only return false without doing anything.
: active?
true if session have been started
=end
require 'socket'
require 'timeout'
module Net
module NetPrivate
end
def self.net_private( &block )
::Net::NetPrivate.module_eval( &block )
end
class Protocol
Version = '1.2.0'
class << self
def start( address = 'localhost', port = nil, *args )
instance = new( address, port )
if block_given? then
instance.start( *args ) { yield instance }
else
instance.start( *args )
instance
end
end
private
def protocol_param( name, val )
module_eval %-
def self.#{name.id2name}
#{val}
end
-
end
end
#
# sub-class requirements
#
# protocol_param command_type
# protocol_param port
#
# private method do_start (optional)
# private method do_finish (optional)
#
protocol_param :port, 'nil'
protocol_param :command_type, 'nil'
protocol_param :socket_type, '::Net::NetPrivate::Socket'
def initialize( addr = nil, port = nil )
@address = addr || 'localhost'
@port = port || type.port
@command = nil
@socket = nil
@active = false
@open_timeout = nil
@read_timeout = nil
@dout = nil
end
attr_reader :address
attr_reader :port
attr_reader :command
attr_reader :socket
attr_accessor :open_timeout
attr_accessor :read_timeout
def active?
@active
end
def set_debug_output( arg ) # un-documented
@dout = arg
end
alias set_pipe set_debug_output
def inspect
"#<#{type} #{address}:#{port} open=#{active?}>"
end
#
# open session
#
def start( *args )
return false if active?
if block_given? then
begin
_start args
yield self
ensure
finish
end
else
_start args
end
end
private
def _start( args )
connect
do_start( *args )
@active = true
end
def connect
conn_socket @address, @port
conn_command @socket
on_connect
end
def re_connect
@socket.reopen @open_timeout
on_connect
end
def conn_socket( addr, port )
@socket = type.socket_type.open(
addr, port, @open_timeout, @read_timeout, @dout )
end
def conn_command( sock )
@command = type.command_type.new( sock )
end
def on_connect
end
def do_start
end
#
# close session
#
public
def finish
return false unless active?
do_finish if @command and not @command.critical?
disconnect
@active = false
true
end
private
def do_finish
@command.quit
end
def disconnect
@command = nil
if @socket and not @socket.closed? then
@socket.close
end
@socket = nil
on_disconnect
end
def on_disconnect
end
end
Session = Protocol
net_private {
class Response
def initialize( ctype, cno, msg )
@code_type = ctype
@code = cno
@message = msg
super()
end
attr_reader :code_type, :code, :message
alias msg message
def inspect
"#<#{type} #{code}>"
end
def error!( data = nil )
raise code_type.error_type.new( code + ' ' + Net.quote(msg), data )
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 ProtocolError
def initialize( msg, data = nil )
super msg
@data = data
end
attr :data
def inspect
"#<#{type}>"
end
end
class Code
def initialize( paren, err )
@parents = paren
@err = err
@parents.push self
end
attr_reader :parents
def inspect
"#<#{type}>"
end
def error_type
@err
end
def ===( response )
response.code_type.parents.reverse_each {|i| return true if i == self }
false
end
def mkchild( err = nil )
type.new( @parents + [self], err || @err )
end
end
ReplyCode = Code.new( [], ProtoUnknownError )
InformationCode = ReplyCode.mkchild( ProtoUnknownError )
SuccessCode = ReplyCode.mkchild( ProtoUnknownError )
ContinueCode = ReplyCode.mkchild( ProtoUnknownError )
ErrorCode = ReplyCode.mkchild( ProtocolError )
SyntaxErrorCode = ErrorCode.mkchild( ProtoSyntaxError )
FatalErrorCode = ErrorCode.mkchild( ProtoFatalError )
ServerErrorCode = ErrorCode.mkchild( ProtoServerError )
AuthErrorCode = ErrorCode.mkchild( ProtoAuthError )
RetriableCode = ReplyCode.mkchild( ProtoRetriableError )
UnknownCode = ReplyCode.mkchild( ProtoUnknownError )
net_private {
class WriteAdapter
def initialize( sock, mid )
@sock = sock
@mid = mid
end
def inspect
"#<#{type}>"
end
def write( str )
@sock.__send__ @mid, str
end
alias << write
end
class ReadAdapter
def initialize( block )
@block = block
end
def inspect
"#<#{type}>"
end
def <<( str )
callblock( str, &@block ) if @block
end
private
def callblock( str )
begin
user_break = true
yield str
user_break = false
rescue Exception
user_break = false
raise
ensure
if user_break then
@block = nil
return # stop break
end
end
end
end
class Command
def initialize( sock )
@socket = sock
@last_reply = nil
@critical = false
end
attr_accessor :socket
attr_reader :last_reply
def inspect
"#<#{type}>"
end
# abstract quit
private
# abstract get_reply()
def check_reply( *oks )
@last_reply = get_reply
reply_must( @last_reply, *oks )
end
def reply_must( rep, *oks )
oks.each do |i|
if i === rep then
return rep
end
end
rep.error!
end
def getok( line, expect = SuccessCode )
@socket.writeline line
check_reply expect
end
#
# error handle
#
public
def critical?
@critical
end
def error_ok
@critical = false
end
private
def critical
@critical = true
ret = yield
@critical = false
ret
end
def begin_critical
ret = @critical
@critical = true
not ret
end
def end_critical
@critical = false
end
end
class Socket
def initialize( addr, port, otime = nil, rtime = nil, dout = nil )
@addr = addr
@port = port
@read_timeout = rtime
@debugout = dout
@socket = nil
@sending = ''
@buffer = ''
connect otime
D 'opened'
end
def connect( otime )
D "opening connection to #{@addr}..."
timeout( otime ) {
@socket = TCPsocket.new( @addr, @port )
}
end
private :connect
attr :pipe, true
class << self
alias open new
end
def inspect
"#<#{type} #{closed? ? 'closed' : 'opened'}>"
end
def reopen( otime = nil )
D 'reopening...'
close
connect otime
D 'reopened'
end
attr :socket, true
def close
if @socket then
@socket.close
D 'closed'
else
D 'close call for already closed socket'
end
@socket = nil
@buffer = ''
end
def closed?
not @socket
end
def address
@addr.dup
end
alias addr address
attr_reader :port
def ip_address
@socket or return ''
@socket.addr[3]
end
alias ipaddr ip_address
attr_reader :sending
#
# read
#
public
CRLF = "\r\n"
def read( len, dest = '', ignerr = false )
D_off "reading #{len} bytes..."
rsize = 0
begin
while rsize + @buffer.size < len do
rsize += rbuf_moveto( dest, @buffer.size )
rbuf_fill
end
rbuf_moveto dest, len - rsize
rescue EOFError
raise unless igneof
end
D_on "read #{len} bytes"
dest
end
def read_all( dest = '' )
D_off 'reading all...'
rsize = 0
begin
while true do
rsize += rbuf_moveto( dest, @buffer.size )
rbuf_fill
end
rescue EOFError
;
end
D_on "read #{rsize} bytes"
dest
end
def readuntil( target, igneof = false )
dest = ''
begin
while true do
idx = @buffer.index( target )
break if idx
rbuf_fill
end
rbuf_moveto dest, idx + target.size
rescue EOFError
raise unless igneof
rbuf_moveto dest, @buffer.size
end
dest
end
def readline
ret = readuntil( "\n" )
ret.chop!
ret
end
def read_pendstr( dest )
D_off 'reading text...'
rsize = 0
while (str = readuntil("\r\n")) != ".\r\n" do
rsize += str.size
str.gsub!( /\A\./, '' )
dest << str
end
D_on "read #{rsize} bytes"
dest
end
# private use only (can not handle 'break')
def read_pendlist
D_off 'reading list...'
str = nil
i = 0
while (str = readuntil("\r\n")) != ".\r\n" do
i += 1
str.chop!
yield str
end
D_on "read #{i} items"
end
private
READ_SIZE = 1024 * 4
def rbuf_fill
unless IO.select [@socket], nil, nil, @read_timeout then
on_read_timeout
end
@buffer << @socket.sysread( READ_SIZE )
end
def on_read_timeout
raise TimeoutError, "socket read timeout (#{@read_timeout} sec)"
end
def rbuf_moveto( dest, len )
bsi = @buffer.size
s = @buffer[ 0, len ]
dest << s
@buffer = @buffer[ len, bsi - len ]
@debugout << %<read "#{Net.quote s}"\n> if @debugout
len
end
#
# write interfece
#
public
def write( str )
writing {
do_write str
}
end
def writeline( str )
writing {
do_write str + "\r\n"
}
end
def write_bin( src, block )
writing {
if block then
block.call ::Net::NetPrivate::WriteAdapter.new( self, :do_write )
else
src.each do |bin|
do_write bin
end
end
}
end
def write_pendstr( src, block )
D_off "writing text from #{src.type}"
wsize = use_each_crlf_line {
if block then
block.call ::Net::NetPrivate::WriteAdapter.new( self, :wpend_in )
else
wpend_in src
end
}
D_on "wrote #{wsize} bytes text"
wsize
end
private
def wpend_in( src )
line = nil
pre = @writtensize
each_crlf_line( src ) do |line|
do_write '.' if line[0] == ?.
do_write line
end
@writtensize - pre
end
def use_each_crlf_line
writing {
@wbuf = ''
yield
if not @wbuf.empty? then # un-terminated last line
if @wbuf[-1] == ?\r then
@wbuf.chop!
end
@wbuf.concat "\r\n"
do_write @wbuf
elsif @writtensize == 0 then # empty src
do_write "\r\n"
end
do_write ".\r\n"
@wbuf = nil
}
end
def each_crlf_line( src )
str = m = beg = nil
adding( src ) do
beg = 0
buf = @wbuf
while buf.index( /\n|\r\n|\r/, beg ) do
m = Regexp.last_match
if m.begin(0) == buf.size - 1 and buf[-1] == ?\r then
# "...\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.size ]
end
end
def adding( src )
i = nil
case src
when String
0.step( src.size - 1, 2048 ) do |i|
@wbuf << src[i,2048]
yield
end
when File
while true do
i = src.read( 2048 )
break unless i
i[0,0] = @wbuf
@wbuf = i
yield
end
else
src.each do |i|
@wbuf << i
if @wbuf.size > 2048 then
yield
end
end
yield unless @wbuf.empty?
end
end
def writing
@writtensize = 0
@sending = ''
yield
if @debugout then
@debugout << 'write "'
@debugout << @sending
@debugout << "\"\n"
end
@socket.flush
@writtensize
end
def do_write( arg )
if @debugout or @sending.size < 128 then
@sending << Net.quote( arg )
else
@sending << '...' unless @sending[-1] == ?.
end
s = @socket.write( arg )
@writtensize += s
s
end
def D_off( msg )
D msg
@savedo, @debugout = @debugout, nil
end
def D_on( msg )
@debugout = @savedo
D msg
end
def D( msg )
@debugout or return
@debugout << msg
@debugout << "\n"
end
end
}
def Net.quote( str )
str = str.gsub( "\n", '\\n' )
str.gsub!( "\r", '\\r' )
str.gsub!( "\t", '\\t' )
str
end
end # module Net