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