thrift 0.0.751142
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 +2 -0
- data/COPYING +14 -0
- data/LICENSE +14 -0
- data/Makefile.am +15 -0
- data/Manifest +78 -0
- data/README +30 -0
- data/Rakefile +102 -0
- data/benchmark/Benchmark.thrift +5 -0
- data/benchmark/benchmark.rb +254 -0
- data/benchmark/client.rb +56 -0
- data/benchmark/gen-rb/BenchmarkService.rb +81 -0
- data/benchmark/gen-rb/Benchmark_constants.rb +11 -0
- data/benchmark/gen-rb/Benchmark_types.rb +10 -0
- data/benchmark/server.rb +64 -0
- data/benchmark/thin_server.rb +26 -0
- data/ext/binary_protocol_accelerated.c +463 -0
- data/ext/binary_protocol_accelerated.h +1 -0
- data/ext/constants.h +77 -0
- data/ext/extconf.rb +7 -0
- data/ext/memory_buffer.c +52 -0
- data/ext/memory_buffer.h +1 -0
- data/ext/protocol.c +166 -0
- data/ext/protocol.h +1 -0
- data/ext/struct.c +574 -0
- data/ext/struct.h +48 -0
- data/ext/thrift_native.c +173 -0
- data/lib/thrift/client.rb +44 -0
- data/lib/thrift/deprecation.rb +155 -0
- data/lib/thrift/exceptions.rb +65 -0
- data/lib/thrift/processor.rb +39 -0
- data/lib/thrift/protocol/binaryprotocol.rb +213 -0
- data/lib/thrift/protocol/binaryprotocolaccelerated.rb +19 -0
- data/lib/thrift/protocol/tbinaryprotocol.rb +2 -0
- data/lib/thrift/protocol/tprotocol.rb +2 -0
- data/lib/thrift/protocol.rb +270 -0
- data/lib/thrift/serializer.rb +27 -0
- data/lib/thrift/server/httpserver.rb +44 -0
- data/lib/thrift/server/nonblockingserver.rb +278 -0
- data/lib/thrift/server/thttpserver.rb +2 -0
- data/lib/thrift/server/tserver.rb +2 -0
- data/lib/thrift/server.rb +135 -0
- data/lib/thrift/struct.rb +272 -0
- data/lib/thrift/thrift.rb +14 -0
- data/lib/thrift/transport/httpclient.rb +29 -0
- data/lib/thrift/transport/socket.rb +167 -0
- data/lib/thrift/transport/thttpclient.rb +2 -0
- data/lib/thrift/transport/tsocket.rb +2 -0
- data/lib/thrift/transport/ttransport.rb +2 -0
- data/lib/thrift/transport/unixsocket.rb +58 -0
- data/lib/thrift/transport.rb +319 -0
- data/lib/thrift/types.rb +83 -0
- data/lib/thrift.rb +28 -0
- data/setup.rb +1585 -0
- data/spec/ThriftSpec.thrift +46 -0
- data/spec/backwards_compatibility_spec.rb +136 -0
- data/spec/binaryprotocol_spec.rb +45 -0
- data/spec/binaryprotocol_spec_shared.rb +274 -0
- data/spec/binaryprotocolaccelerated_spec.rb +101 -0
- data/spec/client_spec.rb +81 -0
- data/spec/deprecation_spec.rb +443 -0
- data/spec/exception_spec.rb +123 -0
- data/spec/gen-rb/NonblockingService.rb +268 -0
- data/spec/gen-rb/ThriftSpec_constants.rb +11 -0
- data/spec/gen-rb/ThriftSpec_types.rb +134 -0
- data/spec/httpclient_spec.rb +31 -0
- data/spec/httpserver_spec.rb +98 -0
- data/spec/nonblockingserver_spec.rb +245 -0
- data/spec/processor_spec.rb +64 -0
- data/spec/protocol_spec.rb +142 -0
- data/spec/serializer_spec.rb +52 -0
- data/spec/server_spec.rb +141 -0
- data/spec/socket_spec.rb +97 -0
- data/spec/socket_spec_shared.rb +85 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/struct_spec.rb +244 -0
- data/spec/transport_spec.rb +359 -0
- data/spec/types_spec.rb +98 -0
- data/spec/unixsocket_spec.rb +90 -0
- data/thrift.gemspec +33 -0
- data.tar.gz.sig +0 -0
- metadata +200 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,135 @@
|
|
1
|
+
# Copyright (c) 2006- Facebook
|
2
|
+
# Distributed under the Apache Software License
|
3
|
+
#
|
4
|
+
# See accompanying file LICENSE or visit the Thrift site at:
|
5
|
+
# http://developers.facebook.com/thrift/
|
6
|
+
#
|
7
|
+
# Author: Mark Slee <mcslee@facebook.com>
|
8
|
+
#
|
9
|
+
require 'thrift/protocol'
|
10
|
+
require 'thrift/protocol/binaryprotocol'
|
11
|
+
require 'thrift/transport'
|
12
|
+
|
13
|
+
module Thrift
|
14
|
+
class Server
|
15
|
+
def initialize(processor, serverTransport, transportFactory=nil, protocolFactory=nil)
|
16
|
+
@processor = processor
|
17
|
+
@serverTransport = serverTransport
|
18
|
+
@transportFactory = transportFactory ? transportFactory : Thrift::TransportFactory.new
|
19
|
+
@protocolFactory = protocolFactory ? protocolFactory : Thrift::BinaryProtocolFactory.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def serve; nil; end
|
23
|
+
end
|
24
|
+
deprecate_class! :TServer => Server
|
25
|
+
|
26
|
+
class SimpleServer < Server
|
27
|
+
def serve
|
28
|
+
begin
|
29
|
+
@serverTransport.listen
|
30
|
+
loop do
|
31
|
+
client = @serverTransport.accept
|
32
|
+
trans = @transportFactory.get_transport(client)
|
33
|
+
prot = @protocolFactory.get_protocol(trans)
|
34
|
+
begin
|
35
|
+
loop do
|
36
|
+
@processor.process(prot, prot)
|
37
|
+
end
|
38
|
+
rescue Thrift::TransportException, Thrift::ProtocolException
|
39
|
+
ensure
|
40
|
+
trans.close
|
41
|
+
end
|
42
|
+
end
|
43
|
+
ensure
|
44
|
+
@serverTransport.close
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
deprecate_class! :TSimpleServer => SimpleServer
|
49
|
+
end
|
50
|
+
|
51
|
+
# do *not* use fastthread
|
52
|
+
# it has a bug that triggers on NonblockingServer
|
53
|
+
require 'thread'
|
54
|
+
|
55
|
+
module Thrift
|
56
|
+
class ThreadedServer < Server
|
57
|
+
def serve
|
58
|
+
begin
|
59
|
+
@serverTransport.listen
|
60
|
+
loop do
|
61
|
+
client = @serverTransport.accept
|
62
|
+
trans = @transportFactory.get_transport(client)
|
63
|
+
prot = @protocolFactory.get_protocol(trans)
|
64
|
+
Thread.new(prot, trans) do |p, t|
|
65
|
+
begin
|
66
|
+
loop do
|
67
|
+
@processor.process(p, p)
|
68
|
+
end
|
69
|
+
rescue Thrift::TransportException, Thrift::ProtocolException
|
70
|
+
ensure
|
71
|
+
t.close
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
ensure
|
76
|
+
@serverTransport.close
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
deprecate_class! :TThreadedServer => ThreadedServer
|
81
|
+
|
82
|
+
class ThreadPoolServer < Server
|
83
|
+
def initialize(processor, serverTransport, transportFactory=nil, protocolFactory=nil, num=20)
|
84
|
+
super(processor, serverTransport, transportFactory, protocolFactory)
|
85
|
+
@thread_q = SizedQueue.new(num)
|
86
|
+
@exception_q = Queue.new
|
87
|
+
@running = false
|
88
|
+
end
|
89
|
+
|
90
|
+
## exceptions that happen in worker threads will be relayed here and
|
91
|
+
## must be caught. 'retry' can be used to continue. (threads will
|
92
|
+
## continue to run while the exception is being handled.)
|
93
|
+
def rescuable_serve
|
94
|
+
Thread.new { serve } unless @running
|
95
|
+
@running = true
|
96
|
+
raise @exception_q.pop
|
97
|
+
end
|
98
|
+
|
99
|
+
## exceptions that happen in worker threads simply cause that thread
|
100
|
+
## to die and another to be spawned in its place.
|
101
|
+
def serve
|
102
|
+
@serverTransport.listen
|
103
|
+
|
104
|
+
begin
|
105
|
+
loop do
|
106
|
+
@thread_q.push(:token)
|
107
|
+
Thread.new do
|
108
|
+
begin
|
109
|
+
loop do
|
110
|
+
client = @serverTransport.accept
|
111
|
+
trans = @transportFactory.get_transport(client)
|
112
|
+
prot = @protocolFactory.get_protocol(trans)
|
113
|
+
begin
|
114
|
+
loop do
|
115
|
+
@processor.process(prot, prot)
|
116
|
+
end
|
117
|
+
rescue Thrift::TransportException, Thrift::ProtocolException => e
|
118
|
+
ensure
|
119
|
+
trans.close
|
120
|
+
end
|
121
|
+
end
|
122
|
+
rescue => e
|
123
|
+
@exception_q.push(e)
|
124
|
+
ensure
|
125
|
+
@thread_q.pop # thread died!
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
ensure
|
130
|
+
@serverTransport.close
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
deprecate_class! :TThreadPoolServer => ThreadPoolServer
|
135
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
require 'thrift/types'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Thrift
|
5
|
+
module Struct
|
6
|
+
def initialize(d={})
|
7
|
+
# get a copy of the default values to work on, removing defaults in favor of arguments
|
8
|
+
fields_with_defaults = fields_with_default_values.dup
|
9
|
+
|
10
|
+
# check if the defaults is empty, or if there are no parameters for this
|
11
|
+
# instantiation, and if so, don't bother overriding defaults.
|
12
|
+
unless fields_with_defaults.empty? || d.empty?
|
13
|
+
d.each_key do |name|
|
14
|
+
fields_with_defaults.delete(name.to_s)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# assign all the user-specified arguments
|
19
|
+
unless d.empty?
|
20
|
+
d.each do |name, value|
|
21
|
+
unless name_to_id(name.to_s)
|
22
|
+
raise Exception, "Unknown key given to #{self.class}.new: #{name}"
|
23
|
+
end
|
24
|
+
Thrift.check_type(value, struct_fields[name_to_id(name.to_s)], name) if Thrift.type_checking
|
25
|
+
instance_variable_set("@#{name}", value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# assign all the default values
|
30
|
+
unless fields_with_defaults.empty?
|
31
|
+
fields_with_defaults.each do |name, default_value|
|
32
|
+
instance_variable_set("@#{name}", (default_value.dup rescue default_value))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def fields_with_default_values
|
38
|
+
fields_with_default_values = self.class.instance_variable_get("@fields_with_default_values")
|
39
|
+
unless fields_with_default_values
|
40
|
+
fields_with_default_values = {}
|
41
|
+
struct_fields.each do |fid, field_def|
|
42
|
+
if field_def[:default]
|
43
|
+
fields_with_default_values[field_def[:name]] = field_def[:default]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
self.class.instance_variable_set("@fields_with_default_values", fields_with_default_values)
|
47
|
+
end
|
48
|
+
fields_with_default_values
|
49
|
+
end
|
50
|
+
|
51
|
+
def name_to_id(name)
|
52
|
+
names_to_ids = self.class.instance_variable_get("@names_to_ids")
|
53
|
+
unless names_to_ids
|
54
|
+
names_to_ids = {}
|
55
|
+
struct_fields.each do |fid, field_def|
|
56
|
+
names_to_ids[field_def[:name]] = fid
|
57
|
+
end
|
58
|
+
self.class.instance_variable_set("@names_to_ids", names_to_ids)
|
59
|
+
end
|
60
|
+
names_to_ids[name]
|
61
|
+
end
|
62
|
+
|
63
|
+
# Obsoleted by THRIFT-246, which generates this method inline
|
64
|
+
# TODO: Should be removed at some point. -- Kevin Clark
|
65
|
+
def struct_fields
|
66
|
+
self.class.const_get(:FIELDS)
|
67
|
+
end
|
68
|
+
|
69
|
+
def each_field
|
70
|
+
struct_fields.each do |fid, data|
|
71
|
+
yield fid, data[:type], data[:name], data[:default], data[:optional]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def inspect(skip_optional_nulls = true)
|
76
|
+
fields = []
|
77
|
+
each_field do |fid, type, name, default, optional|
|
78
|
+
value = instance_variable_get("@#{name}")
|
79
|
+
unless skip_optional_nulls && optional && value.nil?
|
80
|
+
fields << "#{name}:#{value.inspect}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
"<#{self.class} #{fields.join(", ")}>"
|
84
|
+
end
|
85
|
+
|
86
|
+
def read(iprot)
|
87
|
+
# TODO(kevinclark): Make sure transport is C readable
|
88
|
+
if iprot.respond_to?(:decode_binary)
|
89
|
+
iprot.decode_binary(self, iprot.trans)
|
90
|
+
else
|
91
|
+
iprot.read_struct_begin
|
92
|
+
loop do
|
93
|
+
fname, ftype, fid = iprot.read_field_begin
|
94
|
+
break if (ftype == Types::STOP)
|
95
|
+
handle_message(iprot, fid, ftype)
|
96
|
+
iprot.read_field_end
|
97
|
+
end
|
98
|
+
iprot.read_struct_end
|
99
|
+
end
|
100
|
+
validate
|
101
|
+
end
|
102
|
+
|
103
|
+
def write(oprot)
|
104
|
+
validate
|
105
|
+
# if oprot.respond_to?(:encode_binary)
|
106
|
+
# # TODO(kevinclark): Clean this so I don't have to access the transport.
|
107
|
+
# oprot.trans.write oprot.encode_binary(self)
|
108
|
+
# else
|
109
|
+
oprot.write_struct_begin(self.class.name)
|
110
|
+
each_field do |fid, type, name|
|
111
|
+
unless (value = instance_variable_get("@#{name}")).nil?
|
112
|
+
if is_container? type
|
113
|
+
oprot.write_field_begin(name, type, fid)
|
114
|
+
write_container(oprot, value, struct_fields[fid])
|
115
|
+
oprot.write_field_end
|
116
|
+
else
|
117
|
+
oprot.write_field(name, type, fid, value)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
oprot.write_field_stop
|
122
|
+
oprot.write_struct_end
|
123
|
+
# end
|
124
|
+
end
|
125
|
+
|
126
|
+
def ==(other)
|
127
|
+
return false unless other.is_a?(self.class)
|
128
|
+
each_field do |fid, type, name, default|
|
129
|
+
return false unless self.instance_variable_get("@#{name}") == other.instance_variable_get("@#{name}")
|
130
|
+
end
|
131
|
+
true
|
132
|
+
end
|
133
|
+
|
134
|
+
def eql?(other)
|
135
|
+
self.class == other.class && self == other
|
136
|
+
end
|
137
|
+
|
138
|
+
# for the time being, we're ok with a naive hash. this could definitely be improved upon.
|
139
|
+
def hash
|
140
|
+
0
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.field_accessor(klass, *fields)
|
144
|
+
fields.each do |field|
|
145
|
+
klass.send :attr_reader, field
|
146
|
+
klass.send :define_method, "#{field}=" do |value|
|
147
|
+
Thrift.check_type(value, klass::FIELDS.values.find { |f| f[:name].to_s == field.to_s }, field) if Thrift.type_checking
|
148
|
+
instance_variable_set("@#{field}", value)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
protected
|
154
|
+
|
155
|
+
def self.append_features(mod)
|
156
|
+
if mod.ancestors.include? ::Exception
|
157
|
+
mod.send :class_variable_set, :'@@__thrift_struct_real_initialize', mod.instance_method(:initialize)
|
158
|
+
super
|
159
|
+
# set up our custom initializer so `raise Xception, 'message'` works
|
160
|
+
mod.send :define_method, :struct_initialize, mod.instance_method(:initialize)
|
161
|
+
mod.send :define_method, :initialize, mod.instance_method(:exception_initialize)
|
162
|
+
else
|
163
|
+
super
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def exception_initialize(*args, &block)
|
168
|
+
if args.size == 1 and args.first.is_a? Hash
|
169
|
+
# looks like it's a regular Struct initialize
|
170
|
+
method(:struct_initialize).call(args.first)
|
171
|
+
else
|
172
|
+
# call the Struct initializer first with no args
|
173
|
+
# this will set our field default values
|
174
|
+
method(:struct_initialize).call()
|
175
|
+
# now give it to the exception
|
176
|
+
self.class.send(:class_variable_get, :'@@__thrift_struct_real_initialize').bind(self).call(*args, &block) if args.size > 0
|
177
|
+
# self.class.instance_method(:initialize).bind(self).call(*args, &block)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def handle_message(iprot, fid, ftype)
|
182
|
+
field = struct_fields[fid]
|
183
|
+
if field and field[:type] == ftype
|
184
|
+
value = read_field(iprot, field)
|
185
|
+
instance_variable_set("@#{field[:name]}", value)
|
186
|
+
else
|
187
|
+
iprot.skip(ftype)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def read_field(iprot, field = {})
|
192
|
+
case field[:type]
|
193
|
+
when Types::STRUCT
|
194
|
+
value = field[:class].new
|
195
|
+
value.read(iprot)
|
196
|
+
when Types::MAP
|
197
|
+
key_type, val_type, size = iprot.read_map_begin
|
198
|
+
value = {}
|
199
|
+
size.times do
|
200
|
+
k = read_field(iprot, field_info(field[:key]))
|
201
|
+
v = read_field(iprot, field_info(field[:value]))
|
202
|
+
value[k] = v
|
203
|
+
end
|
204
|
+
iprot.read_map_end
|
205
|
+
when Types::LIST
|
206
|
+
e_type, size = iprot.read_list_begin
|
207
|
+
value = Array.new(size) do |n|
|
208
|
+
read_field(iprot, field_info(field[:element]))
|
209
|
+
end
|
210
|
+
iprot.read_list_end
|
211
|
+
when Types::SET
|
212
|
+
e_type, size = iprot.read_set_begin
|
213
|
+
value = Set.new
|
214
|
+
size.times do
|
215
|
+
element = read_field(iprot, field_info(field[:element]))
|
216
|
+
value << element
|
217
|
+
end
|
218
|
+
iprot.read_set_end
|
219
|
+
else
|
220
|
+
value = iprot.read_type(field[:type])
|
221
|
+
end
|
222
|
+
value
|
223
|
+
end
|
224
|
+
|
225
|
+
def write_data(oprot, value, field)
|
226
|
+
if is_container? field[:type]
|
227
|
+
write_container(oprot, value, field)
|
228
|
+
else
|
229
|
+
oprot.write_type(field[:type], value)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def write_container(oprot, value, field = {})
|
234
|
+
case field[:type]
|
235
|
+
when Types::MAP
|
236
|
+
oprot.write_map_begin(field[:key][:type], field[:value][:type], value.size)
|
237
|
+
value.each do |k, v|
|
238
|
+
write_data(oprot, k, field[:key])
|
239
|
+
write_data(oprot, v, field[:value])
|
240
|
+
end
|
241
|
+
oprot.write_map_end
|
242
|
+
when Types::LIST
|
243
|
+
oprot.write_list_begin(field[:element][:type], value.size)
|
244
|
+
value.each do |elem|
|
245
|
+
write_data(oprot, elem, field[:element])
|
246
|
+
end
|
247
|
+
oprot.write_list_end
|
248
|
+
when Types::SET
|
249
|
+
oprot.write_set_begin(field[:element][:type], value.size)
|
250
|
+
value.each do |v,| # the , is to preserve compatibility with the old Hash-style sets
|
251
|
+
write_data(oprot, v, field[:element])
|
252
|
+
end
|
253
|
+
oprot.write_set_end
|
254
|
+
else
|
255
|
+
raise "Not a container type: #{field[:type]}"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def is_container?(type)
|
260
|
+
[Types::LIST, Types::MAP, Types::SET].include? type
|
261
|
+
end
|
262
|
+
|
263
|
+
def field_info(field)
|
264
|
+
{ :type => field[:type],
|
265
|
+
:class => field[:class],
|
266
|
+
:key => field[:key],
|
267
|
+
:value => field[:value],
|
268
|
+
:element => field[:element] }
|
269
|
+
end
|
270
|
+
end
|
271
|
+
deprecate_module! :ThriftStruct => Struct
|
272
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# This file kept for backwards compatability
|
2
|
+
# require File.join(File.dirname(__FILE__), '../thrift')
|
3
|
+
$:.unshift File.dirname(File.dirname(__FILE__))
|
4
|
+
require 'thrift/deprecation'
|
5
|
+
require 'thrift/types'
|
6
|
+
require 'thrift/processor'
|
7
|
+
require 'thrift/exceptions'
|
8
|
+
require 'thrift/client'
|
9
|
+
require 'thrift/struct'
|
10
|
+
begin
|
11
|
+
require "thrift_native"
|
12
|
+
rescue
|
13
|
+
puts "Could not load thrift_native libraries. Using pure ruby version."
|
14
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'thrift/transport'
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'net/https'
|
5
|
+
require 'uri'
|
6
|
+
require 'stringio'
|
7
|
+
|
8
|
+
## Very simple HTTP client
|
9
|
+
module Thrift
|
10
|
+
class HTTPClient < Transport
|
11
|
+
def initialize(url)
|
12
|
+
@url = URI url
|
13
|
+
@outbuf = ""
|
14
|
+
end
|
15
|
+
|
16
|
+
def open?; true end
|
17
|
+
def read(sz); @inbuf.read sz end
|
18
|
+
def write(buf); @outbuf << buf end
|
19
|
+
def flush
|
20
|
+
http = Net::HTTP.new @url.host, @url.port
|
21
|
+
http.use_ssl = @url.scheme == "https"
|
22
|
+
headers = { 'Content-Type' => 'application/x-thrift' }
|
23
|
+
resp, data = http.post(@url.path, @outbuf, headers)
|
24
|
+
@inbuf = StringIO.new data
|
25
|
+
@outbuf = ""
|
26
|
+
end
|
27
|
+
end
|
28
|
+
deprecate_class! :THttpClient => HTTPClient
|
29
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
# Copyright (c) 2006- Facebook
|
2
|
+
# Distributed under the Apache Software License
|
3
|
+
#
|
4
|
+
# See accompanying file LICENSE or visit the Thrift site at:
|
5
|
+
# http://developers.facebook.com/thrift/
|
6
|
+
#
|
7
|
+
# Author: Mark Slee <mcslee@facebook.com>
|
8
|
+
#
|
9
|
+
require 'thrift/transport'
|
10
|
+
require 'socket'
|
11
|
+
|
12
|
+
module Thrift
|
13
|
+
class Socket < Transport
|
14
|
+
def initialize(host='localhost', port=9090, timeout=nil)
|
15
|
+
@host = host
|
16
|
+
@port = port
|
17
|
+
@timeout = timeout
|
18
|
+
@desc = "#{host}:#{port}"
|
19
|
+
@handle = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_accessor :handle, :timeout
|
23
|
+
|
24
|
+
def open
|
25
|
+
begin
|
26
|
+
addrinfo = ::Socket::getaddrinfo(@host, @port).first
|
27
|
+
@handle = ::Socket.new(addrinfo[4], ::Socket::SOCK_STREAM, 0)
|
28
|
+
sockaddr = ::Socket.sockaddr_in(addrinfo[1], addrinfo[3])
|
29
|
+
begin
|
30
|
+
@handle.connect_nonblock(sockaddr)
|
31
|
+
rescue Errno::EINPROGRESS
|
32
|
+
unless IO.select(nil, [ @handle ], nil, @timeout)
|
33
|
+
raise TransportException.new(TransportException::NOT_OPEN, "Connection timeout to #{@desc}")
|
34
|
+
end
|
35
|
+
begin
|
36
|
+
@handle.connect_nonblock(sockaddr)
|
37
|
+
rescue Errno::EISCONN
|
38
|
+
end
|
39
|
+
end
|
40
|
+
@handle
|
41
|
+
rescue StandardError => e
|
42
|
+
raise TransportException.new(TransportException::NOT_OPEN, "Could not connect to #{@desc}: #{e}")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def open?
|
47
|
+
!@handle.nil? and !@handle.closed?
|
48
|
+
end
|
49
|
+
|
50
|
+
def write(str)
|
51
|
+
raise IOError, "closed stream" unless open?
|
52
|
+
begin
|
53
|
+
if @timeout.nil? or @timeout == 0
|
54
|
+
@handle.write(str)
|
55
|
+
else
|
56
|
+
len = 0
|
57
|
+
start = Time.now
|
58
|
+
while Time.now - start < @timeout
|
59
|
+
rd, wr, = IO.select(nil, [@handle], nil, @timeout)
|
60
|
+
if wr and not wr.empty?
|
61
|
+
len += @handle.write_nonblock(str[len..-1])
|
62
|
+
break if len >= str.length
|
63
|
+
end
|
64
|
+
end
|
65
|
+
if len < str.length
|
66
|
+
raise TransportException.new(TransportException::TIMED_OUT, "Socket: Timed out writing #{str.length} bytes to #{@desc}")
|
67
|
+
else
|
68
|
+
len
|
69
|
+
end
|
70
|
+
end
|
71
|
+
rescue TransportException => e
|
72
|
+
# pass this on
|
73
|
+
raise e
|
74
|
+
rescue StandardError => e
|
75
|
+
@handle.close
|
76
|
+
@handle = nil
|
77
|
+
raise TransportException.new(TransportException::NOT_OPEN, e.message)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def read(sz)
|
82
|
+
raise IOError, "closed stream" unless open?
|
83
|
+
|
84
|
+
begin
|
85
|
+
if @timeout.nil? or @timeout == 0
|
86
|
+
data = @handle.readpartial(sz)
|
87
|
+
else
|
88
|
+
# it's possible to interrupt select for something other than the timeout
|
89
|
+
# so we need to ensure we've waited long enough
|
90
|
+
start = Time.now
|
91
|
+
rd = nil # scoping
|
92
|
+
loop do
|
93
|
+
rd, = IO.select([@handle], nil, nil, @timeout)
|
94
|
+
break if (rd and not rd.empty?) or Time.now - start >= @timeout
|
95
|
+
end
|
96
|
+
if rd.nil? or rd.empty?
|
97
|
+
raise TransportException.new(TransportException::TIMED_OUT, "Socket: Timed out reading #{sz} bytes from #{@desc}")
|
98
|
+
else
|
99
|
+
data = @handle.readpartial(sz)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
rescue TransportException => e
|
103
|
+
# don't let this get caught by the StandardError handler
|
104
|
+
raise e
|
105
|
+
rescue StandardError => e
|
106
|
+
@handle.close unless @handle.closed?
|
107
|
+
@handle = nil
|
108
|
+
raise TransportException.new(TransportException::NOT_OPEN, e.message)
|
109
|
+
end
|
110
|
+
if (data.nil? or data.length == 0)
|
111
|
+
raise TransportException.new(TransportException::UNKNOWN, "Socket: Could not read #{sz} bytes from #{@desc}")
|
112
|
+
end
|
113
|
+
data
|
114
|
+
end
|
115
|
+
|
116
|
+
def close
|
117
|
+
@handle.close unless @handle.nil? or @handle.closed?
|
118
|
+
@handle = nil
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_io
|
122
|
+
@handle
|
123
|
+
end
|
124
|
+
end
|
125
|
+
deprecate_class! :TSocket => Socket
|
126
|
+
|
127
|
+
class ServerSocket < ServerTransport
|
128
|
+
# call-seq: initialize(host = nil, port)
|
129
|
+
def initialize(host_or_port, port = nil)
|
130
|
+
if port
|
131
|
+
@host = host_or_port
|
132
|
+
@port = port
|
133
|
+
else
|
134
|
+
@host = nil
|
135
|
+
@port = host_or_port
|
136
|
+
end
|
137
|
+
@handle = nil
|
138
|
+
end
|
139
|
+
|
140
|
+
attr_reader :handle
|
141
|
+
|
142
|
+
def listen
|
143
|
+
@handle = TCPServer.new(@host, @port)
|
144
|
+
end
|
145
|
+
|
146
|
+
def accept
|
147
|
+
unless @handle.nil?
|
148
|
+
sock = @handle.accept
|
149
|
+
trans = Socket.new
|
150
|
+
trans.handle = sock
|
151
|
+
trans
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def close
|
156
|
+
@handle.close unless @handle.nil? or @handle.closed?
|
157
|
+
@handle = nil
|
158
|
+
end
|
159
|
+
|
160
|
+
def closed?
|
161
|
+
@handle.nil? or @handle.closed?
|
162
|
+
end
|
163
|
+
|
164
|
+
alias to_io handle
|
165
|
+
end
|
166
|
+
deprecate_class! :TServerSocket => ServerSocket
|
167
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'thrift/transport'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
module Thrift
|
5
|
+
class UNIXSocket < Socket
|
6
|
+
def initialize(path, timeout=nil)
|
7
|
+
@path = path
|
8
|
+
@timeout = timeout
|
9
|
+
@desc = @path # for read()'s error
|
10
|
+
@handle = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def open
|
14
|
+
begin
|
15
|
+
@handle = ::UNIXSocket.new(@path)
|
16
|
+
rescue StandardError
|
17
|
+
raise TransportException.new(TransportException::NOT_OPEN, "Could not open UNIX socket at #{@path}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class UNIXServerSocket < ServerTransport
|
23
|
+
def initialize(path)
|
24
|
+
@path = path
|
25
|
+
@handle = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_accessor :handle
|
29
|
+
|
30
|
+
def listen
|
31
|
+
@handle = ::UNIXServer.new(@path)
|
32
|
+
end
|
33
|
+
|
34
|
+
def accept
|
35
|
+
unless @handle.nil?
|
36
|
+
sock = @handle.accept
|
37
|
+
trans = UNIXSocket.new(nil)
|
38
|
+
trans.handle = sock
|
39
|
+
trans
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def close
|
44
|
+
if @handle
|
45
|
+
@handle.close unless @handle.closed?
|
46
|
+
@handle = nil
|
47
|
+
# UNIXServer doesn't delete the socket file, so we have to do it ourselves
|
48
|
+
File.delete(@path)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def closed?
|
53
|
+
@handle.nil? or @handle.closed?
|
54
|
+
end
|
55
|
+
|
56
|
+
alias to_io handle
|
57
|
+
end
|
58
|
+
end
|