thrift 0.23.0 → 0.25.0
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 +4 -4
- data/README.md +203 -40
- data/ext/binary_protocol_accelerated.c +130 -40
- data/ext/bytes.c +14 -0
- data/ext/compact_protocol.c +184 -91
- data/ext/constants.h +3 -0
- data/ext/extconf.rb +12 -4
- data/ext/macros.h +11 -0
- data/ext/memory_buffer.c +67 -12
- data/ext/struct.c +192 -71
- data/ext/thrift_native.c +7 -0
- data/lib/thrift/bytes.rb +6 -3
- data/lib/thrift/client.rb +31 -33
- data/lib/thrift/exceptions.rb +11 -8
- data/lib/thrift/multiplexed_processor.rb +17 -8
- data/lib/thrift/processor.rb +25 -3
- data/lib/thrift/protocol/base_protocol.rb +66 -31
- data/lib/thrift/protocol/binary_protocol.rb +80 -37
- data/lib/thrift/protocol/binary_protocol_accelerated.rb +7 -2
- data/lib/thrift/protocol/compact_protocol.rb +204 -88
- data/lib/thrift/protocol/header_protocol.rb +15 -3
- data/lib/thrift/protocol/json_protocol.rb +158 -109
- data/lib/thrift/protocol/multiplexed_protocol.rb +3 -3
- data/lib/thrift/protocol/protocol_decorator.rb +6 -2
- data/lib/thrift/serializer/deserializer.rb +1 -0
- data/lib/thrift/serializer/serializer.rb +7 -0
- data/lib/thrift/server/base_server.rb +11 -2
- data/lib/thrift/server/nonblocking_server.rb +69 -20
- data/lib/thrift/server/rack_application.rb +69 -0
- data/lib/thrift/server/simple_server.rb +27 -15
- data/lib/thrift/server/thin_http_server.rb +21 -44
- data/lib/thrift/server/thread_pool_server.rb +21 -17
- data/lib/thrift/server/threaded_server.rb +22 -18
- data/lib/thrift/struct.rb +34 -18
- data/lib/thrift/struct_union.rb +37 -36
- data/lib/thrift/thrift_native.rb +2 -1
- data/lib/thrift/transport/base_server_transport.rb +3 -0
- data/lib/thrift/transport/base_transport.rb +26 -4
- data/lib/thrift/transport/buffered_transport.rb +5 -4
- data/lib/thrift/transport/framed_transport.rb +33 -7
- data/lib/thrift/transport/header_transport.rb +159 -33
- data/lib/thrift/transport/http_client_transport.rb +28 -14
- data/lib/thrift/transport/io_stream_transport.rb +3 -1
- data/lib/thrift/transport/memory_buffer_transport.rb +14 -9
- data/lib/thrift/transport/server_socket.rb +10 -8
- data/lib/thrift/transport/socket.rb +85 -48
- data/lib/thrift/transport/ssl_server_socket.rb +67 -6
- data/lib/thrift/transport/ssl_socket.rb +73 -14
- data/lib/thrift/transport/unix_server_socket.rb +6 -2
- data/lib/thrift/transport/unix_socket.rb +5 -6
- data/lib/thrift/types.rb +23 -22
- data/lib/thrift/union.rb +21 -11
- data/lib/thrift/uuid.rb +9 -8
- data/lib/thrift.rb +40 -39
- metadata +7 -266
- data/benchmark/Benchmark.thrift +0 -24
- data/benchmark/benchmark.rb +0 -285
- data/benchmark/client.rb +0 -117
- data/benchmark/server.rb +0 -120
- data/benchmark/thin_server.rb +0 -45
- data/lib/thrift/server/mongrel_http_server.rb +0 -60
- data/spec/BaseService.thrift +0 -27
- data/spec/ExtendedService.thrift +0 -25
- data/spec/Referenced.thrift +0 -44
- data/spec/ThriftNamespacedSpec.thrift +0 -53
- data/spec/ThriftSpec.thrift +0 -187
- data/spec/base_protocol_spec.rb +0 -224
- data/spec/base_transport_spec.rb +0 -387
- data/spec/binary_protocol_accelerated_spec.rb +0 -46
- data/spec/binary_protocol_spec.rb +0 -72
- data/spec/binary_protocol_spec_shared.rb +0 -445
- data/spec/bytes_spec.rb +0 -99
- data/spec/client_spec.rb +0 -166
- data/spec/compact_protocol_spec.rb +0 -196
- data/spec/constants_demo_spec.rb +0 -101
- data/spec/exception_spec.rb +0 -140
- data/spec/flat_spec.rb +0 -62
- data/spec/header_protocol_spec.rb +0 -475
- data/spec/header_transport_spec.rb +0 -386
- data/spec/http_client_spec.rb +0 -159
- data/spec/json_protocol_spec.rb +0 -552
- data/spec/namespaced_spec.rb +0 -66
- data/spec/nonblocking_server_spec.rb +0 -361
- data/spec/processor_spec.rb +0 -79
- data/spec/serializer_spec.rb +0 -66
- data/spec/server_socket_spec.rb +0 -84
- data/spec/server_spec.rb +0 -186
- data/spec/socket_spec.rb +0 -67
- data/spec/socket_spec_shared.rb +0 -167
- data/spec/spec_helper.rb +0 -69
- data/spec/ssl_server_socket_spec.rb +0 -45
- data/spec/ssl_socket_spec.rb +0 -87
- data/spec/struct_nested_containers_spec.rb +0 -190
- data/spec/struct_spec.rb +0 -397
- data/spec/support/header_protocol_helper.rb +0 -54
- data/spec/thin_http_server_spec.rb +0 -126
- data/spec/types_spec.rb +0 -117
- data/spec/union_spec.rb +0 -272
- data/spec/unix_socket_spec.rb +0 -115
- data/spec/uuid_validation_spec.rb +0 -238
- data/test/fuzz/Makefile.am +0 -173
- data/test/fuzz/README.md +0 -149
- data/test/fuzz/fuzz_common.rb +0 -95
- data/test/fuzz/fuzz_parse_binary_protocol.rb +0 -22
- data/test/fuzz/fuzz_parse_binary_protocol_accelerated.rb +0 -22
- data/test/fuzz/fuzz_parse_binary_protocol_accelerated_harness.rb +0 -22
- data/test/fuzz/fuzz_parse_binary_protocol_harness.rb +0 -22
- data/test/fuzz/fuzz_parse_compact_protocol.rb +0 -22
- data/test/fuzz/fuzz_parse_compact_protocol_harness.rb +0 -22
- data/test/fuzz/fuzz_parse_json_protocol.rb +0 -22
- data/test/fuzz/fuzz_parse_json_protocol_harness.rb +0 -22
- data/test/fuzz/fuzz_roundtrip_binary_protocol.rb +0 -22
- data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated.rb +0 -22
- data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated_harness.rb +0 -22
- data/test/fuzz/fuzz_roundtrip_binary_protocol_harness.rb +0 -22
- data/test/fuzz/fuzz_roundtrip_compact_protocol.rb +0 -22
- data/test/fuzz/fuzz_roundtrip_compact_protocol_harness.rb +0 -22
- data/test/fuzz/fuzz_roundtrip_json_protocol.rb +0 -22
- data/test/fuzz/fuzz_roundtrip_json_protocol_harness.rb +0 -22
- data/test/fuzz/fuzz_tracer.rb +0 -28
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# encoding: ascii-8bit
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
#
|
|
3
4
|
# Licensed to the Apache Software Foundation (ASF) under one
|
|
4
5
|
# or more contributor license agreements. See the NOTICE file
|
|
@@ -18,8 +19,8 @@
|
|
|
18
19
|
# under the License.
|
|
19
20
|
#
|
|
20
21
|
|
|
21
|
-
require
|
|
22
|
-
require
|
|
22
|
+
require "stringio"
|
|
23
|
+
require "zlib"
|
|
23
24
|
|
|
24
25
|
module Thrift
|
|
25
26
|
# Client type constants for Header protocol
|
|
@@ -70,9 +71,18 @@ module Thrift
|
|
|
70
71
|
# Header magic value (first 2 bytes of header)
|
|
71
72
|
HEADER_MAGIC = 0x0FFF
|
|
72
73
|
|
|
73
|
-
#
|
|
74
|
+
# The largest frame the length field can express (~1GB). This is the
|
|
75
|
+
# protocol's ceiling, not a sensible default -- see DEFAULT_MAX_FRAME_SIZE.
|
|
74
76
|
MAX_FRAME_SIZE = 0x3FFFFFFF
|
|
75
77
|
|
|
78
|
+
# The largest frame accepted by default (~15.6 MB, matches other Thrift
|
|
79
|
+
# bindings). Defaulting to MAX_FRAME_SIZE let a four-byte header ask for a
|
|
80
|
+
# gigabyte, which is the ceiling rather than a limit.
|
|
81
|
+
DEFAULT_MAX_FRAME_SIZE = 16_384_000
|
|
82
|
+
|
|
83
|
+
# Default decompressed-size cap for ZLIB transform (~15.6 MB, matches other Thrift bindings)
|
|
84
|
+
DEFAULT_MAX_DECOMPRESSED_SIZE = 16_384_000
|
|
85
|
+
|
|
76
86
|
# Binary protocol version mask and version 1
|
|
77
87
|
BINARY_VERSION_MASK = 0xffff0000
|
|
78
88
|
BINARY_VERSION_1 = 0x80010000
|
|
@@ -82,6 +92,9 @@ module Thrift
|
|
|
82
92
|
COMPACT_VERSION_MASK = 0x1f
|
|
83
93
|
COMPACT_VERSION = 0x01
|
|
84
94
|
|
|
95
|
+
MAX_VARINT32_BYTES = 5
|
|
96
|
+
MAX_VARINT32_LAST_BYTE = 0x0f
|
|
97
|
+
|
|
85
98
|
attr_reader :protocol_id, :sequence_id, :flags
|
|
86
99
|
|
|
87
100
|
# Creates a new HeaderTransport wrapping the given transport.
|
|
@@ -99,7 +112,7 @@ module Thrift
|
|
|
99
112
|
HeaderClientType::FRAMED_BINARY,
|
|
100
113
|
HeaderClientType::UNFRAMED_BINARY,
|
|
101
114
|
HeaderClientType::FRAMED_COMPACT,
|
|
102
|
-
HeaderClientType::UNFRAMED_COMPACT
|
|
115
|
+
HeaderClientType::UNFRAMED_COMPACT,
|
|
103
116
|
]
|
|
104
117
|
|
|
105
118
|
@read_buffer = StringIO.new(Bytes.empty_byte_buffer)
|
|
@@ -111,7 +124,9 @@ module Thrift
|
|
|
111
124
|
|
|
112
125
|
@sequence_id = 0
|
|
113
126
|
@flags = 0
|
|
114
|
-
@max_frame_size =
|
|
127
|
+
@max_frame_size = DEFAULT_MAX_FRAME_SIZE
|
|
128
|
+
@max_decompressed_size = DEFAULT_MAX_DECOMPRESSED_SIZE
|
|
129
|
+
@unframed_bytes_read = 0
|
|
115
130
|
end
|
|
116
131
|
|
|
117
132
|
def sequence_id=(sequence_id)
|
|
@@ -122,6 +137,14 @@ module Thrift
|
|
|
122
137
|
@sequence_id = sequence_id
|
|
123
138
|
end
|
|
124
139
|
|
|
140
|
+
def protocol_id=(protocol_id)
|
|
141
|
+
unless [HeaderSubprotocolID::BINARY, HeaderSubprotocolID::COMPACT].include?(protocol_id)
|
|
142
|
+
raise ArgumentError, "Unknown protocol ID: #{protocol_id}"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
@protocol_id = protocol_id
|
|
146
|
+
end
|
|
147
|
+
|
|
125
148
|
def open?
|
|
126
149
|
@transport.open?
|
|
127
150
|
end
|
|
@@ -164,6 +187,9 @@ module Thrift
|
|
|
164
187
|
@write_transforms << transform_id unless @write_transforms.include?(transform_id)
|
|
165
188
|
end
|
|
166
189
|
|
|
190
|
+
# The maximum frame size currently in force
|
|
191
|
+
attr_reader :max_frame_size
|
|
192
|
+
|
|
167
193
|
# Sets the maximum allowed frame size
|
|
168
194
|
def set_max_frame_size(size)
|
|
169
195
|
if size <= 0 || size > MAX_FRAME_SIZE
|
|
@@ -172,6 +198,14 @@ module Thrift
|
|
|
172
198
|
@max_frame_size = size
|
|
173
199
|
end
|
|
174
200
|
|
|
201
|
+
# Sets the maximum allowed decompressed size for ZLIB transforms
|
|
202
|
+
def set_max_decompressed_size(size)
|
|
203
|
+
if size <= 0 || size > MAX_FRAME_SIZE
|
|
204
|
+
raise ArgumentError, "max_decompressed_size must be > 0 and <= #{MAX_FRAME_SIZE}"
|
|
205
|
+
end
|
|
206
|
+
@max_decompressed_size = size
|
|
207
|
+
end
|
|
208
|
+
|
|
175
209
|
def read(sz)
|
|
176
210
|
# Try reading from existing buffer
|
|
177
211
|
data = @read_buffer.read(sz)
|
|
@@ -183,7 +217,7 @@ module Thrift
|
|
|
183
217
|
# Handle unframed passthrough - read directly from underlying transport
|
|
184
218
|
if @client_type == HeaderClientType::UNFRAMED_BINARY ||
|
|
185
219
|
@client_type == HeaderClientType::UNFRAMED_COMPACT
|
|
186
|
-
return data +
|
|
220
|
+
return data + read_unframed(bytes_left)
|
|
187
221
|
end
|
|
188
222
|
|
|
189
223
|
# Need to read the next frame
|
|
@@ -201,16 +235,15 @@ module Thrift
|
|
|
201
235
|
@write_buffer = StringIO.new(Bytes.empty_byte_buffer)
|
|
202
236
|
|
|
203
237
|
return if payload.empty?
|
|
204
|
-
if payload.bytesize > @max_frame_size
|
|
205
|
-
raise TransportException.new(TransportException::UNKNOWN, "Attempting to send frame that is too large")
|
|
206
|
-
end
|
|
207
238
|
|
|
208
239
|
case @client_type
|
|
209
240
|
when HeaderClientType::HEADERS
|
|
210
241
|
flush_header_format(payload)
|
|
211
242
|
when HeaderClientType::FRAMED_BINARY, HeaderClientType::FRAMED_COMPACT
|
|
243
|
+
validate_frame_size!(payload.bytesize)
|
|
212
244
|
flush_framed(payload)
|
|
213
245
|
when HeaderClientType::UNFRAMED_BINARY, HeaderClientType::UNFRAMED_COMPACT
|
|
246
|
+
validate_frame_size!(payload.bytesize)
|
|
214
247
|
@transport.write(payload)
|
|
215
248
|
@transport.flush
|
|
216
249
|
else
|
|
@@ -219,7 +252,7 @@ module Thrift
|
|
|
219
252
|
end
|
|
220
253
|
|
|
221
254
|
def to_s
|
|
222
|
-
"header(#{@transport
|
|
255
|
+
"header(#{@transport})"
|
|
223
256
|
end
|
|
224
257
|
|
|
225
258
|
# Reads the next frame to detect protocol/client type before decoding.
|
|
@@ -229,6 +262,17 @@ module Thrift
|
|
|
229
262
|
read_frame(0)
|
|
230
263
|
end
|
|
231
264
|
|
|
265
|
+
def message_boundaries?
|
|
266
|
+
true
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Starts a new protocol message without forcing client-type detection.
|
|
270
|
+
def reset_message_size
|
|
271
|
+
return unless @read_buffer.nil? || @read_buffer.eof?
|
|
272
|
+
|
|
273
|
+
@unframed_bytes_read = 0
|
|
274
|
+
end
|
|
275
|
+
|
|
232
276
|
private
|
|
233
277
|
|
|
234
278
|
# Sets the client type after validation
|
|
@@ -241,9 +285,16 @@ module Thrift
|
|
|
241
285
|
|
|
242
286
|
# Reads the next frame, detecting client type on first read
|
|
243
287
|
def read_frame(req_sz)
|
|
288
|
+
@read_headers = {}
|
|
289
|
+
@unframed_bytes_read = 0
|
|
290
|
+
|
|
244
291
|
# Read first 4 bytes - could be frame length or protocol magic
|
|
245
|
-
|
|
246
|
-
|
|
292
|
+
begin
|
|
293
|
+
first_word = @transport.read_all(4)
|
|
294
|
+
rescue EOFError
|
|
295
|
+
raise TransportException.new(TransportException::END_OF_FILE, "Unexpected EOF reading frame size")
|
|
296
|
+
end
|
|
297
|
+
frame_size = first_word.unpack1("N")
|
|
247
298
|
|
|
248
299
|
# Check for unframed binary protocol
|
|
249
300
|
if (frame_size & BINARY_VERSION_MASK) == BINARY_VERSION_1
|
|
@@ -266,16 +317,23 @@ module Thrift
|
|
|
266
317
|
if frame_size > @max_frame_size
|
|
267
318
|
raise TransportException.new(TransportException::UNKNOWN, "Frame size #{frame_size} exceeds maximum #{@max_frame_size}")
|
|
268
319
|
end
|
|
320
|
+
if frame_size < 4
|
|
321
|
+
raise TransportException.new(TransportException::UNKNOWN, "Frame size #{frame_size} is too small")
|
|
322
|
+
end
|
|
269
323
|
|
|
270
324
|
# Read the complete frame
|
|
271
|
-
|
|
325
|
+
begin
|
|
326
|
+
frame_data = @transport.read_all(frame_size)
|
|
327
|
+
rescue EOFError
|
|
328
|
+
raise TransportException.new(TransportException::END_OF_FILE, "Unexpected EOF reading frame")
|
|
329
|
+
end
|
|
272
330
|
frame_buf = StringIO.new(frame_data)
|
|
273
331
|
|
|
274
332
|
# Check the second word for protocol type
|
|
275
333
|
second_word = frame_buf.read(4)
|
|
276
334
|
frame_buf.rewind
|
|
277
335
|
|
|
278
|
-
magic = second_word.
|
|
336
|
+
magic = second_word.unpack1("n")
|
|
279
337
|
|
|
280
338
|
if magic == HEADER_MAGIC
|
|
281
339
|
if frame_size < 10
|
|
@@ -283,7 +341,7 @@ module Thrift
|
|
|
283
341
|
end
|
|
284
342
|
set_client_type(HeaderClientType::HEADERS)
|
|
285
343
|
@read_buffer = parse_header_format(frame_buf)
|
|
286
|
-
elsif (second_word.
|
|
344
|
+
elsif (second_word.unpack1("N") & BINARY_VERSION_MASK) == BINARY_VERSION_1
|
|
287
345
|
set_client_type(HeaderClientType::FRAMED_BINARY)
|
|
288
346
|
@protocol_id = HeaderSubprotocolID::BINARY
|
|
289
347
|
@read_buffer = frame_buf
|
|
@@ -299,26 +357,45 @@ module Thrift
|
|
|
299
357
|
|
|
300
358
|
# Handles unframed protocol - puts first_word back in buffer
|
|
301
359
|
def handle_unframed(first_word, req_sz)
|
|
360
|
+
@unframed_bytes_read = first_word.bytesize
|
|
361
|
+
raise_unframed_size_limit if @unframed_bytes_read > @max_frame_size
|
|
362
|
+
|
|
302
363
|
bytes_left = req_sz - 4
|
|
303
|
-
if bytes_left > 0
|
|
304
|
-
rest =
|
|
364
|
+
if bytes_left > 0 && @unframed_bytes_read < @max_frame_size
|
|
365
|
+
rest = read_unframed(bytes_left)
|
|
305
366
|
@read_buffer = StringIO.new(first_word + rest)
|
|
306
367
|
else
|
|
307
368
|
@read_buffer = StringIO.new(first_word)
|
|
308
369
|
end
|
|
309
370
|
end
|
|
310
371
|
|
|
372
|
+
def read_unframed(size)
|
|
373
|
+
remaining = @max_frame_size - @unframed_bytes_read
|
|
374
|
+
raise_unframed_size_limit if remaining <= 0
|
|
375
|
+
|
|
376
|
+
data = @transport.read([size, remaining].min)
|
|
377
|
+
@unframed_bytes_read += data.bytesize
|
|
378
|
+
data
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def raise_unframed_size_limit
|
|
382
|
+
raise TransportException.new(
|
|
383
|
+
TransportException::SIZE_LIMIT,
|
|
384
|
+
"Unframed message size exceeds maximum #{@max_frame_size}",
|
|
385
|
+
)
|
|
386
|
+
end
|
|
387
|
+
|
|
311
388
|
# Parses a Header format frame
|
|
312
389
|
def parse_header_format(buf)
|
|
313
390
|
# Skip magic (already identified)
|
|
314
391
|
buf.read(2)
|
|
315
392
|
|
|
316
393
|
# Read flags and sequence ID
|
|
317
|
-
@flags = buf.read(2).
|
|
318
|
-
@sequence_id = signed_int32(buf.read(4).
|
|
394
|
+
@flags = buf.read(2).unpack1("n")
|
|
395
|
+
@sequence_id = signed_int32(buf.read(4).unpack1("N"))
|
|
319
396
|
|
|
320
397
|
# Read header length (in 32-bit words)
|
|
321
|
-
header_words = buf.read(2).
|
|
398
|
+
header_words = buf.read(2).unpack1("n")
|
|
322
399
|
if header_words >= 16_384
|
|
323
400
|
raise TransportException.new(TransportException::UNKNOWN, "Header size is unreasonable")
|
|
324
401
|
end
|
|
@@ -343,7 +420,6 @@ module Thrift
|
|
|
343
420
|
transforms << transform_id
|
|
344
421
|
end
|
|
345
422
|
# Read info headers
|
|
346
|
-
@read_headers = {}
|
|
347
423
|
while buf.pos < end_of_headers
|
|
348
424
|
info_type = read_varint32(buf, end_of_headers)
|
|
349
425
|
if info_type == 0
|
|
@@ -369,13 +445,39 @@ module Thrift
|
|
|
369
445
|
payload = buf.read
|
|
370
446
|
transforms.each do |transform_id|
|
|
371
447
|
if transform_id == HeaderTransformID::ZLIB
|
|
372
|
-
payload =
|
|
448
|
+
payload = bounded_inflate(payload)
|
|
373
449
|
end
|
|
374
450
|
end
|
|
375
451
|
|
|
376
452
|
StringIO.new(payload)
|
|
377
453
|
end
|
|
378
454
|
|
|
455
|
+
# Inflates +compressed+ with a running byte-count check against @max_decompressed_size.
|
|
456
|
+
# Raises TransportException::SIZE_LIMIT if the decompressed output would exceed the limit.
|
|
457
|
+
def bounded_inflate(compressed)
|
|
458
|
+
inflater = Zlib::Inflate.new
|
|
459
|
+
buffer = Bytes.empty_byte_buffer
|
|
460
|
+
append_chunk = lambda do |chunk|
|
|
461
|
+
if buffer.bytesize + chunk.bytesize > @max_decompressed_size
|
|
462
|
+
raise TransportException.new(
|
|
463
|
+
TransportException::SIZE_LIMIT,
|
|
464
|
+
"Decompressed size exceeds limit of #{@max_decompressed_size}",
|
|
465
|
+
)
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
buffer << chunk
|
|
469
|
+
end
|
|
470
|
+
begin
|
|
471
|
+
inflater.inflate(compressed, &append_chunk)
|
|
472
|
+
inflater.finish(&append_chunk)
|
|
473
|
+
buffer
|
|
474
|
+
rescue Zlib::DataError, Zlib::BufError
|
|
475
|
+
raise TransportException.new(TransportException::UNKNOWN, "Invalid ZLIB payload")
|
|
476
|
+
ensure
|
|
477
|
+
inflater.close rescue nil
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
|
|
379
481
|
# Flushes data in Header format
|
|
380
482
|
def flush_header_format(payload)
|
|
381
483
|
# Apply transforms
|
|
@@ -403,7 +505,6 @@ module Thrift
|
|
|
403
505
|
write_varstring(header_buf, key)
|
|
404
506
|
write_varstring(header_buf, value)
|
|
405
507
|
end
|
|
406
|
-
@write_headers = {}
|
|
407
508
|
end
|
|
408
509
|
|
|
409
510
|
# Pad header to 4-byte boundary
|
|
@@ -414,14 +515,16 @@ module Thrift
|
|
|
414
515
|
# Calculate total frame size (excludes the 4-byte length field itself)
|
|
415
516
|
# Frame = magic(2) + flags(2) + seqid(4) + header_len(2) + header_data + payload
|
|
416
517
|
frame_size = 2 + 2 + 4 + 2 + header_data.bytesize + payload.bytesize
|
|
518
|
+
validate_frame_size!(frame_size)
|
|
519
|
+
@write_headers = {}
|
|
417
520
|
|
|
418
521
|
# Write complete frame
|
|
419
522
|
frame = Bytes.empty_byte_buffer
|
|
420
|
-
frame << [frame_size].pack(
|
|
421
|
-
frame << [HEADER_MAGIC].pack(
|
|
422
|
-
frame << [@flags].pack(
|
|
423
|
-
frame << [unsigned_int32(@sequence_id)].pack(
|
|
424
|
-
frame << [header_data.bytesize / 4].pack(
|
|
523
|
+
frame << [frame_size].pack("N") # Length
|
|
524
|
+
frame << [HEADER_MAGIC].pack("n") # Magic
|
|
525
|
+
frame << [@flags].pack("n") # Flags
|
|
526
|
+
frame << [unsigned_int32(@sequence_id)].pack("N") # Sequence ID
|
|
527
|
+
frame << [header_data.bytesize / 4].pack("n") # Header length (in 32-bit words)
|
|
425
528
|
frame << header_data # Header data
|
|
426
529
|
frame << payload # Payload
|
|
427
530
|
|
|
@@ -429,9 +532,18 @@ module Thrift
|
|
|
429
532
|
@transport.flush
|
|
430
533
|
end
|
|
431
534
|
|
|
535
|
+
def validate_frame_size!(frame_size)
|
|
536
|
+
return if frame_size <= @max_frame_size
|
|
537
|
+
|
|
538
|
+
raise TransportException.new(
|
|
539
|
+
TransportException::UNKNOWN,
|
|
540
|
+
"Frame size #{frame_size} exceeds maximum #{@max_frame_size}",
|
|
541
|
+
)
|
|
542
|
+
end
|
|
543
|
+
|
|
432
544
|
# Flushes data in simple framed format (for legacy compatibility)
|
|
433
545
|
def flush_framed(payload)
|
|
434
|
-
frame = [payload.bytesize].pack(
|
|
546
|
+
frame = [payload.bytesize].pack("N") + payload
|
|
435
547
|
@transport.write(frame)
|
|
436
548
|
@transport.flush
|
|
437
549
|
end
|
|
@@ -446,21 +558,35 @@ module Thrift
|
|
|
446
558
|
end
|
|
447
559
|
byte = io.getbyte
|
|
448
560
|
raise TransportException.new(TransportException::END_OF_FILE, "Unexpected EOF reading varint") if byte.nil?
|
|
561
|
+
|
|
562
|
+
if shift == (MAX_VARINT32_BYTES - 1) * 7
|
|
563
|
+
if (byte & 0x80) != 0
|
|
564
|
+
raise TransportException.new(TransportException::UNKNOWN, "Variable-length int over 5 bytes.")
|
|
565
|
+
end
|
|
566
|
+
if (byte & ~MAX_VARINT32_LAST_BYTE) != 0
|
|
567
|
+
raise TransportException.new(TransportException::UNKNOWN, "Variable-length int overflows uint32.")
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
|
|
449
571
|
result |= (byte & 0x7f) << shift
|
|
450
|
-
|
|
572
|
+
return result if (byte & 0x80) == 0
|
|
451
573
|
shift += 7
|
|
452
574
|
end
|
|
453
|
-
result
|
|
454
575
|
end
|
|
455
576
|
|
|
456
577
|
# Writes a varint32 to the given IO
|
|
457
578
|
def write_varint32(io, n)
|
|
579
|
+
# Encode the low 32 bits: Ruby's >> is arithmetic, so a negative value
|
|
580
|
+
# would keep its sign bit set and never satisfy the (n & ~0x7F) == 0 exit
|
|
581
|
+
# condition below. Masking yields the same unsigned varint read_varint32
|
|
582
|
+
# produces and accepts.
|
|
583
|
+
n &= 0xFFFFFFFF
|
|
458
584
|
loop do
|
|
459
585
|
if (n & ~0x7F) == 0
|
|
460
|
-
io.write([n].pack(
|
|
586
|
+
io.write([n].pack("C"))
|
|
461
587
|
break
|
|
462
588
|
else
|
|
463
|
-
io.write([(n & 0x7F) | 0x80].pack(
|
|
589
|
+
io.write([(n & 0x7F) | 0x80].pack("C"))
|
|
464
590
|
n >>= 7
|
|
465
591
|
end
|
|
466
592
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# encoding: ascii-8bit
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
#
|
|
3
4
|
# Licensed to the Apache Software Foundation (ASF) under one
|
|
4
5
|
# or more contributor license agreements. See the NOTICE file
|
|
@@ -18,24 +19,31 @@
|
|
|
18
19
|
# under the License.
|
|
19
20
|
#
|
|
20
21
|
|
|
21
|
-
require
|
|
22
|
-
require
|
|
23
|
-
require
|
|
24
|
-
require
|
|
25
|
-
require
|
|
22
|
+
require "net/http"
|
|
23
|
+
require "net/https"
|
|
24
|
+
require "openssl"
|
|
25
|
+
require "uri"
|
|
26
|
+
require "stringio"
|
|
26
27
|
|
|
27
28
|
module Thrift
|
|
28
29
|
class HTTPClientTransport < BaseTransport
|
|
29
|
-
|
|
30
30
|
def initialize(url, opts = {})
|
|
31
31
|
@url = URI url
|
|
32
|
-
@headers = {
|
|
32
|
+
@headers = {"Content-Type" => "application/x-thrift"}
|
|
33
33
|
@outbuf = Bytes.empty_byte_buffer
|
|
34
34
|
@ssl_verify_mode = opts.fetch(:ssl_verify_mode, OpenSSL::SSL::VERIFY_PEER)
|
|
35
|
+
@ssl_ca_file = opts[:ssl_ca_file]
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def open?; true end
|
|
38
|
-
|
|
39
|
+
|
|
40
|
+
def read(sz)
|
|
41
|
+
data = @inbuf.read sz
|
|
42
|
+
return data unless data.nil?
|
|
43
|
+
|
|
44
|
+
raise TransportException.new(TransportException::END_OF_FILE, "#{self.class.name} reached EOF reading response from #{self}, HTTP status code #{@response_code}")
|
|
45
|
+
end
|
|
46
|
+
|
|
39
47
|
def write(buf); @outbuf << Bytes.force_binary_encoding(buf) end
|
|
40
48
|
|
|
41
49
|
def add_headers(headers)
|
|
@@ -44,20 +52,26 @@ module Thrift
|
|
|
44
52
|
|
|
45
53
|
def flush
|
|
46
54
|
http = Net::HTTP.new @url.host, @url.port
|
|
47
|
-
http.use_ssl = @url.scheme ==
|
|
48
|
-
|
|
55
|
+
http.use_ssl = @url.scheme == "https"
|
|
56
|
+
if @url.scheme == "https"
|
|
57
|
+
http.verify_mode = @ssl_verify_mode
|
|
58
|
+
http.ca_file = @ssl_ca_file if @ssl_ca_file
|
|
59
|
+
end
|
|
49
60
|
resp = http.post(@url.request_uri, @outbuf, @headers)
|
|
50
|
-
|
|
61
|
+
response_code = resp.code.to_i
|
|
62
|
+
raise TransportException.new(TransportException::UNKNOWN, "#{self.class.name} Could not connect to #{self}, HTTP status code #{response_code}") unless (200..299).cover?(response_code)
|
|
51
63
|
|
|
52
|
-
|
|
53
|
-
data = Bytes.force_binary_encoding(
|
|
64
|
+
@response_code = response_code
|
|
65
|
+
data = Bytes.force_binary_encoding(resp.body || Bytes.empty_byte_buffer)
|
|
54
66
|
@inbuf = StringIO.new data
|
|
55
67
|
ensure
|
|
56
68
|
@outbuf = Bytes.empty_byte_buffer
|
|
57
69
|
end
|
|
58
70
|
|
|
59
71
|
def to_s
|
|
60
|
-
|
|
72
|
+
path = @url.path.to_s
|
|
73
|
+
path = "/" if path.empty?
|
|
74
|
+
"#{@url.scheme}(#{@url.host}:#{@url.port}#{path})"
|
|
61
75
|
end
|
|
62
76
|
end
|
|
63
77
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# encoding: ascii-8bit
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
#
|
|
3
4
|
# Licensed to the Apache Software Foundation (ASF) under one
|
|
4
5
|
# or more contributor license agreements. See the NOTICE file
|
|
@@ -35,8 +36,9 @@ module Thrift
|
|
|
35
36
|
def write(buf); @output.write(Bytes.force_binary_encoding(buf)) end
|
|
36
37
|
def close; @input.close; @output.close end
|
|
37
38
|
def to_io; @input end # we're assuming this is used in a IO.select for reading
|
|
39
|
+
|
|
38
40
|
def to_s
|
|
39
|
-
"iostream(input=#{@input
|
|
41
|
+
"iostream(input=#{@input},output=#{@output})"
|
|
40
42
|
end
|
|
41
43
|
end
|
|
42
44
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# encoding: ascii-8bit
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
#
|
|
3
4
|
# Licensed to the Apache Software Foundation (ASF) under one
|
|
4
5
|
# or more contributor license agreements. See the NOTICE file
|
|
@@ -20,20 +21,16 @@
|
|
|
20
21
|
|
|
21
22
|
module Thrift
|
|
22
23
|
class MemoryBufferTransport < BaseTransport
|
|
23
|
-
GARBAGE_BUFFER_SIZE = 4*(2**10) # 4kB
|
|
24
|
+
GARBAGE_BUFFER_SIZE = 4 * (2**10) # 4kB
|
|
24
25
|
|
|
25
|
-
#
|
|
26
|
-
# unless you want it to be modified by #read and #write
|
|
27
|
-
#--
|
|
28
|
-
# this behavior is no longer required. If you wish to change it
|
|
29
|
-
# go ahead, just make sure the specs pass
|
|
26
|
+
# The transport copies the input buffer and keeps its own mutable storage.
|
|
30
27
|
def initialize(buffer = nil)
|
|
31
|
-
@buf = buffer ? Bytes.force_binary_encoding(buffer) : Bytes.empty_byte_buffer
|
|
28
|
+
@buf = buffer ? Bytes.force_binary_encoding(buffer.dup) : Bytes.empty_byte_buffer
|
|
32
29
|
@index = 0
|
|
33
30
|
end
|
|
34
31
|
|
|
35
32
|
def open?
|
|
36
|
-
|
|
33
|
+
true
|
|
37
34
|
end
|
|
38
35
|
|
|
39
36
|
def open
|
|
@@ -47,7 +44,7 @@ module Thrift
|
|
|
47
44
|
end
|
|
48
45
|
|
|
49
46
|
# this method does not use the passed object directly but copies it
|
|
50
|
-
def reset_buffer(new_buf =
|
|
47
|
+
def reset_buffer(new_buf = "")
|
|
51
48
|
@buf.replace Bytes.force_binary_encoding(new_buf)
|
|
52
49
|
@index = 0
|
|
53
50
|
end
|
|
@@ -57,6 +54,8 @@ module Thrift
|
|
|
57
54
|
end
|
|
58
55
|
|
|
59
56
|
def read(len)
|
|
57
|
+
raise TransportException.new(TransportException::NEGATIVE_SIZE, "Negative size") unless len >= 0
|
|
58
|
+
|
|
60
59
|
data = @buf.slice(@index, len)
|
|
61
60
|
@index += len
|
|
62
61
|
@index = @buf.size if @index > @buf.size
|
|
@@ -70,6 +69,12 @@ module Thrift
|
|
|
70
69
|
data
|
|
71
70
|
end
|
|
72
71
|
|
|
72
|
+
def read_all(size)
|
|
73
|
+
raise TransportException.new(TransportException::NEGATIVE_SIZE, "Negative size") unless size >= 0
|
|
74
|
+
|
|
75
|
+
read(size)
|
|
76
|
+
end
|
|
77
|
+
|
|
73
78
|
def read_byte
|
|
74
79
|
raise EOFError.new("Not enough bytes remain in buffer") if @index >= @buf.size
|
|
75
80
|
val = Bytes.get_string_byte(@buf, @index)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# encoding: ascii-8bit
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
#
|
|
3
4
|
# Licensed to the Apache Software Foundation (ASF) under one
|
|
4
5
|
# or more contributor license agreements. See the NOTICE file
|
|
@@ -18,12 +19,12 @@
|
|
|
18
19
|
# under the License.
|
|
19
20
|
#
|
|
20
21
|
|
|
21
|
-
require
|
|
22
|
+
require "socket"
|
|
22
23
|
|
|
23
24
|
module Thrift
|
|
24
25
|
class ServerSocket < BaseServerTransport
|
|
25
|
-
# call-seq: initialize(host = nil, port)
|
|
26
|
-
def initialize(host_or_port, port = nil)
|
|
26
|
+
# call-seq: initialize(host = nil, port, client_timeout: DEFAULT_CLIENT_TIMEOUT)
|
|
27
|
+
def initialize(host_or_port, port = nil, client_timeout: DEFAULT_CLIENT_TIMEOUT)
|
|
27
28
|
if port
|
|
28
29
|
@host = host_or_port
|
|
29
30
|
@port = port
|
|
@@ -31,10 +32,11 @@ module Thrift
|
|
|
31
32
|
@host = nil
|
|
32
33
|
@port = host_or_port
|
|
33
34
|
end
|
|
35
|
+
@client_timeout = client_timeout
|
|
34
36
|
@handle = nil
|
|
35
37
|
end
|
|
36
38
|
|
|
37
|
-
attr_reader :handle
|
|
39
|
+
attr_reader :handle, :client_timeout
|
|
38
40
|
|
|
39
41
|
def listen
|
|
40
42
|
@handle = TCPServer.new(@host, @port)
|
|
@@ -45,14 +47,15 @@ module Thrift
|
|
|
45
47
|
sock = @handle.accept
|
|
46
48
|
sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
|
|
47
49
|
trans = Socket.new
|
|
50
|
+
trans.timeout = @client_timeout
|
|
48
51
|
trans.handle = sock
|
|
49
52
|
trans
|
|
50
53
|
end
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
def close
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
@handle.close unless @handle.nil? or @handle.closed?
|
|
58
|
+
@handle = nil
|
|
56
59
|
end
|
|
57
60
|
|
|
58
61
|
def closed?
|
|
@@ -60,12 +63,11 @@ module Thrift
|
|
|
60
63
|
end
|
|
61
64
|
|
|
62
65
|
def to_io
|
|
63
|
-
@handle&.to_io || raise(IOError,
|
|
66
|
+
@handle&.to_io || raise(IOError, "closed stream")
|
|
64
67
|
end
|
|
65
68
|
|
|
66
69
|
def to_s
|
|
67
70
|
"socket(#{@host}:#{@port})"
|
|
68
71
|
end
|
|
69
|
-
|
|
70
72
|
end
|
|
71
73
|
end
|