diff options
Diffstat (limited to 'lib/net/http.rb')
-rw-r--r-- | lib/net/http.rb | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/lib/net/http.rb b/lib/net/http.rb new file mode 100644 index 0000000000..4847f88bf7 --- /dev/null +++ b/lib/net/http.rb @@ -0,0 +1,312 @@ +=begin + += net/http.rb + +maintained by Minero Aoki <aamine@dp.u-netsurf.ne.jp> +This file is derived from http-access.rb + +This library is distributed under the terms of the Ruby license. +You can freely distribute/modify this library. + +=end + +require 'net/session' + + +module Net + + +class HTTPError < ProtocolError; end +class HTTPBadResponse < HTTPError; end + + +=begin + += HTTP class + +== Class Methods + +: new( address, port = 80 ) + create new HTTP object. + +: port + returns HTTP default port, 80 + +: command_type + returns Command class, HTTPCommand + + +== Methods + +: get( path, header = nil, ret = '' ) + get data from "path" on connecting host. + "header" is a Hash like { 'Accept' => '*/*', ... }. + The data will be written to "ret" using "<<" method. + This method returns response header (Hash) and "ret". + +: head( path, header = nil ) + get only header from "path" on connecting host. + "header" is a Hash like { 'Accept' => '*/*', ... }. + This method returns header as a Hash like + + { 'content-length' => 'Content-Length: 2554', + 'content-type' => 'Content-Type: text/html', + ... } + +=end + + class HTTP < Protocol + + protocol_param :port, '80' + protocol_param :command_type, '::Net::HTTPCommand' + + + def get( path, u_header = nil, ret = '' ) + header = connecting { + @command.get ret, edit_path(path), u_header + } + return header, ret + end + + def head( path, u_header = nil ) + connecting { + @command.head edit_path(path), u_header + } + end + + + private + + + def connecting + if @socket.closed? then + @socket.reopen + end + header = yield + @socket.close unless keep_alive? header + + header + end + + def keep_alive?( header ) + if str = header[ 'connection' ] then + if /\Aconnection:\s*keep-alive/i === str then + return true + end + else + if @http_version == '1.1' then + return true + end + end + + false + end + + + def do_finish + unless @command.error_occured or @socket.closed? then + head '/', { 'Connection' => 'Close' } + end + end + + + def edit_path( path ) + path + end + + class << self + def Proxy( p_addr, p_port ) + klass = super + klass.module_eval %- + def edit_path( path ) + 'http://' + address + + (@port == #{self.port} ? '' : ':' + @port.to_s) + path + end + - + klass + end + end + + end + + HTTPSession = HTTP + + + class HTTPCommand < Command + + HTTPVersion = '1.1' + + def initialize( sock ) + @http_version = HTTPVersion + + @in_header = {} + @in_header[ 'Host' ] = sock.addr + @in_header[ 'Connection' ] = 'Keep-Alive' + @in_header[ 'Accept' ] = '*/*' + + super sock + end + + + attr :http_version + + def get( ret, path, u_header = nil ) + header = get_response( + sprintf( 'GET %s HTTP/%s', path, HTTPVersion ), u_header ) + + if chunked? header then + clen = read_chunked_body( ret ) + header.delete 'transfer-encoding' + header[ 'content-length' ] = "Content-Length: #{clen}" + else + if clen = content_length( header ) then + @socket.read clen, ret + else + @socket.read_all ret + end + end + + header + end + + + def head( path, u_header = nil ) + get_response sprintf( 'HEAD %s HTTP/%s', path, HTTPVersion ), u_header + end + + + # def put + + # def delete + + # def trace + + # def options + + + private + + + def do_quit + unless @socket.closed? then + @socket.close + end + end + + def get_response( line, u_header ) + @socket.writeline line + write_header u_header + rep = get_reply + header = read_header + reply_must rep, SuccessCode + + header + end + + def get_reply + str = @socket.readline + unless /\AHTTP\/(\d+\.\d+)?\s+(\d\d\d)\s*(.*)\z/i === str then + raise HTTPBadResponse, "wrong status line format: #{str}" + end + @http_version = $1 + status = $2 + discrip = $3 + + klass = case status[0] + when ?1 then + case status[2] + when ?0 then ContinueCode + when ?1 then SuccessCode + else UnknownCode + end + when ?2 then SuccessCode + when ?3 then RetryCode + when ?4 then ServerBusyCode + when ?5 then FatalErrorCode + else UnknownCode + end + klass.new( status, discrip ) + end + + + def content_length( header ) + unless str = header[ 'content-length' ] then + return nil + end + unless /\Acontent-length:\s*(\d+)/i === str then + raise HTTPBadResponse, "content-length format error" + end + $1.to_i + end + + def chunked?( header ) + if str = header[ 'transfer-encoding' ] then + if /\Atransfer-encoding:\s*chunked/i === str then + return true + end + end + + false + end + + + def read_header + header = {} + while true do + line = @socket.readline + break if line.empty? + /\A[^:]+/ === line + nm = $& + nm.strip! + nm.downcase! + header[ nm ] = line + end + + header + end + + def write_header( user ) + if user then + header = @in_header.dup.update user + else + header = @in_header + end + header.each do |n,v| + @socket.writeline n + ': ' + v + end + @socket.writeline '' + + if tmp = header['Connection'] then + /close/i === tmp + else + false + end + end + + def read_chunked_body( ret ) + line = nil + len = nil + total = 0 + + while true do + line = @socket.readline + unless /[0-9a-hA-H]+/ === line then + raise HTTPBadResponse, "chunk size not given" + end + len = $&.hex + break if len == 0 + @socket.read( len, ret ); total += len + @socket.read 2 # \r\n + end + while true do + line = @socket.readline + break if line.empty? + end + + total + end + + end + + +end # module Net |