class Stripe::ConnectionManager

Manages connections across multiple hosts which is useful because the library may connect to multiple hosts during a typical session (main API, Connect, Uploads). Ruby doesn't provide an easy way to make this happen easily, so this class is designed to track what we're connected to and manage the lifecycle of those connections.

Note that this class in itself is not thread safe. We expect it to be instantiated once per thread.

Attributes

config[R]
last_used[R]

Timestamp (in seconds procured from the system's monotonic clock) indicating when the connection manager last made a request. This is used by `StripeClient` to determine whether a connection manager should be garbage collected or not.

Public Class Methods

new(config = Stripe.config) click to toggle source
# File lib/stripe/connection_manager.rb, line 20
def initialize(config = Stripe.config)
  @config = config
  @active_connections = {}
  @last_used = Util.monotonic_time

  # A connection manager may be accessed across threads as one thread makes
  # requests on it while another is trying to clear it (either because it's
  # trying to garbage collect the manager or trying to clear it because a
  # configuration setting has changed). That's probably thread-safe already
  # because of Ruby's GIL, but just in case the library's running on JRuby
  # or the like, use a mutex to synchronize access in this connection
  # manager.
  @mutex = Mutex.new
end

Public Instance Methods

clear() click to toggle source

Finishes any active connections by closing their TCP connection and clears them from internal tracking.

# File lib/stripe/connection_manager.rb, line 37
def clear
  @mutex.synchronize do
    @active_connections.each do |_, connection|
      connection.finish
    end
    @active_connections = {}
  end
end
connection_for(uri) click to toggle source

Gets a connection for a given URI. This is for internal use only as it's subject to change (we've moved between HTTP client schemes in the past and may do it again).

`uri` is expected to be a string.

# File lib/stripe/connection_manager.rb, line 51
def connection_for(uri)
  @mutex.synchronize do
    u = URI.parse(uri)
    connection = @active_connections[[u.host, u.port]]

    if connection.nil?
      connection = create_connection(u)
      connection.start

      @active_connections[[u.host, u.port]] = connection
    end

    connection
  end
end
execute_request(method, uri, body: nil, headers: nil, query: nil, &block) click to toggle source

Executes an HTTP request to the given URI with the given method. Also allows a request body, headers, and query string to be specified.

# File lib/stripe/connection_manager.rb, line 69
def execute_request(method, uri, body: nil, headers: nil, query: nil,
                    &block)
  # Perform some basic argument validation because it's easy to get
  # confused between strings and hashes for things like body and query
  # parameters.
  raise ArgumentError, "method should be a symbol" \
    unless method.is_a?(Symbol)
  raise ArgumentError, "uri should be a string" \
    unless uri.is_a?(String)
  raise ArgumentError, "body should be a string" \
    if body && !body.is_a?(String)
  raise ArgumentError, "headers should be a hash" \
    if headers && !headers.is_a?(Hash)
  raise ArgumentError, "query should be a string" \
    if query && !query.is_a?(String)

  @last_used = Util.monotonic_time

  connection = connection_for(uri)

  u = URI.parse(uri)
  path = if query
           u.path + "?" + query
         else
           u.path
         end

  method_name = method.to_s.upcase
  has_response_body = method_name != "HEAD"
  request = Net::HTTPGenericRequest.new(
    method_name,
    (body ? true : false),
    has_response_body,
    path,
    headers
  )

  @mutex.synchronize do
    # The block parameter is special here. If a block is provided, the block
    # is invoked with the Net::HTTPResponse. However, the body will not have
    # been read yet in the block, and can be streamed by calling
    # HTTPResponse#read_body.
    connection.request(request, body, &block)
  end
end

Private Instance Methods

create_connection(uri) click to toggle source

`uri` should be a parsed `URI` object.

# File lib/stripe/connection_manager.rb, line 120
        def create_connection(uri)
  # These all come back as `nil` if no proxy is configured.
  proxy_host, proxy_port, proxy_user, proxy_pass = proxy_parts

  connection = Net::HTTP.new(uri.host, uri.port,
                             proxy_host, proxy_port,
                             proxy_user, proxy_pass)

  # Time in seconds within which Net::HTTP will try to reuse an already
  # open connection when issuing a new operation. Outside this window, Ruby
  # will transparently close and re-open the connection without trying to
  # reuse it.
  #
  # Ruby's default of 2 seconds is almost certainly too short. Here I've
  # reused Go's default for `DefaultTransport`.
  connection.keep_alive_timeout = 30

  connection.open_timeout = config.open_timeout
  connection.read_timeout = config.read_timeout
  if connection.respond_to?(:write_timeout=)
    connection.write_timeout = config.write_timeout
  end

  connection.use_ssl = uri.scheme == "https"

  if config.verify_ssl_certs
    connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
    connection.cert_store = config.ca_store
  else
    connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
    warn_ssl_verify_none
  end

  connection
end
proxy_parts() click to toggle source

`Net::HTTP` somewhat awkwardly requires each component of a proxy URI (host, port, etc.) rather than the URI itself. This method simply parses out those pieces to make passing them into a new connection a little less ugly.

# File lib/stripe/connection_manager.rb, line 160
        def proxy_parts
  if config.proxy.nil?
    [nil, nil, nil, nil]
  else
    u = URI.parse(config.proxy)
    [u.host, u.port, u.user, u.password]
  end
end
warn_ssl_verify_none() click to toggle source
# File lib/stripe/connection_manager.rb, line 169
        def warn_ssl_verify_none
  return if @verify_ssl_warned

  @verify_ssl_warned = true
  warn("WARNING: Running without SSL cert verification. " \
    "You should never do this in production. " \
    "Execute `Stripe.verify_ssl_certs = true` to enable " \
    "verification.")
end