From 1d2c192d35dcaa0483eebd891fddef0e972c8a59 Mon Sep 17 00:00:00 2001 From: "(no author)" <(no author)@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> Date: Thu, 24 May 2001 07:13:56 +0000 Subject: This commit was manufactured by cvs2svn to create tag 'v1_6_4_preview3'. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/tags/v1_6_4_preview3@1446 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- lib/net/http.rb | 1118 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 635 insertions(+), 483 deletions(-) (limited to 'lib/net/http.rb') diff --git a/lib/net/http.rb b/lib/net/http.rb index 3900ed6c68..26e5285525 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1,8 +1,9 @@ =begin -= net/http.rb version 1.1.32 += net/http.rb version 1.1.34 + +written by Minero Aoki -maintained by Minero Aoki This file is derived from "http-access.rb". This program is free software. @@ -14,9 +15,34 @@ You can get it from RAA (Ruby Application Archive: http://www.ruby-lang.org/en/raa.html). -= class HTTP +== http.rb version 1.2 features + +You can use 1.2 features by calling HTTP.version_1_2. And +calling Net::HTTP.version_1_1 allows to use 1.1 features. + + # example + HTTP.start {|http1| ...(http1 has 1.1 features)... } + + HTTP.version_1_2 + HTTP.start {|http2| ...(http2 has 1.2 features)... } + + HTTP.version_1_1 + HTTP.start {|http3| ...(http3 has 1.1 features)... } + +Changes are: -== Class Methods + * HTTP#get, head, post does not raise ProtocolError + * HTTP#get, head, post returns only one object, a HTTPResponse object + * HTTPResponseReceiver is joined into HTTPResponse + * request object: HTTP::Get, Head, Post; and HTTP#request(req) + +WARNING: These features are not definite yet. +They will change without notice! + + +== class HTTP + +=== Class Methods : new( address = 'localhost', port = 80, proxy_addr = nil, proxy_port = nil ) creates a new Net::HTTP object. @@ -49,7 +75,7 @@ You can get it from RAA HTTP default port (80). -== Methods +=== Methods : start : start {|http| .... } @@ -72,7 +98,7 @@ You can get it from RAA get data from "path" on connecting host. "header" must be a Hash like { 'Accept' => '*/*', ... }. Data is written to "dest" by using "<<" method. - This method returns Net::HTTPResponse object, and "dest". + This method returns HTTPResponse object, and "dest". # example response, body = http.get( '/index.html' ) @@ -95,7 +121,7 @@ You can get it from RAA : head( path, header = nil ) gets only header from "path" on connecting host. "header" is a Hash like { 'Accept' => '*/*', ... }. - This method returns a Net::HTTPResponse object. + This method returns a HTTPResponse object. You can http header from this object like: response['content-length'] #-> '2554' @@ -109,7 +135,7 @@ You can get it from RAA If the body exists, also gets entity body. Data is written to "dest" by using "<<" method. "header" must be a Hash like { 'Accept' => '*/*', ... }. - This method returns Net::HTTPResponse object and "dest". + This method returns HTTPResponse object and "dest". If called with block, gives a part of entity body string. @@ -181,21 +207,21 @@ You can get it from RAA response.body -= class HTTPResponse +== class HTTPResponse HTTP response object. All "key" is case-insensitive. -== Methods +=== Methods : body - the entity body. ("dest" argument for HTTP#get, post, put) + the entity body (String). : self[ key ] returns header field for "key". for HTTP, value is a string like 'text/plain'(for Content-Type), - '2045'(for Content-Length), 'bytes 0-1024/10024'(for Content-Range). - Multiple header had be joined by HTTP1.1 scheme. + '2045'(for Content-Length), 'bytes 0-1023/10024'(for Content-Range). + If there's some fields which has same name, they are joined with ','. : self[ key ] = val set field value for "key". @@ -204,88 +230,92 @@ All "key" is case-insensitive. true if key exists : each {|name,value| .... } - iterates for each field name and value pair + iterates for each field name and value pair. : code - HTTP result code string. For example, '302' + HTTP result code string. For example, '302'. : message - HTTP result message. For example, 'Not Found' + HTTP result message. For example, 'Not Found'. -= class HTTPResponseReceiver +== class HTTPResponseReceiver -== Methods +=== Methods : header : response - Net::HTTPResponse object + HTTPResponse object -: body( dest = '' ) -: entity( dest = '' ) - entity body. A body is written to "dest" using "<<" method. +: read_body( dest = '' ) + reads entity body into DEST by calling "<<" method and + returns DEST. -: body {|str| ... } - gets entity body with block. - If this method is called twice, block is not executed and - returns first "dest". +: read_body {|string| ... } + reads entity body little by little and gives it to block + until entity ends. +: body +: entity + entity body. If #read_body is called already, returns its + argument DEST. Else returns entity body as String. -= http.rb version 1.2 features + Calling this method any times causes returning same + object (does not read entity again). -You can use 1.2 features by calling HTTP.version_1_2. And -calling Net::HTTP.version_1_1 allows to use 1.1 features. +=end - # example - HTTP.start {|http1| ...(http1 has 1.1 features)... } +require 'net/protocol' - HTTP.version_1_2 - HTTP.start {|http2| ...(http2 has 1.2 features)... } - HTTP.version_1_1 - HTTP.start {|http3| ...(http3 has 1.1 features)... } +module Net -== Method (only diff to 1.1) + class HTTPBadResponse < StandardError; end + class HTTPHeaderSyntaxError < StandardError; end -: get( path, u_header = nil ) -: get( path, u_header = nil ) {|str| .... } - gets document from "path". - returns HTTPResponse object. -: head( path, u_header = nil ) - gets only document header from "path". - returns HTTPResponse object. + class HTTP < Protocol -: post( path, data, u_header = nil ) -: post( path, data, u_header = nil ) {|str| .... } - posts "data" to "path" entity and gets document. - returns HTTPResponse object. + HTTPVersion = '1.1' -=end + # + # connection + # -require 'net/protocol' + protocol_param :port, '80' -module Net + def initialize( addr = nil, port = nil ) + super - class HTTPBadResponse < StandardError; end + @proxy_address = nil + @proxy_port = nil + @curr_http_version = HTTPVersion + @seems_1_0_server = false + end - class HTTP < Protocol - protocol_param :port, '80' - protocol_param :command_type, '::Net::NetPrivate::HTTPCommand' + private + def conn_command( sock ) + end + + def do_finish + end + + + # + # proxy + # + + public - ### - ### proxy - ### 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 @@ -293,7 +323,7 @@ module Net def new( address = nil, port = nil, p_addr = nil, p_port = nil ) c = p_addr ? self::Proxy(p_addr, p_port) : self i = c.orig_new( address, port ) - setimplv i + setvar i i end @@ -332,385 +362,230 @@ module Net end - ### - ### 1.2 implementation - ### + module ProxyMod - @@newimpl = false + class << self - #class << self + def create_proxy_class( p_addr, p_port ) + mod = self + klass = Class.new( HTTP ) + klass.module_eval { + include mod + @proxy_address = p_addr + @proxy_port = p_port + } + def klass.proxy_class? + true + end - def self.version_1_2 - @@newimpl = true - end + def klass.proxy_address + @proxy_address + end - def self.version_1_1 - @@newimpl = false - end + def klass.proxy_port + @proxy_port + end - #private + klass + end - def self.setimplv( obj ) - f = @@newimpl - obj.instance_eval { @newimpl = f } end - #end - - - ### - ### http operations - ### - - def get( path, u_header = nil, dest = nil, &block ) - resp = get2( path, u_header ) {|f| f.body( dest, &block ) } - if @newimpl then - resp - else - resp.value - return resp, resp.body + def initialize( addr, port ) + super + @proxy_address = type.proxy_address + @proxy_port = type.proxy_port end - end - - def get2( path, u_header = nil, &block ) - common_oper( u_header, true, block ) {|uh| - @command.get edit_path(path), uh - } - end + + attr_reader :proxy_address, :proxy_port + alias proxyaddr proxy_address + alias proxyport proxy_port - def head( path, u_header = nil ) - resp = head2( path, u_header ) - unless @newimpl then - resp.value + def proxy? + true end - resp - end - - def head2( path, u_header = nil, &block ) - common_oper( u_header, false, block ) {|uh| - @command.head edit_path(path), uh - } - end - - - def post( path, data, u_header = nil, dest = nil, &block ) - resp = post2( path, data, u_header ) {|f| f.body( dest, &block ) } - if @newimpl then - resp - else - resp.value - return resp, resp.body + + private + + def conn_socket( addr, port ) + super @proxy_address, @proxy_port end - end - - def post2( path, data, u_header = nil, &block ) - common_oper( u_header, true, block ) {|uh| - @command.post edit_path(path), uh, data - } - end - - # not tested because I could not setup apache (__;;; - def put( path, src, u_header = nil ) - resp = put2( path, src, u_header ) {|f| f.body } - if @newimpl then - resp - else - resp.value - return resp, resp.body + def edit_path( path ) + 'http://' + addr_port + path end - end + + end # module ProxyMod - def put2( path, src, u_header = nil, &block ) - common_oper( u_header, true, block ) {|uh| - @command.put path, uh, src - } - end - - - private - - - def common_oper( u_header, body_exist, block ) - header = procheader( u_header ) - recv = err = nil - - connecting( header ) { - recv = HTTPResponseReceiver.new( @command, body_exist ) - yield header - begin - block.call recv if block - rescue Exception => err - ; - end - recv.terminate - - recv.response - } - raise err if err - recv.response - end + # + # for backward compatibility + # - def connecting( header ) - if not @socket then - header['Connection'] = 'close' - start - elsif @socket.closed? then - @socket.reopen - end + @@newimpl = false - resp = yield + class << self - unless keep_alive? header, resp then - @socket.close + def version_1_2 + @@newimpl = true end - end - def keep_alive?( header, resp ) - if resp.key? 'connection' then - if /keep-alive/i === resp['connection'] then - return true - end - elsif resp.key? 'proxy-connection' then - if /keep-alive/i === resp['proxy-connection'] then - return true - end - elsif header.key? 'Connection' then - if /keep-alive/i === header['Connection'] then - return true - end - else - if @command.http_version == '1.1' then - return true - end + def version_1_1 + @@newimpl = false end - false - end - - def procheader( h ) - ret = {} - ret[ 'Host' ] = address + - ((port == HTTP.port) ? '' : ":#{port}") - ret[ 'Connection' ] = 'Keep-Alive' - ret[ 'Accept' ] = '*/*' + private - return ret unless h - tmp = {} - h.each do |k,v| - key = k.split('-').collect {|i| i.capitalize }.join('-') - if tmp[key] then - $stderr.puts "'#{key}' http header appered twice" if $VERBOSE - end - tmp[key] = v + def setvar( obj ) + f = @@newimpl + obj.instance_eval { @newimpl = f } end - ret.update tmp - ret end - def do_finish - end + # + # http operations + # - end - - HTTPSession = HTTP - - - module NetPrivate - - 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 + public - def klass.proxy_address - @proxy_address + def self.def_http_method( nm, hasdest, hasdata ) + name = nm.id2name.downcase + cname = nm.id2name + lineno = __LINE__ + 2 + src = <<" ----" + + def #{name}( path, #{hasdata ? 'data,' : ''} + u_header = nil #{hasdest ? ',dest = nil, &block' : ''} ) + resp = nil + request( + #{cname}.new( path, u_header ) #{hasdata ? ',data' : ''} + ) do |resp| + resp.read_body( #{hasdest ? 'dest, &block' : ''} ) + end + if @newimpl then + resp + else + resp.value + #{hasdest ? 'return resp, resp.body' : 'resp'} + end end - def klass.proxy_port - @proxy_port + def #{name}2( path, #{hasdata ? 'data,' : ''} + u_header = nil, &block ) + request( #{cname}.new(path, u_header), + #{hasdata ? 'data,' : ''} &block ) end - - klass - end - - end - - - def initialize( addr, port ) - super - @proxy_address = type.proxy_address - @proxy_port = type.proxy_port + ---- + module_eval src, __FILE__, lineno end - attr_reader :proxy_address, :proxy_port + def_http_method :Get, true, false + def_http_method :Head, false, false + def_http_method :Post, true, true + def_http_method :Put, false, true - 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://' + address + (port == type.port ? '' : ":#{port}") + path + def request( req, *args ) + common_oper( req ) { + req.__send__( :exec, + @socket, @curr_http_version, edit_path(req.path), *args ) + yield req.response if block_given? + } + req.response end - - end - - end # net private + private - class HTTPResponseReceiver - - def initialize( command, body_exist ) - @command = command - @body_exist = body_exist - @header = @body = nil - end - - def inspect - "#<#{type}>" - end - def read_header - unless @header then - stream_check - @header = @command.get_response + def common_oper( req ) + req['connection'] ||= 'keep-alive' + if not @socket then + start + req['connection'] = 'close' + elsif @socket.closed? then + re_connect end - @header - end - - alias header read_header - alias response read_header - - def read_body( dest = nil, &block ) - unless @body then - read_header - - to = procdest( dest, block ) - stream_check - - if @body_exist and @header.code_type.body_exist? then - @command.get_body @header, to - @header.body = @body = to - else - @command.no_body - @header.body = nil - @body = 1 - end + if not req.body_exist? or @seems_1_0_server then + req['connection'] = 'close' end - @body == 1 ? nil : @body - end - - alias body read_body - alias entity read_body + req['host'] = addr_port - def terminate - read_header - read_body - @command = nil - end + yield req + req.response.__send__ :terminate + @curr_http_version = req.response.http_version - - private - - def stream_check - unless @command then - raise IOError, 'receiver was used out of block' - end - end - - def procdest( dest, block ) - if dest and block then - raise ArgumentError, - 'both of arg and block are given for HTTP method' - end - if block then - NetPrivate::ReadAdapter.new block + if not req.response.body then + @socket.close + elsif keep_alive? req, req.response then + D 'Conn keep-alive' + if @socket.closed? then # (only) read stream had been closed + D 'Conn (but seems 1.0 server)' + @seems_1_0_server = true + @socket.close + end else - dest or '' + D 'Conn close' + @socket.close end - end - - end - - HTTPReadAdapter = HTTPResponseReceiver - - class HTTPResponse < Response - - def initialize( code_type, code, msg ) - super - @data = {} - @body = nil + req.response end - attr_accessor :body + def keep_alive?( req, res ) + /close/i === req['connection'].to_s and return false + @seems_1_0_server and return false - def inspect - "#<#{type.name} #{code}>" - end + /keep-alive/i === res['connection'].to_s and return true + /close/i === res['connection'].to_s and return false + /keep-alive/i === res['proxy-connection'].to_s and return true + /close/i === res['proxy-connection'].to_s and return false - def []( key ) - @data[ key.downcase ] + @curr_http_version == '1.1' and return true + false end - def []=( key, val ) - @data[ key.downcase ] = val - end - def each( &block ) - @data.each( &block ) - end + # + # utils + # - def each_key( &block ) - @data.each_key( &block ) - end + public - def each_value( &block ) - @data.each_value( &block ) + def self.get( addr, path, port = nil ) + req = Get.new( path ) + resp = nil + new( addr, port || HTTP.port ).start {|http| + resp = http.request( req ) + } + resp.body end - def delete( key ) - @data.delete key.downcase + def self.get_print( addr, path, port = nil ) + print get( addr, path, port ) end - def key?( key ) - @data.key? key.downcase - end - def to_hash - @data.dup + private + + def addr_port + address + (port == HTTP.port ? '' : ":#{port}") end - def value - unless SuccessCode === self then - error! self + def D( msg ) + if @dout then + @dout << msg + @dout << "\n" end end end + HTTPSession = HTTP + + class Code @@ -776,101 +651,246 @@ module Net HTTPVersionNotSupported = HTTPServerErrorCode.http_mkchild - module NetPrivate + ### + ### header + ### - class HTTPCommand < Command + net_private { - HTTPVersion = '1.1' + module HTTPHeader - def initialize( sock ) - @http_version = HTTPVersion - super sock + def size + @header.size end - attr_reader :http_version + alias length size - def inspect - "#" + def []( key ) + @header[ key.downcase ] end + def []=( key, val ) + @header[ key.downcase ] = val + end - ### - ### request - ### + def each( &block ) + @header.each( &block ) + end - public + def each_key( &block ) + @header.each_key( &block ) + end - def get( path, u_header ) - return unless begin_critical - request sprintf('GET %s HTTP/%s', path, HTTPVersion), u_header + def each_value( &block ) + @header.each_value( &block ) end - - def head( path, u_header ) - return unless begin_critical - request sprintf('HEAD %s HTTP/%s', path, HTTPVersion), u_header + + def delete( key ) + @header.delete key.downcase + end + + def key?( key ) + @header.key? key.downcase + end + + def to_hash + @header.dup end - def post( path, u_header, data ) - return unless begin_critical - u_header[ 'Content-Length' ] = data.size.to_s - request sprintf('POST %s HTTP/%s', path, HTTPVersion), u_header - @socket.write data + def canonical_each + @header.each do |k,v| + yield canonical(k), v + end end - def put( path, u_header, src ) - return unless begin_critical - request sprintf('PUT %s HTTP/%s', path, HTTPVersion), u_header - @socket.write_bin src + def canonical( k ) + k.split('-').collect {|i| i.capitalize }.join('-') end - # def delete + def range + s = @header['range'] + s or return nil - # def trace + arr = [] + s.split(',').each do |spec| + m = /bytes\s*=\s*(\d+)?\s*-\s*(\d+)?/i.match( spec ) + m or raise HTTPHeaderSyntaxError, "wrong Range: #{spec}" - # def options + d1 = m[1].to_i + d2 = m[2].to_i + if m[1] and m[2] then arr.push (d1 .. d2) + elsif m[1] then arr.push (d1 .. -1) + elsif m[2] then arr.push (-d2 .. -1) + else + raise HTTPHeaderSyntaxError, 'range is not specified' + end + end - def quit + return *arr end + def range=( r, fin = nil ) + if fin then + r = r ... r+fin + end - private + case r + when Numeric + s = r > 0 ? "0-#{r - 1}" : "-#{-r}" + when Range + first = r.first + last = r.last + if r.exclude_end? then + last -= 1 + end - def request( req, u_header ) - @socket.writeline req - u_header.each do |n,v| - @socket.writeline n + ': ' + v + if last == -1 then + s = first > 0 ? "#{first}-" : "-#{-first}" + else + first >= 0 or raise HTTPHeaderSyntaxError, 'range.first is negative' + last > 0 or raise HTTPHeaderSyntaxError, 'range.last is negative' + first < last or raise HTTPHeaderSyntaxError, 'must be .first < .last' + s = "#{first}-#{last}" + end + else + raise TypeError, 'Range/Integer is required' end - @socket.writeline '' + + @header['range'] = "bytes=#{s}" + r end + alias set_range range= - ### - ### response line & header - ### + def content_length + s = @header['content-length'] + s or return nil - public + m = /\d+/.match(s) + m or raise HTTPHeaderSyntaxError, 'wrong Content-Length format' + m[0].to_i + end - def get_response - resp = get_resp0 - resp = get_resp0 while ContinueCode === resp - resp + def chunked? + s = @header['transfer-encoding'] + (s and /(?:\A|[^\-\w])chunked(?:[^\-\w]|\z)/i === s) ? true : false + end + + def content_range + s = @header['content-range'] + s or return nil + + m = %ri.match( s ) + m or raise HTTPHeaderSyntaxError, 'wrong Content-Range format' + + m[1].to_i .. m[2].to_i + 1 + end + + def range_length + r = content_range + r and r.length + end + + def basic_auth( acc, pass ) + @header['authorization'] = + 'Basic ' + ["#{acc}:#{pass}"].pack('m').gsub(/\s+/, '') + end + + end + + } + + + ### + ### request + ### + + net_private { + + class HTTPRequest + + include ::Net::NetPrivate::HTTPHeader + + def initialize( path, uhead = nil ) + @path = path + @header = tmp = {} + return unless uhead + uhead.each do |k,v| + key = k.downcase + if tmp.key? key then + $stderr.puts "WARNING: duplicated HTTP header: #{k}" if $VERBOSE + end + tmp[ key ] = v.strip + end + tmp['accept'] ||= '*/*' + + @socket = nil + @response = nil + end + + attr_reader :path + attr_reader :response + + def inspect + "\#<#{type}>" + end + + def body_exist? + type::HAS_BODY end private - def get_resp0 - resp = get_reply + # + # write + # + + def exec( sock, ver, path ) + ready( sock ) { + request ver, path + } + @response + end + + def ready( sock ) + @response = nil + @socket = sock + yield + @response = get_response + @socket = nil + end + + def request( ver, path ) + @socket.writeline sprintf('%s %s HTTP/%s', type::METHOD, path, ver) + canonical_each do |k,v| + @socket.writeline k + ': ' + v + end + @socket.writeline '' + end + + # + # read + # + + def get_response + begin + resp = read_response + end while ContinueCode === resp + resp + end + + def read_response + 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 @@ -883,23 +903,117 @@ module Net resp end - def get_reply + def get_resline str = @socket.readline - m = /\AHTTP\/(\d+\.\d+)?\s+(\d\d\d)\s*(.*)\z/i.match( str ) - unless m then - raise HTTPBadResponse, "wrong status line: #{str}" - end - @http_version = m[1] + m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/i.match( str ) + m or raise HTTPBadResponse, "wrong status line: #{str}" + httpver = m[1] status = m[2] discrip = m[3] - code = HTTPCODE_TO_OBJ[status] || - HTTPCODE_CLASS_TO_OBJ[status[0,1]] || - UnknownCode - HTTPResponse.new( code, status, discrip ) + ::Net::NetPrivate::HTTPResponse.new( + status, discrip, @socket, type::HAS_BODY, httpver ) + end + + end + + + class HTTPRequestWithBody < HTTPRequest + + private + + def exec( sock, ver, path, str = nil ) + check_arg str, block_given? + + if block_given? then + ac = Accumulator.new + yield ac # must be yield, DO NOT USE block.call + data = ac.terminate + else + data = str + end + @header['content-length'] = data.size.to_s + @header.delete 'transfer-encoding' + + ready( sock ) { + request ver, path + @socket.write data + } + @response + end + + def check_arg( data, blkp ) + if data and blkp then + raise ArgumentError, 'both of data and block given' + end + unless data or blkp then + raise ArgumentError, 'str or block required' + end + end + + end + + + class Accumulator + + def initialize + @buf = '' + end + + def write( s ) + @buf.concat s + end + + alias << write + + def terminate + ret = @buf + @buf = nil + ret + end + + end + + } + + + class HTTP + + class Get < ::Net::NetPrivate::HTTPRequest + HAS_BODY = true + METHOD = 'GET' + end + + class Head < ::Net::NetPrivate::HTTPRequest + HAS_BODY = false + METHOD = 'HEAD' + end + + class Post < ::Net::NetPrivate::HTTPRequestWithBody + HAS_BODY = true + METHOD = 'POST' + end + + class Put < ::Net::NetPrivate::HTTPRequestWithBody + HAS_BODY = true + METHOD = 'PUT' end - HTTPCODE_CLASS_TO_OBJ = { + end + + + + ### + ### response + ### + + net_private { + + class HTTPResponse < Response + + include ::Net::NetPrivate::HTTPHeader + + CODE_CLASS_TO_OBJ = { '1' => HTTPInformationCode, '2' => HTTPSuccessCode, '3' => HTTPRedirectionCode, @@ -907,7 +1021,7 @@ module Net '5' => HTTPServerErrorCode } - HTTPCODE_TO_OBJ = { + CODE_TO_OBJ = { '100' => ContinueCode, '101' => HTTPSwitchProtocol, @@ -951,22 +1065,87 @@ module Net '505' => HTTPVersionNotSupported } + def initialize( stat, msg, sock, be, hv ) + code = CODE_TO_OBJ[stat] || + CODE_CLASS_TO_OBJ[stat[0,1]] || + UnknownCode + super code, stat, msg + @socket = sock + @body_exist = be + @http_version = hv - ### - ### body - ### + @header = {} + @body = nil + @read = false + end - public + attr_reader :http_version + + def inspect + "#<#{type} #{code}>" + end + + def value + SuccessCode === self or error! self + end - def get_body( resp, dest ) - if chunked? resp then + + # + # header (for backward compatibility) + # + + def read_header + self + end + + alias header read_header + alias response read_header + + # + # body + # + + def read_body( dest = nil, &block ) + if @read and (dest or block) then + raise IOError, "#{type}\#read_body called twice with argument" + end + + unless @read then + to = procdest( dest, block ) + stream_check + + if @body_exist and code_type.body_exist? then + read_body_0 to + @body = to + else + @body = nil + end + @read = true + end + + @body + end + + alias body read_body + alias entity read_body + + + private + + + def terminate + read_body + end + + def read_body_0( dest ) + if chunked? then read_chunked dest else - clen = content_length( resp ) + clen = content_length if clen then - @socket.read clen, dest + @socket.read clen, dest, true # ignore EOF else - clen = range_length( resp ) + clen = range_length if clen then @socket.read clen, dest else @@ -974,16 +1153,8 @@ module Net end end end - end_critical - end - - def no_body - end_critical end - - private - def read_chunked( dest ) len = nil total = 0 @@ -991,9 +1162,7 @@ module Net while true do line = @socket.readline m = /[0-9a-fA-F]+/.match( line ) - unless m then - raise HTTPBadResponse, "wrong chunk size line: #{line}" - end + m or raise HTTPBadResponse, "wrong chunk size line: #{line}" len = m[0].hex break if len == 0 @socket.read( len, dest ); total += len @@ -1004,44 +1173,27 @@ module Net end end - def content_length( resp ) - if resp.key? 'content-length' then - m = /\d+/.match( resp['content-length'] ) - unless m then - raise HTTPBadResponse, 'wrong Content-Length format' - end - m[0].to_i - else - nil - end + def stream_check + @socket.closed? and raise IOError, 'try to read body out of block' end - def chunked?( resp ) - tmp = resp['transfer-encoding'] - tmp and /(?:\A|\s+)chunked(?:\s+|\z)/i === tmp - end - - def range_length( resp ) - if resp.key? 'content-range' then - m = %r.match( resp['content-range'] ) - unless m then - raise HTTPBadResponse, 'wrong Content-Range format' - end - l = m[2].to_i - u = m[1].to_i - if l > u then - nil - else - u - l - end + def procdest( dest, block ) + if dest and block then + raise ArgumentError, 'both of arg and block are given for HTTP method' + end + if block then + ::Net::NetPrivate::ReadAdapter.new block else - nil + dest || '' end end end + } + - end # module Net::NetPrivate + HTTPResponse = NetPrivate::HTTPResponse + HTTPResponseReceiver = NetPrivate::HTTPResponse end # module Net -- cgit v1.2.3