Contents

Disclaimer

This website, nor it's author isn't in any form affiliated or endorsed by the Apache Software Foundation.

Opinions or suggestions included in this document are based on the author's knowledge of Apache Thrift, which is still work-in-progress.


Our own "Hello World!"

This tutorial "first application" is something, that you can as well deduce from the official Apache Thrift tutorial, although in my opinion, it explains things more clearly.

Our first application using Apache Thrift will be quite simple. We will define object "User". This object will be created in PHP, and stored in the "database" (this means a list:) in Python. One could also retrieve the object from Python and pass it to PHP or clear the list. Although this example is very primitive, it will give you some idea about how the Apache Thrift works.

First, we will define methods and objects, that are going to be exchanged between both applications. These definitions we will put in the file named "hello.thrift". This file has C-like syntax, with some Thrift-specific modifications – as will be explained later.

Thrift files can include other files. In our example we will not include any other files, however in the tutorial project you may notice, that file "shared.thrift" is needed. This file declares the class, that the main class Calculator inherits from. In our example we don't need that.

At the beginning you may define namespaces:

namespace php hello

Namespaces are defined per language – in this example, only objects in PHP file will be prefixed with "hello_".

Object "User" will have fields "firstname" and "lastname" of type string, "user_id" of type integer, "active" of type boolean, and "sex", being our own enum type.

Defining such object is simple. First, we have to define our enum type:

enum SexType {
  MALE = 1,
  FEMALE = 2
}

And then user object:

struct User {
  1: string firstname,
  2: string lastname,
  3: i32 user_id = 0,
  4: SexType sex,
  5: bool active = false,
  6: optional string description
}

As you may have noticed, we have to number the parameters – that's the convention required by Thrift. By default, all parameters are required, but you can make them optional (by using "optional" keyword), as well as setting their default value.

We may also declare an exception. It will be used, when wrong parameter is passed (in our simple example, it may be, i.e. negative user_id number).

exception InvalidValueException {
  1: i32 error_code,
  2: string error_msg
}

Now it's time to declare service. Services are the main thing, that makes serialized objects to be available through different programs. Declaration of our simple service is as follows:

service UserExchange {
  void ping(),
  i32 add_user(1:User u) throws (1: InvalidValue e),
  User get_user(1:i32 uid) throws (1: InvalidValue e),
  oneway void clear_list()
}

When declaring methods, the same rule of numbering parameters applies. As you see, method declaration is very C-like.

Quick explanation of our methods:

  • ping – simple ping-pong method. We send "are you there?" message and receive "yes, I am here" response – of course if everything goes well.
  • add_user – we send user object (notice, that it's an object, so it will be created in PHP application). In response we get this user's id number. If something goes wrong – we have to catch error thrown by this method.
  • get_user – same as above, but here we supply user_id and receive User object.
  • clear_list – this function is declared as "oneway" - that means, request will be sent, but our PHP application is not going to wait for the result (as there will be none). Obviously, return value of such function should be void.

Now it's time to generate PHP and Python files, to be used in our client and server, respectively. It is easily done by:

thrift -r --gen php hello.thrift
thrift -r --gen py hello.thrift

In the directories "gen-php" and "gen-py" we have PHP and Python files, respectively.

Let's create the server now. We create file, let's say, "python_server.py". Don't forget to make it runnable, by using command:

chmod +x ./python_server.py

In this file we will include all needed libraries, define class to handle the requests, with method names the same as these defined in the thrift file. As the code is self explanatory, I will paste it here:

#!/usr/bin/env python

import sys
sys.path.append('./gen-py')

from hello import UserManager
from hello.ttypes import *

from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer

users = []

class UserManagerHandler:
	def __init__(self):
		pass
		#self.log = {}

	def ping(self):
		print 'ping()'

	def add_user(self, user):
		if user.firstname == None:
			raise InvalidValueException(1,'no firstname exception')
		if user.lastname == None:
			raise InvalidValueException(2, 'no lastname exception')
		if user.user_id <= 0:
			raise InvalidValueException(3, 'wrong user_id')
		if user.sex != SexType.MALE and user.sex != SexType.FEMALE:
			raise InvalidValueException(4, 'wrong sex id')
		print 'Processing user '+user.firstname+' '+user.lastname
		users.append(user)
		print users
		return True

	def get_user(self, user_id):
		if user_id < 0:
			raise InvalidValueException(5, 'wrong id')
		return users[user_id]
		
	def clear_list(self):
		print 'Clearing list'
		print users
		del users [:]
		print users



handler = UserManagerHandler()
processor = UserManager.Processor(handler)
transport = TSocket.TServerSocket(port=9090)
tfactory = TTransport.TBufferedTransportFactory()
pfactory = TBinaryProtocol.TBinaryProtocolFactory()

server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)

# You could do one of these for a multithreaded server
#server = TServer.TThreadedServer(processor, transport, tfactory, pfactory)
#server = TServer.TThreadPoolServer(processor, transport, tfactory, pfactory)

print 'Starting the server...'
server.serve()
print 'done.'

As you see, this script stores users in the 'users' list. Provided methods allow manipulation of the contents of the list.

Line, that you should take into consideration is:

sys.path.append('./gen-py')

It is path to the "gen-py" directory, relative to the path from where the script would be run (so, in fact, it's best to put here absolute path).

You can run your script by typing:

./python_server.py

You may encounter following error:

ImportError: No module named Thrift

To fix this, you should go to the directory "lib/py" of thrift package and run command to install thrift module in to your python libraries:

sudo python setup.py install

Thanks for the tip on that to Kris Van den Bergh :)

Before creating client PHP script, prepare the structure of the files. First of all, choose directory that is accessible by your Apache (or any other) http server. Then, create "src" directory and put there all the files and directories from the "lib/php/src" directory in the thrift package. Create also "src/packages" directory and put there "hello" directory from the "gen-php" directory (the one generated by thrift). In the main directory put your PHP file (i.e. "hello.php"). In this script you will include Thrift libraries, include your auto-generated UserManager class file, establish connection with the server and perform exchange of the objects. As the contents of the file is self-explanatory to anyone with basic PHP knowledge, I paste it entirely here:

<?php 
$GLOBALS['THRIFT_ROOT'] = 'src';

require_once $GLOBALS['THRIFT_ROOT'].'/Thrift.php';
require_once $GLOBALS['THRIFT_ROOT'].'/protocol/TBinaryProtocol.php';
require_once $GLOBALS['THRIFT_ROOT'].'/transport/TSocket.php';
require_once $GLOBALS['THRIFT_ROOT'].'/transport/THttpClient.php';
require_once $GLOBALS['THRIFT_ROOT'].'/transport/TBufferedTransport.php';


require_once $GLOBALS['THRIFT_ROOT'].'/packages/hello/UserManager.php';
try {

	$socket = new TSocket('localhost', 9090);
	$transport = new TBufferedTransport($socket, 1024, 1024);
	$protocol = new TBinaryProtocol($transport);
	$client = new UserManagerClient($protocol);

	$transport->open();
	$client->ping();
	$u = new hello_User();
	$u->user_id = 1;
	$u->firstname = 'John';
	$u->lastname = 'Smith';
	$u->sex = hello_SexType::MALE;
	if ($client->add_user($u))
	{
		echo 'user added succesfully</br>';
	}
	
	var_dump($client->get_user(0));
	
	$client->clear_list();
	
	
	
	$u2 = new hello_User();
	$client->add_user($u2);
	
} catch (hello_InvalidValueException $e) {
	echo $e->error_msg.'<br/>';
}


?>

That's it! Now run your server (if it's not running already) and open yours PHP file's address in the web browser. You should get result like this:

user added succesfully
object(hello_User)[7]
  public 'firstname' => string 'John' (length=3)
  public 'lastname' => string 'Smith' (length=8)
  public 'user_id' => int 1
  public 'active' => boolean true
  public 'sex' => int 1
  public 'description' => null
no firstname exception 

(var_dump's result may be different, if you don't have xdebug installed)

As you analyze contents of the PHP script, you will notice, that first user was added succesfully, while on the second try with different data, exception was thrown (and caught).

In the console, where you ran the server script you will see:

home-debian:~/www/thrift-test/server# ./python_server.py
Starting the server...
ping()
Processing user John Smith
[User(user_id=1, description=None, firstname='John', lastname='Smith', sex=1, active=True)]
Clearing list
[User(user_id=1, description=None, firstname='John', lastname='Smith', sex=1, active=True)]
[]

Congratulations! You ran your first Thrift-enabled application!

I will be glad to hear from you about how do you like my tutorial. Please, leave a comment here.


Copyrights information

This document is published under the Creative Commons Attribute-Share Alike 3.0 license. Creative Commons License

This website template was prepaded by Studio7designs and is shared under the Creative Commons Attribution 3.0 Unported license.

About the author

Krzysztof Rakowski is living in Warsaw, Poland. He works as a Senior PHP Programmer at K2 Internet SA.

You may contact author by writing to the email krzysztof [at] rakowski.pl