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.
- checksums.yaml +4 -4
- data/README.md +175 -17
- data/benchmark/benchmark.rb +22 -8
- data/benchmark/client.rb +49 -6
- data/benchmark/server.rb +45 -7
- data/benchmark/thin_server.rb +1 -0
- data/ext/binary_protocol_accelerated.c +76 -19
- data/ext/compact_protocol.c +80 -15
- data/ext/constants.h +12 -0
- data/ext/extconf.rb +10 -9
- data/ext/memory_buffer.c +7 -7
- data/ext/protocol.c +29 -0
- data/ext/protocol.h +35 -0
- data/ext/struct.c +36 -5
- data/ext/thrift_native.c +27 -3
- data/lib/thrift/bytes.rb +68 -101
- data/lib/thrift/client.rb +61 -9
- data/lib/thrift/exceptions.rb +5 -5
- data/lib/thrift/multiplexed_processor.rb +6 -6
- data/lib/thrift/processor.rb +6 -6
- data/lib/thrift/protocol/base_protocol.rb +37 -15
- data/lib/thrift/protocol/binary_protocol.rb +25 -9
- data/lib/thrift/protocol/binary_protocol_accelerated.rb +5 -5
- data/lib/thrift/protocol/compact_protocol.rb +61 -37
- data/lib/thrift/protocol/header_protocol.rb +320 -0
- data/lib/thrift/protocol/json_protocol.rb +26 -16
- data/lib/thrift/protocol/multiplexed_protocol.rb +5 -5
- data/lib/thrift/protocol/protocol_decorator.rb +12 -4
- data/lib/thrift/serializer/deserializer.rb +5 -5
- data/lib/thrift/serializer/serializer.rb +4 -5
- data/lib/thrift/server/base_server.rb +4 -4
- data/lib/thrift/server/mongrel_http_server.rb +6 -6
- data/lib/thrift/server/nonblocking_server.rb +8 -8
- data/lib/thrift/server/simple_server.rb +4 -4
- data/lib/thrift/server/thin_http_server.rb +3 -3
- data/lib/thrift/server/thread_pool_server.rb +6 -6
- data/lib/thrift/server/threaded_server.rb +4 -4
- data/lib/thrift/struct.rb +11 -11
- data/lib/thrift/struct_union.rb +19 -9
- data/lib/thrift/thrift_native.rb +1 -1
- data/lib/thrift/transport/base_server_transport.rb +5 -5
- data/lib/thrift/transport/base_transport.rb +12 -12
- data/lib/thrift/transport/buffered_transport.rb +6 -6
- data/lib/thrift/transport/framed_transport.rb +7 -7
- data/lib/thrift/transport/header_transport.rb +516 -0
- data/lib/thrift/transport/http_client_transport.rb +1 -1
- data/lib/thrift/transport/io_stream_transport.rb +3 -3
- data/lib/thrift/transport/memory_buffer_transport.rb +6 -6
- data/lib/thrift/transport/server_socket.rb +8 -5
- data/lib/thrift/transport/socket.rb +58 -31
- data/lib/thrift/transport/ssl_server_socket.rb +1 -1
- data/lib/thrift/transport/ssl_socket.rb +2 -2
- data/lib/thrift/transport/unix_server_socket.rb +4 -4
- data/lib/thrift/transport/unix_socket.rb +6 -6
- data/lib/thrift/types.rb +9 -6
- data/lib/thrift/union.rb +14 -8
- data/lib/thrift/uuid.rb +49 -0
- data/lib/thrift.rb +3 -1
- data/spec/ThriftSpec.thrift +5 -1
- data/spec/base_protocol_spec.rb +1 -2
- data/spec/base_transport_spec.rb +6 -7
- data/spec/binary_protocol_spec.rb +0 -2
- data/spec/binary_protocol_spec_shared.rb +129 -142
- data/spec/bytes_spec.rb +57 -118
- data/spec/client_spec.rb +85 -19
- data/spec/compact_protocol_spec.rb +54 -16
- data/spec/constants_demo_spec.rb +101 -0
- data/spec/exception_spec.rb +0 -1
- data/spec/header_protocol_spec.rb +475 -0
- data/spec/header_transport_spec.rb +386 -0
- data/spec/http_client_spec.rb +4 -6
- data/spec/json_protocol_spec.rb +47 -47
- data/spec/namespaced_spec.rb +0 -1
- data/spec/nonblocking_server_spec.rb +102 -4
- data/spec/processor_spec.rb +0 -1
- data/spec/serializer_spec.rb +0 -1
- data/spec/server_socket_spec.rb +1 -1
- data/spec/server_spec.rb +8 -9
- data/spec/socket_spec.rb +0 -1
- data/spec/socket_spec_shared.rb +72 -9
- data/spec/spec_helper.rb +1 -1
- data/spec/ssl_server_socket_spec.rb +12 -1
- data/spec/ssl_socket_spec.rb +10 -1
- data/spec/struct_nested_containers_spec.rb +1 -2
- data/spec/struct_spec.rb +113 -9
- data/spec/support/header_protocol_helper.rb +54 -0
- data/spec/thin_http_server_spec.rb +3 -18
- data/spec/types_spec.rb +25 -26
- data/spec/union_spec.rb +69 -11
- data/spec/unix_socket_spec.rb +1 -2
- data/spec/uuid_validation_spec.rb +238 -0
- data/test/fuzz/Makefile.am +173 -0
- data/test/fuzz/README.md +149 -0
- data/test/fuzz/fuzz_common.rb +95 -0
- data/{lib/thrift/core_ext.rb → test/fuzz/fuzz_parse_binary_protocol.rb} +3 -4
- data/{lib/thrift/core_ext/fixnum.rb → test/fuzz/fuzz_parse_binary_protocol_accelerated.rb} +6 -13
- data/test/fuzz/fuzz_parse_binary_protocol_accelerated_harness.rb +22 -0
- data/test/fuzz/fuzz_parse_binary_protocol_harness.rb +22 -0
- data/test/fuzz/fuzz_parse_compact_protocol.rb +22 -0
- data/test/fuzz/fuzz_parse_compact_protocol_harness.rb +22 -0
- data/test/fuzz/fuzz_parse_json_protocol.rb +22 -0
- data/test/fuzz/fuzz_parse_json_protocol_harness.rb +22 -0
- data/test/fuzz/fuzz_roundtrip_binary_protocol.rb +22 -0
- data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated.rb +22 -0
- data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated_harness.rb +22 -0
- data/test/fuzz/fuzz_roundtrip_binary_protocol_harness.rb +22 -0
- data/test/fuzz/fuzz_roundtrip_compact_protocol.rb +22 -0
- data/test/fuzz/fuzz_roundtrip_compact_protocol_harness.rb +22 -0
- data/test/fuzz/fuzz_roundtrip_json_protocol.rb +22 -0
- data/test/fuzz/fuzz_roundtrip_json_protocol_harness.rb +22 -0
- data/test/fuzz/fuzz_tracer.rb +28 -0
- 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
|
|
@@ -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
|
-
|
|
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})"
|