fl-thrift_client 0.4.2
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.
- 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
|