Application of D-Bus in web systems

In the process of developing several Internet services, we noticed that a significant part of their functionality is common, and guided by the principle of DRY (Don’t Repeat Yourself - Do Not Repeat), we decided to put the common functionality in a separate module.

The following requirements were imposed on the module:
  • independence from the services using it;
  • simplicity of the "client" code;
  • multithreading and high speed.

I will make a reservation that our services are written in PHP and run on the Apache server running Linux. The main questions were: “what to write the module on?” and "how to access the module from PHP scripts?". An analysis of the means of implementing the module was carried out taking into account the software used, as well as our personal preferences, knowledge and experience.
3 main options were proposed:

  1. Implement the module as another web service in PHP and make calls from client services using CUrl or using SOAP. The disadvantages of this approach are the slow operation of the interpreted language, the cost of network requests (currently unnecessary, since it is assumed that the module and services will be running on the same server), the complexity of implementing shared objects in parallel requests.
  2. Implement the module as a FastCGI application using multithreading. The positive aspects of this option include: the ability to write a module in C / C ++, which would increase performance and allow the implementation of shared objects in a multi-threaded environment. Calls to the module from PHP scripts could be done using Unix domain sockets, which would avoid the cost of making unnecessary network calls (when the services and the module are located on the same server).
  3. In addition to the approaches described, our attention was drawn to the D-Bus interprocess communication system (IPC), which is currently widely used in Linux. Opportunities and characteristics of D-Bus, such as speed, reliability, customization, the presence of high-level wrapper libraries, seemed to us attractive and satisfying our requirements. In fact, the use of the second option would lead us to write our own analogue of D-Bus. Next, the question arose about module calls from client PHP code. On the Internet, we met two libraries implementing D-Bus in PHP: from Pecl and Japanese from GREE Labs. But, since we already managed to write a working test case on Pecl D-Bus, we did not receive due attention to the Japanese implementation. From the side of C ++, we decided to use QtDBus, due to the familiarity of programmers with the Qt library.

"Client" code


So, let's move on to implementation. Let's start with the "client" PHP code. Suppose that there is some application (our module written in Qt) that has registered a D-Bus service with the unique name test.service. A service is a hierarchical structure of the objects registered in it. If there is no need for a hierarchy, you can use the path to the "/" object (similar to the root directory in Linux). In our case, the service has an object "/ test". Objects provide interfaces containing sets of methods. The "/ test" object has the interface "test.iface" with the method "sum".

client.php:
// Создание объекта, соединяющегося с системной шиной D-Bus.
$dbus = new DBus(DBus::BUS_SYSTEM);
// Создание объекта, для осуществления межпроцессных вызовов.
$proxy = $dbus->createProxy("test.service", "/test", "test.iface");
try {
	// Осуществляем вызов метода
	$result = $proxy->sum(42, 13);
	var_dump($result);
}
catch (Exception $e) {
	print $e->getMessage()."\n";
}


In the code, the sum method is called from the test.iface interface of the object located along the path / test on the test.service service via the D-Bus system bus. The method is called with two integer arguments. As a result of executing this script on the service “test.service”, addition of 42 and 13 should be performed, and the result should be output using the var_dump function.

D-bus module implementation


When designing the module architecture, we decided to use the ZendFramework terminology (which may seem strange for a program written in C ++). This was due to the fact that such terms as “service”, “interface”, “object” were already used by us in relation to D-Bus. And to avoid confusion, we took the concepts of “Action” and “Controller” from ZendFramework.
By the term “action” we decided to understand the class inherited from QThread, which is a thread in which any necessary functionality will be implemented.
A “controller” was called a class that encapsulates action calls in its methods. In this case, the controller must be inherited from QObject and QDBusContext.

Head function

Here is the code of the module head function (main.cpp file). Here, the controller is registered on the D-Bus system bus.

#include 
#include 
#include 
#include "TestController.h"
#define SERVICE_NAME "test.service"
#define OBJECT_PATH "/test"
int main(int argc, char *argv[]) {
	QCoreApplication app(argc, argv);
	// Создаём соединение с системной шиной D-Bus
	QDBusConnection conn = QDBusConnection::systemBus();
	// Регистрируем сервис
	if (! conn.registerService(SERVICE_NAME)) {
		qDebug() << "Error:" <<  conn.lastError().message();
		exit(EXIT_FAILURE);
	}
	TestController controller;
	// Регистрируем контроллер
	conn.registerObject(OBJECT_PATH, &controller, QDBusConnection::ExportAllContents);
	return app.exec();
}


"Controller"

It should be noted that controller methods that are open for interprocessor calls via D-Bus operate sequentially. That is, if the first client makes a call to the sum method, the second must wait until the execution of the method ends. Therefore, we decided to reduce the code of methods to a minimum in order to avoid a long wait. Thus, with each client call, a working thread (action) is launched and the method exits.

Consider the controller class (file TestController.h). We will write the implementation of the method in the header file for brevity.

#ifndef TEST_CONTROLLER_H
#define TEST_CONTROLLER_H
#include 
#include 
#include 
#include 
#include "SumAction.h"
class TestController: public QObject, protected QDBusContext {
	Q_OBJECT
	Q_CLASSINFO("D-Bus Interface", "test.iface") // Имя интерфейса
public:
	Q_INVOKABLE int sum(int a, int b) {
		// Сообщаем D-Bus, что ответ прийдёт позже.
		setDelayedReply(true);
		// Запускаем нить
		(new SumAction(a, b, this))->start();
		// Формальный возврат значения для компилятора. Реальный результат будет возвращён из нити.
		return 0;
	};
};
#endif // TEST_CONTROLLER_H


"Actions"

In the actions we will place the module functionality. Each controller method will have an action class. Therefore, it is advisable to write an Action class that is basic to all actions.
Action.h
#ifndef ACTION_H
#define ACTION_H
#include 
#include 
#include 
#include 
#include 
class QDBusContext;
class Action: public QThread {
	Q_OBJECT
public:
	Action(const QDBusContext* context);
	virtual ~Action();
	// Получение результата указаного в шаблоне типа
	template
	QDBusReply reply() const { return _reply; }
protected:
	// Неконстантная ссылка для записи результата в классе наследнике.
	inline QDBusMessage& reply() { return _reply; }
	// Полученный запрос
	inline const QDBusMessage& request() { return _request; }
private slots:
	void onFinished();
private:
	QDBusConnection* _connection;
	QDBusMessage _request;
	QDBusMessage _reply;
};
#endif // ACTION_H

Action.cpp
#include "Action.h"
#include 
#include 
#include 
#include 
Action::Action(const QDBusContext* context) {
	if (context != 0) {
		// Создаём копию соединения
		_connection = new QDBusConnection(context->connection());
		_request = context->message();
	}
	else {
		_connection = 0;
		_request = QDBusMessage();
	}
	// Создаём ответ на запрос
	_reply = _request.createReply();
	// Присоединяем обработчик завершения нити
	if (! connect(this, SIGNAL(finished()), this, SLOT(onFinished())))
		qFatal("SIGNAL/SLOT connection error");
}
Action::~Action() {
	if (_connection != 0)
		delete _connection;
}
void Action::onFinished() {
	if (_connection != 0) {
		// Отсылка результата по D-Bus
		_connection->send(_reply);
	}
	/*
	 * Удаление объекта произойдёт только в случае если нить была запущена из
	 * нити, находящейся в цикле обработки событий (event loop).
	 */
	deleteLater();
}


Inheriting this class, we can focus on the implementation of the necessary functionality without worrying about the details of interaction with D-Bus. All you need to do is save the parameters in the class properties, add a and b, and write the result via the reply () link.

SumAction.h:
#ifndef SUMACTION_H
#define SUMACTION_H
#include "Action.h"
class SumAction: public Action {
	Q_OBJECT
public:
	SumAction(int a, int b, const QDBusContext* context):
		Action(context),
		_a(a),
		_b(b)
	{}
	virtual ~SumAction() {};
protected:
	void run() {
		reply() << _a + _b;
	}
private:
	int _a;
	int _b;
};
#endif // SUMACTION_H

D-Bus Configuration


Having compiled the module described above, we get an application registering the D-Bus service "test.service". Let's try to run it. Most likely, the result will be as follows:

$ ./dbus-test
Error: "Connection ":1.66" is not allowed to own the service "test.service" due to security policies in the configuration file"

To solve this problem, you need to make changes to the D-Bus configuration. D-Bus provides the flexibility to configure security and functionality. For our example to work, just create the following contents in the file: /etc/dbus-1/system.d/dbus-test.conf:


There is no need to restart the D-Bus daemon. Changes will take effect after saving the file.
We’ll restart the module and, if it has started successfully, try to access it from a PHP script.

$ php client.php
int(55)

Here is the expected result: 42 + 13 = 55. Sources can be taken here .

Conclusion


The method of interprocess communication described above allowed us to establish the interaction of a module written in C ++ with several web services that need its functionality. Thus, we got high performance and flexibility in building the complex information system that C ++ (and Qt in particular) provides us with, and the convenience of developing and supporting web services in PHP.

Also popular now: