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

View File

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

View File

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

View File

@ -30,10 +30,8 @@ Net::Protocol
=== Methods
: start( helo_domain = Socket.gethostname, \
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 )
: start( helo_domain = Socket.gethostname, account = nil, password = nil, authtype = nil ) {|smtp| .... }
opens TCP connection and starts SMTP session.
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.
Exceptions which SMTP raises are:
* Net::ProtoSyntaxError: syntax error (errno.500)
* Net::ProtoFatalError: fatal error (errno.550)
* Net::ProtoUnknownError: unknown error
* Net::ProtoServerBusy: temporary error (errno.420/450)
* Net::ProtoSyntaxError: syntax error (errno.500)
* Net::ProtoFatalError: fatal error (errno.550)
* Net::ProtoUnknownError: unknown error
* Net::ProtoServerBusy: temporary error (errno.420/450)
# usage example