thrift 0.22.0 → 0.23.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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +175 -17
  3. data/benchmark/benchmark.rb +22 -8
  4. data/benchmark/client.rb +49 -6
  5. data/benchmark/server.rb +45 -7
  6. data/benchmark/thin_server.rb +1 -0
  7. data/ext/binary_protocol_accelerated.c +76 -19
  8. data/ext/compact_protocol.c +80 -15
  9. data/ext/constants.h +12 -0
  10. data/ext/extconf.rb +10 -9
  11. data/ext/memory_buffer.c +7 -7
  12. data/ext/protocol.c +29 -0
  13. data/ext/protocol.h +35 -0
  14. data/ext/struct.c +36 -5
  15. data/ext/thrift_native.c +27 -3
  16. data/lib/thrift/bytes.rb +68 -101
  17. data/lib/thrift/client.rb +61 -9
  18. data/lib/thrift/exceptions.rb +5 -5
  19. data/lib/thrift/multiplexed_processor.rb +6 -6
  20. data/lib/thrift/processor.rb +6 -6
  21. data/lib/thrift/protocol/base_protocol.rb +37 -15
  22. data/lib/thrift/protocol/binary_protocol.rb +25 -9
  23. data/lib/thrift/protocol/binary_protocol_accelerated.rb +5 -5
  24. data/lib/thrift/protocol/compact_protocol.rb +61 -37
  25. data/lib/thrift/protocol/header_protocol.rb +320 -0
  26. data/lib/thrift/protocol/json_protocol.rb +26 -16
  27. data/lib/thrift/protocol/multiplexed_protocol.rb +5 -5
  28. data/lib/thrift/protocol/protocol_decorator.rb +12 -4
  29. data/lib/thrift/serializer/deserializer.rb +5 -5
  30. data/lib/thrift/serializer/serializer.rb +4 -5
  31. data/lib/thrift/server/base_server.rb +4 -4
  32. data/lib/thrift/server/mongrel_http_server.rb +6 -6
  33. data/lib/thrift/server/nonblocking_server.rb +8 -8
  34. data/lib/thrift/server/simple_server.rb +4 -4
  35. data/lib/thrift/server/thin_http_server.rb +3 -3
  36. data/lib/thrift/server/thread_pool_server.rb +6 -6
  37. data/lib/thrift/server/threaded_server.rb +4 -4
  38. data/lib/thrift/struct.rb +11 -11
  39. data/lib/thrift/struct_union.rb +19 -9
  40. data/lib/thrift/thrift_native.rb +1 -1
  41. data/lib/thrift/transport/base_server_transport.rb +5 -5
  42. data/lib/thrift/transport/base_transport.rb +12 -12
  43. data/lib/thrift/transport/buffered_transport.rb +6 -6
  44. data/lib/thrift/transport/framed_transport.rb +7 -7
  45. data/lib/thrift/transport/header_transport.rb +516 -0
  46. data/lib/thrift/transport/http_client_transport.rb +1 -1
  47. data/lib/thrift/transport/io_stream_transport.rb +3 -3
  48. data/lib/thrift/transport/memory_buffer_transport.rb +6 -6
  49. data/lib/thrift/transport/server_socket.rb +8 -5
  50. data/lib/thrift/transport/socket.rb +58 -31
  51. data/lib/thrift/transport/ssl_server_socket.rb +1 -1
  52. data/lib/thrift/transport/ssl_socket.rb +2 -2
  53. data/lib/thrift/transport/unix_server_socket.rb +4 -4
  54. data/lib/thrift/transport/unix_socket.rb +6 -6
  55. data/lib/thrift/types.rb +9 -6
  56. data/lib/thrift/union.rb +14 -8
  57. data/lib/thrift/uuid.rb +49 -0
  58. data/lib/thrift.rb +3 -1
  59. data/spec/ThriftSpec.thrift +5 -1
  60. data/spec/base_protocol_spec.rb +1 -2
  61. data/spec/base_transport_spec.rb +6 -7
  62. data/spec/binary_protocol_spec.rb +0 -2
  63. data/spec/binary_protocol_spec_shared.rb +129 -142
  64. data/spec/bytes_spec.rb +57 -118
  65. data/spec/client_spec.rb +85 -19
  66. data/spec/compact_protocol_spec.rb +54 -16
  67. data/spec/constants_demo_spec.rb +101 -0
  68. data/spec/exception_spec.rb +0 -1
  69. data/spec/header_protocol_spec.rb +475 -0
  70. data/spec/header_transport_spec.rb +386 -0
  71. data/spec/http_client_spec.rb +4 -6
  72. data/spec/json_protocol_spec.rb +47 -47
  73. data/spec/namespaced_spec.rb +0 -1
  74. data/spec/nonblocking_server_spec.rb +102 -4
  75. data/spec/processor_spec.rb +0 -1
  76. data/spec/serializer_spec.rb +0 -1
  77. data/spec/server_socket_spec.rb +1 -1
  78. data/spec/server_spec.rb +8 -9
  79. data/spec/socket_spec.rb +0 -1
  80. data/spec/socket_spec_shared.rb +72 -9
  81. data/spec/spec_helper.rb +1 -1
  82. data/spec/ssl_server_socket_spec.rb +12 -1
  83. data/spec/ssl_socket_spec.rb +10 -1
  84. data/spec/struct_nested_containers_spec.rb +1 -2
  85. data/spec/struct_spec.rb +113 -9
  86. data/spec/support/header_protocol_helper.rb +54 -0
  87. data/spec/thin_http_server_spec.rb +3 -18
  88. data/spec/types_spec.rb +25 -26
  89. data/spec/union_spec.rb +69 -11
  90. data/spec/unix_socket_spec.rb +1 -2
  91. data/spec/uuid_validation_spec.rb +238 -0
  92. data/test/fuzz/Makefile.am +173 -0
  93. data/test/fuzz/README.md +149 -0
  94. data/test/fuzz/fuzz_common.rb +95 -0
  95. data/{lib/thrift/core_ext.rb → test/fuzz/fuzz_parse_binary_protocol.rb} +3 -4
  96. data/{lib/thrift/core_ext/fixnum.rb → test/fuzz/fuzz_parse_binary_protocol_accelerated.rb} +6 -13
  97. data/test/fuzz/fuzz_parse_binary_protocol_accelerated_harness.rb +22 -0
  98. data/test/fuzz/fuzz_parse_binary_protocol_harness.rb +22 -0
  99. data/test/fuzz/fuzz_parse_compact_protocol.rb +22 -0
  100. data/test/fuzz/fuzz_parse_compact_protocol_harness.rb +22 -0
  101. data/test/fuzz/fuzz_parse_json_protocol.rb +22 -0
  102. data/test/fuzz/fuzz_parse_json_protocol_harness.rb +22 -0
  103. data/test/fuzz/fuzz_roundtrip_binary_protocol.rb +22 -0
  104. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated.rb +22 -0
  105. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated_harness.rb +22 -0
  106. data/test/fuzz/fuzz_roundtrip_binary_protocol_harness.rb +22 -0
  107. data/test/fuzz/fuzz_roundtrip_compact_protocol.rb +22 -0
  108. data/test/fuzz/fuzz_roundtrip_compact_protocol_harness.rb +22 -0
  109. data/test/fuzz/fuzz_roundtrip_json_protocol.rb +22 -0
  110. data/test/fuzz/fuzz_roundtrip_json_protocol_harness.rb +22 -0
  111. data/test/fuzz/fuzz_tracer.rb +28 -0
  112. metadata +106 -37
@@ -0,0 +1,516 @@
1
+ # encoding: ascii-8bit
2
+ #
3
+ # Licensed to the Apache Software Foundation (ASF) under one
4
+ # or more contributor license agreements. See the NOTICE file
5
+ # distributed with this work for additional information
6
+ # regarding copyright ownership. The ASF licenses this file
7
+ # to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance
9
+ # with the License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing,
14
+ # software distributed under the License is distributed on an
15
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ # KIND, either express or implied. See the License for the
17
+ # specific language governing permissions and limitations
18
+ # under the License.
19
+ #
20
+
21
+ require 'stringio'
22
+ require 'zlib'
23
+
24
+ module Thrift
25
+ # Client type constants for Header protocol
26
+ module HeaderClientType
27
+ HEADERS = 0x00
28
+ FRAMED_BINARY = 0x01
29
+ UNFRAMED_BINARY = 0x02
30
+ FRAMED_COMPACT = 0x03
31
+ UNFRAMED_COMPACT = 0x04
32
+ end
33
+
34
+ # Subprotocol ID constants for Header transport
35
+ module HeaderSubprotocolID
36
+ BINARY = 0x00
37
+ COMPACT = 0x02
38
+ end
39
+
40
+ # Transform ID constants for Header transport
41
+ module HeaderTransformID
42
+ ZLIB = 0x01
43
+ end
44
+
45
+ # Info header type constants
46
+ module HeaderInfoType
47
+ KEY_VALUE = 0x01
48
+ end
49
+
50
+ # HeaderTransport implements the THeader framing protocol.
51
+ #
52
+ # THeader is a transport that adds headers and supports multiple protocols
53
+ # and transforms. It can auto-detect and communicate with legacy protocols
54
+ # (framed/unframed binary/compact) for backward compatibility.
55
+ #
56
+ # Wire format:
57
+ # +----------------------------------------------------------------+
58
+ # | LENGTH (4 bytes, big-endian, excludes itself) |
59
+ # +----------------------------------------------------------------+
60
+ # | HEADER MAGIC (2 bytes: 0x0FFF) | FLAGS (2 bytes) |
61
+ # +----------------------------------------------------------------+
62
+ # | SEQUENCE NUMBER (4 bytes) |
63
+ # +----------------------------------------------------------------+
64
+ # | HEADER SIZE/4 (2 bytes) | HEADER DATA (variable)... |
65
+ # +----------------------------------------------------------------+
66
+ # | PAYLOAD (variable) |
67
+ # +----------------------------------------------------------------+
68
+ #
69
+ class HeaderTransport < BaseTransport
70
+ # Header magic value (first 2 bytes of header)
71
+ HEADER_MAGIC = 0x0FFF
72
+
73
+ # Maximum frame size (~1GB)
74
+ MAX_FRAME_SIZE = 0x3FFFFFFF
75
+
76
+ # Binary protocol version mask and version 1
77
+ BINARY_VERSION_MASK = 0xffff0000
78
+ BINARY_VERSION_1 = 0x80010000
79
+
80
+ # Compact protocol ID
81
+ COMPACT_PROTOCOL_ID = 0x82
82
+ COMPACT_VERSION_MASK = 0x1f
83
+ COMPACT_VERSION = 0x01
84
+
85
+ attr_reader :protocol_id, :sequence_id, :flags
86
+
87
+ # Creates a new HeaderTransport wrapping the given transport.
88
+ #
89
+ # @param transport [BaseTransport] The underlying transport to wrap
90
+ # @param allowed_client_types [Array<Integer>] Allowed client types for auto-detection.
91
+ # Defaults to all types for backward compatibility.
92
+ # @param default_protocol [Integer] Default protocol ID (BINARY or COMPACT)
93
+ def initialize(transport, allowed_client_types = nil, default_protocol = HeaderSubprotocolID::COMPACT)
94
+ @transport = transport
95
+ @client_type = HeaderClientType::HEADERS
96
+ @protocol_id = default_protocol
97
+ @allowed_client_types = allowed_client_types || [
98
+ HeaderClientType::HEADERS,
99
+ HeaderClientType::FRAMED_BINARY,
100
+ HeaderClientType::UNFRAMED_BINARY,
101
+ HeaderClientType::FRAMED_COMPACT,
102
+ HeaderClientType::UNFRAMED_COMPACT
103
+ ]
104
+
105
+ @read_buffer = StringIO.new(Bytes.empty_byte_buffer)
106
+ @write_buffer = StringIO.new(Bytes.empty_byte_buffer)
107
+
108
+ @read_headers = {}
109
+ @write_headers = {}
110
+ @write_transforms = []
111
+
112
+ @sequence_id = 0
113
+ @flags = 0
114
+ @max_frame_size = MAX_FRAME_SIZE
115
+ end
116
+
117
+ def sequence_id=(sequence_id)
118
+ if sequence_id < -(2**31) || sequence_id > (2**31) - 1
119
+ raise RangeError, "sequence_id must be a signed int32"
120
+ end
121
+
122
+ @sequence_id = sequence_id
123
+ end
124
+
125
+ def open?
126
+ @transport.open?
127
+ end
128
+
129
+ def open
130
+ @transport.open
131
+ end
132
+
133
+ def close
134
+ @transport.close
135
+ end
136
+
137
+ # Returns the headers read from the last frame
138
+ def get_headers
139
+ @read_headers
140
+ end
141
+
142
+ # Sets a header to be written with the next flush
143
+ #
144
+ # @param key [String] Header key (must be binary string)
145
+ # @param value [String] Header value (must be binary string)
146
+ def set_header(key, value)
147
+ key = Bytes.force_binary_encoding(key.to_s)
148
+ value = Bytes.force_binary_encoding(value.to_s)
149
+ @write_headers[key] = value
150
+ end
151
+
152
+ # Clears all write headers
153
+ def clear_headers
154
+ @write_headers.clear
155
+ end
156
+
157
+ # Adds a transform to apply when writing
158
+ #
159
+ # @param transform_id [Integer] Transform ID (e.g., HeaderTransformID::ZLIB)
160
+ def add_transform(transform_id)
161
+ unless transform_id == HeaderTransformID::ZLIB
162
+ raise TransportException.new(TransportException::UNKNOWN, "Unknown transform: #{transform_id}")
163
+ end
164
+ @write_transforms << transform_id unless @write_transforms.include?(transform_id)
165
+ end
166
+
167
+ # Sets the maximum allowed frame size
168
+ def set_max_frame_size(size)
169
+ if size <= 0 || size > MAX_FRAME_SIZE
170
+ raise ArgumentError, "max_frame_size must be > 0 and <= #{MAX_FRAME_SIZE}"
171
+ end
172
+ @max_frame_size = size
173
+ end
174
+
175
+ def read(sz)
176
+ # Try reading from existing buffer
177
+ data = @read_buffer.read(sz)
178
+ data = Bytes.empty_byte_buffer if data.nil?
179
+
180
+ bytes_left = sz - data.bytesize
181
+ return data if bytes_left == 0
182
+
183
+ # Handle unframed passthrough - read directly from underlying transport
184
+ if @client_type == HeaderClientType::UNFRAMED_BINARY ||
185
+ @client_type == HeaderClientType::UNFRAMED_COMPACT
186
+ return data + @transport.read(bytes_left)
187
+ end
188
+
189
+ # Need to read the next frame
190
+ read_frame(bytes_left)
191
+ additional = @read_buffer.read(bytes_left)
192
+ data + (additional || Bytes.empty_byte_buffer)
193
+ end
194
+
195
+ def write(buf)
196
+ @write_buffer.write(Bytes.force_binary_encoding(buf))
197
+ end
198
+
199
+ def flush
200
+ payload = @write_buffer.string
201
+ @write_buffer = StringIO.new(Bytes.empty_byte_buffer)
202
+
203
+ 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
+
208
+ case @client_type
209
+ when HeaderClientType::HEADERS
210
+ flush_header_format(payload)
211
+ when HeaderClientType::FRAMED_BINARY, HeaderClientType::FRAMED_COMPACT
212
+ flush_framed(payload)
213
+ when HeaderClientType::UNFRAMED_BINARY, HeaderClientType::UNFRAMED_COMPACT
214
+ @transport.write(payload)
215
+ @transport.flush
216
+ else
217
+ flush_header_format(payload)
218
+ end
219
+ end
220
+
221
+ def to_s
222
+ "header(#{@transport.to_s})"
223
+ end
224
+
225
+ # Reads the next frame to detect protocol/client type before decoding.
226
+ def reset_protocol
227
+ return unless @read_buffer.nil? || @read_buffer.eof?
228
+
229
+ read_frame(0)
230
+ end
231
+
232
+ private
233
+
234
+ # Sets the client type after validation
235
+ def set_client_type(client_type)
236
+ unless @allowed_client_types.include?(client_type)
237
+ raise TransportException.new(TransportException::UNKNOWN, "Client type #{client_type} not allowed by server")
238
+ end
239
+ @client_type = client_type
240
+ end
241
+
242
+ # Reads the next frame, detecting client type on first read
243
+ def read_frame(req_sz)
244
+ # Read first 4 bytes - could be frame length or protocol magic
245
+ first_word = @transport.read_all(4)
246
+ frame_size = first_word.unpack('N').first
247
+
248
+ # Check for unframed binary protocol
249
+ if (frame_size & BINARY_VERSION_MASK) == BINARY_VERSION_1
250
+ set_client_type(HeaderClientType::UNFRAMED_BINARY)
251
+ @protocol_id = HeaderSubprotocolID::BINARY
252
+ handle_unframed(first_word, req_sz)
253
+ return
254
+ end
255
+
256
+ # Check for unframed compact protocol
257
+ if Bytes.get_string_byte(first_word, 0) == COMPACT_PROTOCOL_ID &&
258
+ (Bytes.get_string_byte(first_word, 1) & COMPACT_VERSION_MASK) == COMPACT_VERSION
259
+ set_client_type(HeaderClientType::UNFRAMED_COMPACT)
260
+ @protocol_id = HeaderSubprotocolID::COMPACT
261
+ handle_unframed(first_word, req_sz)
262
+ return
263
+ end
264
+
265
+ # It's a framed protocol - validate frame size
266
+ if frame_size > @max_frame_size
267
+ raise TransportException.new(TransportException::UNKNOWN, "Frame size #{frame_size} exceeds maximum #{@max_frame_size}")
268
+ end
269
+
270
+ # Read the complete frame
271
+ frame_data = @transport.read_all(frame_size)
272
+ frame_buf = StringIO.new(frame_data)
273
+
274
+ # Check the second word for protocol type
275
+ second_word = frame_buf.read(4)
276
+ frame_buf.rewind
277
+
278
+ magic = second_word.unpack('n').first
279
+
280
+ if magic == HEADER_MAGIC
281
+ if frame_size < 10
282
+ raise TransportException.new(TransportException::UNKNOWN, "Header transport frame is too small")
283
+ end
284
+ set_client_type(HeaderClientType::HEADERS)
285
+ @read_buffer = parse_header_format(frame_buf)
286
+ elsif (second_word.unpack('N').first & BINARY_VERSION_MASK) == BINARY_VERSION_1
287
+ set_client_type(HeaderClientType::FRAMED_BINARY)
288
+ @protocol_id = HeaderSubprotocolID::BINARY
289
+ @read_buffer = frame_buf
290
+ elsif Bytes.get_string_byte(second_word, 0) == COMPACT_PROTOCOL_ID &&
291
+ (Bytes.get_string_byte(second_word, 1) & COMPACT_VERSION_MASK) == COMPACT_VERSION
292
+ set_client_type(HeaderClientType::FRAMED_COMPACT)
293
+ @protocol_id = HeaderSubprotocolID::COMPACT
294
+ @read_buffer = frame_buf
295
+ else
296
+ raise TransportException.new(TransportException::UNKNOWN, "Could not detect client transport type")
297
+ end
298
+ end
299
+
300
+ # Handles unframed protocol - puts first_word back in buffer
301
+ def handle_unframed(first_word, req_sz)
302
+ bytes_left = req_sz - 4
303
+ if bytes_left > 0
304
+ rest = @transport.read(bytes_left)
305
+ @read_buffer = StringIO.new(first_word + rest)
306
+ else
307
+ @read_buffer = StringIO.new(first_word)
308
+ end
309
+ end
310
+
311
+ # Parses a Header format frame
312
+ def parse_header_format(buf)
313
+ # Skip magic (already identified)
314
+ buf.read(2)
315
+
316
+ # Read flags and sequence ID
317
+ @flags = buf.read(2).unpack('n').first
318
+ @sequence_id = signed_int32(buf.read(4).unpack('N').first)
319
+
320
+ # Read header length (in 32-bit words)
321
+ header_words = buf.read(2).unpack('n').first
322
+ if header_words >= 16_384
323
+ raise TransportException.new(TransportException::UNKNOWN, "Header size is unreasonable")
324
+ end
325
+ header_length = header_words * 4
326
+ end_of_headers = buf.pos + header_length
327
+
328
+ if end_of_headers > buf.string.bytesize
329
+ raise TransportException.new(TransportException::UNKNOWN, "Header size exceeds frame size")
330
+ end
331
+
332
+ # Read protocol ID
333
+ @protocol_id = read_varint32(buf, end_of_headers)
334
+
335
+ # Read transforms
336
+ transforms = []
337
+ transform_count = read_varint32(buf, end_of_headers)
338
+ transform_count.times do
339
+ transform_id = read_varint32(buf, end_of_headers)
340
+ unless transform_id == HeaderTransformID::ZLIB
341
+ raise TransportException.new(TransportException::UNKNOWN, "Unknown transform: #{transform_id}")
342
+ end
343
+ transforms << transform_id
344
+ end
345
+ # Read info headers
346
+ @read_headers = {}
347
+ while buf.pos < end_of_headers
348
+ info_type = read_varint32(buf, end_of_headers)
349
+ if info_type == 0
350
+ # header padding
351
+ break
352
+ elsif info_type == HeaderInfoType::KEY_VALUE
353
+ count = read_varint32(buf, end_of_headers)
354
+ count.times do
355
+ key = read_varstring(buf, end_of_headers)
356
+ value = read_varstring(buf, end_of_headers)
357
+ @read_headers[key] = value
358
+ end
359
+ else
360
+ # Unknown info type, skip to end of headers
361
+ break
362
+ end
363
+ end
364
+
365
+ # Skip any remaining header padding
366
+ buf.pos = end_of_headers
367
+
368
+ # Read payload and apply transforms
369
+ payload = buf.read
370
+ transforms.each do |transform_id|
371
+ if transform_id == HeaderTransformID::ZLIB
372
+ payload = Zlib::Inflate.inflate(payload)
373
+ end
374
+ end
375
+
376
+ StringIO.new(payload)
377
+ end
378
+
379
+ # Flushes data in Header format
380
+ def flush_header_format(payload)
381
+ # Apply transforms
382
+ @write_transforms.each do |transform_id|
383
+ if transform_id == HeaderTransformID::ZLIB
384
+ payload = Zlib::Deflate.deflate(payload)
385
+ end
386
+ end
387
+
388
+ # Build header data
389
+ header_buf = StringIO.new(Bytes.empty_byte_buffer)
390
+
391
+ # Protocol ID
392
+ write_varint32(header_buf, @protocol_id)
393
+
394
+ # Transforms
395
+ write_varint32(header_buf, @write_transforms.size)
396
+ @write_transforms.each { |t| write_varint32(header_buf, t) }
397
+
398
+ # Info headers (key-value pairs)
399
+ unless @write_headers.empty?
400
+ write_varint32(header_buf, HeaderInfoType::KEY_VALUE)
401
+ write_varint32(header_buf, @write_headers.size)
402
+ @write_headers.each do |key, value|
403
+ write_varstring(header_buf, key)
404
+ write_varstring(header_buf, value)
405
+ end
406
+ @write_headers = {}
407
+ end
408
+
409
+ # Pad header to 4-byte boundary
410
+ header_data = header_buf.string
411
+ padding = (4 - (header_data.bytesize % 4)) % 4
412
+ header_data += "\x00" * padding
413
+
414
+ # Calculate total frame size (excludes the 4-byte length field itself)
415
+ # Frame = magic(2) + flags(2) + seqid(4) + header_len(2) + header_data + payload
416
+ frame_size = 2 + 2 + 4 + 2 + header_data.bytesize + payload.bytesize
417
+
418
+ # Write complete frame
419
+ frame = Bytes.empty_byte_buffer
420
+ frame << [frame_size].pack('N') # Length
421
+ frame << [HEADER_MAGIC].pack('n') # Magic
422
+ frame << [@flags].pack('n') # Flags
423
+ frame << [unsigned_int32(@sequence_id)].pack('N') # Sequence ID
424
+ frame << [header_data.bytesize / 4].pack('n') # Header length (in 32-bit words)
425
+ frame << header_data # Header data
426
+ frame << payload # Payload
427
+
428
+ @transport.write(frame)
429
+ @transport.flush
430
+ end
431
+
432
+ # Flushes data in simple framed format (for legacy compatibility)
433
+ def flush_framed(payload)
434
+ frame = [payload.bytesize].pack('N') + payload
435
+ @transport.write(frame)
436
+ @transport.flush
437
+ end
438
+
439
+ # Reads a varint32 from the given IO
440
+ def read_varint32(io, boundary_pos = nil)
441
+ shift = 0
442
+ result = 0
443
+ loop do
444
+ if boundary_pos && io.pos >= boundary_pos
445
+ raise TransportException.new(TransportException::UNKNOWN, "Trying to read past header boundary")
446
+ end
447
+ byte = io.getbyte
448
+ raise TransportException.new(TransportException::END_OF_FILE, "Unexpected EOF reading varint") if byte.nil?
449
+ result |= (byte & 0x7f) << shift
450
+ break if (byte & 0x80) == 0
451
+ shift += 7
452
+ end
453
+ result
454
+ end
455
+
456
+ # Writes a varint32 to the given IO
457
+ def write_varint32(io, n)
458
+ loop do
459
+ if (n & ~0x7F) == 0
460
+ io.write([n].pack('C'))
461
+ break
462
+ else
463
+ io.write([(n & 0x7F) | 0x80].pack('C'))
464
+ n >>= 7
465
+ end
466
+ end
467
+ end
468
+
469
+ # Reads a varint-prefixed string from the given IO
470
+ def read_varstring(io, boundary_pos = nil)
471
+ size = read_varint32(io, boundary_pos)
472
+ if size < 0
473
+ raise TransportException.new(TransportException::UNKNOWN, "Negative string size: #{size}")
474
+ end
475
+ if boundary_pos && size > (boundary_pos - io.pos)
476
+ raise TransportException.new(TransportException::UNKNOWN, "Info header length exceeds header size")
477
+ end
478
+ data = io.read(size)
479
+ if data.nil? || data.bytesize < size
480
+ raise TransportException.new(TransportException::END_OF_FILE, "Unexpected EOF reading string")
481
+ end
482
+ data
483
+ end
484
+
485
+ # Writes a varint-prefixed string to the given IO
486
+ def write_varstring(io, value)
487
+ value = Bytes.force_binary_encoding(value)
488
+ write_varint32(io, value.bytesize)
489
+ io.write(value)
490
+ end
491
+
492
+ def signed_int32(value)
493
+ value > 0x7fffffff ? value - (2**32) : value
494
+ end
495
+
496
+ def unsigned_int32(value)
497
+ value < 0 ? value + (2**32) : value
498
+ end
499
+ end
500
+
501
+ # Factory for creating HeaderTransport instances
502
+ class HeaderTransportFactory < BaseTransportFactory
503
+ def initialize(allowed_client_types = nil, default_protocol = HeaderSubprotocolID::BINARY)
504
+ @allowed_client_types = allowed_client_types
505
+ @default_protocol = default_protocol
506
+ end
507
+
508
+ def get_transport(transport)
509
+ HeaderTransport.new(transport, @allowed_client_types, @default_protocol)
510
+ end
511
+
512
+ def to_s
513
+ "header"
514
+ end
515
+ end
516
+ end
@@ -55,7 +55,7 @@ module Thrift
55
55
  ensure
56
56
  @outbuf = Bytes.empty_byte_buffer
57
57
  end
58
-
58
+
59
59
  def to_s
60
60
  "@{self.url}"
61
61
  end
@@ -1,5 +1,5 @@
1
1
  # encoding: ascii-8bit
2
- #
2
+ #
3
3
  # Licensed to the Apache Software Foundation (ASF) under one
4
4
  # or more contributor license agreements. See the NOTICE file
5
5
  # distributed with this work for additional information
@@ -7,9 +7,9 @@
7
7
  # to you under the Apache License, Version 2.0 (the
8
8
  # "License"); you may not use this file except in compliance
9
9
  # with the License. You may obtain a copy of the License at
10
- #
10
+ #
11
11
  # http://www.apache.org/licenses/LICENSE-2.0
12
- #
12
+ #
13
13
  # Unless required by applicable law or agreed to in writing,
14
14
  # software distributed under the License is distributed on an
15
15
  # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -1,5 +1,5 @@
1
1
  # encoding: ascii-8bit
2
- #
2
+ #
3
3
  # Licensed to the Apache Software Foundation (ASF) under one
4
4
  # or more contributor license agreements. See the NOTICE file
5
5
  # distributed with this work for additional information
@@ -7,9 +7,9 @@
7
7
  # to you under the Apache License, Version 2.0 (the
8
8
  # "License"); you may not use this file except in compliance
9
9
  # with the License. You may obtain a copy of the License at
10
- #
10
+ #
11
11
  # http://www.apache.org/licenses/LICENSE-2.0
12
- #
12
+ #
13
13
  # Unless required by applicable law or agreed to in writing,
14
14
  # software distributed under the License is distributed on an
15
15
  # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -112,16 +112,16 @@ module Thrift
112
112
  # if idx != 0
113
113
  # out << " "
114
114
  # end
115
-
115
+
116
116
  if idx == @index
117
117
  out << ">"
118
118
  end
119
-
119
+
120
120
  out << @buf[idx].ord.to_s(16)
121
121
  end
122
122
  out.join(" ")
123
123
  end
124
-
124
+
125
125
  def to_s
126
126
  "memory"
127
127
  end
@@ -1,5 +1,5 @@
1
1
  # encoding: ascii-8bit
2
- #
2
+ #
3
3
  # Licensed to the Apache Software Foundation (ASF) under one
4
4
  # or more contributor license agreements. See the NOTICE file
5
5
  # distributed with this work for additional information
@@ -7,16 +7,16 @@
7
7
  # to you under the Apache License, Version 2.0 (the
8
8
  # "License"); you may not use this file except in compliance
9
9
  # with the License. You may obtain a copy of the License at
10
- #
10
+ #
11
11
  # http://www.apache.org/licenses/LICENSE-2.0
12
- #
12
+ #
13
13
  # Unless required by applicable law or agreed to in writing,
14
14
  # software distributed under the License is distributed on an
15
15
  # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
16
  # KIND, either express or implied. See the License for the
17
17
  # specific language governing permissions and limitations
18
18
  # under the License.
19
- #
19
+ #
20
20
 
21
21
  require 'socket'
22
22
 
@@ -43,6 +43,7 @@ module Thrift
43
43
  def accept
44
44
  unless @handle.nil?
45
45
  sock = @handle.accept
46
+ sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
46
47
  trans = Socket.new
47
48
  trans.handle = sock
48
49
  trans
@@ -58,7 +59,9 @@ module Thrift
58
59
  @handle.nil? or @handle.closed?
59
60
  end
60
61
 
61
- alias to_io handle
62
+ def to_io
63
+ @handle&.to_io || raise(IOError, 'closed stream')
64
+ end
62
65
 
63
66
  def to_s
64
67
  "socket(#{@host}:#{@port})"