Class: Stripe::StripeClient
- Inherits:
-
Object
- Object
- Stripe::StripeClient
- Extended by:
- Gem::Deprecate
- Defined in:
- lib/stripe/stripe_client.rb
Overview
StripeClient 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 " \ "support@stripe.com."
- 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_client ⇒ Object
Gets a currently active ‘StripeClient`.
-
.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 ‘StripeClient` within the thread’s current context.
-
.default_client ⇒ Object
A default client for the current thread.
-
.default_connection_manager(config = Stripe.config) ⇒ Object
A default connection manager for the current thread scoped to the configuration object that may be provided.
-
.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
-
#connection_manager ⇒ Object
Gets the connection manager in use for the current ‘StripeClient`.
- #execute_request(method, path, api_base: nil, api_key: nil, headers: {}, params: {}, api_mode: nil, usage: []) ⇒ Object
-
#execute_request_stream(method, path, api_base: nil, api_key: nil, usage: [], headers: {}, params: {}, api_mode: nil, &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 = {}) ⇒ StripeClient
constructor
Initializes a new StripeClient.
- #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 = {}) ⇒ StripeClient
Initializes a new StripeClient
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/stripe/stripe_client.rb', line 17 def initialize(config_arg = {}) @system_profiler = SystemProfiler.new @last_request_metrics = nil # The following attribute is only used to log whether or not # StripeClient#request has been called. To be removed in a # future major version. @usage = [] @config = case config_arg when Hash Stripe.config.reverse_duplicate_merge(config_arg) when Stripe::ConnectionManager # Supports accepting a connection manager object for backwards # compatibility only, and that use is DEPRECATED. Stripe.config.dup when Stripe::StripeConfiguration config_arg when String Stripe.config.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.
44 45 46 |
# File 'lib/stripe/stripe_client.rb', line 44 def config @config end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
44 45 46 |
# File 'lib/stripe/stripe_client.rb', line 44 def @options end |
Class Method Details
.active_client ⇒ Object
Gets a currently active ‘StripeClient`. Set for the current thread when `StripeClient#request` is being run so that API operations being executed inside of that block can find the currently active client. 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.
53 54 55 |
# File 'lib/stripe/stripe_client.rb', line 53 def self.active_client current_thread_context.active_client || default_client 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.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/stripe/stripe_client.rb', line 66 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 ‘StripeClient` 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.
389 390 391 |
# File 'lib/stripe/stripe_client.rb', line 389 def self.current_thread_context Thread.current[:stripe_client__internal_use_only] ||= ThreadContext.new end |
.default_client ⇒ Object
A default client for the current thread.
95 96 97 |
# File 'lib/stripe/stripe_client.rb', line 95 def self.default_client current_thread_context.default_client ||= StripeClient.new(Stripe.config) 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.
101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/stripe/stripe_client.rb', line 101 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 |
.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.
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 |
# File 'lib/stripe/stripe_client.rb', line 410 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.
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 152 153 154 155 156 157 158 159 160 |
# File 'lib/stripe/stripe_client.rb', line 117 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
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/stripe/stripe_client.rb', line 162 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
#connection_manager ⇒ Object
Gets the connection manager in use for the current ‘StripeClient`.
This method is DEPRECATED and for backwards compatibility only.
182 183 184 |
# File 'lib/stripe/stripe_client.rb', line 182 def connection_manager self.class.default_connection_manager end |
#execute_request(method, path, api_base: nil, api_key: nil, headers: {}, params: {}, api_mode: nil, usage: []) ⇒ Object
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/stripe/stripe_client.rb', line 219 def execute_request(method, path, api_base: nil, api_key: nil, headers: {}, params: {}, api_mode: nil, usage: []) http_resp, api_key = execute_request_internal( method, path, api_base, api_key, headers, params, api_mode, usage ) begin resp = StripeResponse.from_net_http(http_resp) rescue JSON::ParserError raise general_api_error(http_resp.code.to_i, http_resp.body) end # If being called from `StripeClient#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) [resp, api_key] end |
#execute_request_stream(method, path, api_base: nil, api_key: nil, usage: [], headers: {}, params: {}, api_mode: nil, &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.
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/stripe/stripe_client.rb', line 251 def execute_request_stream(method, path, api_base: nil, api_key: nil, usage: [], headers: {}, params: {}, api_mode: nil, &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, api_base, api_key, headers, params, api_mode, 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 |
#last_response_has_key?(object_id) ⇒ Boolean
280 281 282 |
# File 'lib/stripe/stripe_client.rb', line 280 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 = StripeClient.new
charge, resp = client.request { Charge.create }
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/stripe/stripe_client.rb', line 194 def request @usage = ["stripe_client_request"] old_stripe_client = self.class.current_thread_context.active_client self.class.current_thread_context.active_client = self if self.class.current_thread_context.last_responses&.key?(object_id) raise "calls to StripeClient#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 @usage = [] self.class.current_thread_context.active_client = old_stripe_client self.class.current_thread_context.last_responses.delete(object_id) end end |
#store_last_response(object_id, resp) ⇒ Object
274 275 276 277 278 |
# File 'lib/stripe/stripe_client.rb', line 274 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 |