fl-thrift_client 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +33 -0
- data/LICENSE +202 -0
- data/Manifest +23 -0
- data/README.rdoc +50 -0
- data/Rakefile +16 -0
- data/lib/thrift_client/abstract_thrift_client.rb +217 -0
- data/lib/thrift_client/connection/base.rb +19 -0
- data/lib/thrift_client/connection/factory.rb +11 -0
- data/lib/thrift_client/connection/http.rb +18 -0
- data/lib/thrift_client/connection/socket.rb +22 -0
- data/lib/thrift_client/connection.rb +5 -0
- data/lib/thrift_client/event_machine.rb +145 -0
- data/lib/thrift_client/simple.rb +263 -0
- data/lib/thrift_client/thrift.rb +21 -0
- data/lib/thrift_client.rb +45 -0
- data/test/greeter/greeter.rb +77 -0
- data/test/greeter/greeter.thrift +3 -0
- data/test/greeter/server.rb +40 -0
- data/test/multiple_working_servers_test.rb +112 -0
- data/test/simple_test.rb +137 -0
- data/test/test_helper.rb +12 -0
- data/test/thrift_client_http_test.rb +54 -0
- data/test/thrift_client_test.rb +166 -0
- data/thrift_client.gemspec +26 -0
- metadata +112 -0
@@ -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
|
+
@connection
|
30
|
+
end
|
31
|
+
|
32
|
+
def close
|
33
|
+
@connection.close
|
34
|
+
end
|
35
|
+
|
36
|
+
def read(sz)
|
37
|
+
@connection.blocking_read(sz)
|
38
|
+
end
|
39
|
+
|
40
|
+
def write(buf)
|
41
|
+
@connection.send_data(buf)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module EventMachineConnection
|
46
|
+
GARBAGE_BUFFER_SIZE = 4096 # 4kB
|
47
|
+
|
48
|
+
include EM::Deferrable
|
49
|
+
|
50
|
+
def self.connect(host='localhost', port=9090, timeout=5, &block)
|
51
|
+
EM.connect(host, port, self, host, port) do |conn|
|
52
|
+
conn.pending_connect_timeout = timeout
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def trap
|
57
|
+
begin
|
58
|
+
yield
|
59
|
+
rescue Exception => ex
|
60
|
+
puts ex.message
|
61
|
+
puts ex.backtrace.join("\n")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def initialize(host, port=9090)
|
66
|
+
@host, @port = host, port
|
67
|
+
@index = 0
|
68
|
+
@connected = false
|
69
|
+
@buf = ''
|
70
|
+
end
|
71
|
+
|
72
|
+
def close
|
73
|
+
trap do
|
74
|
+
@connected = false
|
75
|
+
close_connection(true)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def blocking_read(size)
|
80
|
+
raise IOError, "lost connection to #{@host}:#{@port}" unless @connected
|
81
|
+
trap do
|
82
|
+
if can_read?(size)
|
83
|
+
yank(size)
|
84
|
+
else
|
85
|
+
raise ArgumentError, "Unexpected state" if @size or @callback
|
86
|
+
|
87
|
+
fiber = Fiber.current
|
88
|
+
@size = size
|
89
|
+
@callback = proc { |data|
|
90
|
+
fiber.resume(data)
|
91
|
+
}
|
92
|
+
Fiber.yield
|
93
|
+
end
|
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
|
+
@connected
|
112
|
+
end
|
113
|
+
|
114
|
+
def connection_completed
|
115
|
+
@connected = true
|
116
|
+
succeed
|
117
|
+
end
|
118
|
+
|
119
|
+
def unbind
|
120
|
+
if @connected
|
121
|
+
@connected = false
|
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,263 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'getoptlong'
|
3
|
+
|
4
|
+
class ThriftClient
|
5
|
+
|
6
|
+
# This is a simplified form of thrift, useful for clients only, and not
|
7
|
+
# making any attempt to have good performance. It's intended to be used by
|
8
|
+
# small command-line tools that don't want to install a dozen ruby files.
|
9
|
+
module Simple
|
10
|
+
VERSION_1 = 0x8001
|
11
|
+
|
12
|
+
# message types
|
13
|
+
CALL, REPLY, EXCEPTION = (1..3).to_a
|
14
|
+
|
15
|
+
# value types
|
16
|
+
STOP, VOID, BOOL, BYTE, DOUBLE, _, I16, _, I32, _, I64, STRING, STRUCT, MAP, SET, LIST = (0..15).to_a
|
17
|
+
|
18
|
+
FORMATS = {
|
19
|
+
BYTE => "c",
|
20
|
+
DOUBLE => "G",
|
21
|
+
I16 => "n",
|
22
|
+
I32 => "N",
|
23
|
+
}
|
24
|
+
|
25
|
+
SIZES = {
|
26
|
+
BYTE => 1,
|
27
|
+
DOUBLE => 8,
|
28
|
+
I16 => 2,
|
29
|
+
I32 => 4,
|
30
|
+
}
|
31
|
+
|
32
|
+
module ComplexType
|
33
|
+
module Extends
|
34
|
+
def type_id=(n)
|
35
|
+
@type_id = n
|
36
|
+
end
|
37
|
+
|
38
|
+
def type_id
|
39
|
+
@type_id
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module Includes
|
44
|
+
def to_i
|
45
|
+
self.class.type_id
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
args = self.values.map { |v| self.class.type_id == STRUCT ? v.name : v.to_s }.join(", ")
|
50
|
+
"#{self.class.name}.new(#{args})"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.make_type(type_id, name, *args)
|
56
|
+
klass = Struct.new("STT_#{name}", *args)
|
57
|
+
klass.send(:extend, ComplexType::Extends)
|
58
|
+
klass.send(:include, ComplexType::Includes)
|
59
|
+
klass.type_id = type_id
|
60
|
+
klass
|
61
|
+
end
|
62
|
+
|
63
|
+
ListType = make_type(LIST, "ListType", :element_type)
|
64
|
+
MapType = make_type(MAP, "MapType", :key_type, :value_type)
|
65
|
+
SetType = make_type(SET, "SetType", :element_type)
|
66
|
+
StructType = make_type(STRUCT, "StructType", :struct_class)
|
67
|
+
|
68
|
+
class << self
|
69
|
+
def pack_value(type, value)
|
70
|
+
case type
|
71
|
+
when BOOL
|
72
|
+
[ value ? 1 : 0 ].pack("c")
|
73
|
+
when STRING
|
74
|
+
[ value.size, value ].pack("Na*")
|
75
|
+
when I64
|
76
|
+
[ value >> 32, value & 0xffffffff ].pack("NN")
|
77
|
+
when ListType
|
78
|
+
[ type.element_type.to_i, value.size ].pack("cN") + value.map { |item| pack_value(type.element_type, item) }.join("")
|
79
|
+
when MapType
|
80
|
+
[ type.key_type.to_i, type.value_type.to_i, value.size ].pack("ccN") + value.map { |k, v| pack_value(type.key_type, k) + pack_value(type.value_type, v) }.join("")
|
81
|
+
when SetType
|
82
|
+
[ type.element_type.to_i, value.size ].pack("cN") + value.map { |item| pack_value(type.element_type, item) }.join("")
|
83
|
+
when StructType
|
84
|
+
value._pack
|
85
|
+
else
|
86
|
+
[ value ].pack(FORMATS[type])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def pack_request(method_name, arg_struct, request_id=0)
|
91
|
+
[ VERSION_1, CALL, method_name.to_s.size, method_name.to_s, request_id, arg_struct._pack ].pack("nnNa*Na*")
|
92
|
+
end
|
93
|
+
|
94
|
+
def read_value(s, type)
|
95
|
+
case type
|
96
|
+
when BOOL
|
97
|
+
s.read(1).unpack("c").first != 0
|
98
|
+
when STRING
|
99
|
+
len = s.read(4).unpack("N").first
|
100
|
+
s.read(len)
|
101
|
+
when I64
|
102
|
+
hi, lo = s.read(8).unpack("NN")
|
103
|
+
(hi << 32) | lo
|
104
|
+
when LIST
|
105
|
+
read_list(s)
|
106
|
+
when MAP
|
107
|
+
read_map(s)
|
108
|
+
when STRUCT
|
109
|
+
read_struct(s)
|
110
|
+
when ListType
|
111
|
+
read_list(s, type.element_type)
|
112
|
+
when MapType
|
113
|
+
read_map(s, type.key_type, type.value_type)
|
114
|
+
when StructType
|
115
|
+
read_struct(s, type.struct_class)
|
116
|
+
else
|
117
|
+
s.read(SIZES[type]).unpack(FORMATS[type]).first
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def read_list(s, element_type=nil)
|
122
|
+
etype, len = s.read(5).unpack("cN")
|
123
|
+
expected_type = (element_type and element_type.to_i == etype.to_i) ? element_type : etype
|
124
|
+
rv = []
|
125
|
+
len.times do
|
126
|
+
rv << read_value(s, expected_type)
|
127
|
+
end
|
128
|
+
rv
|
129
|
+
end
|
130
|
+
|
131
|
+
def read_map(s, key_type=nil, value_type=nil)
|
132
|
+
ktype, vtype, len = s.read(6).unpack("ccN")
|
133
|
+
rv = {}
|
134
|
+
expected_key_type, expected_value_type = if key_type and value_type and key_type.to_i == ktype and value_type.to_i == vtype
|
135
|
+
[ key_type, value_type ]
|
136
|
+
else
|
137
|
+
[ ktype, vtype ]
|
138
|
+
end
|
139
|
+
len.times do
|
140
|
+
key = read_value(s, expected_key_type)
|
141
|
+
value = read_value(s, expected_value_type)
|
142
|
+
rv[key] = value
|
143
|
+
end
|
144
|
+
rv
|
145
|
+
end
|
146
|
+
|
147
|
+
def read_struct(s, struct_class=nil)
|
148
|
+
rv = struct_class.new()
|
149
|
+
while true
|
150
|
+
type = s.read(1).unpack("c").first
|
151
|
+
return rv if type == STOP
|
152
|
+
fid = s.read(2).unpack("n").first
|
153
|
+
field = struct_class ? struct_class._fields.find { |f| (f.fid == fid) and (f.type.to_i == type) } : nil
|
154
|
+
value = read_value(s, field ? field.type : type)
|
155
|
+
rv[field.name] = value if field
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def read_response(s, rv_class)
|
160
|
+
version, message_type, method_name_len = s.read(8).unpack("nnN")
|
161
|
+
method_name = s.read(method_name_len)
|
162
|
+
seq_id = s.read(4).unpack("N").first
|
163
|
+
[ method_name, seq_id, read_struct(s, rv_class).rv ]
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
## ----------------------------------------
|
168
|
+
|
169
|
+
class Field
|
170
|
+
attr_accessor :name, :type, :fid
|
171
|
+
|
172
|
+
def initialize(name, type, fid)
|
173
|
+
@name = name
|
174
|
+
@type = type
|
175
|
+
@fid = fid
|
176
|
+
end
|
177
|
+
|
178
|
+
def pack(value)
|
179
|
+
value.nil? ? "" : [ type.to_i, fid, ThriftClient::Simple.pack_value(type, value) ].pack("cna*")
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
class ThriftException < RuntimeError
|
184
|
+
def initialize(reason)
|
185
|
+
@reason = reason
|
186
|
+
end
|
187
|
+
|
188
|
+
def to_s
|
189
|
+
"ThriftException(#{@reason.inspect})"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
module ThriftStruct
|
194
|
+
module Include
|
195
|
+
def _pack
|
196
|
+
self.class._fields.map { |f| f.pack(self[f.name]) }.join + [ STOP ].pack("c")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
module Extend
|
201
|
+
def _fields
|
202
|
+
@fields
|
203
|
+
end
|
204
|
+
|
205
|
+
def _fields=(f)
|
206
|
+
@fields = f
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.make_struct(name, *fields)
|
212
|
+
st_name = "ST_#{name}"
|
213
|
+
if Struct.constants.include?(st_name)
|
214
|
+
warn "#{caller[0]}: Struct::#{st_name} is already defined; returning original class."
|
215
|
+
Struct.const_get(st_name)
|
216
|
+
else
|
217
|
+
names = fields.map { |f| f.name.to_sym }
|
218
|
+
klass = Struct.new(st_name, *names)
|
219
|
+
klass.send(:include, ThriftStruct::Include)
|
220
|
+
klass.send(:extend, ThriftStruct::Extend)
|
221
|
+
klass._fields = fields
|
222
|
+
klass
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
class ThriftService
|
227
|
+
def initialize(sock)
|
228
|
+
@sock = sock
|
229
|
+
end
|
230
|
+
|
231
|
+
def self._arg_structs
|
232
|
+
@_arg_structs = {} if @_arg_structs.nil?
|
233
|
+
@_arg_structs
|
234
|
+
end
|
235
|
+
|
236
|
+
def self.thrift_method(name, rtype, *args)
|
237
|
+
arg_struct = ThriftClient::Simple.make_struct("Args__#{self.name}__#{name}", *args)
|
238
|
+
rv_struct = ThriftClient::Simple.make_struct("Retval__#{self.name}__#{name}", ThriftClient::Simple::Field.new(:rv, rtype, 0))
|
239
|
+
_arg_structs[name.to_sym] = [ arg_struct, rv_struct ]
|
240
|
+
|
241
|
+
arg_names = args.map { |a| a.name.to_s }.join(", ")
|
242
|
+
class_eval "def #{name}(#{arg_names}); _proxy(:#{name}#{args.size > 0 ? ', ' : ''}#{arg_names}); end"
|
243
|
+
end
|
244
|
+
|
245
|
+
def _proxy(method_name, *args)
|
246
|
+
cls = self.class.ancestors.find { |cls| cls.respond_to?(:_arg_structs) and cls._arg_structs[method_name.to_sym] }
|
247
|
+
arg_class, rv_class = cls._arg_structs[method_name.to_sym]
|
248
|
+
arg_struct = arg_class.new(*args)
|
249
|
+
@sock.write(ThriftClient::Simple.pack_request(method_name, arg_struct))
|
250
|
+
rv = ThriftClient::Simple.read_response(@sock, rv_class)
|
251
|
+
rv[2]
|
252
|
+
end
|
253
|
+
|
254
|
+
# convenience. robey is lazy.
|
255
|
+
[[ :field, "Field.new" ], [ :struct, "StructType.new" ],
|
256
|
+
[ :list, "ListType.new" ], [ :map, "MapType.new" ]].each do |new_name, old_name|
|
257
|
+
class_eval "def self.#{new_name}(*args); ThriftClient::Simple::#{old_name}(*args); end"
|
258
|
+
end
|
259
|
+
|
260
|
+
[ :void, :bool, :byte, :double, :i16, :i32, :i64, :string ].each { |sym| class_eval "def self.#{sym}; ThriftClient::Simple::#{sym.to_s.upcase}; end" }
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Thrift
|
2
|
+
class BufferedTransport
|
3
|
+
def timeout=(timeout)
|
4
|
+
@transport.timeout = timeout
|
5
|
+
end
|
6
|
+
|
7
|
+
def timeout
|
8
|
+
@transport.timeout
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Client
|
13
|
+
def timeout=(timeout)
|
14
|
+
@iprot.trans.timeout = timeout
|
15
|
+
end
|
16
|
+
|
17
|
+
def timeout
|
18
|
+
@iprot.trans.timeout
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
if ENV["ANCIENT_THRIFT"]
|
2
|
+
$LOAD_PATH.unshift("/Users/eweaver/p/twitter/rails/vendor/gems/thrift-751142/lib")
|
3
|
+
$LOAD_PATH.unshift("/Users/eweaver/p/twitter/rails/vendor/gems/thrift-751142/ext")
|
4
|
+
require 'thrift'
|
5
|
+
else
|
6
|
+
require 'rubygems'
|
7
|
+
require 'thrift'
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'rubygems'
|
11
|
+
require 'thrift_client/thrift'
|
12
|
+
require 'thrift_client/connection'
|
13
|
+
require 'thrift_client/abstract_thrift_client'
|
14
|
+
|
15
|
+
class ThriftClient < AbstractThriftClient
|
16
|
+
# This error is for backwards compatibility only. If defined in
|
17
|
+
# RetryingThriftClient instead, causes the test suite will break.
|
18
|
+
class NoServersAvailable < StandardError; end
|
19
|
+
include RetryingThriftClient
|
20
|
+
include TimingOutThriftClient
|
21
|
+
|
22
|
+
=begin rdoc
|
23
|
+
Create a new ThriftClient instance. Accepts an internal Thrift client class (such as CassandraRb::Client), a list of servers with ports, and optional parameters.
|
24
|
+
|
25
|
+
Valid optional parameters are:
|
26
|
+
|
27
|
+
<tt>:protocol</tt>:: Which Thrift protocol to use. Defaults to <tt>Thrift::BinaryProtocol</tt>.
|
28
|
+
<tt>:protocol_extra_params</tt>:: An array of additional parameters to pass to the protocol initialization call. Defaults to <tt>[]</tt>.
|
29
|
+
<tt>:transport</tt>:: Which Thrift transport to use. Defaults to <tt>Thrift::FramedTransport</tt>.
|
30
|
+
<tt>:randomize_server_list</tt>:: Whether to connect to the servers randomly, instead of in order. Defaults to <tt>true</tt>.
|
31
|
+
<tt>:exception_classes</tt>:: Which exceptions to catch and retry when sending a request. Defaults to <tt>[IOError, Thrift::Exception, Thrift::ProtocolException, Thrift::ApplicationException, Thrift::TransportException, NoServersAvailable]</tt>
|
32
|
+
<tt>:raise</tt>:: Whether to reraise errors if no responsive servers are found. Defaults to <tt>true</tt>.
|
33
|
+
<tt>:retries</tt>:: How many times to retry a request. Defaults to the number of servers defined.
|
34
|
+
<tt>:server_retry_period</tt>:: How many seconds to wait before trying to reconnect after marking all servers as down. Defaults to <tt>1</tt>. Set to <tt>nil</tt> to retry endlessly.
|
35
|
+
<tt>:server_max_requests</tt>:: How many requests to perform before moving on to the next server in the pool, regardless of error status. Defaults to <tt>nil</tt> (no limit).
|
36
|
+
<tt>:timeout</tt>:: Specify the default timeout in seconds. Defaults to <tt>1</tt>.
|
37
|
+
<tt>:timeout_overrides</tt>:: Specify additional timeouts on a per-method basis, in seconds. Only works with <tt>Thrift::BufferedTransport</tt>.
|
38
|
+
<tt>:defaults</tt>:: Specify default values to return on a per-method basis, if <tt>:raise</tt> is set to false.
|
39
|
+
|
40
|
+
=end rdoc
|
41
|
+
|
42
|
+
def initialize(client_class, servers, options = {})
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#
|
2
|
+
# Autogenerated by Thrift
|
3
|
+
#
|
4
|
+
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'thrift'
|
8
|
+
|
9
|
+
module Greeter
|
10
|
+
class Client
|
11
|
+
include ::Thrift::Client
|
12
|
+
|
13
|
+
def greeting(name)
|
14
|
+
send_greeting(name)
|
15
|
+
return recv_greeting()
|
16
|
+
end
|
17
|
+
|
18
|
+
def send_greeting(name)
|
19
|
+
send_message('greeting', Greeting_args, :name => name)
|
20
|
+
end
|
21
|
+
|
22
|
+
def recv_greeting()
|
23
|
+
result = receive_message(Greeting_result)
|
24
|
+
return result.success unless result.success.nil?
|
25
|
+
raise ::Thrift::ApplicationException.new(::Thrift::ApplicationException::MISSING_RESULT, 'greeting failed: unknown result')
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
class Processor
|
31
|
+
include ::Thrift::Processor
|
32
|
+
|
33
|
+
def process_greeting(seqid, iprot, oprot)
|
34
|
+
args = read_args(iprot, Greeting_args)
|
35
|
+
result = Greeting_result.new()
|
36
|
+
result.success = @handler.greeting(args.name)
|
37
|
+
write_result(result, oprot, 'greeting', seqid)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
# HELPER FUNCTIONS AND STRUCTURES
|
43
|
+
|
44
|
+
class Greeting_args
|
45
|
+
include ::Thrift::Struct
|
46
|
+
NAME = 1
|
47
|
+
|
48
|
+
::Thrift::Struct.field_accessor self, :name
|
49
|
+
FIELDS = {
|
50
|
+
NAME => {:type => ::Thrift::Types::STRING, :name => 'name'}
|
51
|
+
}
|
52
|
+
|
53
|
+
def struct_fields; FIELDS; end
|
54
|
+
|
55
|
+
def validate
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
class Greeting_result
|
61
|
+
include ::Thrift::Struct
|
62
|
+
SUCCESS = 0
|
63
|
+
|
64
|
+
::Thrift::Struct.field_accessor self, :success
|
65
|
+
FIELDS = {
|
66
|
+
SUCCESS => {:type => ::Thrift::Types::STRING, :name => 'success'}
|
67
|
+
}
|
68
|
+
|
69
|
+
def struct_fields; FIELDS; end
|
70
|
+
|
71
|
+
def validate
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Greeter
|
2
|
+
class Handler
|
3
|
+
def greeting(name)
|
4
|
+
"hello there #{name}!"
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class Server
|
9
|
+
def initialize(port)
|
10
|
+
@port = port
|
11
|
+
handler = Greeter::Handler.new
|
12
|
+
processor = Greeter::Processor.new(handler)
|
13
|
+
transport = Thrift::ServerSocket.new("127.0.0.1", port)
|
14
|
+
transportFactory = Thrift::FramedTransportFactory.new()
|
15
|
+
@server = Thrift::SimpleServer.new(processor, transport, transportFactory)
|
16
|
+
end
|
17
|
+
|
18
|
+
def serve
|
19
|
+
@server.serve()
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# client:
|
24
|
+
# trans = Thrift::HTTPClientTransport.new("http://127.0.0.1:9292/greeter")
|
25
|
+
# prot = Thrift::BinaryProtocol.new(trans)
|
26
|
+
# c = Greeter::Client.new(prot)
|
27
|
+
class HTTPServer
|
28
|
+
def initialize(uri)
|
29
|
+
uri = URI.parse(uri)
|
30
|
+
handler = Greeter::Handler.new
|
31
|
+
processor = Greeter::Processor.new(handler)
|
32
|
+
path = uri.path[1..-1]
|
33
|
+
@server = Thrift::MongrelHTTPServer.new(processor, :port => uri.port, :ip => uri.host, :path => path)
|
34
|
+
end
|
35
|
+
|
36
|
+
def serve
|
37
|
+
@server.serve()
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/test_helper"
|
2
|
+
|
3
|
+
class MultipleWorkingServersTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@servers = ["127.0.0.1:1461", "127.0.0.1:1462", "127.0.0.1:1463"]
|
6
|
+
@socket = 1461
|
7
|
+
@timeout = 0.2
|
8
|
+
@options = {:protocol_extra_params => [false]}
|
9
|
+
@pids = []
|
10
|
+
@servers.each do |s|
|
11
|
+
@pids << Process.fork do
|
12
|
+
Signal.trap("INT") { exit }
|
13
|
+
Greeter::Server.new(s.split(':').last).serve
|
14
|
+
end
|
15
|
+
end
|
16
|
+
# Need to give the child process a moment to open the listening socket or
|
17
|
+
# we get occasional "could not connect" errors in tests.
|
18
|
+
sleep 0.05
|
19
|
+
end
|
20
|
+
|
21
|
+
def teardown
|
22
|
+
@pids.each do |pid|
|
23
|
+
Process.kill("INT", pid)
|
24
|
+
Process.wait(pid)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_server_creates_new_client_that_can_talk_to_all_servers_after_disconnect
|
29
|
+
client = ThriftClient.new(Greeter::Client, @servers, @options)
|
30
|
+
client.greeting("someone")
|
31
|
+
internal_client = client.client
|
32
|
+
client.greeting("someone")
|
33
|
+
assert_equal internal_client, client.client # Sanity check
|
34
|
+
|
35
|
+
client.disconnect!
|
36
|
+
client.greeting("someone")
|
37
|
+
internal_client = client.client
|
38
|
+
client.greeting("someone")
|
39
|
+
assert_equal internal_client, client.client
|
40
|
+
internal_client = client.client
|
41
|
+
client.greeting("someone")
|
42
|
+
assert_equal internal_client, client.client
|
43
|
+
|
44
|
+
# Moves on to the second server
|
45
|
+
assert_nothing_raised {
|
46
|
+
client.greeting("someone")
|
47
|
+
client.greeting("someone")
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_server_doesnt_max_out_after_explicit_disconnect
|
52
|
+
client = ThriftClient.new(Greeter::Client, @servers, @options.merge(:server_max_requests => 2))
|
53
|
+
client.greeting("someone")
|
54
|
+
internal_client = client.client
|
55
|
+
client.greeting("someone")
|
56
|
+
assert_equal internal_client, client.client # Sanity check
|
57
|
+
|
58
|
+
client.disconnect!
|
59
|
+
|
60
|
+
client.greeting("someone")
|
61
|
+
internal_client = client.client
|
62
|
+
client.greeting("someone")
|
63
|
+
assert_equal internal_client, client.client, "ThriftClient should not have reset the internal client if the counter was reset on disconnect"
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_server_disconnect_doesnt_drop_servers_with_retry_period
|
67
|
+
client = ThriftClient.new(Greeter::Client, @servers, @options.merge(:server_max_requests => 2, :retry_period => 1))
|
68
|
+
3.times {
|
69
|
+
client.greeting("someone")
|
70
|
+
internal_client = client.client
|
71
|
+
client.greeting("someone")
|
72
|
+
assert_equal internal_client, client.client # Sanity check
|
73
|
+
|
74
|
+
client.disconnect!
|
75
|
+
|
76
|
+
client.greeting("someone")
|
77
|
+
internal_client = client.client
|
78
|
+
client.greeting("someone")
|
79
|
+
assert_equal internal_client, client.client, "ThriftClient should not have reset the internal client if the counter was reset on disconnect"
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
def test_server_max_requests
|
85
|
+
client = ThriftClient.new(Greeter::Client, @servers, @options.merge(:server_max_requests => 2))
|
86
|
+
|
87
|
+
client.greeting("someone")
|
88
|
+
internal_client = client.client
|
89
|
+
|
90
|
+
client.greeting("someone")
|
91
|
+
assert_equal internal_client, client.client
|
92
|
+
|
93
|
+
# This next call maxes out the requests for that "client" object
|
94
|
+
# and moves on to the next.
|
95
|
+
client.greeting("someone")
|
96
|
+
assert_not_equal internal_client, new_client = client.client
|
97
|
+
|
98
|
+
# And here we should still have the same client as the last one...
|
99
|
+
client.greeting("someone")
|
100
|
+
assert_equal new_client, client.client
|
101
|
+
|
102
|
+
# Until we max it out, too.
|
103
|
+
client.greeting("someone")
|
104
|
+
assert_not_equal new_client, client.client
|
105
|
+
assert_not_nil client.client
|
106
|
+
|
107
|
+
new_new_client = client.client
|
108
|
+
# And we should still have one server left
|
109
|
+
client.greeting("someone")
|
110
|
+
assert_equal new_new_client, client.client
|
111
|
+
end
|
112
|
+
end
|