Sarav's Weblog

Technical Articles for RoR Developers

Tag Archives: Ruby Web-Services with Thrift

Apache Thrift with Ruby – A basic Tutorial

Thrift is a software framework for scalable cross-language services development. It combines a software stack with a code generation engine to build services that work efficiently and seamlessly between C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk, and OCaml.

Originally developed at Facebook, Thrift was open sourced in April 2007 and entered the Apache Incubator in May, 2008.

Thrift is arguably one of the best lightweight systems for cross-language programming using code-generation, RPC, and object serialization. Designed from the ground up by the Facebook development team it enables seamless integration of the most commonly used languages (Ruby, Perl, C/C++, Haskell, Python, Java, and more) via a set of common RPC libraries and interfaces.

In this post I’ll outline how to setup and connect a thrift client in ruby

To install thrift

gem install thrift

Steps:

  • Defining a thrift service
  • Run the server script(ruby)
  • Run the client script(ruby)

Defining a service in Thrift

The core goal of Thrift is to enable efficient and reliable communications across multiple languages. All datatypes and services are defined in a single language-neutral file and the necessary code is auto-generated for the developer by the thrift compiler.

One of the great things about thrift is that it simplifies the communication between a client and server to a simple .thrift file. This file describes the data structures, and functions available to your remote service. In this tutorial, we use a simple calculator mechanism.

Define a struct and a service

	enum BinaryOperation {
		ADDITION = 1,
		SUBTRACTION = 2,
		MULTIPLICATION = 3,
		DIVISION = 4,
		MODULUS = 5
	}

	/** Structs are the basic complex data structures. They are comprised of fields 
	   * which each have an integer identifier, a type, a symbolic name, and an 
	   * optional default value. */
   
	struct ArithmeticOperation {
		1:BinaryOperation op,
		2:double lh_term,
		3:double rh_term,
	}

	/* Structs can also be exceptions, if they are nasty. */
	exception ArithmeticException {
		1:string msg,
		2:optional double x,
	}


	service Calculator {

		/**
		* A method definition looks like C code. It has a return type, arguments,
		* and optionally a list of exceptions that it may throw. Note that argument
		* lists and exception lists are specified using the exact same syntax as
		* field lists in struct or exception definitions.
		*/
		
		double calc(1:ArithmeticOperation op) throws (1:ArithmeticException ae),
		
		
		/**
		* This method has an oneway modifier. That means the client only makes
		* a request and does not listen for any response at all. Oneway methods
		* must be void.
		*
		* The server may execute async invocations of the same client in parallel/
		* out of order.
		*/
		
		oneway void run_task()
	}

Open a empty file and create service(copy the above code) and save it with .thrift extension(eg:calc.thrift)

We’ve defined two different functions: remote calculationand an asynchronous method call. To generate the required code, we simply call the thrift generator:

# Generate C++, Ruby, and Python implementations
# generated code will be in ‘gen-cpp’, ‘gen-rb’, ‘gen-py’ folder

thrift --gen rb calc.thrift

The beauty of this approach is, of course, the ability to mix and match implementations of services: server may be written in C++, but we can access it seamlessly via a Python, Java, or a Ruby client. Thrift will take care of the communications links, object serialization, and socket management!

Implementing a Thrift powered server in Ruby

A very simple example of interaction with a calculator server application whose actions are facilitated by thrift. Both the client and server negotiate on the common interface defined by calc.thrift.

# include thrift-generated code
$:.push('../gen-rb')

require 'thrift'
require 'calculator'

# provide an implementation of Calculator
class CalculatorHandler
	def initialize()
		#nothing goes here
	end

	def calc(val)
		lh_term = val.lh_term
		rh_term = val.rh_term
		case val.op
			when 1 #Addition
				lh_term+rh_term
			when 2 #subraction
				lh_term-rh_term
			when 3 #multiplication
				lh_term*rh_term
			when 4 #division
				lh_term/rh_term
			when 5	#modulas
				lh_term%rh_term
		end
	end
	
	def run_task()
		puts "Kicking off the task"
	end
end

# Thrift provides mutiple communication endpoints
#  - Here we will expose our service via a TCP socket
#  - Web-service will run as a single thread, on port 9090

handler = CalculatorHandler.new()
processor = Calculator::Processor.new(handler)
transport = Thrift::ServerSocket.new(9090)
transportFactory = Thrift::BufferedTransportFactory.new()
server = Thrift::SimpleServer.new(processor, transport, transportFactory)
puts "Starting the Calculator server..."
server.serve()
puts "done."

To expose our thrift service, we wrap it into a TCP socket listening on port 9090. From this point on, Thrift takes over all communications, serialization and handling of the incoming requests. Because the protocol is identical in every language, the client may be written in any language of choice.

Building the Ruby Client

You may have the client program in any language which supports Thrift.

In similar fashion, we can build a Ruby client for any Thrift service with just a few lines of code. To interface with our server implementation above, refer the following code:

# include thrift-generated code
$:.push('../gen-rb')

require 'thrift'
require 'calculator'

begin
	port = ARGV[0] || 9090

	transport = Thrift::BufferedTransport.new(Thrift::Socket.new('localhost', 9090))
	protocol = Thrift::BinaryProtocol.new(transport)
	client = Calculator::Client.new(protocol)

	transport.open()

	ar = ArithmeticOperation.new()    
	ar.op = BinaryOperation::ADDITION
	ar.lh_term = 99
	ar.rh_term = 3
	
	# Run a remote calculation
	result = client.calc(ar)  #it accessing the ruby server program method calc via thrift service
	puts result.inspect
	
	#Run a Async call
	client.run_task()
	
	transport.close()
rescue
	puts $!
end

And there you have it, a simple thrift client. I hope you think about thrift next time you need to interface between 2 applications, or require some kind of client->server model for your system. Thrift makes it very fast and convenient to share data across different programming languages and makes it easy/flexible to develop to a shared interface contract. Cheers!

Download Files
calc.thrift
ruby_server
ruby_client

Summary:

  • Download the calc.thrift and place it into the new folder(name it whatever you like. Eg:- thrift-ruby)
  • cd thrift-ruby
  • thrift –gen rb calc.thrift (it will create the auto generated ruby code. Dont change anything on the auto generated code)
  • create the folder named “rb” and place the ruby_server.rb and ruby_client.rb
  • run server program first. $ruby ruby_server.rb (it will start the server)
  • then run the client program in new cmd prompt. $ruby ruby_client.rb

References: