thrift 0.24.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.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +107 -4
  3. data/ext/binary_protocol_accelerated.c +52 -17
  4. data/ext/compact_protocol.c +135 -70
  5. data/ext/constants.h +3 -0
  6. data/ext/extconf.rb +4 -4
  7. data/ext/macros.h +11 -0
  8. data/ext/memory_buffer.c +35 -17
  9. data/ext/struct.c +185 -64
  10. data/ext/thrift_native.c +6 -0
  11. data/lib/thrift/bytes.rb +2 -2
  12. data/lib/thrift/client.rb +31 -22
  13. data/lib/thrift/exceptions.rb +10 -8
  14. data/lib/thrift/multiplexed_processor.rb +5 -6
  15. data/lib/thrift/processor.rb +24 -3
  16. data/lib/thrift/protocol/base_protocol.rb +53 -27
  17. data/lib/thrift/protocol/binary_protocol.rb +54 -40
  18. data/lib/thrift/protocol/binary_protocol_accelerated.rb +6 -2
  19. data/lib/thrift/protocol/compact_protocol.rb +193 -88
  20. data/lib/thrift/protocol/header_protocol.rb +14 -3
  21. data/lib/thrift/protocol/json_protocol.rb +146 -104
  22. data/lib/thrift/protocol/multiplexed_protocol.rb +2 -3
  23. data/lib/thrift/protocol/protocol_decorator.rb +5 -2
  24. data/lib/thrift/serializer/serializer.rb +6 -0
  25. data/lib/thrift/server/base_server.rb +10 -2
  26. data/lib/thrift/server/nonblocking_server.rb +49 -26
  27. data/lib/thrift/server/rack_application.rb +69 -0
  28. data/lib/thrift/server/simple_server.rb +27 -23
  29. data/lib/thrift/server/thin_http_server.rb +20 -44
  30. data/lib/thrift/server/thread_pool_server.rb +20 -17
  31. data/lib/thrift/server/threaded_server.rb +22 -26
  32. data/lib/thrift/struct.rb +33 -18
  33. data/lib/thrift/struct_union.rb +36 -30
  34. data/lib/thrift/transport/base_transport.rb +23 -5
  35. data/lib/thrift/transport/buffered_transport.rb +4 -4
  36. data/lib/thrift/transport/framed_transport.rb +32 -7
  37. data/lib/thrift/transport/header_transport.rb +130 -50
  38. data/lib/thrift/transport/http_client_transport.rb +27 -14
  39. data/lib/thrift/transport/io_stream_transport.rb +2 -1
  40. data/lib/thrift/transport/memory_buffer_transport.rb +8 -10
  41. data/lib/thrift/transport/server_socket.rb +4 -5
  42. data/lib/thrift/transport/socket.rb +20 -22
  43. data/lib/thrift/transport/ssl_server_socket.rb +64 -4
  44. data/lib/thrift/transport/ssl_socket.rb +32 -5
  45. data/lib/thrift/transport/unix_server_socket.rb +1 -1
  46. data/lib/thrift/transport/unix_socket.rb +4 -6
  47. data/lib/thrift/types.rb +22 -22
  48. data/lib/thrift/union.rb +20 -11
  49. data/lib/thrift/uuid.rb +8 -8
  50. data/lib/thrift.rb +39 -39
  51. metadata +5 -230
  52. data/benchmark/Benchmark.thrift +0 -24
  53. data/benchmark/benchmark.rb +0 -286
  54. data/benchmark/client.rb +0 -118
  55. data/benchmark/server.rb +0 -121
  56. data/benchmark/thin_server.rb +0 -46
  57. data/lib/thrift/server/mongrel_http_server.rb +0 -61
  58. data/spec/BaseService.thrift +0 -27
  59. data/spec/ExtendedService.thrift +0 -25
  60. data/spec/Referenced.thrift +0 -44
  61. data/spec/ThriftNamespacedSpec.thrift +0 -53
  62. data/spec/ThriftSpec.thrift +0 -203
  63. data/spec/base_protocol_spec.rb +0 -244
  64. data/spec/base_transport_spec.rb +0 -428
  65. data/spec/binary_protocol_accelerated_spec.rb +0 -47
  66. data/spec/binary_protocol_spec.rb +0 -73
  67. data/spec/binary_protocol_spec_shared.rb +0 -509
  68. data/spec/bytes_spec.rb +0 -117
  69. data/spec/client_spec.rb +0 -167
  70. data/spec/compact_protocol_spec.rb +0 -295
  71. data/spec/constants_demo_spec.rb +0 -102
  72. data/spec/exception_spec.rb +0 -141
  73. data/spec/flat_spec.rb +0 -63
  74. data/spec/header_protocol_spec.rb +0 -476
  75. data/spec/header_transport_spec.rb +0 -431
  76. data/spec/http_client_spec.rb +0 -160
  77. data/spec/json_protocol_spec.rb +0 -574
  78. data/spec/multiplexed_processor_spec.rb +0 -75
  79. data/spec/namespaced_spec.rb +0 -67
  80. data/spec/nonblocking_server_spec.rb +0 -429
  81. data/spec/processor_spec.rb +0 -80
  82. data/spec/recursion_depth_spec.rb +0 -223
  83. data/spec/serializer_spec.rb +0 -67
  84. data/spec/server_socket_spec.rb +0 -121
  85. data/spec/server_spec.rb +0 -238
  86. data/spec/socket_spec.rb +0 -174
  87. data/spec/socket_spec_shared.rb +0 -168
  88. data/spec/spec_helper.rb +0 -70
  89. data/spec/ssl_server_socket_spec.rb +0 -85
  90. data/spec/ssl_socket_spec.rb +0 -248
  91. data/spec/struct_nested_containers_spec.rb +0 -191
  92. data/spec/struct_spec.rb +0 -398
  93. data/spec/support/header_protocol_helper.rb +0 -55
  94. data/spec/thin_http_server_spec.rb +0 -127
  95. data/spec/types_spec.rb +0 -118
  96. data/spec/union_spec.rb +0 -273
  97. data/spec/unix_socket_spec.rb +0 -131
  98. data/spec/uuid_validation_spec.rb +0 -239
  99. data/test/fuzz/Makefile +0 -779
  100. data/test/fuzz/Makefile.am +0 -173
  101. data/test/fuzz/Makefile.in +0 -775
  102. data/test/fuzz/README.md +0 -149
  103. data/test/fuzz/fuzz_common.rb +0 -96
  104. data/test/fuzz/fuzz_parse_binary_protocol.rb +0 -23
  105. data/test/fuzz/fuzz_parse_binary_protocol_accelerated.rb +0 -23
  106. data/test/fuzz/fuzz_parse_binary_protocol_accelerated_harness.rb +0 -23
  107. data/test/fuzz/fuzz_parse_binary_protocol_harness.rb +0 -23
  108. data/test/fuzz/fuzz_parse_compact_protocol.rb +0 -23
  109. data/test/fuzz/fuzz_parse_compact_protocol_harness.rb +0 -23
  110. data/test/fuzz/fuzz_parse_json_protocol.rb +0 -23
  111. data/test/fuzz/fuzz_parse_json_protocol_harness.rb +0 -23
  112. data/test/fuzz/fuzz_roundtrip_binary_protocol.rb +0 -23
  113. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated.rb +0 -23
  114. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated_harness.rb +0 -23
  115. data/test/fuzz/fuzz_roundtrip_binary_protocol_harness.rb +0 -23
  116. data/test/fuzz/fuzz_roundtrip_compact_protocol.rb +0 -23
  117. data/test/fuzz/fuzz_roundtrip_compact_protocol_harness.rb +0 -23
  118. data/test/fuzz/fuzz_roundtrip_json_protocol.rb +0 -23
  119. data/test/fuzz/fuzz_roundtrip_json_protocol_harness.rb +0 -23
  120. data/test/fuzz/fuzz_tracer.rb +0 -29
@@ -19,8 +19,8 @@
19
19
  # under the License.
20
20
  #
21
21
 
22
- require 'stringio'
23
- require 'zlib'
22
+ require "stringio"
23
+ require "zlib"
24
24
 
25
25
  module Thrift
26
26
  # Client type constants for Header protocol
@@ -71,15 +71,18 @@ module Thrift
71
71
  # Header magic value (first 2 bytes of header)
72
72
  HEADER_MAGIC = 0x0FFF
73
73
 
74
- # Maximum frame size (~1GB)
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.
75
76
  MAX_FRAME_SIZE = 0x3FFFFFFF
76
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
+
77
83
  # Default decompressed-size cap for ZLIB transform (~15.6 MB, matches other Thrift bindings)
78
84
  DEFAULT_MAX_DECOMPRESSED_SIZE = 16_384_000
79
85
 
80
- # Chunk size for streaming inflate loop
81
- ZLIB_INFLATE_CHUNK_SIZE = 16_384
82
-
83
86
  # Binary protocol version mask and version 1
84
87
  BINARY_VERSION_MASK = 0xffff0000
85
88
  BINARY_VERSION_1 = 0x80010000
@@ -89,6 +92,9 @@ module Thrift
89
92
  COMPACT_VERSION_MASK = 0x1f
90
93
  COMPACT_VERSION = 0x01
91
94
 
95
+ MAX_VARINT32_BYTES = 5
96
+ MAX_VARINT32_LAST_BYTE = 0x0f
97
+
92
98
  attr_reader :protocol_id, :sequence_id, :flags
93
99
 
94
100
  # Creates a new HeaderTransport wrapping the given transport.
@@ -106,7 +112,7 @@ module Thrift
106
112
  HeaderClientType::FRAMED_BINARY,
107
113
  HeaderClientType::UNFRAMED_BINARY,
108
114
  HeaderClientType::FRAMED_COMPACT,
109
- HeaderClientType::UNFRAMED_COMPACT
115
+ HeaderClientType::UNFRAMED_COMPACT,
110
116
  ]
111
117
 
112
118
  @read_buffer = StringIO.new(Bytes.empty_byte_buffer)
@@ -118,8 +124,9 @@ module Thrift
118
124
 
119
125
  @sequence_id = 0
120
126
  @flags = 0
121
- @max_frame_size = MAX_FRAME_SIZE
127
+ @max_frame_size = DEFAULT_MAX_FRAME_SIZE
122
128
  @max_decompressed_size = DEFAULT_MAX_DECOMPRESSED_SIZE
129
+ @unframed_bytes_read = 0
123
130
  end
124
131
 
125
132
  def sequence_id=(sequence_id)
@@ -130,6 +137,14 @@ module Thrift
130
137
  @sequence_id = sequence_id
131
138
  end
132
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
+
133
148
  def open?
134
149
  @transport.open?
135
150
  end
@@ -172,6 +187,9 @@ module Thrift
172
187
  @write_transforms << transform_id unless @write_transforms.include?(transform_id)
173
188
  end
174
189
 
190
+ # The maximum frame size currently in force
191
+ attr_reader :max_frame_size
192
+
175
193
  # Sets the maximum allowed frame size
176
194
  def set_max_frame_size(size)
177
195
  if size <= 0 || size > MAX_FRAME_SIZE
@@ -199,7 +217,7 @@ module Thrift
199
217
  # Handle unframed passthrough - read directly from underlying transport
200
218
  if @client_type == HeaderClientType::UNFRAMED_BINARY ||
201
219
  @client_type == HeaderClientType::UNFRAMED_COMPACT
202
- return data + @transport.read(bytes_left)
220
+ return data + read_unframed(bytes_left)
203
221
  end
204
222
 
205
223
  # Need to read the next frame
@@ -217,16 +235,15 @@ module Thrift
217
235
  @write_buffer = StringIO.new(Bytes.empty_byte_buffer)
218
236
 
219
237
  return if payload.empty?
220
- if payload.bytesize > @max_frame_size
221
- raise TransportException.new(TransportException::UNKNOWN, "Attempting to send frame that is too large")
222
- end
223
238
 
224
239
  case @client_type
225
240
  when HeaderClientType::HEADERS
226
241
  flush_header_format(payload)
227
242
  when HeaderClientType::FRAMED_BINARY, HeaderClientType::FRAMED_COMPACT
243
+ validate_frame_size!(payload.bytesize)
228
244
  flush_framed(payload)
229
245
  when HeaderClientType::UNFRAMED_BINARY, HeaderClientType::UNFRAMED_COMPACT
246
+ validate_frame_size!(payload.bytesize)
230
247
  @transport.write(payload)
231
248
  @transport.flush
232
249
  else
@@ -235,7 +252,7 @@ module Thrift
235
252
  end
236
253
 
237
254
  def to_s
238
- "header(#{@transport.to_s})"
255
+ "header(#{@transport})"
239
256
  end
240
257
 
241
258
  # Reads the next frame to detect protocol/client type before decoding.
@@ -245,6 +262,17 @@ module Thrift
245
262
  read_frame(0)
246
263
  end
247
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
+
248
276
  private
249
277
 
250
278
  # Sets the client type after validation
@@ -257,9 +285,16 @@ module Thrift
257
285
 
258
286
  # Reads the next frame, detecting client type on first read
259
287
  def read_frame(req_sz)
288
+ @read_headers = {}
289
+ @unframed_bytes_read = 0
290
+
260
291
  # Read first 4 bytes - could be frame length or protocol magic
261
- first_word = @transport.read_all(4)
262
- frame_size = first_word.unpack('N').first
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")
263
298
 
264
299
  # Check for unframed binary protocol
265
300
  if (frame_size & BINARY_VERSION_MASK) == BINARY_VERSION_1
@@ -282,16 +317,23 @@ module Thrift
282
317
  if frame_size > @max_frame_size
283
318
  raise TransportException.new(TransportException::UNKNOWN, "Frame size #{frame_size} exceeds maximum #{@max_frame_size}")
284
319
  end
320
+ if frame_size < 4
321
+ raise TransportException.new(TransportException::UNKNOWN, "Frame size #{frame_size} is too small")
322
+ end
285
323
 
286
324
  # Read the complete frame
287
- frame_data = @transport.read_all(frame_size)
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
288
330
  frame_buf = StringIO.new(frame_data)
289
331
 
290
332
  # Check the second word for protocol type
291
333
  second_word = frame_buf.read(4)
292
334
  frame_buf.rewind
293
335
 
294
- magic = second_word.unpack('n').first
336
+ magic = second_word.unpack1("n")
295
337
 
296
338
  if magic == HEADER_MAGIC
297
339
  if frame_size < 10
@@ -299,7 +341,7 @@ module Thrift
299
341
  end
300
342
  set_client_type(HeaderClientType::HEADERS)
301
343
  @read_buffer = parse_header_format(frame_buf)
302
- elsif (second_word.unpack('N').first & BINARY_VERSION_MASK) == BINARY_VERSION_1
344
+ elsif (second_word.unpack1("N") & BINARY_VERSION_MASK) == BINARY_VERSION_1
303
345
  set_client_type(HeaderClientType::FRAMED_BINARY)
304
346
  @protocol_id = HeaderSubprotocolID::BINARY
305
347
  @read_buffer = frame_buf
@@ -315,26 +357,45 @@ module Thrift
315
357
 
316
358
  # Handles unframed protocol - puts first_word back in buffer
317
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
+
318
363
  bytes_left = req_sz - 4
319
- if bytes_left > 0
320
- rest = @transport.read(bytes_left)
364
+ if bytes_left > 0 && @unframed_bytes_read < @max_frame_size
365
+ rest = read_unframed(bytes_left)
321
366
  @read_buffer = StringIO.new(first_word + rest)
322
367
  else
323
368
  @read_buffer = StringIO.new(first_word)
324
369
  end
325
370
  end
326
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
+
327
388
  # Parses a Header format frame
328
389
  def parse_header_format(buf)
329
390
  # Skip magic (already identified)
330
391
  buf.read(2)
331
392
 
332
393
  # Read flags and sequence ID
333
- @flags = buf.read(2).unpack('n').first
334
- @sequence_id = signed_int32(buf.read(4).unpack('N').first)
394
+ @flags = buf.read(2).unpack1("n")
395
+ @sequence_id = signed_int32(buf.read(4).unpack1("N"))
335
396
 
336
397
  # Read header length (in 32-bit words)
337
- header_words = buf.read(2).unpack('n').first
398
+ header_words = buf.read(2).unpack1("n")
338
399
  if header_words >= 16_384
339
400
  raise TransportException.new(TransportException::UNKNOWN, "Header size is unreasonable")
340
401
  end
@@ -359,7 +420,6 @@ module Thrift
359
420
  transforms << transform_id
360
421
  end
361
422
  # Read info headers
362
- @read_headers = {}
363
423
  while buf.pos < end_of_headers
364
424
  info_type = read_varint32(buf, end_of_headers)
365
425
  if info_type == 0
@@ -397,26 +457,22 @@ module Thrift
397
457
  def bounded_inflate(compressed)
398
458
  inflater = Zlib::Inflate.new
399
459
  buffer = Bytes.empty_byte_buffer
400
- offset = 0
401
- begin
402
- while offset < compressed.bytesize
403
- buffer << inflater.inflate(compressed.byteslice(offset, ZLIB_INFLATE_CHUNK_SIZE))
404
- if buffer.bytesize > @max_decompressed_size
405
- raise TransportException.new(
406
- TransportException::SIZE_LIMIT,
407
- "Decompressed size exceeds limit of #{@max_decompressed_size}"
408
- )
409
- end
410
- offset += ZLIB_INFLATE_CHUNK_SIZE
411
- end
412
- buffer << inflater.finish
413
- if buffer.bytesize > @max_decompressed_size
460
+ append_chunk = lambda do |chunk|
461
+ if buffer.bytesize + chunk.bytesize > @max_decompressed_size
414
462
  raise TransportException.new(
415
463
  TransportException::SIZE_LIMIT,
416
- "Decompressed size exceeds limit of #{@max_decompressed_size}"
464
+ "Decompressed size exceeds limit of #{@max_decompressed_size}",
417
465
  )
418
466
  end
467
+
468
+ buffer << chunk
469
+ end
470
+ begin
471
+ inflater.inflate(compressed, &append_chunk)
472
+ inflater.finish(&append_chunk)
419
473
  buffer
474
+ rescue Zlib::DataError, Zlib::BufError
475
+ raise TransportException.new(TransportException::UNKNOWN, "Invalid ZLIB payload")
420
476
  ensure
421
477
  inflater.close rescue nil
422
478
  end
@@ -449,7 +505,6 @@ module Thrift
449
505
  write_varstring(header_buf, key)
450
506
  write_varstring(header_buf, value)
451
507
  end
452
- @write_headers = {}
453
508
  end
454
509
 
455
510
  # Pad header to 4-byte boundary
@@ -460,14 +515,16 @@ module Thrift
460
515
  # Calculate total frame size (excludes the 4-byte length field itself)
461
516
  # Frame = magic(2) + flags(2) + seqid(4) + header_len(2) + header_data + payload
462
517
  frame_size = 2 + 2 + 4 + 2 + header_data.bytesize + payload.bytesize
518
+ validate_frame_size!(frame_size)
519
+ @write_headers = {}
463
520
 
464
521
  # Write complete frame
465
522
  frame = Bytes.empty_byte_buffer
466
- frame << [frame_size].pack('N') # Length
467
- frame << [HEADER_MAGIC].pack('n') # Magic
468
- frame << [@flags].pack('n') # Flags
469
- frame << [unsigned_int32(@sequence_id)].pack('N') # Sequence ID
470
- frame << [header_data.bytesize / 4].pack('n') # Header length (in 32-bit words)
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)
471
528
  frame << header_data # Header data
472
529
  frame << payload # Payload
473
530
 
@@ -475,9 +532,18 @@ module Thrift
475
532
  @transport.flush
476
533
  end
477
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
+
478
544
  # Flushes data in simple framed format (for legacy compatibility)
479
545
  def flush_framed(payload)
480
- frame = [payload.bytesize].pack('N') + payload
546
+ frame = [payload.bytesize].pack("N") + payload
481
547
  @transport.write(frame)
482
548
  @transport.flush
483
549
  end
@@ -492,21 +558,35 @@ module Thrift
492
558
  end
493
559
  byte = io.getbyte
494
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
+
495
571
  result |= (byte & 0x7f) << shift
496
- break if (byte & 0x80) == 0
572
+ return result if (byte & 0x80) == 0
497
573
  shift += 7
498
574
  end
499
- result
500
575
  end
501
576
 
502
577
  # Writes a varint32 to the given IO
503
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
504
584
  loop do
505
585
  if (n & ~0x7F) == 0
506
- io.write([n].pack('C'))
586
+ io.write([n].pack("C"))
507
587
  break
508
588
  else
509
- io.write([(n & 0x7F) | 0x80].pack('C'))
589
+ io.write([(n & 0x7F) | 0x80].pack("C"))
510
590
  n >>= 7
511
591
  end
512
592
  end
@@ -19,24 +19,31 @@
19
19
  # under the License.
20
20
  #
21
21
 
22
- require 'net/http'
23
- require 'net/https'
24
- require 'openssl'
25
- require 'uri'
26
- require 'stringio'
22
+ require "net/http"
23
+ require "net/https"
24
+ require "openssl"
25
+ require "uri"
26
+ require "stringio"
27
27
 
28
28
  module Thrift
29
29
  class HTTPClientTransport < BaseTransport
30
-
31
30
  def initialize(url, opts = {})
32
31
  @url = URI url
33
- @headers = {'Content-Type' => 'application/x-thrift'}
32
+ @headers = {"Content-Type" => "application/x-thrift"}
34
33
  @outbuf = Bytes.empty_byte_buffer
35
34
  @ssl_verify_mode = opts.fetch(:ssl_verify_mode, OpenSSL::SSL::VERIFY_PEER)
35
+ @ssl_ca_file = opts[:ssl_ca_file]
36
36
  end
37
37
 
38
38
  def open?; true end
39
- def read(sz); @inbuf.read sz end
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
+
40
47
  def write(buf); @outbuf << Bytes.force_binary_encoding(buf) end
41
48
 
42
49
  def add_headers(headers)
@@ -45,20 +52,26 @@ module Thrift
45
52
 
46
53
  def flush
47
54
  http = Net::HTTP.new @url.host, @url.port
48
- http.use_ssl = @url.scheme == 'https'
49
- http.verify_mode = @ssl_verify_mode if @url.scheme == 'https'
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
50
60
  resp = http.post(@url.request_uri, @outbuf, @headers)
51
- raise TransportException.new(TransportException::UNKNOWN, "#{self.class.name} Could not connect to #{@url}, HTTP status code #{resp.code.to_i}") unless (200..299).include?(resp.code.to_i)
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)
52
63
 
53
- data = resp.body
54
- data = Bytes.force_binary_encoding(data)
64
+ @response_code = response_code
65
+ data = Bytes.force_binary_encoding(resp.body || Bytes.empty_byte_buffer)
55
66
  @inbuf = StringIO.new data
56
67
  ensure
57
68
  @outbuf = Bytes.empty_byte_buffer
58
69
  end
59
70
 
60
71
  def to_s
61
- "@{self.url}"
72
+ path = @url.path.to_s
73
+ path = "/" if path.empty?
74
+ "#{@url.scheme}(#{@url.host}:#{@url.port}#{path})"
62
75
  end
63
76
  end
64
77
  end
@@ -36,8 +36,9 @@ module Thrift
36
36
  def write(buf); @output.write(Bytes.force_binary_encoding(buf)) end
37
37
  def close; @input.close; @output.close end
38
38
  def to_io; @input end # we're assuming this is used in a IO.select for reading
39
+
39
40
  def to_s
40
- "iostream(input=#{@input.to_s},output=#{@output.to_s})"
41
+ "iostream(input=#{@input},output=#{@output})"
41
42
  end
42
43
  end
43
44
  end
@@ -21,20 +21,16 @@
21
21
 
22
22
  module Thrift
23
23
  class MemoryBufferTransport < BaseTransport
24
- GARBAGE_BUFFER_SIZE = 4*(2**10) # 4kB
24
+ GARBAGE_BUFFER_SIZE = 4 * (2**10) # 4kB
25
25
 
26
- # If you pass a string to this, you should #dup that string
27
- # unless you want it to be modified by #read and #write
28
- #--
29
- # this behavior is no longer required. If you wish to change it
30
- # go ahead, just make sure the specs pass
26
+ # The transport copies the input buffer and keeps its own mutable storage.
31
27
  def initialize(buffer = nil)
32
- @buf = buffer ? Bytes.force_binary_encoding(buffer) : Bytes.empty_byte_buffer
28
+ @buf = buffer ? Bytes.force_binary_encoding(buffer.dup) : Bytes.empty_byte_buffer
33
29
  @index = 0
34
30
  end
35
31
 
36
32
  def open?
37
- return true
33
+ true
38
34
  end
39
35
 
40
36
  def open
@@ -48,7 +44,7 @@ module Thrift
48
44
  end
49
45
 
50
46
  # this method does not use the passed object directly but copies it
51
- def reset_buffer(new_buf = '')
47
+ def reset_buffer(new_buf = "")
52
48
  @buf.replace Bytes.force_binary_encoding(new_buf)
53
49
  @index = 0
54
50
  end
@@ -58,6 +54,8 @@ module Thrift
58
54
  end
59
55
 
60
56
  def read(len)
57
+ raise TransportException.new(TransportException::NEGATIVE_SIZE, "Negative size") unless len >= 0
58
+
61
59
  data = @buf.slice(@index, len)
62
60
  @index += len
63
61
  @index = @buf.size if @index > @buf.size
@@ -72,7 +70,7 @@ module Thrift
72
70
  end
73
71
 
74
72
  def read_all(size)
75
- raise TransportException.new(TransportException::NEGATIVE_SIZE, 'Negative size') unless size >= 0
73
+ raise TransportException.new(TransportException::NEGATIVE_SIZE, "Negative size") unless size >= 0
76
74
 
77
75
  read(size)
78
76
  end
@@ -19,7 +19,7 @@
19
19
  # under the License.
20
20
  #
21
21
 
22
- require 'socket'
22
+ require "socket"
23
23
 
24
24
  module Thrift
25
25
  class ServerSocket < BaseServerTransport
@@ -54,8 +54,8 @@ module Thrift
54
54
  end
55
55
 
56
56
  def close
57
- @handle.close unless @handle.nil? or @handle.closed?
58
- @handle = nil
57
+ @handle.close unless @handle.nil? or @handle.closed?
58
+ @handle = nil
59
59
  end
60
60
 
61
61
  def closed?
@@ -63,12 +63,11 @@ module Thrift
63
63
  end
64
64
 
65
65
  def to_io
66
- @handle&.to_io || raise(IOError, 'closed stream')
66
+ @handle&.to_io || raise(IOError, "closed stream")
67
67
  end
68
68
 
69
69
  def to_s
70
70
  "socket(#{@host}:#{@port})"
71
71
  end
72
-
73
72
  end
74
73
  end
@@ -19,11 +19,11 @@
19
19
  # under the License.
20
20
  #
21
21
 
22
- require 'socket'
22
+ require "socket"
23
23
 
24
24
  module Thrift
25
25
  class Socket < BaseTransport
26
- def initialize(host = 'localhost', port = 9090, timeout = nil)
26
+ def initialize(host = "localhost", port = 9090, timeout = nil)
27
27
  @host = host
28
28
  @port = port
29
29
  @timeout = timeout
@@ -34,6 +34,8 @@ module Thrift
34
34
  attr_accessor :handle, :timeout
35
35
 
36
36
  def open
37
+ raise TransportException.new(TransportException::ALREADY_OPEN, "Socket already open") if open?
38
+
37
39
  deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout unless @timeout.nil? || @timeout == 0
38
40
  @handle = connect_socket(deadline)
39
41
  end
@@ -54,7 +56,7 @@ module Thrift
54
56
 
55
57
  while len < str.length
56
58
  begin
57
- len += @handle.write_nonblock(str[len..-1])
59
+ len += @handle.write_nonblock(str[len..])
58
60
  rescue IO::WaitWritable
59
61
  wait_for(:write, deadline, str.length)
60
62
  rescue IO::WaitReadable
@@ -64,12 +66,11 @@ module Thrift
64
66
 
65
67
  len
66
68
  end
67
- rescue TransportException => e
68
- # pass this on
69
- raise e
69
+ rescue TransportException
70
+ close
71
+ raise
70
72
  rescue StandardError => e
71
- close_socket(@handle)
72
- @handle = nil
73
+ close
73
74
  raise TransportException.new(TransportException::NOT_OPEN, e.message)
74
75
  end
75
76
  end
@@ -84,24 +85,21 @@ module Thrift
84
85
  deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout
85
86
 
86
87
  data = loop do
87
- begin
88
- break @handle.read_nonblock(sz)
89
- rescue IO::WaitReadable
90
- wait_for(:read, deadline, sz)
91
- rescue IO::WaitWritable
92
- wait_for(:write, deadline, sz)
93
- end
88
+ break @handle.read_nonblock(sz)
89
+ rescue IO::WaitReadable
90
+ wait_for(:read, deadline, sz)
91
+ rescue IO::WaitWritable
92
+ wait_for(:write, deadline, sz)
94
93
  end
95
94
  end
96
- rescue TransportException => e
97
- # don't let this get caught by the StandardError handler
98
- raise e
95
+ rescue TransportException
96
+ close
97
+ raise
99
98
  rescue StandardError => e
100
- close_socket(@handle)
101
- @handle = nil
99
+ close
102
100
  raise TransportException.new(TransportException::NOT_OPEN, e.message)
103
101
  end
104
- if (data.nil? || data.length == 0)
102
+ if data.nil? || data.length == 0
105
103
  raise TransportException.new(TransportException::UNKNOWN, "Socket: Could not read #{sz} bytes from #{@desc}")
106
104
  end
107
105
  data
@@ -113,7 +111,7 @@ module Thrift
113
111
  end
114
112
 
115
113
  def to_io
116
- @handle&.to_io || raise(IOError, 'closed stream')
114
+ @handle&.to_io || raise(IOError, "closed stream")
117
115
  end
118
116
 
119
117
  def to_s