1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
* lib/net/http.rb: add HTTP#request.
* lib/net/http.rb: take HTTP 1.0 server into account (incomplete).
* lib/net/protocol.rb: timeout for open/read.
* lib/net/protocol.rb: add Protocol#on_connect,on_disconnect.


git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@1160 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
aamine 2001-02-06 11:14:51 +00:00
parent 765255b737
commit cdc7602379
5 changed files with 344 additions and 316 deletions

View file

@ -1,3 +1,13 @@
Tue Feb 6 20:19:10 2001 Minero Aoki <aamine@dp.u-netsurf.ne.jp>
* lib/net/http.rb: add HTTP#request.
* lib/net/http.rb: take HTTP 1.0 server into account (incomplete).
* lib/net/protocol.rb: timeout for open/read.
* lib/net/protocol.rb: add Protocol#on_connect,on_disconnect.
Fri Feb 2 16:14:51 2001 Yukihiro Matsumoto <matz@ruby-lang.org> Fri Feb 2 16:14:51 2001 Yukihiro Matsumoto <matz@ruby-lang.org>
* array.c (rb_ary_sort_bang): returns self, even if its length is * array.c (rb_ary_sort_bang): returns self, even if its length is

View file

@ -14,7 +14,7 @@
(Ruby Application Archive: http://www.ruby-lang.org/en/raa.html). (Ruby Application Archive: http://www.ruby-lang.org/en/raa.html).
= class HTTP = class Net::HTTP
== Class Methods == Class Methods
@ -49,7 +49,7 @@
HTTP default port (80). HTTP default port (80).
== Methods == Instance Methods
: start : start
: start {|http| .... } : start {|http| .... }
@ -100,46 +100,31 @@
If called with block, gives a part of entity body string. If called with block, gives a part of entity body string.
: new_get( path, header = nil ) {|req| .... }
creates a new GET request object and gives it to the block.
see also for Get class reference.
# example : request( request, [src] )
http.new_get( '/~foo/bar.html' ) do |req| : request( request, [src] ) {|response| .... }
req['accept'] = 'text/html' sends REQUEST to (remote) http server. This method also writes
response = req.dispatch string from SRC before it if REQUEST is a post/put request.
p response['Content-Type'] (giving SRC for get/head request causes ArgumentError.)
puts response.read_header
end
: new_head( path, header = nil ) {|req| .... } If called with block, gives a HTTP response object to the block.
creates a new HEAD request object and gives it to the block.
see also Head class reference.
: new_post( path, header = nil ) {|req| .... }
creates a new POST request object and gives it to the block.
see also Post class reference.
= class Get, Head, Post = class Net::HTTP::Get, Head, Post
HTTP request class. This class wraps request header and entity path. HTTP request classes. These classes wraps request header and
All "key" is case-insensitive. entity path. All "key" is case-insensitive.
== Methods == Methods
: self[ key ] : self[ key ]
returns header field for "key". returns header field for "key".
: dispatch [only Get, Head] : self[ key ] = val
dispatches request. set header to "val".
This method returns HTTPResponse object.
: dispatch( data = '' ) [only Post]
: dispatch {|adapter| .... } [only Post]
dispatches request. "data" is
= class HTTPResponse = class Net::HTTPResponse
HTTP response class. This class wraps response header and entity. HTTP response class. This class wraps response header and entity.
All "key" is case-insensitive. All "key" is case-insensitive.
@ -209,13 +194,31 @@ module Net
class HTTP < Protocol class HTTP < Protocol
protocol_param :port, '80'
HTTPVersion = '1.1' HTTPVersion = '1.1'
###
### connection
###
def addr_port protocol_param :port, '80'
address + (port == HTTP.port ? '' : ":#{port}")
def initialize( addr = nil, port = nil )
super
@proxy_address = nil
@proxy_port = nil
@curr_http_version = HTTPVersion
@seems_1_0_server = false
end
private
def conn_command( sock )
end
def do_finish
end end
@ -223,11 +226,13 @@ module Net
### proxy ### proxy
### ###
public
class << self class << self
def Proxy( p_addr, p_port = nil ) def Proxy( p_addr, p_port = nil )
::Net::NetPrivate::HTTPProxy.create_proxy_class( ProxyMod.create_proxy_class( p_addr, p_port || self.port )
p_addr, p_port || self.port )
end end
alias orig_new new alias orig_new new
@ -274,9 +279,65 @@ module Net
end end
### module ProxyMod
### for compatibility
### class << self
def create_proxy_class( p_addr, p_port )
klass = Class.new( HTTP )
klass.module_eval {
include HTTPProxy
@proxy_address = p_addr
@proxy_port = p_port
}
def klass.proxy_class?
true
end
def klass.proxy_address
@proxy_address
end
def klass.proxy_port
@proxy_port
end
klass
end
end
def initialize( addr, port )
super
@proxy_address = type.proxy_address
@proxy_port = type.proxy_port
end
attr_reader :proxy_address, :proxy_port
alias proxyaddr proxy_address
alias proxyport proxy_port
def proxy?
true
end
private
def conn_socket( addr, port )
super @proxy_address, @proxy_port
end
def edit_path( path )
'http://' + addr_port + path
end
end # module ProxyMod
#
# for backward compatibility
#
@@newimpl = true @@newimpl = true
@ -300,23 +361,26 @@ module Net
end end
### #
### http operations # http operations
### #
def self.defrequest( nm, hasdest, hasdata ) public
def self.def_http_method( nm, hasdest, hasdata )
name = nm.id2name.downcase name = nm.id2name.downcase
cname = nm.id2name cname = nm.id2name
lineno = __LINE__ + 2 lineno = __LINE__ + 2
src = <<S src = <<" ----"
def #{name}( path, #{hasdata ? 'data,' : ''} def #{name}( path, #{hasdata ? 'data,' : ''}
u_header = nil #{hasdest ? ',dest = nil, &block' : ''} ) u_header = nil #{hasdest ? ',dest = nil, &block' : ''} )
resp = #{name}2( path, resp = nil
#{hasdata ? 'data,' : ''} request(
u_header ) {|resp| #{cname}.new( path, u_header ) #{hasdata ? ',data' : ''}
) do |resp|
resp.read_body( #{hasdest ? 'dest, &block' : ''} ) resp.read_body( #{hasdest ? 'dest, &block' : ''} )
} end
if @newimpl then if @newimpl then
resp resp
else else
@ -326,78 +390,63 @@ module Net
end end
def #{name}2( path, #{hasdata ? 'data,' : ''} def #{name}2( path, #{hasdata ? 'data,' : ''}
u_header = nil ) u_header = nil, &block )
new_#{name}( path, u_header ) do |req| request( #{cname}.new(path, u_header),
resp = req.dispatch#{hasdata ? '(data)' : ''} #{hasdata ? 'data,' : ''} &block )
yield resp if block_given?
end
end end
----
def new_#{name}( path, u_header = nil, &block ) #puts src
common_oper ::Net::NetPrivate::#{cname}, path, u_header, &block
end
S
# puts src
module_eval src, __FILE__, lineno module_eval src, __FILE__, lineno
end end
def_http_method :Get, true, false
def_http_method :Head, false, false
def_http_method :Post, true, true
def_http_method :Put, false, true
defrequest :Get, true, false def request( req, *args )
defrequest :Head, false, false common_oper( req ) {
defrequest :Post, true, true req.__send__( :exec,
defrequest :Put, false, true @socket, @curr_http_version,
edit_path(req.path),
header_defaults, *args )
yield req.response if block_given?
}
req.response
end
private private
def initialize( addr = nil, port = nil ) def common_oper( req )
super
@command = ::Net::NetPrivate::Switch.new
@curr_http_version = HTTPVersion
end
def connect( addr = @address, port = @port )
@socket = type.socket_type.open( addr, port, @pipe )
end
def disconnect
if @socket and not @socket.closed? then
@socket.close
end
@socket = nil
end
def do_finish
end
def common_oper( reqc, path, u_header )
req = nil
@command.on
if not @socket then if not @socket then
start start
req['connection'] = 'close'
elsif @socket.closed? then elsif @socket.closed? then
@socket.reopen @socket.reopen
end end
if @seems_1_0_server then
req['connection'] = 'close'
end
req = reqc.new( @curr_http_version, yield req
@socket, inihead, req.response.__send__ :terminate
edit_path(path), u_header )
yield req if block_given?
req.terminate
@curr_http_version = req.http_version @curr_http_version = req.http_version
unless keep_alive? req, req.response then if keep_alive? req, req.response then
if @socket.closed? then
@seems_1_0_server = true
@socket.close
end
else
@socket.close @socket.close
end end
@command.off
req.response req.response
end end
def inihead def header_defaults
h = {} h = {}
h['Host'] = addr_port h['Host'] = addr_port
h['Connection'] = 'Keep-Alive' h['Connection'] = 'Keep-Alive'
@ -427,89 +476,16 @@ S
false false
end end
def addr_port
address + (port == HTTP.port ? '' : ":#{port}")
end
end end
HTTPSession = HTTP HTTPSession = HTTP
module NetPrivate
class Switch
def initialize
@critical = false
end
def critical?
@critical
end
def on
@critical = true
end
def off
@critical = false
end
end
module HTTPProxy
class << self
def create_proxy_class( p_addr, p_port )
klass = Class.new( HTTP )
klass.module_eval {
include HTTPProxy
@proxy_address = p_addr
@proxy_port = p_port
}
def klass.proxy_class?
true
end
def klass.proxy_address
@proxy_address
end
def klass.proxy_port
@proxy_port
end
klass
end
end
def initialize( addr, port )
super
@proxy_address = type.proxy_address
@proxy_port = type.proxy_port
end
attr_reader :proxy_address, :proxy_port
alias proxyaddr proxy_address
alias proxyport proxy_port
def proxy?
true
end
def connect( addr = nil, port = nil )
super @proxy_address, @proxy_port
end
def edit_path( path )
'http://' + addr_port + path
end
end
end # net private
class Code class Code
def http_mkchild( bodyexist = nil ) def http_mkchild( bodyexist = nil )
@ -575,8 +551,7 @@ S
module NetPrivate class HTTP
### ###
### request ### request
@ -584,15 +559,10 @@ S
class HTTPRequest class HTTPRequest
def initialize( httpver, sock, inith, path, uhead ) def initialize( path, uhead = nil )
@http_version = httpver
@socket = sock
@path = path @path = path
@response = nil @u_header = tmp = {}
@u_header = inith
return unless uhead return unless uhead
tmp = {}
uhead.each do |k,v| uhead.each do |k,v|
key = canonical(k) key = canonical(k)
if tmp.key? key then if tmp.key? key then
@ -600,13 +570,17 @@ S
end end
tmp[ key ] = v.strip tmp[ key ] = v.strip
end end
@u_header.update tmp
@socket = nil
@response = nil
@http_version = nil
end end
attr_reader :http_version public
attr_reader :path attr_reader :path
attr_reader :response attr_reader :response
attr_reader :http_version
def inspect def inspect
"\#<#{type}>" "\#<#{type}>"
@ -640,38 +614,42 @@ S
@u_header.each_value( &block ) @u_header.each_value( &block )
end end
private
def terminate
@response.terminate
end
private
def canonical( k ) def canonical( k )
k.split('-').collect {|i| i.capitalize }.join('-') k.split('-').collect {|i| i.capitalize }.join('-')
end end
#
# write
#
# write request & header def exec( sock, ver, path, ihead )
ready( sock, ihead ) {|header|
def do_dispatch request ver, path, header
if @response then }
raise IOError, "#{type}\#dispatch called twice"
end
yield
@response = read_response
end end
def request( req ) def ready( sock, ihead )
@socket.writeline req @response = nil
@u_header.each do |n,v| @socket = sock
ihead.update @u_header
yield ihead
@response = read_response
@sock = nil
end
def request( ver, path, header )
@socket.writeline sprintf('%s %s HTTP/%s', type::METHOD, path, ver)
header.each do |n,v|
@socket.writeline n + ': ' + v @socket.writeline n + ': ' + v
end end
@socket.writeline '' @socket.writeline ''
end end
# read response & header #
# read
#
def read_response def read_response
resp = rdresp0 resp = rdresp0
@ -683,13 +661,12 @@ S
resp = get_resline resp = get_resline
while true do while true do
line = @socket.readline line = @socket.readuntil( "\n", true ) # ignore EOF
line.sub!( /\s+\z/, '' ) # don't use chop!
break if line.empty? break if line.empty?
m = /\A([^:]+):\s*/.match( line ) m = /\A([^:]+):\s*/.match( line )
unless m then m or raise HTTPBadResponse, 'wrong header line format'
raise HTTPBadResponse, 'wrong header line format'
end
nm = m[1] nm = m[1]
line = m.post_match line = m.post_match
if resp.key? nm then if resp.key? nm then
@ -704,7 +681,7 @@ S
def get_resline def get_resline
str = @socket.readline str = @socket.readline
m = /\AHTTP\/(\d+\.\d+)?\s+(\d\d\d)\s*(.*)\z/i.match( str ) m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/i.match( str )
unless m then unless m then
raise HTTPBadResponse, "wrong status line: #{str}" raise HTTPBadResponse, "wrong status line: #{str}"
end end
@ -712,52 +689,32 @@ S
status = m[2] status = m[2]
discrip = m[3] discrip = m[3]
HTTPResponse.new( status, discrip, @socket, type::HAS_BODY ) ::Net::NetPrivate::HTTPResponse.new(
status, discrip, @socket, type::HAS_BODY )
end end
end end
class Get < HTTPRequest
HAS_BODY = true
def dispatch
do_dispatch {
request sprintf('GET %s HTTP/%s', @path, @http_version)
}
end
end
class Head < HTTPRequest
HAS_BODY = false
def dispatch
do_dispatch {
request sprintf('HEAD %s HTTP/%s', @path, @http_version)
}
end
end
class HTTPRequestWithData < HTTPRequest class HTTPRequestWithData < HTTPRequest
def dispatch( str = nil ) private
def exec( sock, ver, path, ihead, str = nil )
check_arg str, block_given? check_arg str, block_given?
if block_given? then if block_given? then
ac = Accumulator.new ac = Accumulator.new
yield ac # must be yield, not block.call yield ac # must be yield, DO NOT USE block.call
data = ac.terminate data = ac.terminate
else else
data = str data = str
end end
do_dispatch { ready( sock, ihead ) {|header|
@u_header['Content-Length'] = data.size.to_s header['Content-Length'] = data.size.to_s
@u_header.delete 'Transfer-Encoding' header.delete 'Transfer-Encoding'
request sprintf('%s %s HTTP/%s', type::METHOD, @path, @http_version) request ver, path, header
@socket.write data @socket.write data
} }
end end
@ -773,21 +730,6 @@ S
end end
class Post < HTTPRequestWithData
HAS_BODY = true
METHOD = 'POST'
end
class Put < HTTPRequestWithData
HAS_BODY = true
METHOD = 'PUT'
end
class Accumulator class Accumulator
@ -810,6 +752,31 @@ S
end end
class Get < HTTPRequest
HAS_BODY = true
METHOD = 'GET'
end
class Head < HTTPRequest
HAS_BODY = false
METHOD = 'HEAD'
end
class Post < HTTPRequestWithData
HAS_BODY = true
METHOD = 'POST'
end
class Put < HTTPRequestWithData
HAS_BODY = true
METHOD = 'PUT'
end
end # HTTP::
module NetPrivate
### ###
### response ### response
@ -926,7 +893,9 @@ S
end end
#
# header (for backward compatibility) # header (for backward compatibility)
#
def read_header def read_header
self self
@ -935,8 +904,9 @@ S
alias header read_header alias header read_header
alias response read_header alias response read_header
#
# body # body
#
def read_body( dest = nil, &block ) def read_body( dest = nil, &block )
if @read and (dest or block) then if @read and (dest or block) then
@ -963,22 +933,20 @@ S
alias entity read_body alias entity read_body
# internal use only private
def terminate def terminate
read_body read_body
end end
private
def read_body_0( dest ) def read_body_0( dest )
if chunked? then if chunked? then
read_chunked dest read_chunked dest
else else
clen = content_length clen = content_length
if clen then if clen then
@socket.read clen, dest @socket.read clen, dest, true # ignore EOF
else else
clen = range_length clen = range_length
if clen then if clen then
@ -1066,6 +1034,18 @@ S
end end
class Dummy
def initialize( *args )
end
def critical?
false
end
end
end # module Net::NetPrivate end # module Net::NetPrivate
end # module Net end # module Net

View file

@ -65,7 +65,7 @@ Net::Protocol
m.pop file m.pop file
end end
=== Methods === Instance Methods
: start( account, password ) : start( account, password )
: start( account, password ) {|pop| .... } : start( account, password ) {|pop| .... }

View file

@ -15,7 +15,7 @@ You can get it from RAA
== Net::Protocol == Net::Protocol
the abstract class for Internet protocol the abstract class for some internet protocols
=== Super Class === Super Class
@ -59,6 +59,7 @@ Object
=end =end
require 'socket' require 'socket'
require 'timeout'
module Net module Net
@ -116,8 +117,12 @@ module Net
@command = nil @command = nil
@socket = nil @socket = nil
@active = false @active = false
@pipe = nil
@open_timeout = nil
@read_timeout = nil
@pipe = nil
end end
attr_reader :address attr_reader :address
@ -126,10 +131,24 @@ module Net
attr_reader :command attr_reader :command
attr_reader :socket attr_reader :socket
attr_accessor :open_timeout
attr_accessor :read_timeout
def active?
@active
end
def set_pipe( arg ) # un-documented
@pipe = arg
end
def inspect def inspect
"#<#{type} #{address}:#{port} open=#{active?}>" "#<#{type} #{address}:#{port} open=#{active?}>"
end end
#
# open session
#
def start( *args ) def start( *args )
return false if active? return false if active?
@ -146,53 +165,66 @@ module Net
end end
end end
private
def _start( args ) def _start( args )
connect connect
do_start( *args ) do_start( *args )
@active = true @active = true
end end
private :_start
def connect
conn_socket @address, @port
conn_command @socket
on_connect
end
def conn_socket( addr, port )
@socket = type.socket_type.open(
addr, port, @open_timeout, @read_timeout, @pipe )
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 def finish
return false unless active? return false unless active?
do_finish unless @command.critical? do_finish if @command and not @command.critical?
disconnect disconnect
@active = false @active = false
true true
end end
def active?
@active
end
def set_pipe( arg ) # un-documented
@pipe = arg
end
private private
def do_start
end
def do_finish def do_finish
@command.quit @command.quit
end end
def connect( addr = @address, port = @port )
@socket = type.socket_type.open( addr, port, @pipe )
@command = type.command_type.new( @socket )
end
def disconnect def disconnect
@command = nil @command = nil
if @socket and not @socket.closed? then if @socket and not @socket.closed? then
@socket.close @socket.close
end end
@socket = nil @socket = nil
on_disconnect
end
def on_disconnect
end end
end end
@ -311,6 +343,7 @@ module Net
def write( str ) def write( str )
@sock.__send__ @mid, str @sock.__send__ @mid, str
end end
alias << write alias << write
end end
@ -407,6 +440,7 @@ module Net
@critical = false @critical = false
end end
private private
def critical def critical
@ -431,9 +465,12 @@ module Net
class Socket class Socket
def initialize( addr, port, pipe = nil ) def initialize( addr, port, otime = nil, rtime = nil, pipe = nil )
@addr = addr @addr = addr
@port = port @port = port
@read_timeout = rtime
@pipe = pipe @pipe = pipe
@prepipe = nil @prepipe = nil
@ -442,7 +479,9 @@ module Net
@sending = '' @sending = ''
@buffer = '' @buffer = ''
@socket = TCPsocket.new( addr, port ) timeout( otime ) {
@socket = TCPsocket.new( addr, port )
}
@closed = false @closed = false
@ipaddr = @socket.addr[3] @ipaddr = @socket.addr[3]
end end
@ -494,13 +533,15 @@ module Net
attr_reader :sending attr_reader :sending
### #
### read # read
### #
public
CRLF = "\r\n" CRLF = "\r\n"
def read( len, dest = '' ) def read( len, dest = '', ignerr = false )
@pipe << "reading #{len} bytes...\n" if @pipe; pipeoff @pipe << "reading #{len} bytes...\n" if @pipe; pipeoff
rsize = 0 rsize = 0
@ -509,16 +550,15 @@ module Net
rsize += writeinto( dest, @buffer.size ) rsize += writeinto( dest, @buffer.size )
fill_rbuf fill_rbuf
end end
writeinto( dest, len - rsize )
rescue EOFError rescue EOFError
len = rsize raise unless igneof
end end
writeinto( dest, len - rsize )
@pipe << "read #{len} bytes\n" if pipeon @pipe << "read #{len} bytes\n" if pipeon
dest dest
end end
def read_all( dest = '' ) def read_all( dest = '' )
@pipe << "reading all...\n" if @pipe; pipeoff @pipe << "reading all...\n" if @pipe; pipeoff
@ -536,8 +576,7 @@ module Net
dest dest
end end
def readuntil( target, igneof = false )
def readuntil( target )
dest = '' dest = ''
begin begin
while true do while true do
@ -547,19 +586,18 @@ module Net
end end
writeinto( dest, idx + target.size ) writeinto( dest, idx + target.size )
rescue EOFError rescue EOFError
raise unless igneof
writeinto( dest, @buffer.size ) writeinto( dest, @buffer.size )
end end
dest dest
end end
def readline def readline
ret = readuntil( "\n" ) ret = readuntil( "\n" )
ret.chop! ret.chop!
ret ret
end end
def read_pendstr( dest ) def read_pendstr( dest )
@pipe << "reading text...\n" if @pipe; pipeoff @pipe << "reading text...\n" if @pipe; pipeoff
@ -574,7 +612,6 @@ module Net
dest dest
end end
# private use only (can not handle 'break') # private use only (can not handle 'break')
def read_pendlist def read_pendlist
@pipe << "reading list...\n" if @pipe; pipeoff @pipe << "reading list...\n" if @pipe; pipeoff
@ -594,10 +631,17 @@ module Net
private private
READ_BLOCK = 1024 * 8 READ_SIZE = 1024 * 4
def fill_rbuf def fill_rbuf
@buffer << @socket.sysread( READ_BLOCK ) 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 end
def writeinto( dest, len ) def writeinto( dest, len )
@ -610,20 +654,18 @@ module Net
end end
### #
### write # write interfece
### #
public public
def write( str ) def write( str )
writing { writing {
do_write str do_write str
} }
end end
def writeline( str ) def writeline( str )
writing { writing {
do_write str do_write str
@ -631,7 +673,6 @@ module Net
} }
end end
def write_bin( src, block ) def write_bin( src, block )
writing { writing {
if block then if block then
@ -644,7 +685,6 @@ module Net
} }
end end
def write_pendstr( src, block ) def write_pendstr( src, block )
@pipe << "writing text from #{src.type}\n" if @pipe; pipeoff @pipe << "writing text from #{src.type}\n" if @pipe; pipeoff

View file

@ -30,10 +30,8 @@ Net::Protocol
=== Methods === Methods
: start( helo_domain = Socket.gethostname, \ : start( helo_domain = Socket.gethostname, account = nil, password = nil, authtype = nil )
account = nil, password = nil, authtype = nil ) : start( helo_domain = Socket.gethostname, account = nil, password = nil, authtype = nil ) {|smtp| .... }
: start( helo_domain = Socket.gethostname, \
account = nil, password = nil, authtype = nil ) {|smtp| .... }
opens TCP connection and starts SMTP session. opens TCP connection and starts SMTP session.
If protocol had been started, do nothing and return false. If protocol had been started, do nothing and return false.
@ -53,10 +51,10 @@ Net::Protocol
to_addrs must be a String(s) or an Array of String. to_addrs must be a String(s) or an Array of String.
Exceptions which SMTP raises are: Exceptions which SMTP raises are:
* Net::ProtoSyntaxError: syntax error (errno.500) * Net::ProtoSyntaxError: syntax error (errno.500)
* Net::ProtoFatalError: fatal error (errno.550) * Net::ProtoFatalError: fatal error (errno.550)
* Net::ProtoUnknownError: unknown error * Net::ProtoUnknownError: unknown error
* Net::ProtoServerBusy: temporary error (errno.420/450) * Net::ProtoServerBusy: temporary error (errno.420/450)
# usage example # usage example