Basic HTTP client, with support for adding features via middleware
# File lib/chef/http.rb, line 66 def self.middlewares @middlewares ||= [] end
Create a HTTP client object. The supplied url is used as the base for all subsequent requests. For example, when initialized with a base url localhost:4000, a call to get with ‘nodes’ will make an HTTP GET request to localhost:4000/nodes
# File lib/chef/http.rb, line 84 def initialize(url, options={}) @url = url @default_headers = options[:headers] || {} @sign_on_redirect = true @redirects_followed = 0 @redirect_limit = 10 @middlewares = [] self.class.middlewares.each do |middleware_class| @middlewares << middleware_class.new(options) end end
# File lib/chef/http.rb, line 205 def create_url(path) return path if path.is_a?(URI) if path =~ /^(http|https):\/\// URI.parse(path) elsif path.nil? or path.empty? URI.parse(@url) else # The regular expressions used here are to make sure '@url' does not have # any trailing slashes and 'path' does not have any leading slashes. This # way they are always joined correctly using just one slash. URI.parse(@url.gsub(%{/+$}, '') + '/' + path.gsub(%{^/+}, '')) end end
Send an HTTP DELETE request to the path
path |
path part of the request URL |
# File lib/chef/http.rb, line 133 def delete(path, headers={}) request(:DELETE, path, headers) end
Send an HTTP GET request to the path
path |
The path to GET |
# File lib/chef/http.rb, line 109 def get(path, headers={}) request(:GET, path, headers) end
Send an HTTP HEAD request to the path
path |
path part of the request URL |
# File lib/chef/http.rb, line 101 def head(path, headers={}) request(:HEAD, path, headers) end
# File lib/chef/http.rb, line 198 def http_client(base_url=nil) base_url ||= url BasicClient.new(base_url) end
This is only kept around to provide access to cache control data in lib/chef/provider/remote_file/http.rb Find a better API.
# File lib/chef/http.rb, line 393 def last_response @last_response end
Send an HTTP POST request to the path
path |
path part of the request URL |
# File lib/chef/http.rb, line 125 def post(path, json, headers={}) request(:POST, path, headers, json) end
Send an HTTP PUT request to the path
path |
path part of the request URL |
# File lib/chef/http.rb, line 117 def put(path, json, headers={}) request(:PUT, path, headers, json) end
Makes an HTTP request to path with the given method, headers, and data (if applicable).
# File lib/chef/http.rb, line 139 def request(method, path, headers={}, data=false) url = create_url(path) method, url, headers, data = apply_request_middleware(method, url, headers, data) response, rest_request, return_value = send_http_request(method, url, headers, data) response, rest_request, return_value = apply_response_middleware(response, rest_request, return_value) response.error! unless success_response?(response) return_value rescue Exception => exception log_failed_request(response, return_value) unless response.nil? if exception.respond_to?(:chef_rest_request=) exception.chef_rest_request = rest_request end 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.
# File lib/chef/http.rb, line 162 def streaming_request(path, headers={}, &block) url = create_url(path) 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) end apply_stream_complete_middleware(http_response, rest_request, return_value) end return nil if response.kind_of?(Net::HTTPRedirection) unless response.kind_of?(Net::HTTPSuccess) response.error! end if block_given? begin yield tempfile ensure tempfile && tempfile.close! end 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
# File lib/chef/http.rb, line 219 def apply_request_middleware(method, url, headers, data) middlewares.inject([method, url, headers, data]) do |req_data, middleware| Chef::Log.debug("Chef::HTTP calling #{middleware.class}#handle_request") middleware.handle_request(*req_data) end end
# File lib/chef/http.rb, line 226 def apply_response_middleware(response, rest_request, return_value) middlewares.reverse.inject([response, rest_request, return_value]) do |res_data, middleware| Chef::Log.debug("Chef::HTTP calling #{middleware.class}#handle_response") middleware.handle_response(*res_data) end end
# File lib/chef/http.rb, line 233 def apply_stream_complete_middleware(response, rest_request, return_value) middlewares.reverse.inject([response, rest_request, return_value]) do |res_data, middleware| Chef::Log.debug("Chef::HTTP calling #{middleware.class}#handle_stream_complete") middleware.handle_stream_complete(*res_data) end end
# File lib/chef/http.rb, line 336 def follow_redirect raise Chef::Exceptions::RedirectLimitExceeded if @redirects_followed >= redirect_limit @redirects_followed += 1 Chef::Log.debug("Following redirect #{@redirects_followed}/#{redirect_limit}") yield ensure @redirects_followed = 0 end
# File lib/chef/http.rb, line 328 def http_retry_count config[:http_retry_count] end
# File lib/chef/http.rb, line 324 def http_retry_delay config[:http_retry_delay] end
# File lib/chef/http.rb, line 240 def log_failed_request(response, return_value) return_value ||= {} error_message = "HTTP Request Returned #{response.code} #{response.message}: " error_message << (return_value["error"].respond_to?(:join) ? return_value["error"].join(", ") : return_value["error"].to_s) Chef::Log.info(error_message) end
Wraps an HTTP request with retry logic.
url |
URL of the request, used for error messages |
# File lib/chef/http.rb, line 289 def retrying_http_errors(url) http_attempts = 0 begin http_attempts += 1 yield rescue SocketError, Errno::ETIMEDOUT => e e.message.replace "Error connecting to #{url} - #{e.message}" raise e rescue Errno::ECONNREFUSED if http_retry_count - http_attempts + 1 > 0 Chef::Log.error("Connection refused connecting to #{url}, retry #{http_attempts}/#{http_retry_count}") sleep(http_retry_delay) retry end raise Errno::ECONNREFUSED, "Connection refused connecting to #{url}, giving up" rescue Timeout::Error if http_retry_count - http_attempts + 1 > 0 Chef::Log.error("Timeout connecting to #{url}, retry #{http_attempts}/#{http_retry_count}") sleep(http_retry_delay) retry end raise Timeout::Error, "Timeout connecting to #{url}, giving up" rescue Net::HTTPFatalError => e if http_retry_count - http_attempts + 1 > 0 sleep_time = 1 + (2 ** http_attempts) + rand(2 ** http_attempts) Chef::Log.error("Server returned error for #{url}, retrying #{http_attempts}/#{http_retry_count} in #{sleep_time}s") sleep(sleep_time) retry end raise end 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.
# File lib/chef/http.rb, line 253 def send_http_request(method, url, headers, body, &response_handler) headers = build_headers(method, url, headers, body) retrying_http_errors(url) do client = http_client(url) return_value = nil if block_given? request, response = client.request(method, url, body, headers, &response_handler) else request, response = client.request(method, url, body, headers) {|r| r.read_body } return_value = response.read_body end @last_response = response if response.kind_of?(Net::HTTPSuccess) [response, request, return_value] elsif response.kind_of?(Net::HTTPNotModified) # Must be tested before Net::HTTPRedirection because it's subclass. [response, request, false] elsif redirect_location = redirected_to(response) if [:GET, :HEAD].include?(method) follow_redirect do send_http_request(method, create_url(redirect_location), headers, body, &response_handler) end else raise Exceptions::InvalidRedirect, "#{method} request was redirected from #{url} to #{redirect_location}. Only GET and HEAD support redirects." end else [response, request, nil] end end end
Generated with the Darkfish Rdoc Generator 2.