diff options
Diffstat (limited to 'lib/net/http.rb')
-rw-r--r-- | lib/net/http.rb | 1473 |
1 files changed, 0 insertions, 1473 deletions
diff --git a/lib/net/http.rb b/lib/net/http.rb deleted file mode 100644 index 6a33fd4918..0000000000 --- a/lib/net/http.rb +++ /dev/null @@ -1,1473 +0,0 @@ -=begin - -= net/http.rb - -Copyright (c) 1999-2002 Yukihiro Matsumoto - -written & maintained by Minero Aoki <aamine@loveruby.net> -This file is derived from "http-access.rb". - -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. - -NOTE: You can find Japanese version of this document in -the doc/net directory of the standard ruby interpreter package. - -$Id$ - -== What is this module? - -This module provide your program the functions to access WWW -documents via HTTP, Hyper Text Transfer Protocol version 1.1. -For details of HTTP, refer [RFC2616] -((<URL:http://www.ietf.org/rfc/rfc2616.txt>)). - -== Examples - -=== Getting Document From Server - -Be care to ',' (comma) putted after "response". -This is required for compatibility. - - require 'net/http' - Net::HTTP.start( 'some.www.server', 80 ) {|http| - response , = http.get('/index.html') - puts response.body - } - -(shorter version) - - require 'net/http' - Net::HTTP.get_print 'some.www.server', '/index.html' - -=== Posting Form Data - - require 'net/http' - Net::HTTP.start( 'some.www.server', 80 ) {|http| - response , = http.post( '/cgi-bin/any.rhtml', - 'querytype=subject&target=ruby' ) - } - -=== Accessing via Proxy - -Net::HTTP.Proxy() creates http proxy class. It has same -methods of Net::HTTP but its instances always connect to -proxy, instead of given host. - - require 'net/http' - - $proxy_addr = 'your.proxy.addr' - $proxy_port = 8080 - : - Net::HTTP::Proxy($proxy_addr, $proxy_port).start( 'some.www.server' ) {|http| - # always connect to your.proxy.addr:8080 - : - } - -Since Net::HTTP.Proxy() returns Net::HTTP itself when $proxy_addr is nil, -there's no need to change code if there's proxy or not. - -=== Redirect - - require 'net/http' - Net::HTTP.version_1_1 - - host = 'www.ruby-lang.org' - path = '/' - begin - Net::HTTP.start( host, 80 ) {|http| - response , = http.get(path) - print response.body - } - rescue Net::ProtoRetriableError => err - if m = %r<http://([^/]+)>.match( err.response['location'] ) then - host = m[1].strip - path = m.post_match - retry - end - end - -NOTE: This code is using ad-hoc way to extract host name, but in future -URI class will be included in ruby standard library. - -=== Basic Authentication - - require 'net/http' - - Net::HTTP.start( 'auth.some.domain' ) {|http| - response , = http.get( '/need-auth.cgi', - 'Authorization' => 'Basic ' + ["#{account}:#{password}"].pack('m').strip ) - print response.body - } - -In version 1.2 (Ruby 1.7 or later), you can write like this: - - require 'net/http' - - req = Net::HTTP::Get.new('/need-auth.cgi') - req.basic_auth 'account', 'password' - Net::HTTP.start( 'auth.some.domain' ) {|http| - response = http.request(req) - print response.body - } - -== Switching Net::HTTP versions - -You can use old Net::HTTP (in Ruby 1.6) features by calling -HTTP.version_1_1. And calling Net::HTTP.version_1_2 allows -you to use 1.2 features again. - - # example - Net::HTTP.start {|http1| ...(http1 has 1.2 features)... } - - Net::HTTP.version_1_1 - Net::HTTP.start {|http2| ...(http2 has 1.1 features)... } - - Net::HTTP.version_1_2 - Net::HTTP.start {|http3| ...(http3 has 1.2 features)... } - -Yes, this is not thread-safe. - -== class Net::HTTP - -=== Class Methods - -: new( address, port = 80, proxy_addr = nil, proxy_port = nil ) - creates a new Net::HTTP object. - If proxy_addr is given, creates an Net::HTTP object with proxy support. - -: start( address, port = 80, proxy_addr = nil, proxy_port = nil ) -: start( address, port = 80, proxy_addr = nil, proxy_port = nil ) {|http| .... } - is equals to - - Net::HTTP.new(address, port, proxy_addr, proxy_port).start(&block) - -: get( address, path, port = 80 ) - gets entity body from path and returns it. - return value is a String. - -: get_print( address, path, port = 80 ) - gets entity body from path and output it to $stdout. - -: Proxy( address, port = 80 ) - creates a HTTP proxy class. - Arguments are address/port of proxy host. - You can replace HTTP class with created proxy class. - - If ADDRESS is nil, this method returns self (Net::HTTP). - - # example - proxy_class = Net::HTTP::Proxy( 'proxy.foo.org', 8080 ) - : - proxy_class.start( 'www.ruby-lang.org' ) {|http| - # connecting proxy.foo.org:8080 - : - } - -: proxy_class? - If self is HTTP, false. - If self is a class which was created by HTTP::Proxy(), true. - -: port - default HTTP port (80). - -=== Instance Methods - -: start -: start {|http| .... } - creates a new Net::HTTP object and starts HTTP session. - - When this method is called with block, gives a HTTP object to block - and close the HTTP session after block call finished. - -: active? - true if HTTP session is started. - -: address - the address to connect - -: port - the port number to connect - -: open_timeout -: open_timeout=(n) - seconds to wait until connection is opened. - If HTTP object cannot open a conection in this seconds, - it raises TimeoutError exception. - -: read_timeout -: read_timeout=(n) - seconds to wait until reading one block (by one read(1) call). - If HTTP object cannot open a conection in this seconds, - it raises TimeoutError exception. - -: finish - finishes HTTP session. - If HTTP session had not started, raises an IOError. - -: proxy? - true if self is a HTTP proxy class - -: proxy_address - address of proxy host. If self does not use a proxy, nil. - -: proxy_port - port number of proxy host. If self does not use a proxy, nil. - -: get( path, header = nil ) -: get( path, header = nil ) {|str| .... } - gets data from PATH on the connecting host. - HEADER must be a Hash like { 'Accept' => '*/*', ... }. - - In version 1.1, this method returns a pair of objects, - a Net::HTTPResponse object and entity body string. - In version 1.2, this method returns a Net::HTTPResponse - object. - - If called with block, gives entity body string to the block - little by little. - - In version 1.1, this method might raises exception for also - 3xx (redirect). On the case you can get a HTTPResponse object - by "anException.response". - In version 1.2, this method never raises exception. - - # version 1.1 (bundled with Ruby 1.6) - response, body = http.get( '/index.html' ) - - # version 1.2 (bundled with Ruby 1.7 or later) - response = http.get( '/index.html' ) - - # compatible in both version - response , = http.get( '/index.html' ) - response.body - - # using block - File.open( 'save.txt', 'w' ) {|f| - http.get( '/~foo/', nil ) do |str| - f.write str - end - } - -: head( path, header = nil ) - gets only header from PATH on the connecting host. - HEADER is a Hash like { 'Accept' => '*/*', ... }. - - This method returns a Net::HTTPResponse object. - - In version 1.1, this method might raises exception for also - 3xx (redirect). On the case you can get a HTTPResponse object - by "anException.response". - In version 1.2, this method never raises exception. - - response = nil - Net::HTTP.start( 'some.www.server', 80 ) {|http| - response = http.head( '/index.html' ) - } - p response['content-type'] - -: post( path, data, header = nil ) -: post( path, data, header = nil ) {|str| .... } - posts DATA (must be String) to PATH. HEADER must be a Hash - like { 'Accept' => '*/*', ... }. - - In version 1.1, this method returns a pair of objects, a - Net::HTTPResponse object and an entity body string. - In version 1.2, this method returns a Net::HTTPReponse object. - - If called with block, gives a part of entity body string. - - In version 1.1, this method might raises exception for also - 3xx (redirect). On the case you can get a HTTPResponse object - by "anException.response". - In version 1.2, this method never raises exception. - - # version 1.1 - response, body = http.post( '/cgi-bin/search.rb', 'query=subject&target=ruby' ) - - # version 1.2 - response = http.post( '/cgi-bin/search.rb', 'query=subject&target=ruby' ) - - # compatible in both version - response , = http.post( '/cgi-bin/search.rb', 'query=subject&target=ruby' ) - - # using block - File.open( 'save.html', 'w' ) {|f| - http.post( '/cgi-bin/search.rb', - 'query=subject&target=ruby' ) do |str| - f.write str - end - } - -: request_get( path, header = nil ) -: request_get( path, header = nil ) {|response| .... } - gets entity from PATH. This method returns a HTTPResponse object. - - When called with block, keep connection while block is executed - and gives a HTTPResponse object to the block. - - This method never raises Net::* exceptions. - - # example - response = http.request_get( '/index.html' ) - p response['content-type'] - puts response.body # body is already read - - # using block - http.request_get( '/index.html' ) {|response| - p response['content-type'] - response.read_body do |str| # read body now - print str - end - } - -: request_post( path, data, header = nil ) -: request_post( path, data, header = nil ) {|response| .... } - posts data to PATH. This method returns a HTTPResponse object. - - When called with block, gives a HTTPResponse object to the block - before reading entity body, with keeping connection. - - This method never raises Net::* exceptions. - - # example - response = http.post2( '/cgi-bin/nice.rb', 'datadatadata...' ) - p response.status - puts response.body # body is already read - - # using block - http.post2( '/cgi-bin/nice.rb', 'datadatadata...' ) {|response| - p response.status - p response['content-type'] - response.read_body do |str| # read body now - print str - end - } - -: request( request [, data] ) -: request( request [, data] ) {|response| .... } - sends a HTTPRequest object REQUEST to the HTTP server. - This method also writes DATA string if REQUEST is a post/put request. - Giving DATA for get/head request causes ArgumentError. - - If called with block, this method passes a HTTPResponse object to - the block, without reading entity body. - - This method never raises Net::* exceptions. - -== class Net::HTTP::Get, Head, Post - -HTTP request classes. These classes wraps request header and -entity path. All arguments named "key" is case-insensitive. - -=== Class Methods - -: new - creats HTTP request object. - -=== Instance Methods - -: self[ key ] - returns the header field corresponding to the case-insensitive key. - For example, a key of "Content-Type" might return "text/html" - -: self[ key ] = val - sets the header field corresponding to the case-insensitive key. - -: each {|name, val| .... } - iterates for each field name and value pair. - -: basic_auth( account, password ) - set Authorization: header for basic auth. - -: range - returns a Range object which represents Range: header field. - -: range = r -: set_range( i, len ) - set Range: header from Range (arg r) or beginning index and - length from it (arg i&len). - -: content_length - returns a Integer object which represents Content-Length: header field. - -: content_range - returns a Range object which represents Content-Range: header field. - -== class Net::HTTPResponse - -HTTP response class. This class wraps response header and entity. -All arguments named KEY is case-insensitive. - -=== Instance Methods - -: self[ key ] - returns the header field corresponding to the case-insensitive key. - For example, a key of "Content-Type" might return "text/html". - A key of "Content-Length" might do "2045". - - More than one fields which has same names are joined with ','. - -: self[ key ] = val - sets the header field corresponding to the case-insensitive key. - -: key?( key ) - true if key exists. - KEY is case insensitive. - -: each {|name,value| .... } - iterates for each field name and value pair. - -: canonical_each {|name,value| .... } - iterates for each "canonical" field name and value pair. - -: code - HTTP result code string. For example, '302'. - -: message - HTTP result message. For example, 'Not Found'. - -: read_body( dest = '' ) - gets entity body and write it into DEST using "<<" method. - If this method is called twice or more, nothing will be done - and returns first DEST. - -: read_body {|str| .... } - gets entity body little by little and pass it to block. - -: body - response body. If #read_body has been called, this method returns - arg of #read_body DEST. Else gets body as String and returns it. - -=end - -require 'net/protocol' - - -module Net - - class HTTPBadResponse < StandardError; end - class HTTPHeaderSyntaxError < StandardError; end - - - class HTTP < Protocol - - HTTPVersion = '1.1' - - - # - # for backward compatibility - # - - @@newimpl = true - - def HTTP.version_1_2 - @@newimpl = true - end - - def HTTP.version_1_1 - @@newimpl = false - end - - def HTTP.is_version_1_2? - @@newimpl - end - - def HTTP.setimplversion( obj ) - f = @@newimpl - obj.instance_eval { @newimpl = f } - end - private_class_method :setimplversion - - - # - # short cut methods - # - - def HTTP.get( addr, path, port = nil ) - req = Get.new( path ) - resp = nil - new( addr, port || HTTP.default_port ).start {|http| - resp = http.request( req ) - } - resp.body - end - - def HTTP.get_print( addr, path, port = nil ) - new( addr, port || HTTP.port ).start {|http| - http.get path, nil, $stdout - } - nil - end - - - # - # connection - # - - protocol_param :default_port, '80' - protocol_param :socket_type, '::Net::InternetMessageIO' - - class << HTTP - def start( address, port = nil, p_addr = nil, p_port = nil, &block ) - new( address, port, p_addr, p_port ).start( &block ) - end - - alias newobj new - - def new( address, port = nil, p_addr = nil, p_port = nil ) - obj = Proxy(p_addr, p_port).newobj(address, port) - setimplversion obj - obj - end - end - - def initialize( addr, port = nil ) - super - @curr_http_version = HTTPVersion - @seems_1_0_server = false - end - - private - - def do_start - conn_socket - end - - def do_finish - disconn_socket - end - - - # - # proxy - # - - public - - # no proxy - @is_proxy_class = false - @proxy_addr = nil - @proxy_port = nil - - def HTTP.Proxy( p_addr, p_port = nil ) - p_addr or return self - - p_port ||= port() - delta = ProxyDelta - proxyclass = Class.new(self) - proxyclass.module_eval { - include delta - # with proxy - @is_proxy_class = true - @proxy_address = p_addr - @proxy_port = p_port - } - proxyclass - end - - class << HTTP - def proxy_class? - @is_proxy_class - end - - attr_reader :proxy_address - attr_reader :proxy_port - end - - def proxy? - type.proxy_class? - end - - def proxy_address - type.proxy_address - end - - def proxy_port - type.proxy_port - end - - alias proxyaddr proxy_address - alias proxyport proxy_port - - private - - # no proxy - - def conn_address - address - end - - def conn_port - port - end - - def edit_path( path ) - path - end - - module ProxyDelta - private - - # with proxy - - def conn_address - proxy_address - end - - def conn_port - proxy_port - end - - def edit_path( path ) - 'http://' + addr_port() + path - end - end - - - # - # http operations - # - - public - - def get( path, initheader = nil, dest = nil, &block ) - res = nil - request( Get.new(path,initheader) ) {|res| - res.read_body dest, &block - } - unless @newimpl then - res.value - return res, res.body - end - - res - end - - def head( path, initheader = nil ) - res = request( Head.new(path,initheader) ) - @newimpl or res.value - res - end - - def post( path, data, initheader = nil, dest = nil, &block ) - res = nil - request( Post.new(path,initheader), data ) {|res| - res.read_body dest, &block - } - unless @newimpl then - res.value - return res, res.body - end - - res - end - - def put( path, data, initheader = nil ) - res = request( Put.new(path,initheader), data ) - @newimpl or res.value - res - end - - - def request_get( path, initheader = nil, &block ) - request Get.new(path,initheader), &block - end - - def request_head( path, initheader = nil, &block ) - request Head.new(path,initheader), &block - end - - def request_post( path, data, initheader = nil, &block ) - request Post.new(path,initheader), data, &block - end - - def request_put( path, data, initheader = nil, &block ) - request Put.new(path,initheader), data, &block - end - - alias get2 request_get - alias head2 request_head - alias post2 request_post - alias put2 request_put - - - def send_request( name, path, body = nil, header = nil ) - r = HTTPGenericRequest.new( name, (body ? true : false), true, - path, header ) - request r, body - end - - - def request( req, body = nil, &block ) - unless active? then - start { - req['connection'] = 'close' - return request(req, body, &block) - } - end - - begin_transport req - req.__send__(:exec, - @socket, @curr_http_version, edit_path(req.path), body) - begin - res = HTTPResponse.read_new(@socket, req.response_body_permitted?) - end while HTTPContinue === res - yield res if block_given? - end_transport req, res - - res - end - - private - - def begin_transport( req ) - if @socket.closed? then - reconn_socket - end - if not req.body_exist? or @seems_1_0_server then - req['connection'] = 'close' - end - req['host'] = addr_port() - end - - def end_transport( req, res ) - res.__send__ :terminate - @curr_http_version = res.http_version - - if not res.body then - @socket.close - elsif keep_alive? req, res 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 - D 'Conn close' - @socket.close - end - end - - def keep_alive?( req, res ) - /close/i === req['connection'].to_s and return false - @seems_1_0_server and return false - - /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 - - @curr_http_version == '1.1' and return true - false - end - - - # - # utils - # - - private - - def addr_port - address + (port == HTTP.default_port ? '' : ":#{port}") - end - - def D( msg ) - if @dout then - @dout << msg - @dout << "\n" - end - end - - end - - HTTPSession = HTTP - - - ### - ### header - ### - - module HTTPHeader - - def size - @header.size - end - - alias length size - - def []( key ) - @header[ key.downcase ] - end - - def []=( key, val ) - @header[ key.downcase ] = val - end - - def each_header( &block ) - @header.each( &block ) - end - - alias each each_header - - def each_key( &block ) - @header.each_key( &block ) - end - - def each_value( &block ) - @header.each_value( &block ) - end - - def delete( key ) - @header.delete key.downcase - end - - def key?( key ) - @header.key? key.downcase - end - - def to_hash - @header.dup - end - - def canonical_each - @header.each do |k,v| - yield canonical(k), v - end - end - - def canonical( k ) - k.split('-').collect {|i| i.capitalize }.join('-') - end - - def range - s = @header['range'] or return nil - s.split(',').collect {|spec| - m = /bytes\s*=\s*(\d+)?\s*-\s*(\d+)?/i.match(spec) or - raise HTTPHeaderSyntaxError, "wrong Range: #{spec}" - d1 = m[1].to_i - d2 = m[2].to_i - if m[1] and m[2] then d1..d2 - elsif m[1] then d1..-1 - elsif m[2] then -d2..-1 - else - raise HTTPHeaderSyntaxError, 'range is not specified' - end - } - end - - def range=( r, fin = nil ) - r = (r ... r + fin) if fin - - 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 - - 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 - - @header['range'] = "bytes=#{s}" - r - end - - alias set_range range= - - def content_length - s = @header['content-length'] - s or return nil - - m = /\d+/.match(s) - m or raise HTTPHeaderSyntaxError, 'wrong Content-Length format' - m[0].to_i - end - - 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 = %r<bytes\s+(\d+)-(\d+)/(?:\d+|\*)>i.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').strip - end - - end - - - ### - ### request - ### - - class HTTPGenericRequest - - include HTTPHeader - - def initialize( m, reqbody, resbody, path, initheader = nil ) - @method = m - @request_has_body = reqbody - @response_has_body = resbody - @path = path - - @header = tmp = {} - return unless initheader - initheader.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'] ||= '*/*' - end - - attr_reader :method - attr_reader :path - - def inspect - "\#<#{self.type} #{@method}>" - end - - def request_body_permitted? - @request_has_body - end - - def response_body_permitted? - @response_has_body - end - - alias body_exist? response_body_permitted? - - # - # write - # - - private - - def exec( sock, ver, path, body ) - if body then - check_body_premitted - send_request_with_body sock, ver, path, body - else - request sock, ver, path - end - end - - def check_body_premitted - request_body_permitted? or - raise ArgumentError, 'HTTP request body is not premitted' - end - - def send_request_with_body( sock, ver, path, body ) - if block_given? then - ac = Accumulator.new - yield ac # must be yield, DO NOT USE block.call - data = ac.terminate - else - data = body - end - @header['content-length'] = data.size.to_s - @header.delete 'transfer-encoding' - - unless @header['content-type'] then - $stderr.puts 'Content-Type did not set; using application/x-www-form-urlencoded' if $VERBOSE - @header['content-type'] = 'application/x-www-form-urlencoded' - end - - request sock, ver, path - sock.write data - end - - def request( sock, ver, path ) - sock.writeline sprintf('%s %s HTTP/%s', @method, path, ver) - canonical_each do |k,v| - sock.writeline k + ': ' + v - end - sock.writeline '' - end - - end - - - class HTTPRequest < HTTPGenericRequest - - def initialize( path, initheader = nil ) - super type::METHOD, - type::REQUEST_HAS_BODY, - type::RESPONSE_HAS_BODY, - path, initheader - end - - end - - - class HTTP - - class Get < HTTPRequest - METHOD = 'GET' - REQUEST_HAS_BODY = false - RESPONSE_HAS_BODY = true - end - - class Head < HTTPRequest - METHOD = 'HEAD' - REQUEST_HAS_BODY = false - RESPONSE_HAS_BODY = false - end - - class Post < HTTPRequest - METHOD = 'POST' - REQUEST_HAS_BODY = true - RESPONSE_HAS_BODY = true - end - - class Put < HTTPRequest - METHOD = 'PUT' - REQUEST_HAS_BODY = true - RESPONSE_HAS_BODY = true - end - - end - - - - ### - ### response - ### - - class HTTPResponse - # predefine HTTPResponse class to allow inheritance - - def self.body_permitted? - self::HAS_BODY - end - - def self.exception_type - self::EXCEPTION_TYPE - end - end - - - class HTTPUnknownResponse < HTTPResponse - HAS_BODY = true - EXCEPTION_TYPE = ProtocolError - end - class HTTPInformation < HTTPResponse - HAS_BODY = false - EXCEPTION_TYPE = ProtocolError - end - class HTTPSuccess < HTTPResponse - HAS_BODY = true - EXCEPTION_TYPE = ProtocolError - end - class HTTPRedirection < HTTPResponse - HAS_BODY = true - EXCEPTION_TYPE = ProtoRetriableError - end - class HTTPClientError < HTTPResponse - HAS_BODY = true - EXCEPTION_TYPE = ProtoFatalError - end - class HTTPServerError < HTTPResponse - HAS_BODY = true - EXCEPTION_TYPE = ProtoServerError - end - class HTTPContinue < HTTPInformation - HAS_BODY = false - end - class HTTPSwitchProtocol < HTTPInformation - HAS_BODY = false - end - class HTTPOK < HTTPSuccess - HAS_BODY = true - end - class HTTPCreated < HTTPSuccess - HAS_BODY = true - end - class HTTPAccepted < HTTPSuccess - HAS_BODY = true - end - class HTTPNonAuthoritativeInformation < HTTPSuccess - HAS_BODY = true - end - class HTTPNoContent < HTTPSuccess - HAS_BODY = false - end - class HTTPResetContent < HTTPSuccess - HAS_BODY = false - end - class HTTPPartialContent < HTTPSuccess - HAS_BODY = true - end - class HTTPMultipleChoice < HTTPRedirection - HAS_BODY = true - end - class HTTPMovedPermanently < HTTPRedirection - HAS_BODY = true - end - class HTTPMovedTemporarily < HTTPRedirection - HAS_BODY = true - end - class HTTPNotModified < HTTPRedirection - HAS_BODY = false - end - class HTTPUseProxy < HTTPRedirection - HAS_BODY = false - end - class HTTPBadRequest < HTTPClientError - HAS_BODY = true - end - class HTTPUnauthorized < HTTPClientError - HAS_BODY = true - end - class HTTPPaymentRequired < HTTPClientError - HAS_BODY = true - end - class HTTPForbidden < HTTPClientError - HAS_BODY = true - end - class HTTPNotFound < HTTPClientError - HAS_BODY = true - end - class HTTPMethodNotAllowed < HTTPClientError - HAS_BODY = true - end - class HTTPNotAcceptable < HTTPClientError - HAS_BODY = true - end - class HTTPProxyAuthenticationRequired < HTTPClientError - HAS_BODY = true - end - class HTTPRequestTimeOut < HTTPClientError - HAS_BODY = true - end - class HTTPConflict < HTTPClientError - HAS_BODY = true - end - class HTTPGone < HTTPClientError - HAS_BODY = true - end - class HTTPLengthRequired < HTTPClientError - HAS_BODY = true - end - class HTTPPreconditionFailed < HTTPClientError - HAS_BODY = true - end - class HTTPRequestEntityTooLarge < HTTPClientError - HAS_BODY = true - end - class HTTPRequestURITooLarge < HTTPClientError - HAS_BODY = true - end - class HTTPUnsupportedMediaType < HTTPClientError - HAS_BODY = true - end - class HTTPInternalServerError < HTTPServerError - HAS_BODY = true - end - class HTTPNotImplemented < HTTPServerError - HAS_BODY = true - end - class HTTPBadGateway < HTTPServerError - HAS_BODY = true - end - class HTTPServiceUnavailable < HTTPServerError - HAS_BODY = true - end - class HTTPGatewayTimeOut < HTTPServerError - HAS_BODY = true - end - class HTTPVersionNotSupported < HTTPServerError - HAS_BODY = true - end - - - class HTTPResponse # redefine - - CODE_CLASS_TO_OBJ = { - '1' => HTTPInformation, - '2' => HTTPSuccess, - '3' => HTTPRedirection, - '4' => HTTPClientError, - '5' => HTTPServerError - } - CODE_TO_OBJ = { - '100' => HTTPContinue, - '101' => HTTPSwitchProtocol, - - '200' => HTTPOK, - '201' => HTTPCreated, - '202' => HTTPAccepted, - '203' => HTTPNonAuthoritativeInformation, - '204' => HTTPNoContent, - '205' => HTTPResetContent, - '206' => HTTPPartialContent, - - '300' => HTTPMultipleChoice, - '301' => HTTPMovedPermanently, - '302' => HTTPMovedTemporarily, - '304' => HTTPNotModified, - '305' => HTTPUseProxy, - - '400' => HTTPBadRequest, - '401' => HTTPUnauthorized, - '402' => HTTPPaymentRequired, - '403' => HTTPForbidden, - '404' => HTTPNotFound, - '405' => HTTPMethodNotAllowed, - '406' => HTTPNotAcceptable, - '407' => HTTPProxyAuthenticationRequired, - '408' => HTTPRequestTimeOut, - '409' => HTTPConflict, - '410' => HTTPGone, - '411' => HTTPLengthRequired, - '412' => HTTPPreconditionFailed, - '413' => HTTPRequestEntityTooLarge, - '414' => HTTPRequestURITooLarge, - '415' => HTTPUnsupportedMediaType, - - '501' => HTTPInternalServerError, - '501' => HTTPNotImplemented, - '502' => HTTPBadGateway, - '503' => HTTPServiceUnavailable, - '504' => HTTPGatewayTimeOut, - '505' => HTTPVersionNotSupported - } - - - class << self - - def read_new( sock, hasbody ) - httpv, code, msg = read_status_line(sock) - res = response_class(code).new( httpv, code, msg, sock, hasbody ) - each_response_header(sock) do |k,v| - if res.key? k then - res[k] << ', ' << v - else - res[k] = v - end - end - - res - end - - private - - def read_status_line( sock ) - str = sock.readline - m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/in.match(str) or - raise HTTPBadResponse, "wrong status line: #{str.dump}" - m.to_a[1,3] - end - - def response_class( code ) - CODE_TO_OBJ[code] or - CODE_CLASS_TO_OBJ[code[0,1]] or - HTTPUnknownResponse - end - - def each_response_header( sock ) - while true do - line = sock.readuntil( "\n", true ) # ignore EOF - line.sub!( /\s+\z/, '' ) # don't use chop! - break if line.empty? - - m = /\A([^:]+):\s*/.match(line) or - raise HTTPBadResponse, 'wrong header line format' - yield m[1], m.post_match - end - end - - end - - - include HTTPHeader - - def initialize( httpv, code, msg, sock, hasbody ) - @http_version = httpv - @code = code - @message = msg - @socket = sock - @body_exist = hasbody - - @header = {} - @body = nil - @read = false - end - - attr_reader :http_version - attr_reader :code - attr_reader :message - alias msg message - - def inspect - "#<#{type} #{@code} readbody=#{@read}>" - end - - # - # response <-> exception relationship - # - - def code_type - self.type - end - - def error! - raise error_type.new(@code + ' ' + @message.dump, self) - end - - def error_type - type::EXCEPTION_TYPE - end - - def value - HTTPSuccess === self or error! - end - - # - # header (for backward compatibility) - # - - def response - self - end - - alias header response - alias read_header response - - # - # body - # - - def read_body( dest = nil, &block ) - if @read then - (dest or block) and - raise IOError, "#{type}\#read_body called twice with argument" - return @body - end - - to = procdest(dest, block) - stream_check - if @body_exist and self.type.body_permitted? then - read_body_0 to - @body = to - else - @body = nil - end - @read = true - - @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 - if clen then - @socket.read clen, dest, true # ignore EOF - else - clen = range_length - if clen then - @socket.read clen, dest - else - @socket.read_all dest - end - end - end - end - - def read_chunked( dest ) - len = nil - total = 0 - - while true do - line = @socket.readline - m = /[0-9a-fA-F]+/.match(line) - m or raise HTTPBadResponse, "wrong chunk size line: #{line}" - len = m[0].hex - break if len == 0 - @socket.read len, dest; total += len - @socket.read 2 # \r\n - end - until @socket.readline.empty? do - # none - end - end - - def stream_check - @socket.closed? and raise IOError, 'try to read body out of block' - end - - def procdest( dest, block ) - (dest and block) and - raise ArgumentError, 'both of arg and block are given for HTTP method' - if block then - ReadAdapter.new(block) - else - dest || '' - end - end - - end - - - - # for backward compatibility - - module NetPrivate - HTTPResponse = ::Net::HTTPResponse - HTTPGenericRequest = ::Net::HTTPGenericRequest - HTTPRequest = ::Net::HTTPRequest - HTTPHeader = ::Net::HTTPHeader - end - HTTPInformationCode = HTTPInformation - HTTPSuccessCode = HTTPSuccess - HTTPRedirectionCode = HTTPRedirection - HTTPRetriableCode = HTTPRedirection - HTTPClientErrorCode = HTTPClientError - HTTPFatalErrorCode = HTTPClientError - HTTPServerErrorCode = HTTPServerError - HTTPResponceReceiver = HTTPResponse - -end # module Net |