Pull to refresh

QHessian: Qt & Hessian

Reading time 6 min
Views 6.9K
image
Статья освещает реализацию бинарного 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
Для реализации этого требования в qh используется собственная иерархия типов:
  • 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 чтение/запись ссылки на объект
Механизмы работы с qh копируют механизмы работы со стандартными потоками ввода/вывода. Т.е. чтение осуществляется с помощью оператора “>>”, а запись, соответственно, при помощи оператора “<<”. Чтение ведется из специального объекта QHessianReturnParser (парсер ответа сервера). А запись – в объект QHessianMethodCall (подготовка данных для отправки на сервер).

Например, для вызова метода сервера 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(); – закрыть объект.
Так же возможна ситуация, когда мы не знаем тип объекта, например, сервер возвращает нетипизированный список List<? extends Geometry>. В данном случае нужно использовать специальный метод peek(), который определяет, возможно ли выполнить желаемую операцию чтения из потока (пример есть на сайте проекта).

Выводы
Считаю, что получилась вполне годная реализация протокола, которую можно использовать в реальных приложениях.

В плюсы можно отнести:
  • Полностью базируется на 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: Блог
Tags:
Hubs:
+21
Comments 11
Comments Comments 11

Articles