Class: Stripe::APIRequestor
- Inherits:
-
Object
- Object
- Stripe::APIRequestor
- Extended by:
- Gem::Deprecate
- Defined in:
- lib/stripe/api_requestor.rb
Overview
APIRequestor executes requests against the Stripe API and allows a user to recover both a resource a call returns as well as a response object that contains information on the HTTP call.
Defined Under Namespace
Classes: RequestLogContext, StripeRequestMetrics, SystemProfiler, ThreadContext
Constant Summary collapse
- CONNECTION_MANAGER_GC_LAST_USED_EXPIRY =
Time (in seconds) that a connection manager has not been used before it’s eligible for garbage collection.
120
- CONNECTION_MANAGER_GC_PERIOD =
How often to check (in seconds) for connection managers that haven’t been used in a long time and which should be garbage collected.
60
- ERROR_MESSAGE_CONNECTION =
"Unexpected error communicating when trying to connect to " \ "Stripe (%s). You may be seeing this message because your DNS is not " \ "working or you don't have an internet connection. To check, try " \ "running `host stripe.com` from the command line."
- ERROR_MESSAGE_SSL =
"Could not establish a secure connection to Stripe (%s), you " \ "may need to upgrade your OpenSSL version. To check, try running " \ "`openssl s_client -connect api.stripe.com:443` from the command " \ "line."
- ERROR_MESSAGE_TIMEOUT_SUFFIX =
Common error suffix sared by both connect and read timeout messages.
"Please check your internet connection and try again. " \ "If this problem persists, you should check Stripe's service " \ "status at https://status.stripe.com, or let us know at " \ "[email protected]."
- ERROR_MESSAGE_TIMEOUT_CONNECT =
( "Timed out connecting to Stripe (%s). " + ERROR_MESSAGE_TIMEOUT_SUFFIX ).freeze
- ERROR_MESSAGE_TIMEOUT_READ =
( "Timed out communicating with Stripe (%s). " + ERROR_MESSAGE_TIMEOUT_SUFFIX ).freeze
Instance Attribute Summary collapse
-
#config ⇒ Object
readonly
Returns the value of attribute config.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
Class Method Summary collapse
-
.active_requestor ⇒ Object
Gets a currently active ‘APIRequestor`.
-
.clear_all_connection_managers(config: nil) ⇒ Object
Finishes any active connections by closing their TCP connection and clears them from internal tracking in all connection managers across all threads.
-
.current_thread_context ⇒ Object
Access data stored for ‘APIRequestor` within the thread’s current context.
-
.default_connection_manager(config = Stripe.config) ⇒ Object
A default connection manager for the current thread scoped to the configuration object that may be provided.
-
.default_requestor ⇒ Object
A default requestor for the current thread.
-
.maybe_gc_connection_managers ⇒ Object
Garbage collects connection managers that haven’t been used in some time, with the idea being that we want to remove old connection managers that belong to dead threads and the like.
-
.should_retry?(error, num_retries:, config: Stripe.config) ⇒ Boolean
Checks if an error is a problem that we should retry on.
- .sleep_time(num_retries, config: Stripe.config) ⇒ Object
Instance Method Summary collapse
- #execute_request(method, path, base_address, params: {}, opts: {}, usage: []) ⇒ Object
-
#execute_request_initialize_from(method, path, base_address, object, params: {}, opts: {}, usage: []) ⇒ Object
Execute request without instantiating a new object if the relevant object’s name matches the class.
-
#execute_request_stream(method, path, base_address, params: {}, opts: {}, usage: [], &read_body_chunk_block) ⇒ Object
Executes a request and returns the body as a stream instead of converting it to a StripeObject.
-
#initialize(config_arg = {}) ⇒ APIRequestor
constructor
Initializes a new APIRequestor.
- #interpret_response(http_resp) ⇒ Object
- #last_response_has_key?(object_id) ⇒ Boolean
-
#request ⇒ Object
Executes the API call within the given block.
- #store_last_response(object_id, resp) ⇒ Object
Constructor Details
#initialize(config_arg = {}) ⇒ APIRequestor
Initializes a new APIRequestor
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/stripe/api_requestor.rb', line 17 def initialize(config_arg = {}) @system_profiler = SystemProfiler.new @last_request_metrics = Queue.new @config = case config_arg when Hash StripeConfiguration.new.reverse_duplicate_merge(config_arg) when Stripe::StripeConfiguration config_arg when String StripeConfiguration.new.reverse_duplicate_merge( { api_key: config_arg } ) else raise ArgumentError, "Can't handle argument: #{config_arg}" end end |
Instance Attribute Details
#config ⇒ Object (readonly)
Returns the value of attribute config.
35 36 37 |
# File 'lib/stripe/api_requestor.rb', line 35 def config @config end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
35 36 37 |
# File 'lib/stripe/api_requestor.rb', line 35 def @options end |
Class Method Details
.active_requestor ⇒ Object
Gets a currently active ‘APIRequestor`. Set for the current thread when `APIRequestor#request` is being run so that API operations being executed inside of that block can find the currently active requestor. It’s reset to the original value (hopefully ‘nil`) after the block ends.
For internal use only. Does not provide a stable API and may be broken with future non-major changes.
44 45 46 |
# File 'lib/stripe/api_requestor.rb', line 44 def self.active_requestor current_thread_context.active_requestor || default_requestor end |
.clear_all_connection_managers(config: nil) ⇒ Object
Finishes any active connections by closing their TCP connection and clears them from internal tracking in all connection managers across all threads.
If passed a ‘config` object, only clear connection managers for that particular configuration.
For internal use only. Does not provide a stable API and may be broken with future non-major changes.
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/stripe/api_requestor.rb', line 57 def self.clear_all_connection_managers(config: nil) # Just a quick path for when configuration is being set for the first # time before any connections have been opened. There is technically some # potential for thread raciness here, but not in a practical sense. return if @thread_contexts_with_connection_managers.empty? @thread_contexts_with_connection_managers_mutex.synchronize do pruned_contexts = Set.new @thread_contexts_with_connection_managers.each do |thread_context| # Note that the thread context itself is not destroyed, but we clear # its connection manager and remove our reference to it. If it ever # makes a new request we'll give it a new connection manager and # it'll go back into `@thread_contexts_with_connection_managers`. thread_context.default_connection_managers.reject! do |cm_config, cm| if config.nil? || config.key == cm_config cm.clear true end end pruned_contexts << thread_context if thread_context.default_connection_managers.empty? end @thread_contexts_with_connection_managers.subtract(pruned_contexts) end end |
.current_thread_context ⇒ Object
Access data stored for ‘APIRequestor` within the thread’s current context. Returns ‘ThreadContext`.
For internal use only. Does not provide a stable API and may be broken with future non-major changes.
400 401 402 |
# File 'lib/stripe/api_requestor.rb', line 400 def self.current_thread_context Thread.current[:api_requestor__internal_use_only] ||= ThreadContext.new end |
.default_connection_manager(config = Stripe.config) ⇒ Object
A default connection manager for the current thread scoped to the configuration object that may be provided.
92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/stripe/api_requestor.rb', line 92 def self.default_connection_manager(config = Stripe.config) current_thread_context.default_connection_managers[config.key] ||= begin connection_manager = ConnectionManager.new(config) @thread_contexts_with_connection_managers_mutex.synchronize do maybe_gc_connection_managers @thread_contexts_with_connection_managers << current_thread_context end connection_manager end end |
.default_requestor ⇒ Object
A default requestor for the current thread.
86 87 88 |
# File 'lib/stripe/api_requestor.rb', line 86 def self.default_requestor current_thread_context.default_requestor ||= APIRequestor.new(Stripe.config) end |
.maybe_gc_connection_managers ⇒ Object
Garbage collects connection managers that haven’t been used in some time, with the idea being that we want to remove old connection managers that belong to dead threads and the like.
Prefixed with ‘maybe_` because garbage collection will only run periodically so that we’re not constantly engaged in busy work. If connection managers live a little passed their useful age it’s not harmful, so it’s not necessary to get them right away.
For testability, returns ‘nil` if it didn’t run and the number of connection managers that were garbage collected otherwise.
IMPORTANT: This method is not thread-safe and expects to be called inside a lock on ‘@thread_contexts_with_connection_managers_mutex`.
For internal use only. Does not provide a stable API and may be broken with future non-major changes.
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 |
# File 'lib/stripe/api_requestor.rb', line 421 def self.maybe_gc_connection_managers next_gc_time = @last_connection_manager_gc + CONNECTION_MANAGER_GC_PERIOD return nil if next_gc_time > Util.monotonic_time last_used_threshold = Util.monotonic_time - CONNECTION_MANAGER_GC_LAST_USED_EXPIRY pruned_contexts = [] @thread_contexts_with_connection_managers.each do |thread_context| thread_context .default_connection_managers .each do |config_key, connection_manager| next if connection_manager.last_used > last_used_threshold connection_manager.clear thread_context.default_connection_managers.delete(config_key) end end @thread_contexts_with_connection_managers.each do |thread_context| next unless thread_context.default_connection_managers.empty? pruned_contexts << thread_context end @thread_contexts_with_connection_managers -= pruned_contexts @last_connection_manager_gc = Util.monotonic_time pruned_contexts.count end |
.should_retry?(error, num_retries:, config: Stripe.config) ⇒ Boolean
Checks if an error is a problem that we should retry on. This includes both socket errors that may represent an intermittent problem and some special HTTP statuses.
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/stripe/api_requestor.rb', line 108 def self.should_retry?(error, num_retries:, config: Stripe.config) return false if num_retries >= config.max_network_retries case error when Net::OpenTimeout, Net::ReadTimeout # Retry on timeout-related problems (either on open or read). true when EOFError, Errno::ECONNREFUSED, Errno::ECONNRESET, # rubocop:todo Lint/DuplicateBranch Errno::EHOSTUNREACH, Errno::ETIMEDOUT, SocketError # Destination refused the connection, the connection was reset, or a # variety of other connection failures. This could occur from a single # saturated server, so retry in case it's intermittent. true when Stripe::StripeError # The API may ask us not to retry (e.g. if doing so would be a no-op), # or advise us to retry (e.g. in cases of lock timeouts). Defer to # those instructions if given. return false if error.http_headers["stripe-should-retry"] == "false" return true if error.http_headers["stripe-should-retry"] == "true" # 409 Conflict return true if error.http_status == 409 # 429 Too Many Requests # # There are a few different problems that can lead to a 429. The most # common is rate limiting, on which we *don't* want to retry because # that'd likely contribute to more contention problems. However, some # 429s are lock timeouts, which is when a request conflicted with # another request or an internal process on some particular object. # These 429s are safe to retry. return true if error.http_status == 429 && error.code == "lock_timeout" # Retry on 500, 503, and other internal errors. # # Note that we expect the stripe-should-retry header to be false # in most cases when a 500 is returned, since our idempotency framework # would typically replay it anyway. true if error.http_status >= 500 else false end end |
.sleep_time(num_retries, config: Stripe.config) ⇒ Object
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/stripe/api_requestor.rb', line 153 def self.sleep_time(num_retries, config: Stripe.config) # Apply exponential backoff with initial_network_retry_delay on the # number of num_retries so far as inputs. Do not allow the number to # exceed max_network_retry_delay. sleep_seconds = [ config.initial_network_retry_delay * (2**(num_retries - 1)), config.max_network_retry_delay, ].min # Apply some jitter by randomizing the value in the range of # (sleep_seconds / 2) to (sleep_seconds). sleep_seconds *= (0.5 * (1 + rand)) # But never sleep less than the base sleep seconds. [config.initial_network_retry_delay, sleep_seconds].max end |
Instance Method Details
#execute_request(method, path, base_address, params: {}, opts: {}, usage: []) ⇒ Object
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/stripe/api_requestor.rb', line 197 def execute_request(method, path, base_address, params: {}, opts: {}, usage: []) http_resp, req_opts = execute_request_internal( method, path, base_address, params, opts, usage ) req_opts = RequestOptions.extract_opts_from_hash(req_opts) resp = interpret_response(http_resp) # If being called from `APIRequestor#request`, put the last response in # thread-local memory so that it can be returned to the user. Don't store # anything otherwise so that we don't leak memory. store_last_response(object_id, resp) api_mode = Util.get_api_mode(path) Util.convert_to_stripe_object_with_params(resp.data, params, RequestOptions.persistable(req_opts), resp, api_mode: api_mode, requestor: self) end |
#execute_request_initialize_from(method, path, base_address, object, params: {}, opts: {}, usage: []) ⇒ Object
Execute request without instantiating a new object if the relevant object’s name matches the class
For internal use only. Does not provide a stable API and may be broken with future non-major changes.
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/stripe/api_requestor.rb', line 220 def execute_request_initialize_from(method, path, base_address, object, params: {}, opts: {}, usage: []) opts = RequestOptions.combine_opts(object.instance_variable_get(:@opts), opts) opts = Util.normalize_opts(opts) http_resp, req_opts = execute_request_internal( method, path, base_address, params, opts, usage ) req_opts = RequestOptions.extract_opts_from_hash(req_opts) resp = interpret_response(http_resp) # If being called from `APIRequestor#request`, put the last response in # thread-local memory so that it can be returned to the user. Don't store # anything otherwise so that we don't leak memory. store_last_response(object_id, resp) if Util.object_name_matches_class?(resp.data[:object], object.class) object.send(:initialize_from, resp.data, RequestOptions.persistable(req_opts), resp, api_mode: :v1, requestor: self) else Util.convert_to_stripe_object_with_params(resp.data, params, RequestOptions.persistable(req_opts), resp, api_mode: :v1, requestor: self) end end |
#execute_request_stream(method, path, base_address, params: {}, opts: {}, usage: [], &read_body_chunk_block) ⇒ Object
Executes a request and returns the body as a stream instead of converting it to a StripeObject. This should be used for any request where we expect an arbitrary binary response.
A ‘read_body_chunk` block can be passed, which will be called repeatedly with the body chunks read from the socket.
If a block is passed, a StripeHeadersOnlyResponse is returned as the block is expected to do all the necessary body processing. If no block is passed, then a StripeStreamResponse is returned containing an IO stream with the response body.
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/stripe/api_requestor.rb', line 264 def execute_request_stream(method, path, base_address, params: {}, opts: {}, usage: [], &read_body_chunk_block) unless block_given? raise ArgumentError, "execute_request_stream requires a read_body_chunk_block" end http_resp, api_key = execute_request_internal( method, path, base_address, params, opts, usage, &read_body_chunk_block ) # When the read_body_chunk_block is given, we no longer have access to the # response body at this point and so return a response object containing # only the headers. This is because the body was consumed by the block. resp = StripeHeadersOnlyResponse.from_net_http(http_resp) [resp, api_key] end |
#interpret_response(http_resp) ⇒ Object
247 248 249 250 251 |
# File 'lib/stripe/api_requestor.rb', line 247 def interpret_response(http_resp) StripeResponse.from_net_http(http_resp) rescue JSON::ParserError raise general_api_error(http_resp.code.to_i, http_resp.body) end |
#last_response_has_key?(object_id) ⇒ Boolean
291 292 293 |
# File 'lib/stripe/api_requestor.rb', line 291 def last_response_has_key?(object_id) self.class.current_thread_context.last_responses&.key?(object_id) end |
#request ⇒ Object
Executes the API call within the given block. Usage looks like:
client = APIRequestor.new
charge, resp = client.request { Charge.create }
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/stripe/api_requestor.rb', line 175 def request old_api_requestor = self.class.current_thread_context.active_requestor self.class.current_thread_context.active_requestor = self if self.class.current_thread_context.last_responses&.key?(object_id) raise "calls to APIRequestor#request cannot be nested within a thread" end self.class.current_thread_context.last_responses ||= {} self.class.current_thread_context.last_responses[object_id] = nil begin res = yield [res, self.class.current_thread_context.last_responses[object_id]] ensure self.class.current_thread_context.active_requestor = old_api_requestor self.class.current_thread_context.last_responses.delete(object_id) end end |
#store_last_response(object_id, resp) ⇒ Object
285 286 287 288 289 |
# File 'lib/stripe/api_requestor.rb', line 285 def store_last_response(object_id, resp) return unless last_response_has_key?(object_id) self.class.current_thread_context.last_responses[object_id] = resp end |