diff options
author | danielsdeleo <dan@opscode.com> | 2013-10-08 13:04:52 -0700 |
---|---|---|
committer | danielsdeleo <dan@opscode.com> | 2013-10-08 15:01:48 -0700 |
commit | 63f02a3c75d81003e8a4295f1403e13f91a97a7b (patch) | |
tree | 01172245a79d516699880be3f99fe703108f7037 /lib/chef/http.rb | |
parent | 1ed247de7476eb4e423af97f018a1666fc27f80d (diff) | |
download | chef-63f02a3c75d81003e8a4295f1403e13f91a97a7b.tar.gz |
Move streaming requests to generic HTTP class
Diffstat (limited to 'lib/chef/http.rb')
-rw-r--r-- | lib/chef/http.rb | 111 |
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 ############################################################################ |