Thrift Ruby Software Library¶ ↑
License¶ ↑
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
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.
Using Thrift with Ruby¶ ↑
Ruby bindings for the Apache Thrift RPC system. The gem contains the runtime types, transports, protocols, and servers used by generated Ruby code for both clients and services.
Compatibility¶ ↑
-
Ruby MRI >= 3.0 (tested against current supported releases).
-
JRuby works with the pure-Ruby implementation; the native extension is skipped automatically.
-
For the repo-wide transport, protocol, and server support matrix, see Language Feature Matrix. This README focuses on Ruby-specific behavior and migration notes.
Installation¶ ↑
-
Requirements: Ruby >= 3.0.
-
From RubyGems:
gem install thrift -
From source:
bundle install,gem build thrift.gemspec, then install the resultingthrift-*.gem. The native accelerator is built when the gem is installed on supported runtimes.
Generating Ruby Code¶ ↑
The Ruby library does not include the Thrift compiler. Use a compiler built from the root of this repository to generate Ruby bindings:
thrift --gen rb path/to/service.thrift # with namespaced modules thrift --gen rb:namespaced --recurse path/to/service.thrift
Generated files are typically written to gen-rb/ and can be required directly from your application.
Basic Client Usage¶ ↑
$:.push File.expand_path('gen-rb', __dir__) require 'thrift' require 'calculator' socket = Thrift::Socket.new('localhost', 9090) transport = Thrift::BufferedTransport.new(socket) protocol = Thrift::BinaryProtocol.new(transport) client = Calculator::Client.new(protocol) transport.open puts client.add(1, 1) transport.close
Basic Server Usage¶ ↑
$:.push File.expand_path('gen-rb', __dir__) require 'thrift' require 'calculator' class CalculatorHandler def add(a, b) a + b end end handler = CalculatorHandler.new processor = Calculator::Processor.new(handler) server_transport = Thrift::ServerSocket.new(9090) transport_factory = Thrift::BufferedTransportFactory.new protocol_factory = Thrift::BinaryProtocolFactory.new server = Thrift::ThreadedServer.new(processor, server_transport, transport_factory, protocol_factory) server.serve
Rack HTTP Endpoint¶ ↑
Ruby HTTP transport can be mounted as a Rack application, so applications can run Thrift on an existing Rack server such as Puma or Falcon. The examples below use the generated Calculator::Processor and the CalculatorHandler from Basic Server Usage.
# config.ru require 'thrift' require 'thrift/server/rack_application' processor = Calculator::Processor.new(CalculatorHandler.new) run Thrift::RackApplication.new(processor)
For Rails or another Rack router, mount the endpoint at the route that should receive Thrift HTTP requests:
# config/routes.rb processor = Calculator::Processor.new(CalculatorHandler.new) mount Thrift::RackApplication.new(processor) => "/thrift"
Development and Tests¶ ↑
-
bundle exec rake specruns the Ruby specs. It expects a builtThriftcompiler at../../compiler/cpp/thrift. -
bundle exec rake testruns the cross-language test suite; it must be executed from a fullThriftcheckout. -
bundle exec rake build_ext(implicit in the tasks above) compiles the optional native extension that accelerates protocols and buffers.
More Ruby Code¶ ↑
-
Tutorial client and server:
tutorial/rb/RubyClient.rbandtutorial/rb/RubyServer.rb -
Runtime benchmarks:
lib/rb/benchmark -
Protocol benchmark:
test/rb/benchmarks/protocol_benchmark.rb -
Library specs:
lib/rb/spec -
Fuzzing harnesses and notes:
lib/rb/test/fuzz -
Cross-language and integration tests:
test/rb
Breaking Changes¶ ↑
0.25.0¶ ↑
Ruby 2.7 support has been removed because the runtime reached end of life on March 31, 2023. The Thrift gem now requires Ruby 3.0 or newer. Applications still running Ruby 2.7 must upgrade Ruby before installing Thrift 0.25.0 or remain on an earlier Thrift release.
Ruby HTTP servers now share one Rack endpoint. Run Thrift::RackApplication as a Rack app, or mount it at one path in an app such as Rails. The cross-language HTTP tests now cover Puma and Falcon.
Thrift::MongrelHTTPServer has been removed because Mongrel no longer works with supported Ruby versions. To migrate, mount Thrift::RackApplication in a supported Rack server. See Rack HTTP Endpoint.
Thrift::ThinHTTPServer is deprecated because Thin is no longer maintained. Its EventMachine dependency also does not build on new Ruby development versions. Creating a Thin server now prints a warning. Move to Thrift::RackApplication with a supported Rack server such as Puma or Falcon.
Thrift::SSLSocket now verifies peers by default when no SSL context is provided. It creates a context that uses the system certificate store. A supplied SSL context is not reconfigured by Thrift, so the application is responsible for its verification mode and trust sources. A newly constructed OpenSSL::SSL::SSLContext defaults to OpenSSL::SSL::VERIFY_NONE; configuring a trust source alone does not enable certificate chain verification. The server certificate must still match the connection hostname.
Thrift::NonblockingServer now closes a connection whose frame declares more than max_frame_size before it collects the frame. max_frame_size is a new keyword argument and defaults to Thrift::FramedTransport::DEFAULT_MAX_FRAME_SIZE (16,384,000 bytes), the limit FramedTransport already applies to the frame the worker reads back. A server that accepted larger frames must pass a higher max_frame_size: to keep doing so.
Thrift::Socket#open now raises Thrift::TransportException::ALREADY_OPEN when the TCP transport is already open. Close the transport before opening it again.
Ruby struct, union, and exception serialization methods now accept an optional remaining struct depth: read(protocol, depth = 64) and write(protocol, depth = 64). Unknown and mismatched fields remain independently bounded by Thrift::BaseProtocol#skip. The write_field and write_type helpers accept an optional remaining_depth: custom writers must use write_field(field_info, fid, value, remaining_depth) or write_type(field_info, value, remaining_depth) for struct values. Custom protocols and custom struct, union, or exception serialization overrides with exact argument counts must accept and forward the optional depth argument (or use *args) before upgrading.
Thrift::BinaryProtocol, Thrift::BinaryProtocolAccelerated and Thrift::CompactProtocol now refuse a string or binary field longer than max_string_size before reading it, with a ProtocolException of type SIZE_LIMIT. The maximum defaults to Thrift::BaseProtocol::DEFAULT_MAX_STRING_SIZE, 16384000 bytes, the frame size limit the framed transports apply, and is a keyword argument of the three protocols and their factories. Pass max_string_size: nil to read strings of any length, as before.
0.24.0¶ ↑
Connect timeout handling changed for both Thrift::Socket and Thrift::SSLSocket.
-
timeout == nilandtimeout == 0now use blocking connect/open semantics. Older releases already treatednilthat way, but treated0differently across operations:readandwriteused the blocking path, plain TCPopenused a zero-length poll, and TLSopencould spin in the handshake retry loop. -
Positive timeouts now bound the whole connect/open operation instead of effectively applying a fresh timeout window at each wait or address attempt. For plain TCP this budget is shared across address fallback. For TLS it is shared across the TCP connect and the SSL handshake.
-
Connect/open timeout expiry now raises
Thrift::TransportException::TIMED_OUT. Older releases could report the same condition asNOT_OPEN, andThrift::SSLSocketcould keep retrying the handshake after the wait timed out.
If your application matched NOT_OPEN for connect timeout handling, update it to handle TIMED_OUT. If you relied on timeout = 0 meaning immediate failure or on repeated retries extending the effective timeout during TCP fallback or TLS handshake, update those call paths before upgrading.
Ruby server socket transports now apply a 5-second timeout to accepted client sockets by default. This prevents stalled clients from blocking server threads indefinitely during response writes. Applications that intentionally require blocking accepted sockets can pass client_timeout: nil or client_timeout: 0 when constructing Thrift::ServerSocket, Thrift::SSLServerSocket, or Thrift::UNIXServerSocket.
Generated Ruby structs and unions now suffix field ID constants as *_FIELD_ID instead of exposing bare uppercased field names. For example, MyStruct::FOO becomes MyStruct::FOO_FIELD_ID. This avoids collisions with the generated FIELDS metadata hash for field names such as fields, but it is a source-compatible break if your application referenced the old constants directly. Regenerate Ruby code and update those constant references atomically.
Ruby binary protocol writers now enforce signed Thrift integer ranges before serializing values. byte, i16, i32, and i64 writes reject values outside their declared signed ranges, and binary/string/container sizes must fit in a non-negative signed i32 length. Older releases could silently wrap or clip some out-of-range values, such as writing 255 as a byte that read back as -1. This applies to both Thrift::BinaryProtocol and Thrift::BinaryProtocolAccelerated.
0.23.0¶ ↑
The documented source-build flow now effectively requires Ruby 2.7+. The committed development bundle no longer resolves on Ruby 2.6 (json-2.18.1 requires ruby version >= 2.7), so building and testing this library from source should be treated as 2.7+.
Generated structs and unions now consistently raise Thrift::ProtocolException::INVALID_DATA for invalid payloads such as unset required fields, invalid enum values, or invalid union state. If your application or tests matched older exception types or messages, update them.
Regenerated Ruby clients now validate replies more strictly. Mismatched reply message types, method names, or sequence IDs raise Thrift::ApplicationException::INVALID_MESSAGE_TYPE, Thrift::ApplicationException::WRONG_METHOD_NAME, or Thrift::ApplicationException::BAD_SEQUENCE_ID. If you relied on older, looser reply handling in servers, proxies, or tests, regenerate and update those call paths together.
Generated Ruby clients have never been safe to share across concurrent threads. A client tracks pending sequence IDs on a single reply stream, so use one client/transport pair per thread or serialize access yourself.
Treat Thrift::ApplicationException::BAD_SEQUENCE_ID as a correctness bug that needs immediate attention. It means the client read a reply whose sequence ID did not match the next pending request, so the connection may already be out of sync and you may be reading a reply intended for a different call. The most common cause is sharing one client across threads, but a buggy proxy or server can also cause it.
0.13.0¶ ↑
Ruby development and CI moved to Ruby 2.4+, but the runtime still claimed support for older interpreters. Treat Ruby < 2.4 on the 0.13.x line as best-effort, not guaranteed.
-
Historical note for very old releases: the Ruby runtime was rearranged to use more Ruby-like names, and generated files switched to underscored filenames. If you are upgrading very old code, regenerate your Ruby bindings and update any old
T*constants or legacy require paths such asTBinaryProtocol->Thrift::BinaryProtocol. -
rb:namespacedchanges the generated file layout. Flat output fromthrift --gen rband namespaced output fromthrift --gen rb:namespaceduse different require paths, so switch them atomically with regenerated code.
“‘ruby # –gen rb require ’calculator’
# –gen rb:namespaced require ‘my_namespace/calculator’ “‘
Migration Notes¶ ↑
-
If you upgrade to
0.25.0, note thatThrift::SSLSocketnow verifies peers by default when it creates the SSL context. Supplied contexts are used unchanged and must configure their own verification mode and trust sources. A blank supplied context defaults toOpenSSL::SSL::VERIFY_NONE. Hostname checking is performed for both supplied and default contexts. -
If you upgrade to
0.25.0, close aThrift::Socketbefore callingopenagain. A duplicate open now raisesThrift::TransportException::ALREADY_OPEN. -
If you upgrade to
0.24.0, treattimeoutonThrift::SocketandThrift::SSLSocketas one budget for the whole open path. ForThrift::SSLSocket, that includes both the TCP connect and the TLS handshake. -
If you upgrade to
0.24.0, handle connect/open timeout expiry asThrift::TransportException::TIMED_OUTinstead ofNOT_OPEN. -
If you upgrade across the stricter reply-validation changes, regenerate all Ruby stubs and deploy them with the matching Ruby runtime. Do not mix old generated code, new generated code, and new runtime code on the same client path without testing that combination.
-
If you receive
Thrift::ApplicationException::BAD_SEQUENCE_ID, treat the connection as out of sync. Close it, create a new client/transport pair, and investigate the root cause before retrying. -
Do not share one generated Ruby client across concurrent threads. Use one client/transport pair per thread, or serialize access to a shared client.
-
If you switch between
thrift --gen rbandthrift --gen rb:namespaced, regenerate all Ruby output and updaterequirepaths in the same change.
Runtime Notes¶ ↑
-
Loading the
thrift_nativeextension changes which implementation you are running. It replacesThrift::Struct,Thrift::Union, andThrift::CompactProtocolmethods with C implementations in place.Thrift::BinaryProtocolremains available in pure Ruby, and the C-backed binary protocol is opt-in throughThrift::BinaryProtocolAcceleratedFactoryorThrift::BinaryProtocolAcceleratedwhen that class is available. -
The native extension is optional. If it cannot be built or loaded,
Thriftfalls back to the pure-Ruby implementation. This mainly changes performance and implementation details. -
JRuby skips the native extension automatically and uses the pure-Ruby path.
-
Do not share one client instance across concurrent threads. A client tracks request and reply state on a single transport stream.
-
Thrift::NonblockingServerexpects framed input. UseThrift::FramedTransportwith it on the wire. -
Client and server must agree on transport and protocol choices. If you switch to SSL, HTTP, header transport, compact protocol, or namespaced generated code, update both ends together.
-
HTTPS client transport and
Thrift::SSLSocketverify peers by default. When no SSL context is provided,Thrift::SSLSocketcreates one with peer verification and the system certificate store.Thriftpasses a supplied context to OpenSSL without reconfiguring it.Thrift::SSLSocketalways checks the certificate againstserver_hostname(or the connection host by default).OpenSSL::SSL::SSLContext.newdefaults toOpenSSL::SSL::VERIFY_NONE, and settingca_file,ca_path, orcert_storedoes not enable verification by itself. Setverify_modeexplicitly when supplying a context. For example, to disable certificate chain verification:
ruby context = OpenSSL::SSL::SSLContext.new context.verify_mode = OpenSSL::SSL::VERIFY_NONE socket = Thrift::SSLSocket.new(host, port, nil, context)