thrift_client-adamd 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ OWViNmI3NWQ1NjRmYzZiMmY2MzliMTY5Y2EzNWUwZDY2MTJlYTQ2ZA==
5
+ data.tar.gz: !binary |-
6
+ YTZiNzZmOWRmYjI3OTVjYmRmMmU4YWI3NTdiNjNiZTFiZWEzZTFlNQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ M2YxNjg2YzFhNWQ0MWYxNTFmMjNhOWUyOTQzNjliMDZmMWQyYmI2YjVkMzY3
10
+ MWRkZDRmMmIxZjM5NzYxOWYwMzE5ZWZjNTExYmFkNzVhMzZjNjdmYjBkMjk3
11
+ ZWQ2M2Q4YzlkMWVhMjkxNzgyNzY1NWRkZTYwOTJjMTExMTE0MWM=
12
+ data.tar.gz: !binary |-
13
+ OWViYThkYWRjOWJkNzBlZWJiOWM3NGJlMjc5ZjE4MThmNGE1ZGQ4ODNlYTA4
14
+ Y2QxYWYyM2E1MGNjOTUwZDU4NTRkOThkY2I4MTMzMTE5Y2FkZWRjN2JlNzA1
15
+ ZDQ0YjAyYmUyZTJjY2NmNmZiZTY1NzQ4Zjg0NmQ4YmI3OGExOTU=
@@ -0,0 +1,226 @@
1
+ class AbstractThriftClient
2
+ include ThriftHelpers
3
+
4
+ DISCONNECT_ERRORS = [
5
+ IOError,
6
+ Thrift::Exception,
7
+ Thrift::ApplicationException,
8
+ Thrift::TransportException
9
+ ]
10
+
11
+ DEFAULT_WRAPPED_ERRORS = [
12
+ Thrift::ApplicationException,
13
+ Thrift::TransportException,
14
+ ]
15
+
16
+ DEFAULTS = {
17
+ :protocol => Thrift::BinaryProtocol,
18
+ :protocol_extra_params => [],
19
+ :transport => Thrift::Socket,
20
+ :transport_wrapper => Thrift::FramedTransport,
21
+ :raise => true,
22
+ :defaults => {},
23
+ :exception_classes => DISCONNECT_ERRORS,
24
+ :exception_class_overrides => [],
25
+ :retries => 0,
26
+ :server_retry_period => 1,
27
+ :server_max_requests => nil,
28
+ :retry_overrides => {},
29
+ :wrapped_exception_classes => DEFAULT_WRAPPED_ERRORS,
30
+ :connect_timeout => 0.1,
31
+ :timeout => 1,
32
+ :timeout_overrides => {},
33
+ :cached_connections => false
34
+ }
35
+
36
+ attr_reader :last_client, :client, :client_class, :current_server, :server_list, :options, :client_methods
37
+
38
+ def self.create_wrapped_exception_classes(client_class, wrapped_exception_classes = DEFAULT_WRAPPED_ERRORS)
39
+ wrapped_exception_classes.map do |exception_klass|
40
+ name = exception_klass.to_s.split('::').last
41
+ begin
42
+ client_class.const_get(name)
43
+ rescue NameError
44
+ client_class.const_set(name, Class.new(exception_klass))
45
+ end
46
+ end
47
+ end
48
+
49
+ def initialize(client_class, servers, options = {})
50
+ @options = DEFAULTS.merge(options)
51
+ @options[:server_retry_period] ||= 0
52
+
53
+ @client_class = client_class
54
+ @server_list = Array(servers).collect do |s|
55
+ Server.new(s, @client_class, @options)
56
+ end.sort_by { rand }
57
+
58
+ @current_server = @server_list.first
59
+
60
+ @callbacks = {}
61
+ @client_methods = []
62
+ @client_class.instance_methods.each do |method_name|
63
+ if method_name != 'send_message' && method_name =~ /^send_(.*)$/
64
+ instance_eval("def #{$1}(*args); handled_proxy(:'#{$1}', *args); end", __FILE__, __LINE__)
65
+ @client_methods << $1
66
+ end
67
+ end
68
+ @request_count = 0
69
+ self.class.create_wrapped_exception_classes(@client_class, @options[:wrapped_exception_classes])
70
+ end
71
+
72
+ # Adds a callback that will be invoked at a certain time. The valid callback types are:
73
+ # :post_connect - should accept a single AbstractThriftClient argument, which is the client object to
74
+ # which the callback was added. Called after a connection to the remote thrift server
75
+ # is established.
76
+ # :before_method - should accept a single method name argument. Called before a method is invoked on the
77
+ # thrift server.
78
+ # :on_exception - should accept 2 args: an Exception instance and a method name. Called right before the
79
+ # exception is raised.
80
+ def add_callback(callback_type, &block)
81
+ case callback_type
82
+ when :post_connect, :before_method, :on_exception
83
+ @callbacks[callback_type] ||= []
84
+ @callbacks[callback_type].push(block)
85
+ # Allow chaining
86
+ return self
87
+ else
88
+ return nil
89
+ end
90
+ end
91
+
92
+ def inspect
93
+ "<#{self.class}(#{client_class}) @current_server=#{@current_server}>"
94
+ end
95
+
96
+ # Force the client to connect to the server. Not necessary to be
97
+ # called as the connection will be made on the first RPC method
98
+ # call.
99
+ def connect!(method = nil)
100
+ start_time ||= Time.now
101
+ @current_server = next_live_server
102
+ @client = @current_server.client
103
+ @last_client = @client
104
+ do_callbacks(:post_connect, self)
105
+ rescue IOError, Thrift::TransportException
106
+ disconnect!(true)
107
+ timeout = timeout(method)
108
+ if timeout && Time.now - start_time > timeout
109
+ no_servers_available!
110
+ else
111
+ retry
112
+ end
113
+ end
114
+
115
+ def disconnect!(error = false)
116
+ if @current_server
117
+ @current_server.mark_down!(@options[:server_retry_period]) if error
118
+ @current_server.close
119
+ end
120
+
121
+ @client = nil
122
+ @current_server = nil
123
+ @request_count = 0
124
+ end
125
+
126
+ private
127
+
128
+ # Calls all callbacks of the specified type with the given args
129
+ def do_callbacks(callback_type_sym, *args)
130
+ return unless @callbacks[callback_type_sym]
131
+ @callbacks[callback_type_sym].each do |callback|
132
+ callback.call(*args)
133
+ end
134
+ end
135
+
136
+ def next_live_server
137
+ @server_index ||= 0
138
+ @server_list.length.times do |i|
139
+ cur = (1 + @server_index + i) % @server_list.length
140
+ if @server_list[cur].up?
141
+ @server_index = cur
142
+ return @server_list[cur]
143
+ end
144
+ end
145
+ no_servers_available!
146
+ end
147
+
148
+ def ensure_socket_alignment
149
+ incomplete = true
150
+ result = yield
151
+ incomplete = false
152
+ result
153
+ # Thrift exceptions get read off the wire. We can consider them complete requests
154
+ rescue Thrift::Exception => e
155
+ incomplete = false
156
+ raise e
157
+ ensure
158
+ disconnect! if incomplete
159
+ end
160
+
161
+ def handled_proxy(method_name, *args)
162
+ begin
163
+ connect!(method_name.to_sym) unless @client
164
+ if has_timeouts?
165
+ @client.timeout = timeout(method_name.to_sym)
166
+ end
167
+ @request_count += 1
168
+ do_callbacks(:before_method, method_name)
169
+ ensure_socket_alignment { @client.send(method_name, *args) }
170
+ rescue *@options[:exception_class_overrides] => e
171
+ raise_or_default(e, method_name)
172
+ rescue *@options[:exception_classes] => e
173
+ disconnect!(true)
174
+ tries ||= (@options[:retry_overrides][method_name.to_sym] || @options[:retries]) + 1
175
+ tries -= 1
176
+ if tries > 0
177
+ retry
178
+ else
179
+ raise_or_default(e, method_name)
180
+ end
181
+ rescue Exception => e
182
+ raise_or_default(e, method_name)
183
+ ensure
184
+ disconnect! if @options[:server_max_requests] && @request_count >= @options[:server_max_requests]
185
+ end
186
+ end
187
+
188
+ def raise_or_default(e, method_name)
189
+ if @options[:raise]
190
+ raise_wrapped_error(e, method_name)
191
+ else
192
+ @options[:defaults][method_name.to_sym]
193
+ end
194
+ end
195
+
196
+ def raise_wrapped_error(e, method_name)
197
+ do_callbacks(:on_exception, e, method_name)
198
+ if @options[:wrapped_exception_classes].include?(e.class)
199
+ raise @client_class.const_get(e.class.to_s.split('::').last), e.message, e.backtrace
200
+ else
201
+ raise e
202
+ end
203
+ end
204
+
205
+ def has_timeouts?
206
+ @has_timeouts ||= @options[:timeout_overrides].any? && transport_can_timeout?
207
+ end
208
+
209
+ def timeout(method = nil)
210
+ @options[:timeout_overrides][method] || @options[:timeout]
211
+ end
212
+
213
+ def transport_can_timeout?
214
+ if (@options[:transport_wrapper] || @options[:transport]).method_defined?(:timeout=)
215
+ true
216
+ else
217
+ warn "ThriftClient: Timeout overrides have no effect with with transport type #{(@options[:transport_wrapper] || @options[:transport])}"
218
+ false
219
+ end
220
+ end
221
+
222
+ def no_servers_available!
223
+ servers = @server_list.map { |s| s.to_s }.join(',')
224
+ raise ThriftClient::NoServersAvailable, "No live servers in [#{servers}]."
225
+ end
226
+ end
@@ -0,0 +1,25 @@
1
+ module ThriftHelpers
2
+ module Connection
3
+ class Base
4
+ attr_accessor :transport, :server
5
+
6
+ def initialize(transport, transport_wrapper, server, timeout)
7
+ @transport = transport
8
+ @transport_wrapper = transport_wrapper
9
+ @server = server
10
+ @timeout = timeout
11
+ end
12
+
13
+ def connect!
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def open?
18
+ @transport.open?
19
+ end
20
+
21
+ def close
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ module ThriftHelpers
2
+ module Connection
3
+ class Factory
4
+ def self.create(transport, transport_wrapper, server, timeout)
5
+ if transport == Thrift::HTTPClientTransport
6
+ Connection::HTTP.new(transport, transport_wrapper, server, timeout)
7
+ else
8
+ Connection::Socket.new(transport, transport_wrapper, server, timeout)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ module ThriftHelpers
2
+ module Connection
3
+ class HTTP < Base
4
+ def initialize(*args)
5
+ super *args
6
+
7
+ uri = parse_server(@server)
8
+ @transport = Thrift::HTTPClientTransport.new(@server)
9
+ end
10
+
11
+ def connect!
12
+ http = Net::HTTP.new(uri.host, uri.port)
13
+ http.use_ssl = uri.scheme == "https"
14
+ http.get(uri.path)
15
+ end
16
+
17
+ def open?
18
+ true
19
+ end
20
+
21
+ private
22
+ def parse_server(server)
23
+ uri = URI.parse(server)
24
+ raise ArgumentError, 'Servers must start with http' unless uri.scheme =~ /^http/
25
+ uri
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ module ThriftHelpers
2
+ module Connection
3
+ class Socket < Base
4
+ def initialize(*args)
5
+ super *args
6
+
7
+ host, port = parse_server(@server)
8
+ @transport = @transport.new(host, port.to_i, @timeout)
9
+ @transport = @transport_wrapper.new(@transport) if @transport_wrapper
10
+ end
11
+
12
+ def close
13
+ @transport.close
14
+ end
15
+
16
+ def connect!
17
+ @transport.open
18
+ end
19
+
20
+ private
21
+
22
+ def parse_server(server)
23
+ host, port = server.to_s.split(":")
24
+ raise ArgumentError, 'Servers must be in the form "host:port"' unless host and port
25
+ [host, port]
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,4 @@
1
+ require "thrift_client/connection/base"
2
+ require "thrift_client/connection/socket"
3
+ require "thrift_client/connection/http"
4
+ require "thrift_client/connection/factory"
@@ -0,0 +1,145 @@
1
+ raise RuntimeError, "The eventmachine transport requires Ruby 1.9.x" if RUBY_VERSION < '1.9.0'
2
+
3
+ require 'eventmachine'
4
+ require 'fiber'
5
+
6
+ # EventMachine-ready Thrift connection
7
+ # Should not be used with a transport wrapper since it already performs buffering in Ruby.
8
+ module Thrift
9
+ class EventMachineTransport < BaseTransport
10
+ def initialize(host, port=9090, timeout=5)
11
+ @host, @port, @timeout = host, port, timeout
12
+ @connection = nil
13
+ end
14
+
15
+ def open?
16
+ @connection && @connection.connected?
17
+ end
18
+
19
+ def open
20
+ fiber = Fiber.current
21
+ @connection = EventMachineConnection.connect(@host, @port, @timeout)
22
+ @connection.callback do
23
+ fiber.resume
24
+ end
25
+ @connection.errback do
26
+ fiber.resume
27
+ end
28
+ Fiber.yield
29
+
30
+ raise Thrift::TransportException, "Unable to connect to #{@host}:#{@port}" unless @connection.connected?
31
+ @connection
32
+ end
33
+
34
+ def close
35
+ @connection.close
36
+ end
37
+
38
+ def read(sz)
39
+ @connection.blocking_read(sz)
40
+ end
41
+
42
+ def write(buf)
43
+ @connection.send_data(buf)
44
+ end
45
+ end
46
+
47
+ module EventMachineConnection
48
+ GARBAGE_BUFFER_SIZE = 4096 # 4kB
49
+
50
+ include EM::Deferrable
51
+
52
+ def self.connect(host='localhost', port=9090, timeout=5, &block)
53
+ EM.connect(host, port, self, host, port) do |conn|
54
+ conn.pending_connect_timeout = timeout
55
+ end
56
+ end
57
+
58
+ def trap
59
+ begin
60
+ yield
61
+ rescue Exception => ex
62
+ puts ex.message
63
+ puts ex.backtrace.join("\n")
64
+ end
65
+ end
66
+
67
+ def initialize(host, port=9090)
68
+ @host, @port = host, port
69
+ @index = 0
70
+ @disconnected = 'not connected'
71
+ @buf = ''
72
+ end
73
+
74
+ def close
75
+ trap do
76
+ @disconnected = 'closed'
77
+ close_connection(true)
78
+ end
79
+ end
80
+
81
+ def blocking_read(size)
82
+ raise IOError, "lost connection to #{@host}:#{@port}: #{@disconnected}" if @disconnected
83
+ if can_read?(size)
84
+ yank(size)
85
+ else
86
+ raise ArgumentError, "Unexpected state" if @size or @callback
87
+
88
+ fiber = Fiber.current
89
+ @size = size
90
+ @callback = proc { |data|
91
+ fiber.resume(data)
92
+ }
93
+ Fiber.yield
94
+ end
95
+ end
96
+
97
+ def receive_data(data)
98
+ trap do
99
+ (@buf) << data
100
+
101
+ if @callback and can_read?(@size)
102
+ callback = @callback
103
+ data = yank(@size)
104
+ @callback = @size = nil
105
+ callback.call(data)
106
+ end
107
+ end
108
+ end
109
+
110
+ def connected?
111
+ !@disconnected
112
+ end
113
+
114
+ def connection_completed
115
+ @disconnected = nil
116
+ succeed
117
+ end
118
+
119
+ def unbind
120
+ if !@disconnected
121
+ @disconnected = 'unbound'
122
+ else
123
+ fail
124
+ end
125
+ end
126
+
127
+ def can_read?(size)
128
+ @buf.size >= @index + size
129
+ end
130
+
131
+ private
132
+
133
+ def yank(len)
134
+ data = @buf.slice(@index, len)
135
+ @index += len
136
+ @index = @buf.size if @index > @buf.size
137
+ if @index >= GARBAGE_BUFFER_SIZE
138
+ @buf = @buf.slice(@index..-1)
139
+ @index = 0
140
+ end
141
+ data
142
+ end
143
+
144
+ end
145
+ end
@@ -0,0 +1,108 @@
1
+ require 'thrift_client/connection'
2
+
3
+ module ThriftHelpers
4
+ class Server
5
+ class ServerMarkedDown < StandardError; end
6
+
7
+ def initialize(connection_string, client_class, options = {})
8
+ @connection_string = connection_string
9
+ @client_class = client_class
10
+ @options = options
11
+
12
+ @cached = @options.has_key?(:cached_connections) ? @options[:cached_connections] : true
13
+
14
+ @marked_down_til = nil
15
+ end
16
+
17
+ def mark_down!(til)
18
+ close(true)
19
+ @marked_down_til = Time.now + til
20
+ end
21
+
22
+ def up?
23
+ !down?
24
+ end
25
+
26
+ def down?
27
+ @marked_down_til && @marked_down_til > Time.now
28
+ end
29
+
30
+ def to_s
31
+ @connection_string
32
+ end
33
+
34
+ def connection
35
+ @connection ||= Connection::Factory.create(
36
+ @options[:transport], @options[:transport_wrapper],
37
+ @connection_string, @options[:connect_timeout])
38
+ end
39
+
40
+ def connect!
41
+ return if open?
42
+
43
+ self.timeout = @options[:connect_timeout]
44
+ connection.connect!
45
+ self.timeout = @options[:timeout]
46
+ end
47
+
48
+ def client
49
+ @client ||= begin
50
+ connect!
51
+
52
+ @client_class.new(
53
+ @options[:protocol].new(self, *@options[:protocol_extra_params]))
54
+ end
55
+ end
56
+
57
+ def open?
58
+ connection.open?
59
+ end
60
+
61
+ def close(teardown = false)
62
+ if teardown || !@cached
63
+ connection.close if open?
64
+ @client = nil
65
+ end
66
+ end
67
+
68
+ def transport
69
+ connection.transport
70
+ end
71
+
72
+ module TransportInterface
73
+ def read(sz)
74
+ transport.read(sz)
75
+ end
76
+
77
+ def read_byte
78
+ transport.read_byte
79
+ end
80
+
81
+ def read_into_buffer(buffer, size)
82
+ transport.read_into_buffer(buffer, size)
83
+ end
84
+
85
+ def read_all(sz)
86
+ transport.read_all(sz)
87
+ end
88
+
89
+ def write(buf)
90
+ transport.write(buf)
91
+ end
92
+ alias_method :<<, :write
93
+
94
+ def flush
95
+ transport.flush
96
+ end
97
+
98
+ def timeout=(timeout)
99
+ transport.timeout = timeout if transport.respond_to?(:timeout=)
100
+ end
101
+
102
+ def timeout
103
+ transport.timeout
104
+ end
105
+ end
106
+ include TransportInterface
107
+ end
108
+ end