Apache Thrift

Материал из Викиучебника — открытых книг для открытого мира

Thrift (от англ. бережливость, произносится как θrɪft) — язык описания интерфейсов, который используется для определения и создания служб под разные языки программирования. Является фреймворком к удаленному вызову процедур (RPC).

Подробный пример[править]

Напишем простое приложение на Apache Thrift. Мы будем определять объект User. Этот объект будет создан в PHP и располагаться в списке языка Python. Можно было бы также получить объект из Python и передать его в PHP или очистить список. Хотя этот пример очень примитивный, это даст вам некоторое представление о том, как работает Thrift Apache. Для начала мы должны определить методы и объекты, которые будут взаимодействовать с обоими приложениями. Эти определения мы поместим в файл с именем "hello.thrift". У этого файла C-подобный синтаксис, со специфическими модификаторами Thrift’a, как будет показано позже.

Thrift файл может содержать другие файлы. В нашем примере мы не будем вкладывать никакие другие файлы. Однако в официальном руководстве проекта вы можете заметить, что требуется файл "shared.thrift". Этот файл объявляет класс, от которого наследуется основной класс Calculator.

Для начала давайте зададим пространство имен.

namespace php hello

Пространства имен определяются для каждого языка — в данном примере, только объекты в файле PHP будут с префиксом «hello_».

Объект User содержит поля firstname и lastname строкового типа, user_id типа integer, active типа boolean и sex нашего собственного типа перечисления (enum). Описание такого объекта является очень простым. Для начала опишем наш собственный тип enum.

enum SexType {
  MALE = 1,
  FEMALE = 2
}

После чего опишем объект User.

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

Как вы, наверное, уже заметили, мы пронумеровали параметры, поскольку того требует синтаксис Thrift. По умолчанию все параметры являются обязательными, но вы можете сделать их необязательными (с помощью ключевого слова optional), а также установить их значения по умолчанию. Нам так же надо объявить исключение. Оно будет вызываться, когда передается неправильной параметр (в нашем примере это может произойти, если, например, user_id будет отрицательным).

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

Теперь пришло время объявить службу. Службы — это основная вещь, проводящая сериализацию объектов для их доступа в различных программах. Объявим нашу простую службу с помощью ключевого слова service

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

При объявлении метода правила нумерации параметров сохраняются. Как вы можете видеть, объявление метода очень похоже на C.

Небольшое описание методов[править]

  • ping — простой ping-pong метод. Если мы отправляем сообщение "are you there?" и получаем ответ "yes, I am here", то, конечно же, все идет правильно.
  • add_user — отправляем объект user (это объект, поэтому он должен быть создан в PHP-приложении). В ответ мы получаем user_id. Если что-то не так, нам возвращается ошибка.
  • get_user — то же, что и выше, но здесь мы отправляем user_id и получаем объект user
  • clear_list — эта функция объявлена ​​как односторонняя (oneway) — это значит, запрос будет отправлен, но наше PHP-приложение не будет ждать результата (так как там может не быть ни одного). Очевидно, что значения такой функции должны считаться недействительными.

Генерируем файлы[править]

Теперь мы можем сгенерировать PHP и Python файлы, которые будут использоваться на клиентской стороне и на сервере, соответственно. Это довольно просто:

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

В директориях gen-php и gen-py мы получили PHP и Python файлы, соответственно. Теперь давайте создадим сервер. Создаем файл, назовем его, например, python_server.py. Не забудьте сделать его исполняемым, с помощью команды:

chmod +x ./python_server.py

В этот файл мы запихнем все необходимые библиотеки, определим класс для обработки запросов с такими же именами методов, как и определенный в thrift-файлах. Так как код говорит сам за себя, приведем его ниже.

#!/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.'

Как вы видите, этот скрипт сохраняет пользователей в список users. Методы позволяют проводить нам некоторые манипуляции с содержимым списка.

Следующую строку обязательно надо принять во внимание:

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

Это путь к директории gen-py, относительно того места, откуда будет запущен скрипт (по сути, лучшим решением является указать здесь абсолютный путь).
Вы можете запустить ваш скрипт командой:

./python_server.py


Однако, вы можете столкнуться со следующей ошибкой: ImportError: No module named Thrift
Чтобы это исправить, вам надо перейти в директорию lib/py и запустить команду установки thrift модуля в вашу python библиотеку:

sudo python setup.py install


Перед тем, как создать PHP скрипт подготовим структуру файлов. Для начала выберем директорию, которая доступна из вашего Apache или любого другого http-сервера. Затем создадим директорию src и поместим туда все файлы и директории из директории lib/php/src в пакете Thrift. Создадим так же директорию src/packages и поместим туда директорию hello из директории gen-php (созданную Thrift’ом). В корневую директорию поместите ваш PHP-файл (то есть «hello.php»). В этот скрипт необходимо вставить Thrift-библиотеки, ваш автоматически сгенерированный файл с классом UserManager, установить соединения с сервером и выполнить обмен объектами.

<?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/>';
}

?>


Теперь запустите свой сервер (если, конечно, вы его еще не запустили) и откройте ваш PHP-файл в браузере. Вы должны получить следующий результат:

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'а может отличаться, если у вас не установлен xdebug

Когда вы будете анализировать содержимое сценария PHP, вы заметите, что первый пользователь был добавлен успешно, а на второй попытке с различными данными будет поймано исключение (exception). В консоли это будет выглядеть так:

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)]
[]

Источники[править]