Moving from govendor to dep, updated dependencies (#48)

* Moving from govendor to dep.

* Making the pull request template more friendly.

* Fixing akward space in PR template.

* goimports run on whole project using ` goimports -w $(find . -type f -name '*.go' -not -path "./vendor/*" -not -path "./gen-go/*")`

source of command: https://gist.github.com/bgentry/fd1ffef7dbde01857f66
This commit is contained in:
Renan DelValle 2018-01-07 13:13:47 -08:00 committed by GitHub
parent 9631aa3aab
commit 8d445c1c77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2186 changed files with 400410 additions and 352 deletions

70
vendor/git.apache.org/thrift.git/lib/rb/lib/thrift.rb generated vendored Normal file
View file

@ -0,0 +1,70 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# Contains some contributions under the Thrift Software License.
# Please see doc/old-thrift-license.txt in the Thrift distribution for
# details.
$:.unshift File.dirname(__FILE__)
require 'thrift/bytes'
require 'thrift/core_ext'
require 'thrift/exceptions'
require 'thrift/types'
require 'thrift/processor'
require 'thrift/multiplexed_processor'
require 'thrift/client'
require 'thrift/struct'
require 'thrift/union'
require 'thrift/struct_union'
# serializer
require 'thrift/serializer/serializer'
require 'thrift/serializer/deserializer'
# protocol
require 'thrift/protocol/base_protocol'
require 'thrift/protocol/binary_protocol'
require 'thrift/protocol/binary_protocol_accelerated'
require 'thrift/protocol/compact_protocol'
require 'thrift/protocol/json_protocol'
require 'thrift/protocol/multiplexed_protocol'
# transport
require 'thrift/transport/base_transport'
require 'thrift/transport/base_server_transport'
require 'thrift/transport/socket'
require 'thrift/transport/ssl_socket'
require 'thrift/transport/server_socket'
require 'thrift/transport/ssl_server_socket'
require 'thrift/transport/unix_socket'
require 'thrift/transport/unix_server_socket'
require 'thrift/transport/buffered_transport'
require 'thrift/transport/framed_transport'
require 'thrift/transport/http_client_transport'
require 'thrift/transport/io_stream_transport'
require 'thrift/transport/memory_buffer_transport'
# server
require 'thrift/server/base_server'
require 'thrift/server/nonblocking_server'
require 'thrift/server/simple_server'
require 'thrift/server/threaded_server'
require 'thrift/server/thread_pool_server'
require 'thrift/thrift_native'

View file

@ -0,0 +1,131 @@
# encoding: ascii-8bit
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
module Thrift
# A collection of utilities for working with bytes and byte buffers.
module Bytes
if RUBY_VERSION >= '1.9'
# Creates and empty byte buffer (String with BINARY encoding)
#
# size - The Integer size of the buffer (default: nil) to create
#
# Returns a String with BINARY encoding, filled with null characters
# if size is greater than zero
def self.empty_byte_buffer(size = nil)
if (size && size > 0)
"\0".force_encoding(Encoding::BINARY) * size
else
''.force_encoding(Encoding::BINARY)
end
end
# Forces the encoding of the buffer to BINARY. If the buffer
# passed is frozen, then it will be duplicated.
#
# buffer - The String to force the encoding of.
#
# Returns the String passed with an encoding of BINARY; returned
# String may be a duplicate.
def self.force_binary_encoding(buffer)
buffer = buffer.dup if buffer.frozen?
buffer.force_encoding(Encoding::BINARY)
end
# Gets the byte value of a given position in a String.
#
# string - The String to retrive the byte value from.
# index - The Integer location of the byte value to retrieve.
#
# Returns an Integer value between 0 and 255.
def self.get_string_byte(string, index)
string.getbyte(index)
end
# Sets the byte value given to a given index in a String.
#
# string - The String to set the byte value in.
# index - The Integer location to set the byte value at.
# byte - The Integer value (0 to 255) to set in the string.
#
# Returns an Integer value of the byte value to set.
def self.set_string_byte(string, index, byte)
string.setbyte(index, byte)
end
# Converts the given String to a UTF-8 byte buffer.
#
# string - The String to convert.
#
# Returns a new String with BINARY encoding, containing the UTF-8
# bytes of the original string.
def self.convert_to_utf8_byte_buffer(string)
if string.encoding != Encoding::UTF_8
# transcode to UTF-8
string = string.encode(Encoding::UTF_8)
else
# encoding is already UTF-8, but a duplicate is needed
string = string.dup
end
string.force_encoding(Encoding::BINARY)
end
# Converts the given UTF-8 byte buffer into a String
#
# utf8_buffer - A String, with BINARY encoding, containing UTF-8 bytes
#
# Returns a new String with UTF-8 encoding,
def self.convert_to_string(utf8_buffer)
# duplicate the buffer, force encoding to UTF-8
utf8_buffer.dup.force_encoding(Encoding::UTF_8)
end
else
def self.empty_byte_buffer(size = nil)
if (size && size > 0)
"\0" * size
else
''
end
end
def self.force_binary_encoding(buffer)
buffer
end
def self.get_string_byte(string, index)
string[index]
end
def self.set_string_byte(string, index, byte)
string[index] = byte
end
def self.convert_to_utf8_byte_buffer(string)
# This assumes $KCODE is 'UTF8'/'U', which would mean the String is already a UTF-8 byte buffer
# TODO consider handling other $KCODE values and transcoding with iconv
string
end
def self.convert_to_string(utf8_buffer)
# See comment in 'convert_to_utf8_byte_buffer' for relevant assumptions.
utf8_buffer
end
end
end
end

View file

@ -0,0 +1,71 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
module Thrift
module Client
def initialize(iprot, oprot=nil)
@iprot = iprot
@oprot = oprot || iprot
@seqid = 0
end
def send_message(name, args_class, args = {})
@oprot.write_message_begin(name, MessageTypes::CALL, @seqid)
send_message_args(args_class, args)
end
def send_oneway_message(name, args_class, args = {})
@oprot.write_message_begin(name, MessageTypes::ONEWAY, @seqid)
send_message_args(args_class, args)
end
def send_message_args(args_class, args)
data = args_class.new
args.each do |k, v|
data.send("#{k.to_s}=", v)
end
begin
data.write(@oprot)
rescue StandardError => e
@oprot.trans.close
raise e
end
@oprot.write_message_end
@oprot.trans.flush
end
def receive_message(result_klass)
fname, mtype, rseqid = @iprot.read_message_begin
handle_exception(mtype)
result = result_klass.new
result.read(@iprot)
@iprot.read_message_end
result
end
def handle_exception(mtype)
if mtype == MessageTypes::EXCEPTION
x = ApplicationException.new
x.read(@iprot)
@iprot.read_message_end
raise x
end
end
end
end

View file

@ -0,0 +1,23 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
Dir[File.dirname(__FILE__) + "/core_ext/*.rb"].each do |file|
name = File.basename(file, '.rb')
require "thrift/core_ext/#{name}"
end

View file

@ -0,0 +1,29 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# Versions of ruby pre 1.8.7 do not have an .ord method available in the Fixnum
# class.
#
if RUBY_VERSION < "1.8.7"
class Fixnum
def ord
self
end
end
end

View file

@ -0,0 +1,87 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
module Thrift
class Exception < StandardError
def initialize(message)
super
@message = message
end
attr_reader :message
end
class ApplicationException < Exception
UNKNOWN = 0
UNKNOWN_METHOD = 1
INVALID_MESSAGE_TYPE = 2
WRONG_METHOD_NAME = 3
BAD_SEQUENCE_ID = 4
MISSING_RESULT = 5
INTERNAL_ERROR = 6
PROTOCOL_ERROR = 7
INVALID_TRANSFORM = 8
INVALID_PROTOCOL = 9
UNSUPPORTED_CLIENT_TYPE = 10
attr_reader :type
def initialize(type=UNKNOWN, message=nil)
super(message)
@type = type
end
def read(iprot)
iprot.read_struct_begin
while true
fname, ftype, fid = iprot.read_field_begin
if ftype == Types::STOP
break
end
if fid == 1 and ftype == Types::STRING
@message = iprot.read_string
elsif fid == 2 and ftype == Types::I32
@type = iprot.read_i32
else
iprot.skip(ftype)
end
iprot.read_field_end
end
iprot.read_struct_end
end
def write(oprot)
oprot.write_struct_begin('Thrift::ApplicationException')
unless @message.nil?
oprot.write_field_begin('message', Types::STRING, 1)
oprot.write_string(@message)
oprot.write_field_end
end
unless @type.nil?
oprot.write_field_begin('type', Types::I32, 2)
oprot.write_i32(@type)
oprot.write_field_end
end
oprot.write_field_stop
oprot.write_struct_end
end
end
end

View file

@ -0,0 +1,76 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
require 'thrift/protocol/protocol_decorator'
require 'thrift/protocol/base_protocol'
module Thrift
class MultiplexedProcessor
def initialize
@actual_processors = {}
end
def register_processor(service_name, processor)
@actual_processors[service_name] = processor
end
def process(iprot, oprot)
name, type, seqid = iprot.read_message_begin
check_type(type)
check_separator(name)
service_name, method = name.split(':')
processor(service_name).process(StoredMessageProtocol.new(iprot, [method, type, seqid]), oprot)
end
protected
def processor(service_name)
if @actual_processors.has_key?(service_name)
@actual_processors[service_name]
else
raise Thrift::Exception.new("Service name not found: #{service_name}. Did you forget to call #{self.class.name}#register_processor?")
end
end
def check_type(type)
unless [MessageTypes::CALL, MessageTypes::ONEWAY].include?(type)
raise Thrift::Exception.new('This should not have happened!?')
end
end
def check_separator(name)
if name.count(':') < 1
raise Thrift::Exception.new("Service name not found in message name: #{name}. Did you forget to use a Thrift::Protocol::MultiplexedProtocol in your client?")
end
end
end
class StoredMessageProtocol < BaseProtocol
include ProtocolDecorator
def initialize(protocol, message_begin)
super(protocol)
@message_begin = message_begin
end
def read_message_begin
@message_begin
end
end
end

View file

@ -0,0 +1,66 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
module Thrift
module Processor
def initialize(handler)
@handler = handler
end
def process(iprot, oprot)
name, type, seqid = iprot.read_message_begin
if respond_to?("process_#{name}")
begin
send("process_#{name}", seqid, iprot, oprot)
rescue => e
x = ApplicationException.new(ApplicationException::INTERNAL_ERROR, 'Internal error')
write_error(x, oprot, name, seqid)
end
true
else
iprot.skip(Types::STRUCT)
iprot.read_message_end
x = ApplicationException.new(ApplicationException::UNKNOWN_METHOD, 'Unknown function '+name)
write_error(x, oprot, name, seqid)
false
end
end
def read_args(iprot, args_class)
args = args_class.new
args.read(iprot)
iprot.read_message_end
args
end
def write_result(result, oprot, name, seqid)
oprot.write_message_begin(name, MessageTypes::REPLY, seqid)
result.write(oprot)
oprot.write_message_end
oprot.trans.flush
end
def write_error(err, oprot, name, seqid)
oprot.write_message_begin(name, MessageTypes::EXCEPTION, seqid)
err.write(oprot)
oprot.write_message_end
oprot.trans.flush
end
end
end

View file

@ -0,0 +1,379 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# this require is to make generated struct definitions happy
require 'set'
module Thrift
class ProtocolException < Exception
UNKNOWN = 0
INVALID_DATA = 1
NEGATIVE_SIZE = 2
SIZE_LIMIT = 3
BAD_VERSION = 4
NOT_IMPLEMENTED = 5
DEPTH_LIMIT = 6
attr_reader :type
def initialize(type=UNKNOWN, message=nil)
super(message)
@type = type
end
end
class BaseProtocol
attr_reader :trans
def initialize(trans)
@trans = trans
end
def native?
puts "wrong method is being called!"
false
end
def write_message_begin(name, type, seqid)
raise NotImplementedError
end
def write_message_end; nil; end
def write_struct_begin(name)
raise NotImplementedError
end
def write_struct_end; nil; end
def write_field_begin(name, type, id)
raise NotImplementedError
end
def write_field_end; nil; end
def write_field_stop
raise NotImplementedError
end
def write_map_begin(ktype, vtype, size)
raise NotImplementedError
end
def write_map_end; nil; end
def write_list_begin(etype, size)
raise NotImplementedError
end
def write_list_end; nil; end
def write_set_begin(etype, size)
raise NotImplementedError
end
def write_set_end; nil; end
def write_bool(bool)
raise NotImplementedError
end
def write_byte(byte)
raise NotImplementedError
end
def write_i16(i16)
raise NotImplementedError
end
def write_i32(i32)
raise NotImplementedError
end
def write_i64(i64)
raise NotImplementedError
end
def write_double(dub)
raise NotImplementedError
end
# Writes a Thrift String. In Ruby 1.9+, the String passed will be transcoded to UTF-8.
#
# str - The String to write.
#
# Raises EncodingError if the transcoding to UTF-8 fails.
#
# Returns nothing.
def write_string(str)
raise NotImplementedError
end
# Writes a Thrift Binary (Thrift String with no encoding). In Ruby 1.9+, the String passed
# will forced into BINARY encoding.
#
# buf - The String to write.
#
# Returns nothing.
def write_binary(buf)
raise NotImplementedError
end
def read_message_begin
raise NotImplementedError
end
def read_message_end; nil; end
def read_struct_begin
raise NotImplementedError
end
def read_struct_end; nil; end
def read_field_begin
raise NotImplementedError
end
def read_field_end; nil; end
def read_map_begin
raise NotImplementedError
end
def read_map_end; nil; end
def read_list_begin
raise NotImplementedError
end
def read_list_end; nil; end
def read_set_begin
raise NotImplementedError
end
def read_set_end; nil; end
def read_bool
raise NotImplementedError
end
def read_byte
raise NotImplementedError
end
def read_i16
raise NotImplementedError
end
def read_i32
raise NotImplementedError
end
def read_i64
raise NotImplementedError
end
def read_double
raise NotImplementedError
end
# Reads a Thrift String. In Ruby 1.9+, all Strings will be returned with an Encoding of UTF-8.
#
# Returns a String.
def read_string
raise NotImplementedError
end
# Reads a Thrift Binary (Thrift String without encoding). In Ruby 1.9+, all Strings will be returned
# with an Encoding of BINARY.
#
# Returns a String.
def read_binary
raise NotImplementedError
end
# Writes a field based on the field information, field ID and value.
#
# field_info - A Hash containing the definition of the field:
# :name - The name of the field.
# :type - The type of the field, which must be a Thrift::Types constant.
# :binary - A Boolean flag that indicates if Thrift::Types::STRING is a binary string (string without encoding).
# fid - The ID of the field.
# value - The field's value to write; object type varies based on :type.
#
# Returns nothing.
def write_field(*args)
if args.size == 3
# handles the documented method signature - write_field(field_info, fid, value)
field_info = args[0]
fid = args[1]
value = args[2]
elsif args.size == 4
# handles the deprecated method signature - write_field(name, type, fid, value)
field_info = {:name => args[0], :type => args[1]}
fid = args[2]
value = args[3]
else
raise ArgumentError, "wrong number of arguments (#{args.size} for 3)"
end
write_field_begin(field_info[:name], field_info[:type], fid)
write_type(field_info, value)
write_field_end
end
# Writes a field value based on the field information.
#
# field_info - A Hash containing the definition of the field:
# :type - The Thrift::Types constant that determines how the value is written.
# :binary - A Boolean flag that indicates if Thrift::Types::STRING is a binary string (string without encoding).
# value - The field's value to write; object type varies based on field_info[:type].
#
# Returns nothing.
def write_type(field_info, value)
# if field_info is a Fixnum, assume it is a Thrift::Types constant
# convert it into a field_info Hash for backwards compatibility
if field_info.is_a? Fixnum
field_info = {:type => field_info}
end
case field_info[:type]
when Types::BOOL
write_bool(value)
when Types::BYTE
write_byte(value)
when Types::DOUBLE
write_double(value)
when Types::I16
write_i16(value)
when Types::I32
write_i32(value)
when Types::I64
write_i64(value)
when Types::STRING
if field_info[:binary]
write_binary(value)
else
write_string(value)
end
when Types::STRUCT
value.write(self)
else
raise NotImplementedError
end
end
# Reads a field value based on the field information.
#
# field_info - A Hash containing the pertinent data to write:
# :type - The Thrift::Types constant that determines how the value is written.
# :binary - A flag that indicates if Thrift::Types::STRING is a binary string (string without encoding).
#
# Returns the value read; object type varies based on field_info[:type].
def read_type(field_info)
# if field_info is a Fixnum, assume it is a Thrift::Types constant
# convert it into a field_info Hash for backwards compatibility
if field_info.is_a? Fixnum
field_info = {:type => field_info}
end
case field_info[:type]
when Types::BOOL
read_bool
when Types::BYTE
read_byte
when Types::DOUBLE
read_double
when Types::I16
read_i16
when Types::I32
read_i32
when Types::I64
read_i64
when Types::STRING
if field_info[:binary]
read_binary
else
read_string
end
else
raise NotImplementedError
end
end
def skip(type)
case type
when Types::STOP
nil
when Types::BOOL
read_bool
when Types::BYTE
read_byte
when Types::I16
read_i16
when Types::I32
read_i32
when Types::I64
read_i64
when Types::DOUBLE
read_double
when Types::STRING
read_string
when Types::STRUCT
read_struct_begin
while true
name, type, id = read_field_begin
break if type == Types::STOP
skip(type)
read_field_end
end
read_struct_end
when Types::MAP
ktype, vtype, size = read_map_begin
size.times do
skip(ktype)
skip(vtype)
end
read_map_end
when Types::SET
etype, size = read_set_begin
size.times do
skip(etype)
end
read_set_end
when Types::LIST
etype, size = read_list_begin
size.times do
skip(etype)
end
read_list_end
end
end
end
class BaseProtocolFactory
def get_protocol(trans)
raise NotImplementedError
end
end
end

View file

@ -0,0 +1,237 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
module Thrift
class BinaryProtocol < BaseProtocol
VERSION_MASK = 0xffff0000
VERSION_1 = 0x80010000
TYPE_MASK = 0x000000ff
attr_reader :strict_read, :strict_write
def initialize(trans, strict_read=true, strict_write=true)
super(trans)
@strict_read = strict_read
@strict_write = strict_write
# Pre-allocated read buffer for fixed-size read methods. Needs to be at least 8 bytes long for
# read_i64() and read_double().
@rbuf = Bytes.empty_byte_buffer(8)
end
def write_message_begin(name, type, seqid)
# this is necessary because we added (needed) bounds checking to
# write_i32, and 0x80010000 is too big for that.
if strict_write
write_i16(VERSION_1 >> 16)
write_i16(type)
write_string(name)
write_i32(seqid)
else
write_string(name)
write_byte(type)
write_i32(seqid)
end
end
def write_struct_begin(name); nil; end
def write_field_begin(name, type, id)
write_byte(type)
write_i16(id)
end
def write_field_stop
write_byte(Thrift::Types::STOP)
end
def write_map_begin(ktype, vtype, size)
write_byte(ktype)
write_byte(vtype)
write_i32(size)
end
def write_list_begin(etype, size)
write_byte(etype)
write_i32(size)
end
def write_set_begin(etype, size)
write_byte(etype)
write_i32(size)
end
def write_bool(bool)
write_byte(bool ? 1 : 0)
end
def write_byte(byte)
raise RangeError if byte < -2**31 || byte >= 2**32
trans.write([byte].pack('c'))
end
def write_i16(i16)
trans.write([i16].pack('n'))
end
def write_i32(i32)
raise RangeError if i32 < -2**31 || i32 >= 2**31
trans.write([i32].pack('N'))
end
def write_i64(i64)
raise RangeError if i64 < -2**63 || i64 >= 2**64
hi = i64 >> 32
lo = i64 & 0xffffffff
trans.write([hi, lo].pack('N2'))
end
def write_double(dub)
trans.write([dub].pack('G'))
end
def write_string(str)
buf = Bytes.convert_to_utf8_byte_buffer(str)
write_binary(buf)
end
def write_binary(buf)
write_i32(buf.bytesize)
trans.write(buf)
end
def read_message_begin
version = read_i32
if version < 0
if (version & VERSION_MASK != VERSION_1)
raise ProtocolException.new(ProtocolException::BAD_VERSION, 'Missing version identifier')
end
type = version & TYPE_MASK
name = read_string
seqid = read_i32
[name, type, seqid]
else
if strict_read
raise ProtocolException.new(ProtocolException::BAD_VERSION, 'No version identifier, old protocol client?')
end
name = trans.read_all(version)
type = read_byte
seqid = read_i32
[name, type, seqid]
end
end
def read_struct_begin; nil; end
def read_field_begin
type = read_byte
if (type == Types::STOP)
[nil, type, 0]
else
id = read_i16
[nil, type, id]
end
end
def read_map_begin
ktype = read_byte
vtype = read_byte
size = read_i32
[ktype, vtype, size]
end
def read_list_begin
etype = read_byte
size = read_i32
[etype, size]
end
def read_set_begin
etype = read_byte
size = read_i32
[etype, size]
end
def read_bool
byte = read_byte
byte != 0
end
def read_byte
val = trans.read_byte
if (val > 0x7f)
val = 0 - ((val - 1) ^ 0xff)
end
val
end
def read_i16
trans.read_into_buffer(@rbuf, 2)
val, = @rbuf.unpack('n')
if (val > 0x7fff)
val = 0 - ((val - 1) ^ 0xffff)
end
val
end
def read_i32
trans.read_into_buffer(@rbuf, 4)
val, = @rbuf.unpack('N')
if (val > 0x7fffffff)
val = 0 - ((val - 1) ^ 0xffffffff)
end
val
end
def read_i64
trans.read_into_buffer(@rbuf, 8)
hi, lo = @rbuf.unpack('N2')
if (hi > 0x7fffffff)
hi ^= 0xffffffff
lo ^= 0xffffffff
0 - (hi << 32) - lo - 1
else
(hi << 32) + lo
end
end
def read_double
trans.read_into_buffer(@rbuf, 8)
val = @rbuf.unpack('G').first
val
end
def read_string
buffer = read_binary
Bytes.convert_to_string(buffer)
end
def read_binary
size = read_i32
trans.read_all(size)
end
end
class BinaryProtocolFactory < BaseProtocolFactory
def get_protocol(trans)
return Thrift::BinaryProtocol.new(trans)
end
end
end

View file

@ -0,0 +1,39 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
=begin
The only change required for a transport to support BinaryProtocolAccelerated is to implement 2 methods:
* borrow(size), which takes an optional argument and returns atleast _size_ bytes from the transport,
or the default buffer size if no argument is given
* consume!(size), which removes size bytes from the front of the buffer
See MemoryBuffer and BufferedTransport for examples.
=end
module Thrift
class BinaryProtocolAcceleratedFactory < BaseProtocolFactory
def get_protocol(trans)
if (defined? BinaryProtocolAccelerated)
BinaryProtocolAccelerated.new(trans)
else
BinaryProtocol.new(trans)
end
end
end
end

View file

@ -0,0 +1,435 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
module Thrift
class CompactProtocol < BaseProtocol
PROTOCOL_ID = [0x82].pack('c').unpack('c').first
VERSION = 1
VERSION_MASK = 0x1f
TYPE_MASK = 0xE0
TYPE_BITS = 0x07
TYPE_SHIFT_AMOUNT = 5
TSTOP = ["", Types::STOP, 0]
#
# All of the on-wire type codes.
#
class CompactTypes
BOOLEAN_TRUE = 0x01
BOOLEAN_FALSE = 0x02
BYTE = 0x03
I16 = 0x04
I32 = 0x05
I64 = 0x06
DOUBLE = 0x07
BINARY = 0x08
LIST = 0x09
SET = 0x0A
MAP = 0x0B
STRUCT = 0x0C
def self.is_bool_type?(b)
(b & 0x0f) == BOOLEAN_TRUE || (b & 0x0f) == BOOLEAN_FALSE
end
COMPACT_TO_TTYPE = {
Types::STOP => Types::STOP,
BOOLEAN_FALSE => Types::BOOL,
BOOLEAN_TRUE => Types::BOOL,
BYTE => Types::BYTE,
I16 => Types::I16,
I32 => Types::I32,
I64 => Types::I64,
DOUBLE => Types::DOUBLE,
BINARY => Types::STRING,
LIST => Types::LIST,
SET => Types::SET,
MAP => Types::MAP,
STRUCT => Types::STRUCT
}
TTYPE_TO_COMPACT = {
Types::STOP => Types::STOP,
Types::BOOL => BOOLEAN_TRUE,
Types::BYTE => BYTE,
Types::I16 => I16,
Types::I32 => I32,
Types::I64 => I64,
Types::DOUBLE => DOUBLE,
Types::STRING => BINARY,
Types::LIST => LIST,
Types::SET => SET,
Types::MAP => MAP,
Types::STRUCT => STRUCT
}
def self.get_ttype(compact_type)
val = COMPACT_TO_TTYPE[compact_type & 0x0f]
raise "don't know what type: #{compact_type & 0x0f}" unless val
val
end
def self.get_compact_type(ttype)
val = TTYPE_TO_COMPACT[ttype]
raise "don't know what type: #{ttype & 0x0f}" unless val
val
end
end
def initialize(transport)
super(transport)
@last_field = [0]
@boolean_value = nil
# Pre-allocated read buffer for read_double().
@rbuf = Bytes.empty_byte_buffer(8)
end
def write_message_begin(name, type, seqid)
write_byte(PROTOCOL_ID)
write_byte((VERSION & VERSION_MASK) | ((type << TYPE_SHIFT_AMOUNT) & TYPE_MASK))
write_varint32(seqid)
write_string(name)
nil
end
def write_struct_begin(name)
@last_field.push(0)
nil
end
def write_struct_end
@last_field.pop
nil
end
def write_field_begin(name, type, id)
if type == Types::BOOL
# we want to possibly include the value, so we'll wait.
@boolean_field = [type, id]
else
write_field_begin_internal(type, id)
end
nil
end
#
# The workhorse of writeFieldBegin. It has the option of doing a
# 'type override' of the type header. This is used specifically in the
# boolean field case.
#
def write_field_begin_internal(type, id, type_override=nil)
last_id = @last_field.pop
# if there's a type override, use that.
typeToWrite = type_override || CompactTypes.get_compact_type(type)
# check if we can use delta encoding for the field id
if id > last_id && id - last_id <= 15
# write them together
write_byte((id - last_id) << 4 | typeToWrite)
else
# write them separate
write_byte(typeToWrite)
write_i16(id)
end
@last_field.push(id)
nil
end
def write_field_stop
write_byte(Types::STOP)
end
def write_map_begin(ktype, vtype, size)
if (size == 0)
write_byte(0)
else
write_varint32(size)
write_byte(CompactTypes.get_compact_type(ktype) << 4 | CompactTypes.get_compact_type(vtype))
end
end
def write_list_begin(etype, size)
write_collection_begin(etype, size)
end
def write_set_begin(etype, size)
write_collection_begin(etype, size);
end
def write_bool(bool)
type = bool ? CompactTypes::BOOLEAN_TRUE : CompactTypes::BOOLEAN_FALSE
unless @boolean_field.nil?
# we haven't written the field header yet
write_field_begin_internal(@boolean_field.first, @boolean_field.last, type)
@boolean_field = nil
else
# we're not part of a field, so just write the value.
write_byte(type)
end
end
def write_byte(byte)
@trans.write([byte].pack('c'))
end
def write_i16(i16)
write_varint32(int_to_zig_zag(i16))
end
def write_i32(i32)
write_varint32(int_to_zig_zag(i32))
end
def write_i64(i64)
write_varint64(long_to_zig_zag(i64))
end
def write_double(dub)
@trans.write([dub].pack("G").reverse)
end
def write_string(str)
buf = Bytes.convert_to_utf8_byte_buffer(str)
write_binary(buf)
end
def write_binary(buf)
write_varint32(buf.bytesize)
@trans.write(buf)
end
def read_message_begin
protocol_id = read_byte()
if protocol_id != PROTOCOL_ID
raise ProtocolException.new("Expected protocol id #{PROTOCOL_ID} but got #{protocol_id}")
end
version_and_type = read_byte()
version = version_and_type & VERSION_MASK
if (version != VERSION)
raise ProtocolException.new("Expected version #{VERSION} but got #{version}");
end
type = (version_and_type >> TYPE_SHIFT_AMOUNT) & TYPE_BITS
seqid = read_varint32()
messageName = read_string()
[messageName, type, seqid]
end
def read_struct_begin
@last_field.push(0)
""
end
def read_struct_end
@last_field.pop()
nil
end
def read_field_begin
type = read_byte()
# if it's a stop, then we can return immediately, as the struct is over.
if (type & 0x0f) == Types::STOP
TSTOP
else
field_id = nil
# mask off the 4 MSB of the type header. it could contain a field id delta.
modifier = (type & 0xf0) >> 4
if modifier == 0
# not a delta. look ahead for the zigzag varint field id.
@last_field.pop
field_id = read_i16()
else
# has a delta. add the delta to the last read field id.
field_id = @last_field.pop + modifier
end
# if this happens to be a boolean field, the value is encoded in the type
if CompactTypes.is_bool_type?(type)
# save the boolean value in a special instance variable.
@bool_value = (type & 0x0f) == CompactTypes::BOOLEAN_TRUE
end
# push the new field onto the field stack so we can keep the deltas going.
@last_field.push(field_id)
["", CompactTypes.get_ttype(type & 0x0f), field_id]
end
end
def read_map_begin
size = read_varint32()
key_and_value_type = size == 0 ? 0 : read_byte()
[CompactTypes.get_ttype(key_and_value_type >> 4), CompactTypes.get_ttype(key_and_value_type & 0xf), size]
end
def read_list_begin
size_and_type = read_byte()
size = (size_and_type >> 4) & 0x0f
if size == 15
size = read_varint32()
end
type = CompactTypes.get_ttype(size_and_type)
[type, size]
end
def read_set_begin
read_list_begin
end
def read_bool
unless @bool_value.nil?
bv = @bool_value
@bool_value = nil
bv
else
read_byte() == CompactTypes::BOOLEAN_TRUE
end
end
def read_byte
val = trans.read_byte
if (val > 0x7f)
val = 0 - ((val - 1) ^ 0xff)
end
val
end
def read_i16
zig_zag_to_int(read_varint32())
end
def read_i32
zig_zag_to_int(read_varint32())
end
def read_i64
zig_zag_to_long(read_varint64())
end
def read_double
trans.read_into_buffer(@rbuf, 8)
val = @rbuf.reverse.unpack('G').first
val
end
def read_string
buffer = read_binary
Bytes.convert_to_string(buffer)
end
def read_binary
size = read_varint32()
trans.read_all(size)
end
private
#
# Abstract method for writing the start of lists and sets. List and sets on
# the wire differ only by the type indicator.
#
def write_collection_begin(elem_type, size)
if size <= 14
write_byte(size << 4 | CompactTypes.get_compact_type(elem_type))
else
write_byte(0xf0 | CompactTypes.get_compact_type(elem_type))
write_varint32(size)
end
end
def write_varint32(n)
# int idx = 0;
while true
if (n & ~0x7F) == 0
# i32buf[idx++] = (byte)n;
write_byte(n)
break
# return;
else
# i32buf[idx++] = (byte)((n & 0x7F) | 0x80);
write_byte((n & 0x7F) | 0x80)
n = n >> 7
end
end
# trans_.write(i32buf, 0, idx);
end
SEVEN_BIT_MASK = 0x7F
EVERYTHING_ELSE_MASK = ~SEVEN_BIT_MASK
def write_varint64(n)
while true
if (n & EVERYTHING_ELSE_MASK) == 0 #TODO need to find a way to make this into a long...
write_byte(n)
break
else
write_byte((n & SEVEN_BIT_MASK) | 0x80)
n >>= 7
end
end
end
def read_varint32()
read_varint64()
end
def read_varint64()
shift = 0
result = 0
while true
b = read_byte()
result |= (b & 0x7f) << shift
break if (b & 0x80) != 0x80
shift += 7
end
result
end
def int_to_zig_zag(n)
(n << 1) ^ (n >> 31)
end
def long_to_zig_zag(l)
# puts "zz encoded #{l} to #{(l << 1) ^ (l >> 63)}"
(l << 1) ^ (l >> 63)
end
def zig_zag_to_int(n)
(n >> 1) ^ -(n & 1)
end
def zig_zag_to_long(n)
(n >> 1) ^ -(n & 1)
end
end
class CompactProtocolFactory < BaseProtocolFactory
def get_protocol(trans)
CompactProtocol.new(trans)
end
end
end

View file

@ -0,0 +1,778 @@
# encoding: UTF-8
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
require 'base64'
module Thrift
class LookaheadReader
def initialize(trans)
@trans = trans
@hasData = false
@data = nil
end
def read
if @hasData
@hasData = false
else
@data = @trans.read(1)
end
return @data
end
def peek
if !@hasData
@data = @trans.read(1)
end
@hasData = true
return @data
end
end
#
# Class to serve as base JSON context and as base class for other context
# implementations
#
class JSONContext
@@kJSONElemSeparator = ','
#
# Write context data to the trans. Default is to do nothing.
#
def write(trans)
end
#
# Read context data from the trans. Default is to do nothing.
#
def read(reader)
end
#
# Return true if numbers need to be escaped as strings in this context.
# Default behavior is to return false.
#
def escapeNum
return false
end
end
# Context class for object member key-value pairs
class JSONPairContext < JSONContext
@@kJSONPairSeparator = ':'
def initialize
@first = true
@colon = true
end
def write(trans)
if (@first)
@first = false
@colon = true
else
trans.write(@colon ? @@kJSONPairSeparator : @@kJSONElemSeparator)
@colon = !@colon
end
end
def read(reader)
if (@first)
@first = false
@colon = true
else
ch = (@colon ? @@kJSONPairSeparator : @@kJSONElemSeparator)
@colon = !@colon
JsonProtocol::read_syntax_char(reader, ch)
end
end
# Numbers must be turned into strings if they are the key part of a pair
def escapeNum
return @colon
end
end
# Context class for lists
class JSONListContext < JSONContext
def initialize
@first = true
end
def write(trans)
if (@first)
@first = false
else
trans.write(@@kJSONElemSeparator)
end
end
def read(reader)
if (@first)
@first = false
else
JsonProtocol::read_syntax_char(reader, @@kJSONElemSeparator)
end
end
end
class JsonProtocol < BaseProtocol
@@kJSONObjectStart = '{'
@@kJSONObjectEnd = '}'
@@kJSONArrayStart = '['
@@kJSONArrayEnd = ']'
@@kJSONNewline = '\n'
@@kJSONBackslash = '\\'
@@kJSONStringDelimiter = '"'
@@kThriftVersion1 = 1
@@kThriftNan = "NaN"
@@kThriftInfinity = "Infinity"
@@kThriftNegativeInfinity = "-Infinity"
def initialize(trans)
super(trans)
@context = JSONContext.new
@contexts = Array.new
@reader = LookaheadReader.new(trans)
end
def get_type_name_for_type_id(id)
case id
when Types::BOOL
"tf"
when Types::BYTE
"i8"
when Types::I16
"i16"
when Types::I32
"i32"
when Types::I64
"i64"
when Types::DOUBLE
"dbl"
when Types::STRING
"str"
when Types::STRUCT
"rec"
when Types::MAP
"map"
when Types::SET
"set"
when Types::LIST
"lst"
else
raise NotImplementedError
end
end
def get_type_id_for_type_name(name)
if (name == "tf")
result = Types::BOOL
elsif (name == "i8")
result = Types::BYTE
elsif (name == "i16")
result = Types::I16
elsif (name == "i32")
result = Types::I32
elsif (name == "i64")
result = Types::I64
elsif (name == "dbl")
result = Types::DOUBLE
elsif (name == "str")
result = Types::STRING
elsif (name == "rec")
result = Types::STRUCT
elsif (name == "map")
result = Types::MAP
elsif (name == "set")
result = Types::SET
elsif (name == "lst")
result = Types::LIST
else
result = Types::STOP
end
if (result == Types::STOP)
raise NotImplementedError
end
return result
end
# Static helper functions
# Read 1 character from the trans and verify that it is the expected character ch.
# Throw a protocol exception if it is not.
def self.read_syntax_char(reader, ch)
ch2 = reader.read
if (ch2 != ch)
raise ProtocolException.new(ProtocolException::INVALID_DATA, "Expected \'#{ch}\' got \'#{ch2}\'.")
end
end
# Return true if the character ch is in [-+0-9.Ee]; false otherwise
def is_json_numeric(ch)
case ch
when '+', '-', '.', '0' .. '9', 'E', "e"
return true
else
return false
end
end
def push_context(context)
@contexts.push(@context)
@context = context
end
def pop_context
@context = @contexts.pop
end
# Write the character ch as a JSON escape sequence ("\u00xx")
def write_json_escape_char(ch)
trans.write('\\u')
ch_value = ch[0]
if (ch_value.kind_of? String)
ch_value = ch.bytes.first
end
trans.write(ch_value.to_s(16).rjust(4,'0'))
end
# Write the character ch as part of a JSON string, escaping as appropriate.
def write_json_char(ch)
# This table describes the handling for the first 0x30 characters
# 0 : escape using "\u00xx" notation
# 1 : just output index
# <other> : escape using "\<other>" notation
kJSONCharTable = [
# 0 1 2 3 4 5 6 7 8 9 A B C D E F
0, 0, 0, 0, 0, 0, 0, 0,'b','t','n', 0,'f','r', 0, 0, # 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 1
1, 1,'"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 2
]
ch_value = ch[0]
if (ch_value.kind_of? String)
ch_value = ch.bytes.first
end
if (ch_value >= 0x30)
if (ch == @@kJSONBackslash) # Only special character >= 0x30 is '\'
trans.write(@@kJSONBackslash)
trans.write(@@kJSONBackslash)
else
trans.write(ch)
end
else
outCh = kJSONCharTable[ch_value];
# Check if regular character, backslash escaped, or JSON escaped
if outCh.kind_of? String
trans.write(@@kJSONBackslash)
trans.write(outCh)
elsif outCh == 1
trans.write(ch)
else
write_json_escape_char(ch)
end
end
end
# Write out the contents of the string str as a JSON string, escaping characters as appropriate.
def write_json_string(str)
@context.write(trans)
trans.write(@@kJSONStringDelimiter)
str.split('').each do |ch|
write_json_char(ch)
end
trans.write(@@kJSONStringDelimiter)
end
# Write out the contents of the string as JSON string, base64-encoding
# the string's contents, and escaping as appropriate
def write_json_base64(str)
@context.write(trans)
trans.write(@@kJSONStringDelimiter)
trans.write(Base64.strict_encode64(str))
trans.write(@@kJSONStringDelimiter)
end
# Convert the given integer type to a JSON number, or a string
# if the context requires it (eg: key in a map pair).
def write_json_integer(num)
@context.write(trans)
escapeNum = @context.escapeNum
if (escapeNum)
trans.write(@@kJSONStringDelimiter)
end
trans.write(num.to_s);
if (escapeNum)
trans.write(@@kJSONStringDelimiter)
end
end
# Convert the given double to a JSON string, which is either the number,
# "NaN" or "Infinity" or "-Infinity".
def write_json_double(num)
@context.write(trans)
# Normalize output of boost::lexical_cast for NaNs and Infinities
special = false;
if (num.nan?)
special = true;
val = @@kThriftNan;
elsif (num.infinite?)
special = true;
val = @@kThriftInfinity;
if (num < 0.0)
val = @@kThriftNegativeInfinity;
end
else
val = num.to_s
end
escapeNum = special || @context.escapeNum
if (escapeNum)
trans.write(@@kJSONStringDelimiter)
end
trans.write(val)
if (escapeNum)
trans.write(@@kJSONStringDelimiter)
end
end
def write_json_object_start
@context.write(trans)
trans.write(@@kJSONObjectStart)
push_context(JSONPairContext.new);
end
def write_json_object_end
pop_context
trans.write(@@kJSONObjectEnd)
end
def write_json_array_start
@context.write(trans)
trans.write(@@kJSONArrayStart)
push_context(JSONListContext.new);
end
def write_json_array_end
pop_context
trans.write(@@kJSONArrayEnd)
end
def write_message_begin(name, type, seqid)
write_json_array_start
write_json_integer(@@kThriftVersion1)
write_json_string(name)
write_json_integer(type)
write_json_integer(seqid)
end
def write_message_end
write_json_array_end
end
def write_struct_begin(name)
write_json_object_start
end
def write_struct_end
write_json_object_end
end
def write_field_begin(name, type, id)
write_json_integer(id)
write_json_object_start
write_json_string(get_type_name_for_type_id(type))
end
def write_field_end
write_json_object_end
end
def write_field_stop; nil; end
def write_map_begin(ktype, vtype, size)
write_json_array_start
write_json_string(get_type_name_for_type_id(ktype))
write_json_string(get_type_name_for_type_id(vtype))
write_json_integer(size)
write_json_object_start
end
def write_map_end
write_json_object_end
write_json_array_end
end
def write_list_begin(etype, size)
write_json_array_start
write_json_string(get_type_name_for_type_id(etype))
write_json_integer(size)
end
def write_list_end
write_json_array_end
end
def write_set_begin(etype, size)
write_json_array_start
write_json_string(get_type_name_for_type_id(etype))
write_json_integer(size)
end
def write_set_end
write_json_array_end
end
def write_bool(bool)
write_json_integer(bool ? 1 : 0)
end
def write_byte(byte)
write_json_integer(byte)
end
def write_i16(i16)
write_json_integer(i16)
end
def write_i32(i32)
write_json_integer(i32)
end
def write_i64(i64)
write_json_integer(i64)
end
def write_double(dub)
write_json_double(dub)
end
def write_string(str)
write_json_string(str)
end
def write_binary(str)
write_json_base64(str)
end
##
# Reading functions
##
# Reads 1 byte and verifies that it matches ch.
def read_json_syntax_char(ch)
JsonProtocol::read_syntax_char(@reader, ch)
end
# Decodes the four hex parts of a JSON escaped string character and returns
# the character via out.
#
# Note - this only supports Unicode characters in the BMP (U+0000 to U+FFFF);
# characters above the BMP are encoded as two escape sequences (surrogate pairs),
# which is not yet implemented
def read_json_escape_char
str = @reader.read
str += @reader.read
str += @reader.read
str += @reader.read
if RUBY_VERSION >= '1.9'
str.hex.chr(Encoding::UTF_8)
else
str.hex.chr
end
end
# Decodes a JSON string, including unescaping, and returns the string via str
def read_json_string(skipContext = false)
# This string's characters must match up with the elements in escape_char_vals.
# I don't have '/' on this list even though it appears on www.json.org --
# it is not in the RFC -> it is. See RFC 4627
escape_chars = "\"\\/bfnrt"
# The elements of this array must match up with the sequence of characters in
# escape_chars
escape_char_vals = [
"\"", "\\", "\/", "\b", "\f", "\n", "\r", "\t",
]
if !skipContext
@context.read(@reader)
end
read_json_syntax_char(@@kJSONStringDelimiter)
ch = ""
str = ""
while (true)
ch = @reader.read
if (ch == @@kJSONStringDelimiter)
break
end
if (ch == @@kJSONBackslash)
ch = @reader.read
if (ch == 'u')
ch = read_json_escape_char
else
pos = escape_chars.index(ch);
if (pos.nil?) # not found
raise ProtocolException.new(ProtocolException::INVALID_DATA, "Expected control char, got \'#{ch}\'.")
end
ch = escape_char_vals[pos]
end
end
str += ch
end
return str
end
# Reads a block of base64 characters, decoding it, and returns via str
def read_json_base64
str = read_json_string
m = str.length % 4
if m != 0
# Add missing padding
(4 - m).times do
str += '='
end
end
Base64.strict_decode64(str)
end
# Reads a sequence of characters, stopping at the first one that is not
# a valid JSON numeric character.
def read_json_numeric_chars
str = ""
while (true)
ch = @reader.peek
if (!is_json_numeric(ch))
break;
end
ch = @reader.read
str += ch
end
return str
end
# Reads a sequence of characters and assembles them into a number,
# returning them via num
def read_json_integer
@context.read(@reader)
if (@context.escapeNum)
read_json_syntax_char(@@kJSONStringDelimiter)
end
str = read_json_numeric_chars
begin
num = Integer(str);
rescue
raise ProtocolException.new(ProtocolException::INVALID_DATA, "Expected numeric value; got \"#{str}\"")
end
if (@context.escapeNum)
read_json_syntax_char(@@kJSONStringDelimiter)
end
return num
end
# Reads a JSON number or string and interprets it as a double.
def read_json_double
@context.read(@reader)
num = 0
if (@reader.peek == @@kJSONStringDelimiter)
str = read_json_string(true)
# Check for NaN, Infinity and -Infinity
if (str == @@kThriftNan)
num = (+1.0/0.0)/(+1.0/0.0)
elsif (str == @@kThriftInfinity)
num = +1.0/0.0
elsif (str == @@kThriftNegativeInfinity)
num = -1.0/0.0
else
if (!@context.escapeNum)
# Raise exception -- we should not be in a string in this case
raise ProtocolException.new(ProtocolException::INVALID_DATA, "Numeric data unexpectedly quoted")
end
begin
num = Float(str)
rescue
raise ProtocolException.new(ProtocolException::INVALID_DATA, "Expected numeric value; got \"#{str}\"")
end
end
else
if (@context.escapeNum)
# This will throw - we should have had a quote if escapeNum == true
read_json_syntax_char(@@kJSONStringDelimiter)
end
str = read_json_numeric_chars
begin
num = Float(str)
rescue
raise ProtocolException.new(ProtocolException::INVALID_DATA, "Expected numeric value; got \"#{str}\"")
end
end
return num
end
def read_json_object_start
@context.read(@reader)
read_json_syntax_char(@@kJSONObjectStart)
push_context(JSONPairContext.new)
nil
end
def read_json_object_end
read_json_syntax_char(@@kJSONObjectEnd)
pop_context
nil
end
def read_json_array_start
@context.read(@reader)
read_json_syntax_char(@@kJSONArrayStart)
push_context(JSONListContext.new)
nil
end
def read_json_array_end
read_json_syntax_char(@@kJSONArrayEnd)
pop_context
nil
end
def read_message_begin
read_json_array_start
version = read_json_integer
if (version != @@kThriftVersion1)
raise ProtocolException.new(ProtocolException::BAD_VERSION, 'Message contained bad version.')
end
name = read_json_string
message_type = read_json_integer
seqid = read_json_integer
[name, message_type, seqid]
end
def read_message_end
read_json_array_end
nil
end
def read_struct_begin
read_json_object_start
nil
end
def read_struct_end
read_json_object_end
nil
end
def read_field_begin
# Check if we hit the end of the list
ch = @reader.peek
if (ch == @@kJSONObjectEnd)
field_type = Types::STOP
else
field_id = read_json_integer
read_json_object_start
field_type = get_type_id_for_type_name(read_json_string)
end
[nil, field_type, field_id]
end
def read_field_end
read_json_object_end
end
def read_map_begin
read_json_array_start
key_type = get_type_id_for_type_name(read_json_string)
val_type = get_type_id_for_type_name(read_json_string)
size = read_json_integer
read_json_object_start
[key_type, val_type, size]
end
def read_map_end
read_json_object_end
read_json_array_end
end
def read_list_begin
read_json_array_start
[get_type_id_for_type_name(read_json_string), read_json_integer]
end
def read_list_end
read_json_array_end
end
def read_set_begin
read_json_array_start
[get_type_id_for_type_name(read_json_string), read_json_integer]
end
def read_set_end
read_json_array_end
end
def read_bool
byte = read_byte
byte != 0
end
def read_byte
read_json_integer
end
def read_i16
read_json_integer
end
def read_i32
read_json_integer
end
def read_i64
read_json_integer
end
def read_double
read_json_double
end
def read_string
read_json_string
end
def read_binary
read_json_base64
end
end
class JsonProtocolFactory < BaseProtocolFactory
def get_protocol(trans)
return Thrift::JsonProtocol.new(trans)
end
end
end

View file

@ -0,0 +1,40 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
require 'thrift/protocol/protocol_decorator'
module Thrift
class MultiplexedProtocol < BaseProtocol
include ProtocolDecorator
def initialize(protocol, service_name)
super(protocol)
@service_name = service_name
end
def write_message_begin(name, type, seqid)
case type
when MessageTypes::CALL, MessageTypes::ONEWAY
@protocol.write_message_begin("#{@service_name}:#{name}", type, seqid)
else
@protocol.write_message_begin(name, type, seqid)
end
end
end
end

View file

@ -0,0 +1,194 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
module Thrift
module ProtocolDecorator
def initialize(protocol)
@protocol = protocol
end
def trans
@protocol.trans
end
def write_message_begin(name, type, seqid)
@protocol.write_message_begin
end
def write_message_end
@protocol.write_message_end
end
def write_struct_begin(name)
@protocol.write_struct_begin(name)
end
def write_struct_end
@protocol.write_struct_end
end
def write_field_begin(name, type, id)
@protocol.write_field_begin(name, type, id)
end
def write_field_end
@protocol.write_field_end
end
def write_field_stop
@protocol.write_field_stop
end
def write_map_begin(ktype, vtype, size)
@protocol.write_map_begin(ktype, vtype, size)
end
def write_map_end
@protocol.write_map_end
end
def write_list_begin(etype, size)
@protocol.write_list_begin(etype, size)
end
def write_list_end
@protocol.write_list_end
end
def write_set_begin(etype, size)
@protocol.write_set_begin(etype, size)
end
def write_set_end
@protocol.write_set_end
end
def write_bool(bool)
@protocol.write_bool(bool)
end
def write_byte(byte)
@protocol.write_byte(byte)
end
def write_i16(i16)
@protocol.write_i16(i16)
end
def write_i32(i32)
@protocol.write_i32(i32)
end
def write_i64(i64)
@protocol.write_i64(i64)
end
def write_double(dub)
@protocol.write_double(dub)
end
def write_string(str)
@protocol.write_string(str)
end
def write_binary(buf)
@protocol.write_binary(buf)
end
def read_message_begin
@protocol.read_message_begin
end
def read_message_end
@protocol.read_message_end
end
def read_struct_begin
@protocol.read_struct_begin
end
def read_struct_end
@protocol.read_struct_end
end
def read_field_begin
@protocol.read_field_begin
end
def read_field_end
@protocol.read_field_end
end
def read_map_begin
@protocol.read_map_begin
end
def read_map_end
@protocol.read_map_end
end
def read_list_begin
@protocol.read_list_begin
end
def read_list_end
@protocol.read_list_end
end
def read_set_begin
@protocol.read_set_begin
end
def read_set_end
@protocol.read_set_end
end
def read_bool
@protocol.read_bool
end
def read_byte
@protocol.read_byte
end
def read_i16
@protocol.read_i16
end
def read_i32
@protocol.read_i32
end
def read_i64
@protocol.read_i64
end
def read_double
@protocol.read_double
end
def read_string
@protocol.read_string
end
def read_binary
@protocol.read_binary
end
end
end

View file

@ -0,0 +1,33 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
module Thrift
class Deserializer
def initialize(protocol_factory = BinaryProtocolFactory.new)
@transport = MemoryBufferTransport.new
@protocol = protocol_factory.get_protocol(@transport)
end
def deserialize(base, buffer)
@transport.reset_buffer(buffer)
base.read(@protocol)
base
end
end
end

View file

@ -0,0 +1,34 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
module Thrift
class Serializer
def initialize(protocol_factory = BinaryProtocolFactory.new)
@transport = MemoryBufferTransport.new
@protocol = protocol_factory.get_protocol(@transport)
end
def serialize(base)
@transport.reset_buffer
base.write(@protocol)
@transport.read(@transport.available)
end
end
end

View file

@ -0,0 +1,31 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
module Thrift
class BaseServer
def initialize(processor, server_transport, transport_factory=nil, protocol_factory=nil)
@processor = processor
@server_transport = server_transport
@transport_factory = transport_factory ? transport_factory : Thrift::BaseTransportFactory.new
@protocol_factory = protocol_factory ? protocol_factory : Thrift::BinaryProtocolFactory.new
end
def serve; nil; end
end
end

View file

@ -0,0 +1,60 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
require 'mongrel'
## Sticks a service on a URL, using mongrel to do the HTTP work
# <b>DEPRECATED:</b> Please use <tt>Thrift::ThinHTTPServer</tt> instead.
module Thrift
class MongrelHTTPServer < BaseServer
class Handler < Mongrel::HttpHandler
def initialize(processor, protocol_factory)
@processor = processor
@protocol_factory = protocol_factory
end
def process(request, response)
if request.params["REQUEST_METHOD"] == "POST"
response.start(200) do |head, out|
head["Content-Type"] = "application/x-thrift"
transport = IOStreamTransport.new request.body, out
protocol = @protocol_factory.get_protocol transport
@processor.process protocol, protocol
end
else
response.start(404) { }
end
end
end
def initialize(processor, opts={})
Kernel.warn "[DEPRECATION WARNING] `Thrift::MongrelHTTPServer` is deprecated. Please use `Thrift::ThinHTTPServer` instead."
port = opts[:port] || 80
ip = opts[:ip] || "0.0.0.0"
path = opts[:path] || ""
protocol_factory = opts[:protocol_factory] || BinaryProtocolFactory.new
@server = Mongrel::HttpServer.new ip, port
@server.register "/#{path}", Handler.new(processor, protocol_factory)
end
def serve
@server.run.join
end
end
end

View file

@ -0,0 +1,305 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
require 'logger'
require 'thread'
module Thrift
# this class expects to always use a FramedTransport for reading messages
class NonblockingServer < BaseServer
def initialize(processor, server_transport, transport_factory=nil, protocol_factory=nil, num=20, logger=nil)
super(processor, server_transport, transport_factory, protocol_factory)
@num_threads = num
if logger.nil?
@logger = Logger.new(STDERR)
@logger.level = Logger::WARN
else
@logger = logger
end
@shutdown_semaphore = Mutex.new
@transport_semaphore = Mutex.new
end
def serve
@logger.info "Starting #{self}"
@server_transport.listen
@io_manager = start_io_manager
begin
loop do
break if @server_transport.closed?
begin
rd, = select([@server_transport], nil, nil, 0.1)
rescue Errno::EBADF => e
# In Ruby 1.9, calling @server_transport.close in shutdown paths causes the select() to raise an
# Errno::EBADF. If this happens, ignore it and retry the loop.
break
end
next if rd.nil?
socket = @server_transport.accept
@logger.debug "Accepted socket: #{socket.inspect}"
@io_manager.add_connection socket
end
rescue IOError => e
end
# we must be shutting down
@logger.info "#{self} is shutting down, goodbye"
ensure
@transport_semaphore.synchronize do
@server_transport.close
end
@io_manager.ensure_closed unless @io_manager.nil?
end
def shutdown(timeout = 0, block = true)
@shutdown_semaphore.synchronize do
return if @is_shutdown
@is_shutdown = true
end
# nonblocking is intended for calling from within a Handler
# but we can't change the order of operations here, so lets thread
shutdown_proc = lambda do
@io_manager.shutdown(timeout)
@transport_semaphore.synchronize do
@server_transport.close # this will break the accept loop
end
end
if block
shutdown_proc.call
else
Thread.new &shutdown_proc
end
end
private
def start_io_manager
iom = IOManager.new(@processor, @server_transport, @transport_factory, @protocol_factory, @num_threads, @logger)
iom.spawn
iom
end
class IOManager # :nodoc:
DEFAULT_BUFFER = 2**20
def initialize(processor, server_transport, transport_factory, protocol_factory, num, logger)
@processor = processor
@server_transport = server_transport
@transport_factory = transport_factory
@protocol_factory = protocol_factory
@num_threads = num
@logger = logger
@connections = []
@buffers = Hash.new { |h,k| h[k] = '' }
@signal_queue = Queue.new
@signal_pipes = IO.pipe
@signal_pipes[1].sync = true
@worker_queue = Queue.new
@shutdown_queue = Queue.new
end
def add_connection(socket)
signal [:connection, socket]
end
def spawn
@iom_thread = Thread.new do
@logger.debug "Starting #{self}"
run
end
end
def shutdown(timeout = 0)
@logger.debug "#{self} is shutting down workers"
@worker_queue.clear
@num_threads.times { @worker_queue.push [:shutdown] }
signal [:shutdown, timeout]
@shutdown_queue.pop
@signal_pipes[0].close
@signal_pipes[1].close
@logger.debug "#{self} is shutting down, goodbye"
end
def ensure_closed
kill_worker_threads if @worker_threads
@iom_thread.kill
end
private
def run
spin_worker_threads
loop do
rd, = select([@signal_pipes[0], *@connections])
if rd.delete @signal_pipes[0]
break if read_signals == :shutdown
end
rd.each do |fd|
begin
if fd.handle.eof?
remove_connection fd
else
read_connection fd
end
rescue Errno::ECONNRESET
remove_connection fd
end
end
end
join_worker_threads(@shutdown_timeout)
ensure
@shutdown_queue.push :shutdown
end
def read_connection(fd)
@buffers[fd] << fd.read(DEFAULT_BUFFER)
while(frame = slice_frame!(@buffers[fd]))
@logger.debug "#{self} is processing a frame"
@worker_queue.push [:frame, fd, frame]
end
end
def spin_worker_threads
@logger.debug "#{self} is spinning up worker threads"
@worker_threads = []
@num_threads.times do
@worker_threads << spin_thread
end
end
def spin_thread
Worker.new(@processor, @transport_factory, @protocol_factory, @logger, @worker_queue).spawn
end
def signal(msg)
@signal_queue << msg
@signal_pipes[1].write " "
end
def read_signals
# clear the signal pipe
# note that since read_nonblock is broken in jruby,
# we can only read up to a set number of signals at once
sigstr = @signal_pipes[0].readpartial(1024)
# now read the signals
begin
sigstr.length.times do
signal, obj = @signal_queue.pop(true)
case signal
when :connection
@connections << obj
when :shutdown
@shutdown_timeout = obj
return :shutdown
end
end
rescue ThreadError
# out of signals
# note that in a perfect world this would never happen, since we're
# only reading the number of signals pushed on the pipe, but given the lack
# of locks, in theory we could clear the pipe/queue while a new signal is being
# placed on the pipe, at which point our next read_signals would hit this error
end
end
def remove_connection(fd)
# don't explicitly close it, a thread may still be writing to it
@connections.delete fd
@buffers.delete fd
end
def join_worker_threads(shutdown_timeout)
start = Time.now
@worker_threads.each do |t|
if shutdown_timeout > 0
timeout = (start + shutdown_timeout) - Time.now
break if timeout <= 0
t.join(timeout)
else
t.join
end
end
kill_worker_threads
end
def kill_worker_threads
@worker_threads.each do |t|
t.kill if t.status
end
@worker_threads.clear
end
def slice_frame!(buf)
if buf.length >= 4
size = buf.unpack('N').first
if buf.length >= size + 4
buf.slice!(0, size + 4)
else
nil
end
else
nil
end
end
class Worker # :nodoc:
def initialize(processor, transport_factory, protocol_factory, logger, queue)
@processor = processor
@transport_factory = transport_factory
@protocol_factory = protocol_factory
@logger = logger
@queue = queue
end
def spawn
Thread.new do
@logger.debug "#{self} is spawning"
run
end
end
private
def run
loop do
cmd, *args = @queue.pop
case cmd
when :shutdown
@logger.debug "#{self} is shutting down, goodbye"
break
when :frame
fd, frame = args
begin
otrans = @transport_factory.get_transport(fd)
oprot = @protocol_factory.get_protocol(otrans)
membuf = MemoryBufferTransport.new(frame)
itrans = @transport_factory.get_transport(membuf)
iprot = @protocol_factory.get_protocol(itrans)
@processor.process(iprot, oprot)
rescue => e
@logger.error "#{Thread.current.inspect} raised error: #{e.inspect}\n#{e.backtrace.join("\n")}"
end
end
end
end
end
end
end
end

View file

@ -0,0 +1,43 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
module Thrift
class SimpleServer < BaseServer
def serve
begin
@server_transport.listen
loop do
client = @server_transport.accept
trans = @transport_factory.get_transport(client)
prot = @protocol_factory.get_protocol(trans)
begin
loop do
@processor.process(prot, prot)
end
rescue Thrift::TransportException, Thrift::ProtocolException
ensure
trans.close
end
end
ensure
@server_transport.close
end
end
end
end

View file

@ -0,0 +1,91 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
require 'rack'
require 'thin'
##
# Wraps the Thin web server to provide a Thrift server over HTTP.
module Thrift
class ThinHTTPServer < BaseServer
##
# Accepts a Thrift::Processor
# Options include:
# * :port
# * :ip
# * :path
# * :protocol_factory
def initialize(processor, options={})
port = options[:port] || 80
ip = options[:ip] || "0.0.0.0"
path = options[:path] || "/"
protocol_factory = options[:protocol_factory] || BinaryProtocolFactory.new
app = RackApplication.for(path, processor, protocol_factory)
@server = Thin::Server.new(ip, port, app)
end
##
# Starts the server
def serve
@server.start
end
class RackApplication
THRIFT_HEADER = "application/x-thrift"
def self.for(path, processor, protocol_factory)
Rack::Builder.new do
use Rack::CommonLogger
use Rack::ShowExceptions
use Rack::Lint
map path do
run lambda { |env|
request = Rack::Request.new(env)
if RackApplication.valid_thrift_request?(request)
RackApplication.successful_request(request, processor, protocol_factory)
else
RackApplication.failed_request
end
}
end
end
end
def self.successful_request(rack_request, processor, protocol_factory)
response = Rack::Response.new([], 200, {'Content-Type' => THRIFT_HEADER})
transport = IOStreamTransport.new rack_request.body, response
protocol = protocol_factory.get_protocol transport
processor.process protocol, protocol
response
end
def self.failed_request
Rack::Response.new(['Not Found'], 404, {'Content-Type' => THRIFT_HEADER})
end
def self.valid_thrift_request?(rack_request)
rack_request.post? && rack_request.env["CONTENT_TYPE"] == THRIFT_HEADER
end
end
end
end

View file

@ -0,0 +1,75 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
require 'thread'
module Thrift
class ThreadPoolServer < BaseServer
def initialize(processor, server_transport, transport_factory=nil, protocol_factory=nil, num=20)
super(processor, server_transport, transport_factory, protocol_factory)
@thread_q = SizedQueue.new(num)
@exception_q = Queue.new
@running = false
end
## exceptions that happen in worker threads will be relayed here and
## must be caught. 'retry' can be used to continue. (threads will
## continue to run while the exception is being handled.)
def rescuable_serve
Thread.new { serve } unless @running
@running = true
raise @exception_q.pop
end
## exceptions that happen in worker threads simply cause that thread
## to die and another to be spawned in its place.
def serve
@server_transport.listen
begin
loop do
@thread_q.push(:token)
Thread.new do
begin
loop do
client = @server_transport.accept
trans = @transport_factory.get_transport(client)
prot = @protocol_factory.get_protocol(trans)
begin
loop do
@processor.process(prot, prot)
end
rescue Thrift::TransportException, Thrift::ProtocolException => e
ensure
trans.close
end
end
rescue => e
@exception_q.push(e)
ensure
@thread_q.pop # thread died!
end
end
end
ensure
@server_transport.close
end
end
end
end

View file

@ -0,0 +1,47 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
require 'thread'
module Thrift
class ThreadedServer < BaseServer
def serve
begin
@server_transport.listen
loop do
client = @server_transport.accept
trans = @transport_factory.get_transport(client)
prot = @protocol_factory.get_protocol(trans)
Thread.new(prot, trans) do |p, t|
begin
loop do
@processor.process(p, p)
end
rescue Thrift::TransportException, Thrift::ProtocolException
ensure
t.close
end
end
end
ensure
@server_transport.close
end
end
end
end

View file

@ -0,0 +1,237 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
require 'set'
module Thrift
module Struct
def initialize(d={}, &block)
# get a copy of the default values to work on, removing defaults in favor of arguments
fields_with_defaults = fields_with_default_values.dup
# check if the defaults is empty, or if there are no parameters for this
# instantiation, and if so, don't bother overriding defaults.
unless fields_with_defaults.empty? || d.empty?
d.each_key do |name|
fields_with_defaults.delete(name.to_s)
end
end
# assign all the user-specified arguments
unless d.empty?
d.each do |name, value|
unless name_to_id(name.to_s)
raise Exception, "Unknown key given to #{self.class}.new: #{name}"
end
Thrift.check_type(value, struct_fields[name_to_id(name.to_s)], name) if Thrift.type_checking
instance_variable_set("@#{name}", value)
end
end
# assign all the default values
unless fields_with_defaults.empty?
fields_with_defaults.each do |name, default_value|
instance_variable_set("@#{name}", (default_value.dup rescue default_value))
end
end
yield self if block_given?
end
def fields_with_default_values
fields_with_default_values = self.class.instance_variable_get(:@fields_with_default_values)
unless fields_with_default_values
fields_with_default_values = {}
struct_fields.each do |fid, field_def|
unless field_def[:default].nil?
fields_with_default_values[field_def[:name]] = field_def[:default]
end
end
self.class.instance_variable_set(:@fields_with_default_values, fields_with_default_values)
end
fields_with_default_values
end
def inspect(skip_optional_nulls = true)
fields = []
each_field do |fid, field_info|
name = field_info[:name]
value = instance_variable_get("@#{name}")
unless skip_optional_nulls && field_info[:optional] && value.nil?
fields << "#{name}:#{inspect_field(value, field_info)}"
end
end
"<#{self.class} #{fields.join(", ")}>"
end
def read(iprot)
iprot.read_struct_begin
loop do
fname, ftype, fid = iprot.read_field_begin
break if (ftype == Types::STOP)
handle_message(iprot, fid, ftype)
iprot.read_field_end
end
iprot.read_struct_end
validate
end
def write(oprot)
validate
oprot.write_struct_begin(self.class.name)
each_field do |fid, field_info|
name = field_info[:name]
type = field_info[:type]
value = instance_variable_get("@#{name}")
unless value.nil?
if is_container? type
oprot.write_field_begin(name, type, fid)
write_container(oprot, value, field_info)
oprot.write_field_end
else
oprot.write_field(field_info, fid, value)
end
end
end
oprot.write_field_stop
oprot.write_struct_end
end
def ==(other)
return false if other.nil?
each_field do |fid, field_info|
name = field_info[:name]
return false unless other.respond_to?(name) && self.send(name) == other.send(name)
end
true
end
def eql?(other)
self.class == other.class && self == other
end
# This implementation of hash() is inspired by Apache's Java HashCodeBuilder class.
def hash
total = 17
each_field do |fid, field_info|
name = field_info[:name]
value = self.send(name)
total = (total * 37 + value.hash) & 0xffffffff
end
total
end
def differences(other)
diffs = []
unless other.is_a?(self.class)
diffs << "Different class!"
else
each_field do |fid, field_info|
name = field_info[:name]
diffs << "#{name} differs!" unless self.instance_variable_get("@#{name}") == other.instance_variable_get("@#{name}")
end
end
diffs
end
def self.field_accessor(klass, field_info)
field_name_sym = field_info[:name].to_sym
klass.send :attr_reader, field_name_sym
klass.send :define_method, "#{field_info[:name]}=" do |value|
Thrift.check_type(value, field_info, field_info[:name]) if Thrift.type_checking
instance_variable_set("@#{field_name_sym}", value)
end
end
def self.generate_accessors(klass)
klass::FIELDS.values.each do |field_info|
field_accessor(klass, field_info)
qmark_isset_method(klass, field_info)
end
end
def self.qmark_isset_method(klass, field_info)
klass.send :define_method, "#{field_info[:name]}?" do
!self.send(field_info[:name].to_sym).nil?
end
end
def <=>(other)
if self.class == other.class
each_field do |fid, field_info|
v1 = self.send(field_info[:name])
v1_set = !v1.nil?
v2 = other.send(field_info[:name])
v2_set = !v2.nil?
if v1_set && !v2_set
return -1
elsif !v1_set && v2_set
return 1
elsif v1_set && v2_set
cmp = v1 <=> v2
if cmp != 0
return cmp
end
end
end
0
else
self.class <=> other.class
end
end
protected
def self.append_features(mod)
if mod.ancestors.include? ::Exception
mod.send :class_variable_set, :'@@__thrift_struct_real_initialize', mod.instance_method(:initialize)
super
# set up our custom initializer so `raise Xception, 'message'` works
mod.send :define_method, :struct_initialize, mod.instance_method(:initialize)
mod.send :define_method, :initialize, mod.instance_method(:exception_initialize)
else
super
end
end
def exception_initialize(*args, &block)
if args.size == 1 and args.first.is_a? Hash
# looks like it's a regular Struct initialize
method(:struct_initialize).call(args.first)
else
# call the Struct initializer first with no args
# this will set our field default values
method(:struct_initialize).call()
# now give it to the exception
self.class.send(:class_variable_get, :'@@__thrift_struct_real_initialize').bind(self).call(*args, &block) if args.size > 0
# self.class.instance_method(:initialize).bind(self).call(*args, &block)
end
end
def handle_message(iprot, fid, ftype)
field = struct_fields[fid]
if field and field[:type] == ftype
value = read_field(iprot, field)
instance_variable_set("@#{field[:name]}", value)
else
iprot.skip(ftype)
end
end
end
end

View file

@ -0,0 +1,192 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
require 'set'
module Thrift
module Struct_Union
def name_to_id(name)
names_to_ids = self.class.instance_variable_get(:@names_to_ids)
unless names_to_ids
names_to_ids = {}
struct_fields.each do |fid, field_def|
names_to_ids[field_def[:name]] = fid
end
self.class.instance_variable_set(:@names_to_ids, names_to_ids)
end
names_to_ids[name]
end
def sorted_field_ids
sorted_field_ids = self.class.instance_variable_get(:@sorted_field_ids)
unless sorted_field_ids
sorted_field_ids = struct_fields.keys.sort
self.class.instance_variable_set(:@sorted_field_ids, sorted_field_ids)
end
sorted_field_ids
end
def each_field
sorted_field_ids.each do |fid|
data = struct_fields[fid]
yield fid, data
end
end
def read_field(iprot, field = {})
case field[:type]
when Types::STRUCT
value = field[:class].new
value.read(iprot)
when Types::MAP
key_type, val_type, size = iprot.read_map_begin
# Skip the map contents if the declared key or value types don't match the expected ones.
if (size != 0 && (key_type != field[:key][:type] || val_type != field[:value][:type]))
size.times do
iprot.skip(key_type)
iprot.skip(val_type)
end
value = nil
else
value = {}
size.times do
k = read_field(iprot, field_info(field[:key]))
v = read_field(iprot, field_info(field[:value]))
value[k] = v
end
end
iprot.read_map_end
when Types::LIST
e_type, size = iprot.read_list_begin
# Skip the list contents if the declared element type doesn't match the expected one.
if (e_type != field[:element][:type])
size.times do
iprot.skip(e_type)
end
value = nil
else
value = Array.new(size) do |n|
read_field(iprot, field_info(field[:element]))
end
end
iprot.read_list_end
when Types::SET
e_type, size = iprot.read_set_begin
# Skip the set contents if the declared element type doesn't match the expected one.
if (e_type != field[:element][:type])
size.times do
iprot.skip(e_type)
end
else
value = Set.new
size.times do
element = read_field(iprot, field_info(field[:element]))
value << element
end
end
iprot.read_set_end
else
value = iprot.read_type(field)
end
value
end
def write_data(oprot, value, field)
if is_container? field[:type]
write_container(oprot, value, field)
else
oprot.write_type(field, value)
end
end
def write_container(oprot, value, field = {})
case field[:type]
when Types::MAP
oprot.write_map_begin(field[:key][:type], field[:value][:type], value.size)
value.each do |k, v|
write_data(oprot, k, field[:key])
write_data(oprot, v, field[:value])
end
oprot.write_map_end
when Types::LIST
oprot.write_list_begin(field[:element][:type], value.size)
value.each do |elem|
write_data(oprot, elem, field[:element])
end
oprot.write_list_end
when Types::SET
oprot.write_set_begin(field[:element][:type], value.size)
value.each do |v,| # the , is to preserve compatibility with the old Hash-style sets
write_data(oprot, v, field[:element])
end
oprot.write_set_end
else
raise "Not a container type: #{field[:type]}"
end
end
CONTAINER_TYPES = []
CONTAINER_TYPES[Types::LIST] = true
CONTAINER_TYPES[Types::MAP] = true
CONTAINER_TYPES[Types::SET] = true
def is_container?(type)
CONTAINER_TYPES[type]
end
def field_info(field)
{ :type => field[:type],
:class => field[:class],
:key => field[:key],
:value => field[:value],
:element => field[:element] }
end
def inspect_field(value, field_info)
if enum_class = field_info[:enum_class]
"#{enum_class.const_get(:VALUE_MAP)[value]} (#{value})"
elsif value.is_a? Hash
if field_info[:type] == Types::MAP
map_buf = []
value.each do |k, v|
map_buf << inspect_field(k, field_info[:key]) + ": " + inspect_field(v, field_info[:value])
end
"{" + map_buf.join(", ") + "}"
else
# old-style set
inspect_collection(value.keys, field_info)
end
elsif value.is_a? Array
inspect_collection(value, field_info)
elsif value.is_a? Set
inspect_collection(value, field_info)
elsif value.is_a?(String) && field_info[:binary]
value.unpack("H*").first
else
value.inspect
end
end
def inspect_collection(collection, field_info)
buf = []
collection.each do |k|
buf << inspect_field(k, field_info[:element])
end
"[" + buf.join(", ") + "]"
end
end
end

View file

@ -0,0 +1,24 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
begin
require "thrift_native"
rescue LoadError
puts "Unable to load thrift_native extension. Defaulting to pure Ruby libraries."
end

View file

@ -0,0 +1,37 @@
# encoding: ascii-8bit
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
module Thrift
class BaseServerTransport
def listen
raise NotImplementedError
end
def accept
raise NotImplementedError
end
def close; nil; end
def closed?
raise NotImplementedError
end
end
end

View file

@ -0,0 +1,109 @@
# encoding: ascii-8bit
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
module Thrift
class TransportException < Exception
UNKNOWN = 0
NOT_OPEN = 1
ALREADY_OPEN = 2
TIMED_OUT = 3
END_OF_FILE = 4
attr_reader :type
def initialize(type=UNKNOWN, message=nil)
super(message)
@type = type
end
end
module TransportUtils
# Deprecated: Use Thrift::Bytes instead
def self.get_string_byte(string, index)
Bytes.get_string_byte(string, index)
end
# Deprecated: Use Thrift::Bytes instead
def self.set_string_byte(string, index, byte)
Bytes.set_string_byte(string, index, byte)
end
end
class BaseTransport
def open?; end
def open; end
def close; end
# Reads a number of bytes from the transports. In Ruby 1.9+, the String returned will have a BINARY (aka ASCII8BIT) encoding.
#
# sz - The number of bytes to read from the transport.
#
# Returns a String acting as a byte buffer.
def read(sz)
raise NotImplementedError
end
# Returns an unsigned byte as a Fixnum in the range (0..255).
def read_byte
buf = read_all(1)
return Bytes.get_string_byte(buf, 0)
end
# Reads size bytes and copies them into buffer[0..size].
def read_into_buffer(buffer, size)
tmp = read_all(size)
i = 0
tmp.each_byte do |byte|
Bytes.set_string_byte(buffer, i, byte)
i += 1
end
i
end
def read_all(size)
return Bytes.empty_byte_buffer if size <= 0
buf = read(size)
while (buf.length < size)
chunk = read(size - buf.length)
buf << chunk
end
buf
end
# Writes the byte buffer to the transport. In Ruby 1.9+, the buffer will be forced into BINARY encoding.
#
# buf - A String acting as a byte buffer.
#
# Returns nothing.
def write(buf); end
alias_method :<<, :write
def flush; end
end
class BaseTransportFactory
def get_transport(trans)
return trans
end
end
end

View file

@ -0,0 +1,114 @@
# encoding: ascii-8bit
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
module Thrift
class BufferedTransport < BaseTransport
DEFAULT_BUFFER = 4096
def initialize(transport)
@transport = transport
@wbuf = Bytes.empty_byte_buffer
@rbuf = Bytes.empty_byte_buffer
@index = 0
end
def open?
return @transport.open?
end
def open
@transport.open
end
def close
flush
@transport.close
end
def read(sz)
@index += sz
ret = @rbuf.slice(@index - sz, sz) || Bytes.empty_byte_buffer
if ret.length == 0
@rbuf = @transport.read([sz, DEFAULT_BUFFER].max)
@index = sz
ret = @rbuf.slice(0, sz) || Bytes.empty_byte_buffer
end
ret
end
def read_byte
# If the read buffer is exhausted, try to read up to DEFAULT_BUFFER more bytes into it.
if @index >= @rbuf.size
@rbuf = @transport.read(DEFAULT_BUFFER)
@index = 0
end
# The read buffer has some data now, read a single byte. Using get_string_byte() avoids
# allocating a temp string of size 1 unnecessarily.
@index += 1
return Bytes.get_string_byte(@rbuf, @index - 1)
end
# Reads a number of bytes from the transport into the buffer passed.
#
# buffer - The String (byte buffer) to write data to; this is assumed to have a BINARY encoding.
# size - The number of bytes to read from the transport and write to the buffer.
#
# Returns the number of bytes read.
def read_into_buffer(buffer, size)
i = 0
while i < size
# If the read buffer is exhausted, try to read up to DEFAULT_BUFFER more bytes into it.
if @index >= @rbuf.size
@rbuf = @transport.read(DEFAULT_BUFFER)
@index = 0
end
# The read buffer has some data now, so copy bytes over to the output buffer.
byte = Bytes.get_string_byte(@rbuf, @index)
Bytes.set_string_byte(buffer, i, byte)
@index += 1
i += 1
end
i
end
def write(buf)
@wbuf << Bytes.force_binary_encoding(buf)
end
def flush
unless @wbuf.empty?
@transport.write(@wbuf)
@wbuf = Bytes.empty_byte_buffer
end
@transport.flush
end
end
class BufferedTransportFactory < BaseTransportFactory
def get_transport(transport)
return BufferedTransport.new(transport)
end
end
end

View file

@ -0,0 +1,117 @@
# encoding: ascii-8bit
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
module Thrift
class FramedTransport < BaseTransport
def initialize(transport, read=true, write=true)
@transport = transport
@rbuf = Bytes.empty_byte_buffer
@wbuf = Bytes.empty_byte_buffer
@read = read
@write = write
@index = 0
end
def open?
@transport.open?
end
def open
@transport.open
end
def close
@transport.close
end
def read(sz)
return @transport.read(sz) unless @read
return Bytes.empty_byte_buffer if sz <= 0
read_frame if @index >= @rbuf.length
@index += sz
@rbuf.slice(@index - sz, sz) || Bytes.empty_byte_buffer
end
def read_byte
return @transport.read_byte() unless @read
read_frame if @index >= @rbuf.length
# The read buffer has some data now, read a single byte. Using get_string_byte() avoids
# allocating a temp string of size 1 unnecessarily.
@index += 1
return Bytes.get_string_byte(@rbuf, @index - 1)
end
def read_into_buffer(buffer, size)
i = 0
while i < size
read_frame if @index >= @rbuf.length
# The read buffer has some data now, so copy bytes over to the output buffer.
byte = Bytes.get_string_byte(@rbuf, @index)
Bytes.set_string_byte(buffer, i, byte)
@index += 1
i += 1
end
i
end
def write(buf, sz=nil)
return @transport.write(buf) unless @write
buf = Bytes.force_binary_encoding(buf)
@wbuf << (sz ? buf[0...sz] : buf)
end
#
# Writes the output buffer to the stream in the format of a 4-byte length
# followed by the actual data.
#
def flush
return @transport.flush unless @write
out = [@wbuf.length].pack('N')
# Array#pack should return a BINARY encoded String, so it shouldn't be necessary to force encoding
out << @wbuf
@transport.write(out)
@transport.flush
@wbuf = Bytes.empty_byte_buffer
end
private
def read_frame
sz = @transport.read_all(4).unpack('N').first
@index = 0
@rbuf = @transport.read_all(sz)
end
end
class FramedTransportFactory < BaseTransportFactory
def get_transport(transport)
return FramedTransport.new(transport)
end
end
end

View file

@ -0,0 +1,57 @@
# encoding: ascii-8bit
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
require 'net/http'
require 'net/https'
require 'openssl'
require 'uri'
require 'stringio'
module Thrift
class HTTPClientTransport < BaseTransport
def initialize(url, opts = {})
@url = URI url
@headers = {'Content-Type' => 'application/x-thrift'}
@outbuf = Bytes.empty_byte_buffer
@ssl_verify_mode = opts.fetch(:ssl_verify_mode, OpenSSL::SSL::VERIFY_PEER)
end
def open?; true end
def read(sz); @inbuf.read sz end
def write(buf); @outbuf << Bytes.force_binary_encoding(buf) end
def add_headers(headers)
@headers = @headers.merge(headers)
end
def flush
http = Net::HTTP.new @url.host, @url.port
http.use_ssl = @url.scheme == 'https'
http.verify_mode = @ssl_verify_mode if @url.scheme == 'https'
resp = http.post(@url.request_uri, @outbuf, @headers)
data = resp.body
data = Bytes.force_binary_encoding(data)
@inbuf = StringIO.new data
ensure
@outbuf = Bytes.empty_byte_buffer
end
end
end

View file

@ -0,0 +1,39 @@
# encoding: ascii-8bit
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# Very very simple implementation of wrapping two objects, one with a #read
# method and one with a #write method, into a transport for thrift.
#
# Assumes both objects are open, remain open, don't require flushing, etc.
#
module Thrift
class IOStreamTransport < BaseTransport
def initialize(input, output)
@input = input
@output = output
end
def open?; not @input.closed? or not @output.closed? end
def read(sz); @input.read(sz) end
def write(buf); @output.write(Bytes.force_binary_encoding(buf)) end
def close; @input.close; @output.close end
def to_io; @input end # we're assuming this is used in a IO.select for reading
end
end

View file

@ -0,0 +1,125 @@
# encoding: ascii-8bit
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
module Thrift
class MemoryBufferTransport < BaseTransport
GARBAGE_BUFFER_SIZE = 4*(2**10) # 4kB
# If you pass a string to this, you should #dup that string
# unless you want it to be modified by #read and #write
#--
# this behavior is no longer required. If you wish to change it
# go ahead, just make sure the specs pass
def initialize(buffer = nil)
@buf = buffer ? Bytes.force_binary_encoding(buffer) : Bytes.empty_byte_buffer
@index = 0
end
def open?
return true
end
def open
end
def close
end
def peek
@index < @buf.size
end
# this method does not use the passed object directly but copies it
def reset_buffer(new_buf = '')
@buf.replace Bytes.force_binary_encoding(new_buf)
@index = 0
end
def available
@buf.length - @index
end
def read(len)
data = @buf.slice(@index, len)
@index += len
@index = @buf.size if @index > @buf.size
if @index >= GARBAGE_BUFFER_SIZE
@buf = @buf.slice(@index..-1)
@index = 0
end
if data.size < len
raise EOFError, "Not enough bytes remain in buffer"
end
data
end
def read_byte
raise EOFError.new("Not enough bytes remain in buffer") if @index >= @buf.size
val = Bytes.get_string_byte(@buf, @index)
@index += 1
if @index >= GARBAGE_BUFFER_SIZE
@buf = @buf.slice(@index..-1)
@index = 0
end
val
end
def read_into_buffer(buffer, size)
i = 0
while i < size
raise EOFError.new("Not enough bytes remain in buffer") if @index >= @buf.size
# The read buffer has some data now, so copy bytes over to the output buffer.
byte = Bytes.get_string_byte(@buf, @index)
Bytes.set_string_byte(buffer, i, byte)
@index += 1
i += 1
end
if @index >= GARBAGE_BUFFER_SIZE
@buf = @buf.slice(@index..-1)
@index = 0
end
i
end
def write(wbuf)
@buf << Bytes.force_binary_encoding(wbuf)
end
def flush
end
def inspect_buffer
out = []
for idx in 0...(@buf.size)
# if idx != 0
# out << " "
# end
if idx == @index
out << ">"
end
out << @buf[idx].ord.to_s(16)
end
out.join(" ")
end
end
end

View file

@ -0,0 +1,63 @@
# encoding: ascii-8bit
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
require 'socket'
module Thrift
class ServerSocket < BaseServerTransport
# call-seq: initialize(host = nil, port)
def initialize(host_or_port, port = nil)
if port
@host = host_or_port
@port = port
else
@host = nil
@port = host_or_port
end
@handle = nil
end
attr_reader :handle
def listen
@handle = TCPServer.new(@host, @port)
end
def accept
unless @handle.nil?
sock = @handle.accept
trans = Socket.new
trans.handle = sock
trans
end
end
def close
@handle.close unless @handle.nil? or @handle.closed?
@handle = nil
end
def closed?
@handle.nil? or @handle.closed?
end
alias to_io handle
end
end

View file

@ -0,0 +1,141 @@
# encoding: ascii-8bit
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
require 'socket'
module Thrift
class Socket < BaseTransport
def initialize(host='localhost', port=9090, timeout=nil)
@host = host
@port = port
@timeout = timeout
@desc = "#{host}:#{port}"
@handle = nil
end
attr_accessor :handle, :timeout
def open
for addrinfo in ::Socket::getaddrinfo(@host, @port, nil, ::Socket::SOCK_STREAM) do
begin
socket = ::Socket.new(addrinfo[4], ::Socket::SOCK_STREAM, 0)
socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
sockaddr = ::Socket.sockaddr_in(addrinfo[1], addrinfo[3])
begin
socket.connect_nonblock(sockaddr)
rescue Errno::EINPROGRESS
unless IO.select(nil, [ socket ], nil, @timeout)
next
end
begin
socket.connect_nonblock(sockaddr)
rescue Errno::EISCONN
end
end
return @handle = socket
rescue StandardError => e
next
end
end
raise TransportException.new(TransportException::NOT_OPEN, "Could not connect to #{@desc}: #{e}")
end
def open?
!@handle.nil? and !@handle.closed?
end
def write(str)
raise IOError, "closed stream" unless open?
str = Bytes.force_binary_encoding(str)
begin
if @timeout.nil? or @timeout == 0
@handle.write(str)
else
len = 0
start = Time.now
while Time.now - start < @timeout
rd, wr, = IO.select(nil, [@handle], nil, @timeout)
if wr and not wr.empty?
len += @handle.write_nonblock(str[len..-1])
break if len >= str.length
end
end
if len < str.length
raise TransportException.new(TransportException::TIMED_OUT, "Socket: Timed out writing #{str.length} bytes to #{@desc}")
else
len
end
end
rescue TransportException => e
# pass this on
raise e
rescue StandardError => e
@handle.close
@handle = nil
raise TransportException.new(TransportException::NOT_OPEN, e.message)
end
end
def read(sz)
raise IOError, "closed stream" unless open?
begin
if @timeout.nil? or @timeout == 0
data = @handle.readpartial(sz)
else
# it's possible to interrupt select for something other than the timeout
# so we need to ensure we've waited long enough, but not too long
start = Time.now
timespent = 0
rd = loop do
rd, = IO.select([@handle], nil, nil, @timeout - timespent)
timespent = Time.now - start
break rd if (rd and not rd.empty?) or timespent >= @timeout
end
if rd.nil? or rd.empty?
raise TransportException.new(TransportException::TIMED_OUT, "Socket: Timed out reading #{sz} bytes from #{@desc}")
else
data = @handle.readpartial(sz)
end
end
rescue TransportException => e
# don't let this get caught by the StandardError handler
raise e
rescue StandardError => e
@handle.close unless @handle.closed?
@handle = nil
raise TransportException.new(TransportException::NOT_OPEN, e.message)
end
if (data.nil? or data.length == 0)
raise TransportException.new(TransportException::UNKNOWN, "Socket: Could not read #{sz} bytes from #{@desc}")
end
data
end
def close
@handle.close unless @handle.nil? or @handle.closed?
@handle = nil
end
def to_io
@handle
end
end
end

View file

@ -0,0 +1,37 @@
# encoding: ascii-8bit
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
require 'socket'
module Thrift
class SSLServerSocket < ServerSocket
def initialize(host_or_port, port = nil, ssl_context = nil)
super(host_or_port, port)
@ssl_context = ssl_context
end
attr_accessor :ssl_context
def listen
socket = TCPServer.new(@host, @port)
@handle = OpenSSL::SSL::SSLServer.new(socket, @ssl_context)
end
end
end

View file

@ -0,0 +1,47 @@
# encoding: ascii-8bit
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
module Thrift
class SSLSocket < Socket
def initialize(host='localhost', port=9090, timeout=nil, ssl_context=nil)
super(host, port, timeout)
@ssl_context = ssl_context
end
attr_accessor :ssl_context
def open
socket = super
@handle = OpenSSL::SSL::SSLSocket.new(socket, @ssl_context)
begin
@handle.connect_nonblock
@handle.post_connection_check(@host)
@handle
rescue IO::WaitReadable
IO.select([ @handle ], nil, nil, @timeout)
retry
rescue IO::WaitWritable
IO.select(nil, [ @handle ], nil, @timeout)
retry
rescue StandardError => e
raise TransportException.new(TransportException::NOT_OPEN, "Could not connect to #{@desc}: #{e}")
end
end
end
end

View file

@ -0,0 +1,60 @@
# encoding: ascii-8bit
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
require 'socket'
module Thrift
class UNIXServerSocket < BaseServerTransport
def initialize(path)
@path = path
@handle = nil
end
attr_accessor :handle
def listen
@handle = ::UNIXServer.new(@path)
end
def accept
unless @handle.nil?
sock = @handle.accept
trans = UNIXSocket.new(nil)
trans.handle = sock
trans
end
end
def close
if @handle
@handle.close unless @handle.closed?
@handle = nil
# UNIXServer doesn't delete the socket file, so we have to do it ourselves
File.delete(@path)
end
end
def closed?
@handle.nil? or @handle.closed?
end
alias to_io handle
end
end

View file

@ -0,0 +1,40 @@
# encoding: ascii-8bit
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
require 'socket'
module Thrift
class UNIXSocket < Socket
def initialize(path, timeout=nil)
@path = path
@timeout = timeout
@desc = @path # for read()'s error
@handle = nil
end
def open
begin
@handle = ::UNIXSocket.new(@path)
rescue StandardError
raise TransportException.new(TransportException::NOT_OPEN, "Could not open UNIX socket at #{@path}")
end
end
end
end

View file

@ -0,0 +1,101 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
require 'set'
module Thrift
module Types
STOP = 0
VOID = 1
BOOL = 2
BYTE = 3
DOUBLE = 4
I16 = 6
I32 = 8
I64 = 10
STRING = 11
STRUCT = 12
MAP = 13
SET = 14
LIST = 15
end
class << self
attr_accessor :type_checking
end
class TypeError < Exception
end
def self.check_type(value, field, name, skip_nil=true)
return if value.nil? and skip_nil
klasses = case field[:type]
when Types::VOID
NilClass
when Types::BOOL
[TrueClass, FalseClass]
when Types::BYTE, Types::I16, Types::I32, Types::I64
Integer
when Types::DOUBLE
Float
when Types::STRING
String
when Types::STRUCT
[Struct, Union]
when Types::MAP
Hash
when Types::SET
Set
when Types::LIST
Array
end
valid = klasses && [*klasses].any? { |klass| klass === value }
raise TypeError, "Expected #{type_name(field[:type])}, received #{value.class} for field #{name}" unless valid
# check elements now
case field[:type]
when Types::MAP
value.each_pair do |k,v|
check_type(k, field[:key], "#{name}.key", false)
check_type(v, field[:value], "#{name}.value", false)
end
when Types::SET, Types::LIST
value.each do |el|
check_type(el, field[:element], "#{name}.element", false)
end
when Types::STRUCT
raise TypeError, "Expected #{field[:class]}, received #{value.class} for field #{name}" unless field[:class] == value.class
end
end
def self.type_name(type)
Types.constants.each do |const|
return "Types::#{const}" if Types.const_get(const) == type
end
nil
end
module MessageTypes
CALL = 1
REPLY = 2
EXCEPTION = 3
ONEWAY = 4
end
end
Thrift.type_checking = false if Thrift.type_checking.nil?

View file

@ -0,0 +1,176 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
module Thrift
class Union
def initialize(name=nil, value=nil)
if name
if name.is_a? Hash
if name.size > 1
raise "#{self.class} cannot be instantiated with more than one field!"
end
name, value = name.keys.first, name.values.first
end
if Thrift.type_checking
raise Exception, "#{self.class} does not contain a field named #{name}!" unless name_to_id(name.to_s)
end
if value.nil?
raise Exception, "Union #{self.class} cannot be instantiated with setfield and nil value!"
end
Thrift.check_type(value, struct_fields[name_to_id(name.to_s)], name) if Thrift.type_checking
elsif !value.nil?
raise Exception, "Value provided, but no name!"
end
@setfield = name
@value = value
end
def inspect
if get_set_field
"<#{self.class} #{@setfield}: #{inspect_field(@value, struct_fields[name_to_id(@setfield.to_s)])}>"
else
"<#{self.class} >"
end
end
def read(iprot)
iprot.read_struct_begin
fname, ftype, fid = iprot.read_field_begin
handle_message(iprot, fid, ftype)
iprot.read_field_end
fname, ftype, fid = iprot.read_field_begin
raise "Too many fields for union" unless (ftype == Types::STOP)
iprot.read_struct_end
validate
end
def write(oprot)
validate
oprot.write_struct_begin(self.class.name)
fid = self.name_to_id(@setfield.to_s)
field_info = struct_fields[fid]
type = field_info[:type]
if is_container? type
oprot.write_field_begin(@setfield, type, fid)
write_container(oprot, @value, field_info)
oprot.write_field_end
else
oprot.write_field(@setfield, type, fid, @value)
end
oprot.write_field_stop
oprot.write_struct_end
end
def ==(other)
other.equal?(self) || other.instance_of?(self.class) && @setfield == other.get_set_field && @value == other.get_value
end
alias_method :eql?, :==
def hash
[self.class.name, @setfield, @value].hash
end
def self.field_accessor(klass, field_info)
klass.send :define_method, field_info[:name] do
if field_info[:name].to_sym == @setfield
@value
else
raise RuntimeError, "#{field_info[:name]} is not union's set field."
end
end
klass.send :define_method, "#{field_info[:name]}=" do |value|
Thrift.check_type(value, field_info, field_info[:name]) if Thrift.type_checking
@setfield = field_info[:name].to_sym
@value = value
end
end
def self.qmark_isset_method(klass, field_info)
klass.send :define_method, "#{field_info[:name]}?" do
get_set_field == field_info[:name].to_sym && !get_value.nil?
end
end
def self.generate_accessors(klass)
klass::FIELDS.values.each do |field_info|
field_accessor(klass, field_info)
qmark_isset_method(klass, field_info)
end
end
# get the symbol that indicates what the currently set field type is.
def get_set_field
@setfield
end
# get the current value of this union, regardless of what the set field is.
# generally, you should only use this method when you don't know in advance
# what field to expect.
def get_value
@value
end
def <=>(other)
if self.class == other.class
if get_set_field == other.get_set_field
if get_set_field.nil?
0
else
get_value <=> other.get_value
end
else
if get_set_field && other.get_set_field.nil?
-1
elsif get_set_field.nil? && other.get_set_field
1
elsif get_set_field.nil? && other.get_set_field.nil?
0
else
name_to_id(get_set_field.to_s) <=> name_to_id(other.get_set_field.to_s)
end
end
else
self.class <=> other.class
end
end
protected
def handle_message(iprot, fid, ftype)
field = struct_fields[fid]
if field and field[:type] == ftype
@value = read_field(iprot, field)
name = field[:name].to_sym
@setfield = name
else
iprot.skip(ftype)
end
end
end
end