summaryrefslogtreecommitdiff
path: root/lib/chef/http.rb
diff options
context:
space:
mode:
authordanielsdeleo <dan@opscode.com>2013-10-08 13:04:52 -0700
committerdanielsdeleo <dan@opscode.com>2013-10-08 15:01:48 -0700
commit63f02a3c75d81003e8a4295f1403e13f91a97a7b (patch)
tree01172245a79d516699880be3f99fe703108f7037 /lib/chef/http.rb
parent1ed247de7476eb4e423af97f018a1666fc27f80d (diff)
downloadchef-63f02a3c75d81003e8a4295f1403e13f91a97a7b.tar.gz
Move streaming requests to generic HTTP class
Diffstat (limited to 'lib/chef/http.rb')
-rw-r--r--lib/chef/http.rb111
1 files changed, 99 insertions, 12 deletions
diff --git a/lib/chef/http.rb b/lib/chef/http.rb
index 94d9d4d145..77faa686a0 100644
--- a/lib/chef/http.rb
+++ b/lib/chef/http.rb
@@ -35,6 +35,29 @@ class Chef
# Basic HTTP client, with support for adding features via middleware
class HTTP
+ # Class for applying middleware behaviors to streaming
+ # responses. Collects stream handlers (if any) from each
+ # middleware. When #handle_chunk is called, the chunk gets
+ # passed to all handlers in turn for processing.
+ class StreamHandler
+ def initialize(middlewares, response)
+ middlewares = middlewares.flatten
+ @stream_handlers = []
+ middlewares.each do |middleware|
+ stream_handler = middleware.stream_response_handler(response)
+ @stream_handlers << stream_handler unless stream_handler.nil?
+ end
+ end
+
+ def handle_chunk(next_chunk)
+ @stream_handlers.inject(next_chunk) do |chunk, handler|
+ handler.handle_chunk(chunk)
+ end
+ end
+
+ end
+
+
def self.middlewares
@middlewares ||= []
end
@@ -106,14 +129,8 @@ class Chef
api_request(:DELETE, create_url(path), headers)
end
- def create_url(path)
- if path =~ /^(http|https):\/\//
- URI.parse(path)
- else
- URI.parse("#{@url}/#{path}")
- end
- end
-
+ # Makes an HTTP request to +url+ with the given +method+, +headers+, and
+ # +data+ (if applicable).
def request(method, url, headers={}, data=false)
method, url, headers, data = apply_request_middleware(method, url, headers, data)
@@ -131,6 +148,57 @@ class Chef
raise
end
+ # Makes a streaming download request, streaming the response body to a
+ # tempfile. If a block is given, the tempfile is passed to the block and
+ # the tempfile will automatically be unlinked after the block is executed.
+ #
+ # If no block is given, the tempfile is returned, which means it's up to
+ # you to unlink the tempfile when you're done with it.
+ def streaming_request(url, headers, &block)
+ response, rest_request, return_value = nil, nil, nil
+ tempfile = nil
+
+ method = :GET
+ method, url, headers, data = apply_request_middleware(method, url, headers, data)
+
+ response, rest_request, return_value = send_http_request(method, url, headers, data) do |http_response|
+ if http_response.kind_of?(Net::HTTPSuccess)
+ tempfile = stream_to_tempfile(url, http_response)
+ if block_given?
+ begin
+ yield tempfile
+ ensure
+ tempfile && tempfile.close!
+ end
+ end
+ end
+ end
+ unless response.kind_of?(Net::HTTPSuccess) or response.kind_of?(Net::HTTPRedirection)
+ response.error!
+ end
+ tempfile
+ rescue Exception => e
+ log_failed_request(response, return_value) unless response.nil?
+ if e.respond_to?(:chef_rest_request=)
+ e.chef_rest_request = rest_request
+ end
+ raise
+ end
+
+ def http_client
+ BasicClient.new(create_url(url))
+ end
+
+ protected
+
+ def create_url(path)
+ if path =~ /^(http|https):\/\//
+ URI.parse(path)
+ else
+ URI.parse("#{@url}/#{path}")
+ end
+ end
+
def apply_request_middleware(method, url, headers, data)
middlewares.inject([method, url, headers, data]) do |req_data, middleware|
middleware.handle_request(*req_data)
@@ -154,10 +222,6 @@ class Chef
response.kind_of?(Net::HTTPSuccess) || response.kind_of?(Net::HTTPRedirection)
end
- def http_client
- BasicClient.new(create_url(url))
- end
-
# Runs a synchronous HTTP request, with no middleware applied (use #request
# to have the middleware applied). The entire response will be loaded into memory.
def send_http_request(method, url, headers, body, &response_handler)
@@ -197,6 +261,7 @@ class Chef
end
end
+
# Wraps an HTTP request with retry logic.
# === Arguments
# url:: URL of the request, used for error messages
@@ -274,6 +339,28 @@ class Chef
headers
end
+ def stream_to_tempfile(url, response)
+ tf = Tempfile.open("chef-rest")
+ if Chef::Platform.windows?
+ tf.binmode # required for binary files on Windows platforms
+ end
+ Chef::Log.debug("Streaming download from #{url.to_s} to tempfile #{tf.path}")
+ # Stolen from http://www.ruby-forum.com/topic/166423
+ # Kudos to _why!
+
+ stream_handler = StreamHandler.new(middlewares, response)
+
+ response.read_body do |chunk|
+ tf.write(stream_handler.handle_chunk(chunk))
+ end
+ tf.close
+ tf
+ rescue Exception
+ tf.close!
+ raise
+ end
+
+
public
############################################################################