lib/em-http/client.rb in igrigorik-em-http-request-0.1.7 vs lib/em-http/client.rb in igrigorik-em-http-request-0.1.8

- old
+ new

@@ -1,6 +1,5 @@ -# -*- coding: undecided -*- # #-- # Copyright (C)2008 Ilya Grigorik # # Includes portion originally Copyright (C)2007 Tony Arcieri # Includes portion originally Copyright (C)2005 Zed Shaw @@ -19,10 +18,20 @@ # The HTTP version returned. attr_accessor :http_version # The status code (as a string!) attr_accessor :http_status + + # E-Tag + def etag + self["ETag"] + end + + def last_modified + time = self["Last-Modified"] + Time.parse(time) if time + end # HTTP response status as an integer def status Integer(http_status) rescue nil end @@ -30,10 +39,15 @@ # Length of content as an integer, or nil if chunked/unspecified def content_length Integer(self[HttpClient::CONTENT_LENGTH]) rescue nil end + # Cookie header from the server + def cookie + self[HttpClient::SET_COOKIE] + end + # Is the transfer encoding chunked? def chunked_encoding? /chunked/i === self[HttpClient::TRANSFER_ENCODING] end @@ -92,16 +106,20 @@ def encode_request(method, path, query) HTTP_REQUEST_HEADER % [method.to_s.upcase, encode_query(path, query)] end def encode_query(path, query) - return path unless query - if query.kind_of? String - return "#{path}?#{query}" + encoded_query = if query.kind_of?(Hash) + query.map { |k, v| encode_param(k, v) }.join('&') else - return path + "?" + query.map { |k, v| encode_param(k, v) }.join('&') + query.to_s end + if !@uri.query.to_s.empty? + encoded_query = [encoded_query, @uri.query].reject {|part| part.empty?}.join("&") + end + return path if encoded_query.to_s.empty? + "#{path}?#{encoded_query}" end # URL encodes query parameters: # single k=v, or a URL encoded array, if v is an array of values def encode_param(k, v) @@ -132,12 +150,16 @@ result << encode_basic_auth(key, value) end end end - def encode_cookies(cookies) - cookies.inject('') { |result, (k, v)| result << encode_field('Cookie', encode_param(k, v)) } + def encode_cookie(cookie) + if cookie.is_a? Hash + cookie.inject('') { |result, (k, v)| result << encode_param(k, v) + ";" } + else + cookie + end end end class HttpClient < Connection include EventMachine::Deferrable @@ -165,37 +187,46 @@ @parser_nbytes = 0 @response = '' @inflate = [] @errors = '' @content_decoder = nil + @stream = nil end # start HTTP request once we establish connection to host def connection_completed ssl = @options[:tls] || @options[:ssl] || {} start_tls(ssl) if @uri.scheme == "https" or @uri.port == 443 send_request_header send_request_body end - + # request is done, invoke the callback def on_request_complete begin @content_decoder.finalize! if @content_decoder rescue HttpDecoders::DecoderError on_error "Content-decoder error" end unbind end - + # request failed, invoke errback - def on_error(msg) + def on_error(msg, dns_error = false) @errors = msg - unbind + + # no connection signature on DNS failures + # fail the connection directly + dns_error == true ? fail : unbind end + # assign a stream processing block + def stream(&blk) + @stream = blk + end + def normalize_body if @options[:body].is_a? Hash @options[:body].to_params else @options[:body] @@ -219,10 +250,15 @@ # Set auto-inflate flags if head['accept-encoding'] @inflate = head['accept-encoding'].split(',').map {|t| t.strip} end + # Set the cookie header if provided + if cookie = head.delete('cookie') + head['cookie'] = encode_cookie(cookie) + end + # Build the request request_header = encode_request(@method, @uri.path, query) request_header << encode_headers(head) request_header << CRLF @@ -252,19 +288,23 @@ on_decoded_body_data(data) end end def on_decoded_body_data(data) - if (on_response = @options[:on_response]) - on_response.call(data) + if @stream + @stream.call(data) else @response << data end end def unbind - (@state == :finished) ? succeed(self) : fail + if @state == :finished + succeed(self) + else + fail(self) + end close_connection end # # Response processing @@ -318,14 +358,25 @@ @state = :invalid on_error "no HTTP response" return false end + # shortcircuit on HEAD requests + if @method == "HEAD" + @state = :finished + on_request_complete + end + if @response_header.chunked_encoding? @state = :chunk_header else - @state = :body - @bytes_remaining = @response_header.content_length + if @response_header.content_length > 0 + @state = :body + @bytes_remaining = @response_header.content_length + else + @state = :finished + on_request_complete + end end if @inflate.include?(response_header[CONTENT_ENCODING]) && decoder_class = HttpDecoders.decoder_for_encoding(response_header[CONTENT_ENCODING]) begin