Raised for indicating HTTP response error.
Device for dumping log for debugging
Destination site
Proxy site
Requested protocol version
Boolean value for Socket#sync
# File lib/httpclient/session.rb, line 567 def initialize(client, dest, agent_name, from) @client = client @dest = dest @invalidated = false @proxy = nil @socket_sync = true @requested_version = nil @debug_dev = nil @connect_timeout = nil @connect_retry = 1 @send_timeout = nil @receive_timeout = nil @read_block_size = nil @protocol_retry_count = 5 @ssl_config = nil @ssl_peer_cert = nil @test_loopback_http_response = nil @socket_local = Site::EMPTY @agent_name = agent_name @from = from @state = :INIT @requests = [] @status = nil @reason = nil @headers = [] @socket = nil @readbuf = nil @transparent_gzip_decompression = false @last_used = nil end
# File lib/httpclient/session.rb, line 641 def close if !@socket.nil? and !@socket.closed? # @socket.flush may block when it the socket is already closed by # foreign host and the client runs under MT-condition. @socket.close end @state = :INIT end
# File lib/httpclient/session.rb, line 650 def closed? @state == :INIT end
# File lib/httpclient/session.rb, line 675 def eof? if !@content_length.nil? @content_length == 0 else @socket.closed? or @socket.eof? end end
# File lib/httpclient/session.rb, line 683 def get_body(&block) begin read_header if @state == :META return nil if @state != :DATA if @gzipped and @transparent_gzip_decompression # zlib itself has a functionality to decompress gzip stream. # - zlib 1.2.5 Manual # http://www.zlib.net/manual.html#Advanced # > windowBits can also be greater than 15 for optional gzip decoding. Add 32 to # > windowBits to enable zlib and gzip decoding with automatic header detection, # > or add 16 to decode only the gzip format inflate_stream = Zlib::Inflate.new(Zlib::MAX_WBITS + 32) original_block = block block = Proc.new { |buf| original_block.call(inflate_stream.inflate(buf)) } end if @chunked read_body_chunked(&block) elsif @content_length read_body_length(&block) else read_body_rest(&block) end rescue close raise end if eof? if @next_connection @state = :WAIT else close end end nil end
# File lib/httpclient/session.rb, line 662 def get_header begin if @state != :META raise RuntimeError.new("get_status must be called at the beginning of a session") end read_header rescue close raise end [@version, @status, @reason, @headers] end
# File lib/httpclient/session.rb, line 654 def invalidate @invalidated = true end
# File lib/httpclient/session.rb, line 658 def invalidated? @invalidated end
Send a request to the server
# File lib/httpclient/session.rb, line 608 def query(req) connect if @state == :INIT # Use absolute URI (not absolute path) iif via proxy AND not HTTPS. req.header.request_absolute_uri = !@proxy.nil? && !https?(@dest) begin timeout(@send_timeout, SendTimeoutError) do set_header(req) req.dump(@socket) # flush the IO stream as IO::sync mode is false @socket.flush unless @socket_sync end rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, IOError # JRuby can raise IOError instead of ECONNRESET for now close raise KeepAliveDisconnected.new(self) rescue HTTPClient::TimeoutError close raise rescue close if SSLEnabled and $!.is_a?(OpenSSL::SSL::SSLError) raise KeepAliveDisconnected.new(self) else raise end end @state = :META if @state == :WAIT @next_connection = nil @requests.push(req) @last_used = Time.now end
Connect to the server
# File lib/httpclient/session.rb, line 747 def connect site = @proxy || @dest retry_number = 0 begin timeout(@connect_timeout, ConnectTimeoutError) do @socket = create_socket(site) if https?(@dest) if @socket.is_a?(LoopBackSocket) connect_ssl_proxy(@socket, urify(@dest.to_s)) if @proxy else @socket = create_ssl_socket(@socket) connect_ssl_proxy(@socket, urify(@dest.to_s)) if @proxy begin @socket.ssl_connect(@dest.host) ensure if $DEBUG warn("Protocol version: #{@socket.ssl_version}") warn("Cipher: #{@socket.ssl_cipher.inspect}") warn("State: #{@socket.ssl_state}") end end @socket.post_connection_check(@dest) @ssl_peer_cert = @socket.peer_cert end end # Use Ruby internal buffering instead of passing data immediately # to the underlying layer # => we need to to call explicitly flush on the socket @socket.sync = @socket_sync end rescue RetryableResponse retry_number += 1 if retry_number < @protocol_retry_count retry end raise BadResponseError.new("connect to the server failed with status #{@status} #{@reason}") rescue TimeoutError if @connect_retry == 0 retry else retry_number += 1 retry if retry_number < @connect_retry end close raise end @state = :WAIT end
# File lib/httpclient/session.rb, line 829 def connect_ssl_proxy(socket, uri) req = HTTP::Message.new_connect_request(uri) @client.request_filter.each do |filter| filter.filter_request(req) end set_header(req) req.dump(@socket) @socket.flush unless @socket_sync res = HTTP::Message.new_response('') parse_header res.http_version, res.status, res.reason = @version, @status, @reason @headers.each do |key, value| res.header.set(key.to_s, value) end commands = @client.request_filter.collect { |filter| filter.filter_response(req, res) } if commands.find { |command| command == :retry } raise RetryableResponse.new end unless @status == 200 raise BadResponseError.new("connect to ssl proxy failed with status #{@status} #{@reason}", res) end end
# File lib/httpclient/session.rb, line 796 def create_socket(site) socket = nil begin @debug_dev << "! CONNECT TO #{site.host}:#{site.port}\n" if @debug_dev clean_host = site.host.delete("[]") clean_local = @socket_local.host.delete("[]") if str = @test_loopback_http_response.shift socket = LoopBackSocket.new(clean_host, site.port, str) elsif @socket_local == Site::EMPTY socket = TCPSocket.new(clean_host, site.port) else socket = TCPSocket.new(clean_host, site.port, clean_local, @socket_local.port) end if @debug_dev @debug_dev << "! CONNECTION ESTABLISHED\n" socket.extend(DebugSocket) socket.debug_dev = @debug_dev end rescue SystemCallError => e e.message << " (#{site})" raise rescue SocketError => e e.message << " (#{site})" raise end socket end
wrap socket with OpenSSL.
# File lib/httpclient/session.rb, line 825 def create_ssl_socket(raw_socket) SSLSocketWrap.new(raw_socket, @ssl_config, @debug_dev) end
# File lib/httpclient/session.rb, line 1020 def empty_bin_str str = '' str.force_encoding('BINARY') if str.respond_to?(:force_encoding) str end
# File lib/httpclient/session.rb, line 924 def no_message_body?(status) !status.nil? && # HTTP/0.9 ((status >= 100 && status < 200) || status == 204 || status == 304) end
# File lib/httpclient/session.rb, line 876 def parse_header timeout(@receive_timeout, ReceiveTimeoutError) do initial_line = nil begin begin initial_line = @socket.gets("\n") if initial_line.nil? close raise KeepAliveDisconnected.new(self) end rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, IOError # JRuby can raise IOError instead of ECONNRESET for now close raise KeepAliveDisconnected.new(self) end if StatusParseRegexp !~ initial_line @version = '0.9' @status = nil @reason = nil @next_connection = false @content_length = nil @readbuf = initial_line break end @version, @status, @reason = $1, $2.to_i, $3 @next_connection = HTTP::Message.keep_alive_enabled?(@version) @headers = [] while true line = @socket.gets("\n") unless line raise BadResponseError.new('unexpected EOF') end line.chomp! break if line.empty? if line[0] == \ or line[0] == \t last = @headers.last[1] last << ' ' unless last.empty? last << line.strip else key, value = line.strip.split(/\s*:\s*/, 2) parse_keepalive_header(key, value) @headers << [key, value] end end end while (@version == '1.1' && @status == 100) end end
# File lib/httpclient/session.rb, line 929 def parse_keepalive_header(key, value) key = key.downcase if key == 'content-length' @content_length = value.to_i elsif key == 'content-encoding' and ( value.downcase == 'gzip' or value.downcase == 'x-gzip' or value.downcase == 'deflate' ) @gzipped = true elsif key == 'transfer-encoding' and value.downcase == 'chunked' @chunked = true @chunk_length = 0 @content_length = nil elsif key == 'connection' or key == 'proxy-connection' if value.downcase == 'keep-alive' @next_connection = true else @next_connection = false end end end
# File lib/httpclient/session.rb, line 974 def read_body_chunked(&block) buf = empty_bin_str while true len = @socket.gets(RS) if len.nil? # EOF close return end @chunk_length = len.hex if @chunk_length == 0 @content_length = 0 @socket.gets(RS) return end timeout(@receive_timeout, ReceiveTimeoutError) do @socket.read(@chunk_length, buf) @socket.read(2) end unless buf.empty? yield buf end end end
# File lib/httpclient/session.rb, line 949 def read_body_length(&block) return nil if @content_length == 0 while true buf = empty_bin_str maxbytes = @read_block_size maxbytes = @content_length if maxbytes > @content_length && @content_length > 0 timeout(@receive_timeout, ReceiveTimeoutError) do begin @socket.readpartial(maxbytes, buf) rescue EOFError close buf = nil end end if buf && buf.bytesize > 0 @content_length -= buf.bytesize yield buf else @content_length = 0 end return if @content_length == 0 end end
# File lib/httpclient/session.rb, line 998 def read_body_rest if @readbuf and @readbuf.bytesize > 0 yield @readbuf @readbuf = nil end while true buf = empty_bin_str timeout(@receive_timeout, ReceiveTimeoutError) do begin @socket.readpartial(@read_block_size, buf) rescue EOFError buf = nil end end if buf && buf.bytesize > 0 yield buf else return end end end
Read status block.
# File lib/httpclient/session.rb, line 855 def read_header @content_length = nil @chunked = false @gzipped = false @chunk_length = 0 parse_header # Header of the request has been parsed. @state = :DATA req = @requests.shift if req.header.request_method == 'HEAD' or no_message_body?(@status) @content_length = 0 if @next_connection @state = :WAIT else close end end @next_connection = false if !@content_length and !@chunked end
# File lib/httpclient/session.rb, line 723 def set_header(req) if @requested_version if /^(?:HTTP\/|)(\d+.\d+)$/ =~ @requested_version req.http_version = $1 end end if @agent_name && req.header.get('User-Agent').empty? req.header.set('User-Agent', "#{@agent_name} #{LIB_NAME}") end if @from && req.header.get('From').empty? req.header.set('From', @from) end if req.header.get('Accept').empty? req.header.set('Accept', '*/*') end if @transparent_gzip_decompression req.header.set('Accept-Encoding', 'gzip,deflate') end if req.header.get('Date').empty? req.header.set_date_header end end