Class: Stripe::ConnectionManager

Inherits:
Object
  • Object
show all
Defined in:
lib/stripe/connection_manager.rb

Overview

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.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config = Stripe.config) ⇒ ConnectionManager

Returns a new instance of ConnectionManager.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 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

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



18
19
20
# File 'lib/stripe/connection_manager.rb', line 18

def config
  @config
end

#last_usedObject (readonly)

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



17
18
19
# File 'lib/stripe/connection_manager.rb', line 17

def last_used
  @last_used
end

Instance Method Details

#clearObject

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



37
38
39
40
41
42
43
44
# 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) ⇒ Object

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.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# 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) ⇒ Object

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

Raises:

  • (ArgumentError)


69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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
# 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
  )

  Util.log_debug("ConnectionManager starting request",
                 method_name: method_name,
                 path: path,
                 process_id: Process.pid,
                 thread_object_id: Thread.current.object_id,
                 connection_manager_object_id: object_id,
                 connection_object_id: connection.object_id,
                 log_timestamp: Util.monotonic_time)

  resp = @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

  Util.log_debug("ConnectionManager request complete",
                 method_name: method_name,
                 path: path,
                 process_id: Process.pid,
                 thread_object_id: Thread.current.object_id,
                 connection_manager_object_id: object_id,
                 connection_object_id: connection.object_id,
                 response_object_id: resp.object_id,
                 log_timestamp: Util.monotonic_time)

  resp
end