Thursday, August 26, 2010

Thrift "Hello World!" using PHP

Apache Thrift lets programs talk amongst themselves. No longer must they be isolated or bigoted towards other languages, nor need they use a cumbersome or limited protocol like HTTP or Memcached. Thrift provides a common ground where php web servers can send information to Java collectors, and Python scripts can request feedback from C++ servers.

This post is part of a series exploring Thrift using PHP, with the eventual goal of incorporating Twitter's FlockDB into a PHP Web front end. Today, I present the simplest use of Thrift possible, aptly named, "Hello World." It depends on your first having installed Thrift. I have tested the following code using Thrift 0.2.0 on a Snow Leopard iMac, and made it available for download from Github.

Thrift file


thrift/helloworld.thrift

  1. service HelloWorld {
  2. oneway void hello(1:string juicy)
  3. }

One service, one method taking a string as its single argument. The oneway modifier means the client will not wait for a response after sending the hello request. A natural consequence of this is the return type must be void for oneway methods. Generate php source from the thrift file:

thrift --gen php:server thrift/helloworld.thrift

Two freshly minted php files should have been deposited in gen-php/helloworld: HelloWorld.php and helloworld_types.php. We haven't defined any types, so can ignore helloworld_types.php. The scaffolding of our new service is all in HelloWorld.php. Looking through the source, some things to notice:

  1. include_once $GLOBALS['THRIFT_ROOT'].'/Thrift.php';
  2. include_once $GLOBALS['THRIFT_ROOT'].'/packages/helloworld/helloworld_types.php';

This version of Thrift maintains a liberal reliance on the global THRIFT_ROOT. When we make the actual client and server for HelloWorld, we will have to set the THRIFT_ROOT global. For convenience, I put the whole thrift-0.2.0 directory in /usr/local/thrift-0.2.0, so my THRIFT_ROOT will be /usr/local/thrift-0.2.0/lib/php/src.

Notice the location of helloworld_types.php. The files I just generated have to be placed in the packages directory:

  1. mkdir /usr/local/thrift-0.2.0/lib/php/src/packages
  2. cp -r gen-php/helloworld/ /usr/local/thrift-0.2.0/lib/php/src/packages/helloworld/

The remainder of HelloWorld.php contains type definitions for our HelloWorld service. The interface HelloWorldIf is used by both the client and the server when communicating to the other. The client code, HelloWorldClient, has been completely generated! There is also a processor the server uses to deal with requests as they come in from a client, HelloWorldProcessor. NOTE: If you did not use the 'server' sub-option when generating the php files, the processor will not have been generated. People construct servers using php so infrequently, this is probably a good corner to cut. However, in our example the processor is required if only to avoid getting tri-lingual.

PHP Client


php/stream-client.php

  1. <?php
  2. $GLOBALS['THRIFT_ROOT'] = '/usr/local/thrift-0.2.0/lib/php/src';
  3. require_once $GLOBALS['THRIFT_ROOT'].'/Thrift.php';
  4. require_once $GLOBALS['THRIFT_ROOT'].'/protocol/TBinaryProtocol.php';
  5. require_once $GLOBALS['THRIFT_ROOT'].'/transport/TPhpStream.php';
  6. require_once $GLOBALS['THRIFT_ROOT'].'/packages/helloworld/HelloWorld.php';
  7. $stdout = new TPhpStream(TPhpStream::MODE_W);
  8. $protocol = new TBinaryProtocol($stdout);
  9. $client = new HelloWorldClient($protocol);
  10. $stdout->open();
  11. $send = implode(' ', array_slice($_SERVER['argv'], 1));
  12. $client->hello($send);

Adjust your THRIFT_ROOT for where you have deposited the Thrift download. Usually you would make a client send information through a socket, and that is very possible. But that requires we have a server listening on that socket, and you have to get a little clever to do that in php. So I propose side stepping the issue and creating a client that communicates with STDOUT ($stdout). The binary protocol comes standard with Thrift, use it ($protocol. And we create the HelloWorldClient around the selected stream/protocol pair ($client). Before using the client, the stream (or socket) must be opened. And the string sent to the server will be all the command line arguments. Try it out:

> php php/stream-client.php 'Hello Thrift!!!'
?hello
      Hello Thrift!!! > 

That's pretty ugly, but at least we can see that the raw binary protocol is working.

PHP Server


php/stream-server.php

  1. <?php
  2. $GLOBALS['THRIFT_ROOT'] = '/usr/local/thrift-0.2.0/lib/php/src';
  3. require_once $GLOBALS['THRIFT_ROOT'].'/Thrift.php';
  4. require_once $GLOBALS['THRIFT_ROOT'].'/protocol/TBinaryProtocol.php';
  5. require_once $GLOBALS['THRIFT_ROOT'].'/transport/TPhpStream.php';
  6. require_once $GLOBALS['THRIFT_ROOT'].'/transport/TBufferedTransport.php';
  7. require_once $GLOBALS['THRIFT_ROOT'].'/packages/helloworld/HelloWorld.php';
  8. class HelloWorldHandler implements HelloWorldIf {
  9. public function hello($juicy) {
  10. $time = time();
  11. echo "$time\t$juicy\n";
  12. }
  13. }
  14. $handler = new HelloWorldHandler();
  15. $processor = new HelloWorldProcessor($handler);
  16. $transport = new TBufferedTransport(new TPhpStream(TPhpStream::MODE_R | TPhpStream::MODE_W));
  17. $protocol = new TBinaryProtocol($transport, true, true);
  18. $transport->open();
  19. $processor->process($protocol, $protocol);
  20. $transport->close();

Adjust the THRIFT_ROOT as needed. On the server side we have to actually implement the HelloWorld service. HelloWorldHandler::hello takes the string given, writing it and a timestamp to STDOUT. The rest of this simple example creates a server reading from STDIN and writing to STDOUT. To test it, we can pipe the output from the stream-client to the stream-server:

> php php/stream-client.php 'Hello World!!!' | php php/stream-server.php
1282863497   Hello World!!!

Better! It has begun. We can create a dirt simple client and server using php and the standard streams. Next you might like to try out the slightly more advanced tutorials included with the Thrift download (thrift-0.2.0/tutorial).

Next post: analysis of Flockdb.thrift

1 comment:

  1. Hi Christopher,

    Are you used the TMemoryBuffer class??. I don´t understand the use of this class and I apreciate if you can explain tome. Thanks in advance.

    ReplyDelete