
Статья освещает реализацию бинарного web-протокола Hessian на стеке библиотек Qt. Целевая платформа С++.
Hessian – это бинарый web-протокол, предназначенный для создания полезных web-сервисов (проще говоря, эта штука позволит вам узнать какая сейчас погода за окном из любого вашего приложения). Hessian был разработан в Caucho Technology, Inc. Компания также разработала реализации протокола для Java, Python и ActionScript. Сторонними компаниями разработаны реализации практически для всех языков программирования (C++, C#, Perl, PHP, Ruby, Objective-C, D, Erlang).
Представим, будто у нас есть корзина с яблоками на сервере. И мы хотим по запросу клиента выдавать ему яблоко. Именно в этом нам поможет hessian.
На сервере мы создаем интерфейс сервиса (сервер на Java):
public interface FruitService {
Apple getNextApple();
}
* This source code was highlighted with Source Code Highlighter.
Похожий класс создаем на клиенте (C++):class FruitService {
public:
Apple getNextApple();
}
* This source code was highlighted with Source Code Highlighter.
При вызове метода getNextApple() на клиенте реализация hessian выполняет запрос на сервер (к аналогичному методу сервера) и возвращает результат клиенту. Hessian берет на себя обязанность доставить нам яблоки через сеть. Фактически, он выполняет сериализацию данных на сервере и десериализацию их на клиенте. Превращение яблока в массив байт, передача по сети и восстановление яблока из массива – вот задача hessian. Остальное – ваша задача.Qt
Qt как фреймворк широко известен и вряд ли нуждается в представлении. Отмечу лишь то, что речь пойдет о реализации Hessian-протокола для Qt С++.
Вообще существует несколько реализаций (мне удалось найти 2) протокола под С++:Hessian cpp и hessianorb – замечательные проекты и они вполне функциональны. Изначально для своих разработок я использовал hessianorb – он поддерживает кодогенерацию, т.е. позволяет вам одной командой (после двухчасовой подготовки) создать C++ прокси для всех ваших сервисов.
К сожалению, обе реализации заставляют создавать код, который смотрится инородно в Qt-среде. Они требуют дополнительный стек библиотек: hessian cpp – SSLPP, hessianorb – CURL. Сей факт, понятное дело, усложняет кроcс-платформенную разработку. Очевидно, эти библиотеки нужны для сетевой передачи данных, но в Qt есть свой слой для работы с сетью и мне хотелось его использовать. Помимо этого, обе реализации блокируют поток выполнения при взаимодействии с сервером, в то время как сетевой слой Qt асинхронен.
Таким образом, моей целью было создание реализации протокола, базирующейся на использовании QNetworkAccessManager и его асинхронной природы.
QHessian
QHessian (далее qh) – реализация протокола hessian, не использующая сторонних библиотек. Другими словами, для работы с данной реализацией достаточно стека библиотек Qt.
Как я уже упоминал ранее, задачей hessian является сериализация, передача по сети и десериализация данных. Передачу по сети полностью берет на себя QNetworkAccessManager. Сериализация и десериализация данных осуществляется согласно документу Hessian 2.0 Serialization Protocol. В документе указано, что реализация протокола должна уметь:
- read/write raw binary data
- read/write boolean
- read/write 64-bit millisecond date
- read/write 64-bit double
- read/write 32-bit int
- read/write 64-bit long
- read/write null
- read/write UTF8-encoded string
- read/write lists and arrays
- read/write maps and dictionaries
- read/write objects
- read/write ref for shared and circular object references
- read/write object/list reference map
- read/write class definition reference map
- read/write type (class name) reference map
- Null чтение/запись null-значений
- Boolean чтение/запись bool
- Integer чтение/запись 32-битного числа (qint32)
- Long чтение/запись 64-битного числа (qint64)
- Double чтение/запись 64-битного IEEE 754 не целого числа (qreal)
- String чтение/запись UTF-8 строки (QString)
- DateTime чтение/запись даты (QDateTime)
- Binary чтение/запись массива байт (QByteArray)
- BeginCollection чтение/зпаись начала коллекции
- EndCollection чтение/зпаись конца коллекции
- BeginMap чтение/запись начала map
- HasMoreMap только чтение — проверка конца map
- EndMap чтение/запись конца map
- BeginObject чтение/запись начала объекта
- EndObject чтение/запись конца объекта
- Ref чтение/запись ссылки на объект
Например, для вызова метода сервера sample(Integer, String, Date) потребуется выполнить код:
{
using namespace QHessian::in;
QHessian::QHessianMethodCall call("sample");
call << Long(55) << String(“Василий”) << DateTime(QDateTime::currentDateTime ());
call.invoke(networkManager,
QUrl(“http://serviceUrl”), // адрес сервиса
myQObject, // QObject, который будет обрабаывать ответ
SLOT(reply()), // слот для обработки ответа
SLOT(error(int, const QString&))); // слот для обработки ошибок
}
* This source code was highlighted with Source Code Highlighter.
Этот код вызовет метод sample с параметрами 55, Василий, текущее время. Ответ сервера будет обработан в слоте reply() объекта myQObject, либо, если произойдет ошибка, – в слоте error объекта myQObject. Напомню, что вызов является не блокирующим, т.е. не надо париться с многопоточностью.Представим, что наш сервис просто возвращает переданные ему данные в объекте com.googlecode.AnswerObject. Тогда обработка ответа в слоте reply будет иметь вид:
void MyQObject::reply() {
using namespace QHessian::out;
qint32 long;
QString string;
QDateTime dateTime;
QHessian::QHessianReturnParser& parser =
*(QHessian::QHessianReturnParser*) QObject::sender();
parser >> BeginObject(“com.googlecode.AnswerObject”)
>> Long(long)
>> String(String)
>> DateTime(dateTime)
>> EndObject();
parser.deleteLater();
}
* This source code was highlighted with Source Code Highlighter.
После выполнения, long примет значение 55, string — Василий, dateTime — переданное время.QHessian поддерживает коллекции (массивы, списки, ассоциативные массивы и пр.).
Вот таким могло бы быть чтение класса Polygon из ответа сервера:
{
using namespace QHessian::out;
QHessian::QHessianReturnParser& parser =
*(QHessian::QHessianReturnParser*) QObject::sender();
qint32 pointCount;
parser >> BeginObject("Polygon");
parser >> BeginCollection(“points”, pointCount);
for (int i=0; i<pointCount; ++i) {
qint32 x, y;
parser >> Integer(x) >> Integer(y);
}
parser >> EndCollection();
parser >> EndObject();
parser.deleteLater();
}
* This source code was highlighted with Source Code Highlighter.
В этом коде:- BeginObject(«Polygon»); – открыть объект с классом Polygon;
- BeginCollection(“points”, pointCount); – открыть коллекцию и поместить её длину в pointCount;
- В цикле for читаем позиции точек;
- EndCollection(); – закрыть коллекцию;
- EndObject(); – закрыть объект.
Выводы
Считаю, что получилась вполне годная реализация протокола, которую можно использовать в реальных приложениях.
В плюсы можно отнести:
- Полностью базируется на Qt, т.е. нет необходимости подключать сторонние библиотеки.
- Асинхронная природа.
- Не требует воссоздания полной объектной модели сервера в клиентском приложении (т.е. читаете только то, что вам нужно).
- Оттестирована – проходит все тесты, предложенные Caucho Technology, Inc. плюс собственный стек тестов.
- Прост в использовании (как мне кажется).
- В qh отсутствует кодогенерация. Опыт работы с hessianorb убедил меня в том, что кодогенерация не так полезна, как кажется. Во первых: окружение часто меняется и приходится постоянно выполнять кодогенерацию, что в hessianorb делать не так просто, как хотелось бы; а во вторых: появляется много лишнего ненужного кода (генерируется вся объектная модель, а она зачастую не нужна). Впрочем, qh получился весьма низкоуровневым и при большом желании можно разработать механизм кодогенерации.
- Необходимо знать структуру ответа сервера.
Hessian: http://hessian.caucho.com
QHessian: http://code.google.com/p/qhessian
QHessian FAQ: http://code.google.com/p/qhessian/wiki/FAQ
QHessian QA: http://code.google.com/p/qhessian/wiki/QHessian_QA
Спасибо.
==
Спасибо Сергею Бизину за оказанную помощь в разработке проекта и написании статьи.
UPD: Блог