From 07a3aab207242ef4ee8a45825e2fbab2bbf57510 Mon Sep 17 00:00:00 2001 From: Mark Slee Date: Wed, 7 Mar 2007 05:45:10 +0000 Subject: [PATCH] Thrift Tutorial git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@665051 13f79535-47bb-0310-9956-ffa450edef68 --- tutorial/README | 26 +++++++ tutorial/cpp/CppClient.cpp | 65 ++++++++++++++++++ tutorial/cpp/CppServer.cpp | 133 ++++++++++++++++++++++++++++++++++++ tutorial/cpp/Makefile | 16 +++++ tutorial/php/PhpClient.php | 64 +++++++++++++++++ tutorial/py/PythonClient.py | 58 ++++++++++++++++ tutorial/py/PythonServer.py | 77 +++++++++++++++++++++ tutorial/rb/RubyClient.rb | 43 ++++++++++++ tutorial/rb/RubyServer.rb | 78 +++++++++++++++++++++ tutorial/shared.thrift | 18 +++++ tutorial/tutorial.thrift | 127 ++++++++++++++++++++++++++++++++++ 11 files changed, 705 insertions(+) create mode 100644 tutorial/README create mode 100644 tutorial/cpp/CppClient.cpp create mode 100644 tutorial/cpp/CppServer.cpp create mode 100644 tutorial/cpp/Makefile create mode 100755 tutorial/php/PhpClient.php create mode 100755 tutorial/py/PythonClient.py create mode 100755 tutorial/py/PythonServer.py create mode 100755 tutorial/rb/RubyClient.rb create mode 100755 tutorial/rb/RubyServer.rb create mode 100755 tutorial/shared.thrift create mode 100644 tutorial/tutorial.thrift diff --git a/tutorial/README b/tutorial/README new file mode 100644 index 000000000..8ce657434 --- /dev/null +++ b/tutorial/README @@ -0,0 +1,26 @@ +Thrift Tutorial + +Author: Mark Slee (mcslee@facebook.com) + +Thrift is distributed under the Thrift open source software license. +Please see the included LICENSE file. + +Tutorial +======== + +1) First things first, you'll need to install the Thrift compiler and the + language libraries. Do that using the instructions in the top level + README file. + +2) Read tutorial.thrift to learn about the syntax of a Thrift file + +3) Run tutorial.thrift to compile the code: + + ./tutorial.thrift + +4) Take a look at the generated code. + +5) Look in the language directories for sample client/server code. + +6) That's about it for now. This tutorial is intentionally brief. It should be + just enough to get you started and ready to build your own project. diff --git a/tutorial/cpp/CppClient.cpp b/tutorial/cpp/CppClient.cpp new file mode 100644 index 000000000..b97116062 --- /dev/null +++ b/tutorial/cpp/CppClient.cpp @@ -0,0 +1,65 @@ +#include +#include +#include + +#include +#include +#include + +#include "../gen-cpp/Calculator.h" + +using namespace std; +using namespace facebook::thrift; +using namespace facebook::thrift::protocol; +using namespace facebook::thrift::transport; + +using namespace tutorial; +using namespace shared; + +using namespace boost; + +int main(int argc, char** argv) { + shared_ptr socket(new TSocket("localhost", 9090)); + shared_ptr transport(new TBufferedTransport(socket)); + shared_ptr protocol(new TBinaryProtocol(transport)); + CalculatorClient client(protocol); + + try { + transport->open(); + + client.ping(); + printf("ping()\n"); + + int32_t sum = client.add(1,1); + printf("1+1=%d\n", sum); + + Work work; + work.op = DIVIDE; + work.num1 = 1; + work.num2 = 0; + + try { + int32_t quotient = client.calculate(1, work); + printf("Whoa? We can divide by zero!\n"); + } catch (InvalidOperation &io) { + printf("InvalidOperation: %s\n", io.why.c_str()); + } + + work.op = SUBTRACT; + work.num1 = 15; + work.num2 = 10; + int32_t diff = client.calculate(1, work); + printf("15-10=%d\n", diff); + + // Note that C++ uses return by reference for complex types to avoid + // costly copy construction + SharedStruct ss; + client.getStruct(ss, 1); + printf("Check log: %s\n", ss.value.c_str()); + + transport->close(); + } catch (TException &tx) { + printf("ERROR: %s\n", tx.what()); + } + +} diff --git a/tutorial/cpp/CppServer.cpp b/tutorial/cpp/CppServer.cpp new file mode 100644 index 000000000..4b7fcaf17 --- /dev/null +++ b/tutorial/cpp/CppServer.cpp @@ -0,0 +1,133 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../gen-cpp/Calculator.h" + +using namespace std; +using namespace facebook::thrift; +using namespace facebook::thrift::protocol; +using namespace facebook::thrift::transport; +using namespace facebook::thrift::server; + +using namespace tutorial; +using namespace shared; + +using namespace boost; + +class CalculatorHandler : public CalculatorIf { + public: + CalculatorHandler() {} + + void ping() { + printf("ping()\n"); + } + + int32_t add(const int32_t n1, const int32_t n2) { + printf("add(%d,%d)\n", n1, n2); + return n1 + n2; + } + + int32_t calculate(const int32_t logid, const Work &work) { + printf("calculate(%d,{%d,%d,%d})\n", logid, work.op, work.num1, work.num2); + int32_t val; + + switch (work.op) { + case ADD: + val = work.num1 + work.num2; + break; + case SUBTRACT: + val = work.num1 - work.num2; + break; + case MULTIPLY: + val = work.num1 * work.num2; + break; + case DIVIDE: + if (work.num2 == 0) { + InvalidOperation io; + io.what = work.op; + io.why = "Cannot divide by 0"; + throw io; + } + val = work.num1 / work.num2; + break; + default: + InvalidOperation io; + io.what = work.op; + io.why = "Invalid Operation"; + throw io; + } + + SharedStruct ss; + ss.key = logid; + ss.value = "5"; + + log[logid] = ss; + + return val; + } + + void getStruct(SharedStruct &ret, const int32_t logid) { + printf("getStruct(%d)\n", logid); + ret = log[logid]; + } + + void zip() { + printf("zip()\n"); + } + +protected: + map log; + +}; + +int main(int argc, char **argv) { + + shared_ptr protocolFactory(new TBinaryProtocolFactory()); + shared_ptr handler(new CalculatorHandler()); + shared_ptr processor(new CalculatorProcessor(handler)); + shared_ptr serverTransport(new TServerSocket(9090)); + shared_ptr transportFactory(new TBufferedTransportFactory()); + + TSimpleServer server(processor, + serverTransport, + transportFactory, + protocolFactory); + + + /** + * Or you could do one of these + + shared_ptr threadManager = + ThreadManager::newSimpleThreadManager(workerCount); + shared_ptr threadFactory = + shared_ptr(new PosixThreadFactory()); + threadManager->threadFactory(threadFactory); + threadManager->start(); + TThreadPoolServer server(processor, + serverTransport, + transportFactory, + protocolFactory, + threadManager); + + TThreadedServer server(processor, + serverTransport, + transportFactory, + protocolFactory); + + */ + + printf("Starting the server...\n"); + server.serve(); + printf("done.\n"); + return 0; +} diff --git a/tutorial/cpp/Makefile b/tutorial/cpp/Makefile new file mode 100644 index 000000000..74520f096 --- /dev/null +++ b/tutorial/cpp/Makefile @@ -0,0 +1,16 @@ +BOOST_DIR = /usr/local/boost/include/boost-1_33_1/ +THRIFT_DIR = /usr/local/include/thrift +LIB_DIR = /usr/local/lib + +GEN_SRC = ../gen-cpp/SharedService.cpp ../gen-cpp/shared_types.cpp ../gen-cpp/tutorial_types.cpp ../gen-cpp/Calculator.cpp + +default: server client + +server: CppServer.cpp + g++ -o CppServer -I${THRIFT_DIR} -I${BOOST_DIR} -I../gen-cpp -L${LIB_DIR} -lthrift CppServer.cpp ${GEN_SRC} + +client: CppClient.cpp + g++ -o CppClient -I${THRIFT_DIR} -I${BOOST_DIR} -I../gen-cpp -L${LIB_DIR} -lthrift CppClient.cpp ${GEN_SRC} + +clean: + rm -fr CppClient CppServer diff --git a/tutorial/php/PhpClient.php b/tutorial/php/PhpClient.php new file mode 100755 index 000000000..ca4f50d3b --- /dev/null +++ b/tutorial/php/PhpClient.php @@ -0,0 +1,64 @@ +#!/usr/bin/env php +open(); + +$client->ping(); +print "ping()\n"; + +$sum = $client->add(1,1); +print "1+1=$sum\n"; + +$work = new tutorial_Work(); + +$work->op = tutorial_Operation::DIVIDE; +$work->num1 = 1; +$work->num2 = 0; + +try { + $client->calculate(1, $work); + print "Whoa! We can divide by zero?\n"; +} catch (tutorial_InvalidOperation $io) { + print "InvalidOperation: $io->why\n"; +} + +$work->op = tutorial_Operation::SUBTRACT; +$work->num1 = 15; +$work->num2 = 10; +$diff = $client->calculate(1, $work); +print "15-10=$diff\n"; + +$log = $client->getStruct(1); +print "Log: $log->value\n"; + +$transport->close(); + +?> diff --git a/tutorial/py/PythonClient.py b/tutorial/py/PythonClient.py new file mode 100755 index 000000000..96ca03a91 --- /dev/null +++ b/tutorial/py/PythonClient.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python + +import sys +sys.path.append('../gen-py') + +from tutorial import Calculator +from tutorial.ttypes import * + +from thrift.transport import TSocket +from thrift.transport import TTransport +from thrift.protocol import TBinaryProtocol + +# Make socket +transport = TSocket.TSocket('localhost', 9090) + +# Buffering is critical. Raw sockets are very slow +transport = TTransport.TBufferedTransport(transport) + +# Wrap in a protocol +protocol = TBinaryProtocol.TBinaryProtocol(transport) + +# Create a client to use the protocol encoder +client = Calculator.Client(protocol) + +# Connect! +transport.open() + +client.ping() +print 'ping()' + +sum = client.add(1,1) +print '1+1=%d' % (sum) + +work = Work() + +work.op = Operation.DIVIDE +work.num1 = 1 +work.num2 = 0 + +try: + quotient = client.calculate(1, work) + print 'Whoa? You know how to divide by zero?' +except InvalidOperation, io: + print 'InvalidOperation: %s' % (io.__str__()) + +work.op = Operation.SUBTRACT +work.num1 = 15 +work.num2 = 10 + +diff = client.calculate(1, work) +print '15-10=%d' % (diff) + +log = client.getStruct(1) +print 'Check log: %s' % (log.value) + +# Close! +transport.close() + diff --git a/tutorial/py/PythonServer.py b/tutorial/py/PythonServer.py new file mode 100755 index 000000000..3324bbd55 --- /dev/null +++ b/tutorial/py/PythonServer.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +import sys +sys.path.append('../gen-py') + +from tutorial import Calculator +from tutorial.ttypes import * + +from shared.ttypes import SharedStruct + +from thrift.transport import TSocket +from thrift.transport import TTransport +from thrift.protocol import TBinaryProtocol +from thrift.server import TServer + +class CalculatorHandler: + def __init__(self): + self.log = {} + + def ping(self): + print 'ping()' + + def add(self, n1, n2): + print 'add(%d,%d)' % (n1, n2) + return n1+n2 + + def calculate(self, logid, work): + print 'calculate(%d, %s)' % (logid, work.__str__()) + + if work.op == Operation.ADD: + val = work.num1 + work.num2 + elif work.op == Operation.SUBTRACT: + val = work.num1 - work.num2 + elif work.op == Operation.MULTIPLY: + val = work.num1 * work.num2 + elif work.op == Operation.DIVIDE: + if work.num2 == 0: + x = InvalidOperation() + x.what = work.op + x.why = 'Cannot divide by 0' + raise x + val = work.num1 / work.num2 + else: + x = InvalidOperation() + x.what = work.op + x.why = 'Invalid operation' + raise x + + log = SharedStruct() + log.key = logid + log.value = '%d' % (val) + self.log[logid] = log + + return val + + def getStruct(self, key): + print 'getStruct(%d)' % (key) + return self.log[key] + + def zip(self): + print 'zip()' + +handler = CalculatorHandler() +processor = Calculator.Processor(handler) +transport = TSocket.TServerSocket(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.' diff --git a/tutorial/rb/RubyClient.rb b/tutorial/rb/RubyClient.rb new file mode 100755 index 000000000..e0b47b635 --- /dev/null +++ b/tutorial/rb/RubyClient.rb @@ -0,0 +1,43 @@ +#!/usr/bin/env ruby + +$:.push('../gen-rb') + +require 'thrift/transport/tsocket' +require 'thrift/protocol/tbinaryprotocol' + +require 'Calculator' + +transport = TSocket.new('localhost', 9090) +protocol = TBinaryProtocol.new(transport) +client = Calculator::Client.new(protocol) + +transport.open() + +client.ping() +print "ping()\n" + +sum = client.add(1,1) +print "1+1=", sum, "\n" + +work = Work.new() + +begin + work.op = Operation::DIVIDE + work.num1 = 1 + work.num2 = 0 + quot = client.calculate(1, work) + puts "Whoa, we can divide by 0 now?" +rescue InvalidOperation => io + print "InvalidOperation: ", io.why, "\n" +end + +work.op = Operation::SUBTRACT +work.num1 = 15 +work.num2 = 10 +diff = client.calculate(1, work) +print "15-10=", diff, "\n" + +log = client.getStruct(1) +print "Log: ", log.value, "\n" + +transport.close() diff --git a/tutorial/rb/RubyServer.rb b/tutorial/rb/RubyServer.rb new file mode 100755 index 000000000..2e9145d7f --- /dev/null +++ b/tutorial/rb/RubyServer.rb @@ -0,0 +1,78 @@ +#!/usr/bin/env ruby + +$:.push('../gen-rb') + +require 'thrift/transport/tsocket' +require 'thrift/protocol/tbinaryprotocol' +require 'thrift/server/tserver' + +require 'Calculator' +require 'shared_types' + +class CalculatorHandler + include Calculator::Iface + + def initialize() + @log = {} + end + + def ping() + puts "ping()" + end + + def add(n1, n2) + print "add(", n1, ",", n2, ")\n" + return n1 + n2 + end + + def calculate(logid, work) + print "calculate(", logid, ", {", work.op, ",", work.num1, ",", work.num2,"})\n" + if work.op == Operation::ADD + val = work.num1 + work.num2 + elsif work.op == Operation::SUBTRACT + val = work.num1 - work.num2 + elsif work.op == Operation::MULTIPLY + val = work.num1 * work.num2 + elsif work.op == Operation::DIVIDE + if work.num2 == 0 + x = InvalidOperation.new() + x.what = work.op + x.why = "Cannot divide by 0" + raise x + end + val = work.num1 / work.num2 + else + x = InvalidOperation() + x.what = work.op + x.why = "Invalid operation" + raise x + end + + entry = SharedStruct.new() + entry.key = logid + entry.value = "#{val}" + @log[logid] = entry + + return val + + end + + def getStruct(key) + print "getStruct(", key, ")\n" + return @log[key] + end + + def zip() + print "zip\n" + end + +end + +handler = CalculatorHandler.new() +processor = Calculator::Processor.new(handler) +transport = TServerSocket.new(9090) +server = TSimpleServer.new(processor, transport) + +puts "Starting the server..." +server.serve() +puts "done." diff --git a/tutorial/shared.thrift b/tutorial/shared.thrift new file mode 100755 index 000000000..9b77249df --- /dev/null +++ b/tutorial/shared.thrift @@ -0,0 +1,18 @@ +#!/usr/local/bin/thrift -cpp -java -py -php -xsd + +/** + * This Thrift file can be included by other Thrift files that want to share + * these definitions. + */ + +cpp_namespace shared +java_package shared + +struct SharedStruct { + 1: i32 key + 2: string value +} + +service SharedService { + SharedStruct getStruct(1: i32 key) +} diff --git a/tutorial/tutorial.thrift b/tutorial/tutorial.thrift new file mode 100644 index 000000000..0063c091a --- /dev/null +++ b/tutorial/tutorial.thrift @@ -0,0 +1,127 @@ +#!/usr/local/bin/thrift -cpp -java -py -php -rb -xsd -r +# +# Thrift Tutorial +# Mark Slee (mcslee@facebook.com) +# +# This file aims to teach you how to use Thrift, in a .thrift file. Neato. The +# first thing to notice is that .thrift files support standard shell comments. +# This lets you make your thrift file executable and include your Thrift build +# step on the top line. And you can place comments like this anywhere you like. +# +# Before running this file, you will need to have installed the thrift compiler +# into /usr/local/bin. + +/** + * The first thing to know about are types. The available types in Thrift are: + * + * bool Boolean, one byte + * byte Signed byte + * i16 Signed 16-bit integer + * i32 Signed 32-bit integer + * i64 Signed 64-bit integer + * double 64-bit floating point value + * string String + * map Map from one type to another + * list Ordered list of one type + * set Set of unique elements of one type + * + * Did you also notice that Thrift supports C style comments? + */ + +// Just in case you were wondering... yes. We support simple C comments too. + +/** + * Thrift files can reference other Thrift files to include common struct + * and service definitions. These are found using the current path, or by + * searching relative to any paths specified with the -I compiler flag. + * + * Included objects are accessed using the name of the .thrift file as a + * prefix. i.e. shared.SharedObject + */ +include "shared.thrift" + +/** + * Thrift files can namespace, package, or prefix their output in various + * target languages. + */ +cpp_namespace tutorial +java_package tutorial +php_namespace tutorial + +/** + * Thrift lets you do typedefs to get pretty names for your types. Standard + * C style here. + */ +typedef i32 MyInteger + +/** + * Thrift also lets you define constants for use across languages. Complex + * types and structs are specified using JSON notation. + */ +const i32 INT32CONSTANT = 9853 +const map MAPCONSTANT = {'hello':'world', 'goodnight':'moon'} + +/** + * You can define enums, which are just 32 bit integers. Values are optional + * and start at 1 if not supplied, C style again. + */ +enum Operation { + ADD = 1, + SUBTRACT = 2, + MULTIPLY = 3, + DIVIDE = 4 +} + +/** + * 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 Work { + 1: i32 num1 = 0, + 2: i32 num2, + 3: Operation op +} + +/** + * Structs can also be exceptions, if they are nasty. + */ +exception InvalidOperation { + 1: i32 what, + 2: string why +} + +/** + * Ahh, now onto the cool part, defining a service. Services just need a name + * and can optionally inherit from another service using the extends keyword. + */ +service Calculator extends shared.SharedService { + + /** + * 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. + */ + + void ping(), + + i32 add(1:i32 num1, 2:i32 num2), + + i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch), + + /** + * This method has an async modifier. That means the client only makes + * a request and does not listen for any response at all. Async methods + * must be void. + */ + async void zip() + +} + +/** + * That just about covers the basics. Take a look in the test/ folder for more + * detailed examples. After you run this file, your generated code shows up + * in folders with names gen-. The generated code isn't too scary + * to look at. It even has pretty indentation. + */