SlideShare a Scribd company logo
Простой REST сервер на Qt с
рефлексией
Василий Сорокин
Москва 2017
Введение
● Qt и moc
● Abstract Server
● Concrete Server
● Рефлексия
● Authorization (and tags)
● Сложности/Проблемы
● Рефлексия в тестировании
● Заключение
Meta-Object Compiler
● Когда запускается
● Что делает
● Почему это важно
● Ограничения
Что должен уметь сервер?
● Получать запросы
● Разбирать данные
● Возвращать ответы
● Обрабатывать ошибки
● Авторизация
Abstract Server
#ifndef Q_MOC_RUN
# define NO_AUTH_REQUIRED
#endif
class AbstractRestServer : public QTcpServer
{
public:
explicit AbstractRestServer(const QString &pathPrefix, int port, QObject *parent = 0);
Q_INVOKABLE void startListen();
Q_INVOKABLE void stopListen();
protected:
void incomingConnection(qintptr socketDescriptor) override;
Abstract Server
void tryToCallMethod(QTcpSocket *socket, const
QString &type, const QString &method, QStringList
headers, const QByteArray &body);
QStringList makeMethodName(const QString &type,
const QString &name);
MethodNode *findMethod(const QStringList
&splittedMethod, QStringList &methodVariableParts);
void fillMethods();
void addMethodToTree(const QString &realMethod,
const QString &tag);
Abstract Server
void sendAnswer(QTcpSocket *socket, const
QByteArray &body, const QString &contentType, const
QHash<QString, QString> &headers,
int returnCode = 200, const QString &reason
= QString());
void registerSocket(QTcpSocket *socket);
void deleteSocket(QTcpSocket *socket, WorkerThread
*worker);
Abstract Server
private:
QThread *m_serverThread = nullptr;
QList<WorkerThreadInfo> m_threadPool;
QSet<QTcpSocket *> m_sockets;
QMutex m_socketsMutex;
MethodNode m_methodsTreeRoot;
int m_maxThreadsCount;
WorkerThread
class WorkerThread: public QThread
…
public:
WorkerThread(Proof::AbstractRestServer *const _server);
void sendAnswer(QTcpSocket *socket, const QByteArray &body, const
QString &contentType,
const QHash<QString, QString> &headers, int returnCode, const
QString &reason);
void handleNewConnection(qintptr socketDescriptor);
void deleteSocket(QTcpSocket *socket);
void onReadyRead(QTcpSocket *socket);
void stop();
WorkerThread
private:
Proof::AbstractRestServer* const m_server;
QHash<QTcpSocket *, SocketInfo> m_sockets;
WorkerThreadInfo
struct WorkerThreadInfo
{
explicit WorkerThreadInfo(WorkerThread *thread,
quint32 socketCount)
: thread(thread), socketCount(socketCount) {}
WorkerThread *thread;
quint32 socketCount;
};
SocketInfo
struct SocketInfo
{
Proof::HttpParser parser;
QMetaObject::Connection readyReadConnection;
QMetaObject::Connection disconnectConnection;
QMetaObject::Connection errorConnection;
};
Abstract Server implementation
static const QString NO_AUTH_TAG = QString("NO_AUTH_REQUIRED");
AbstractRestServer::AbstractRestServer(...) : QTcpServer(parent) {
m_serverThread = new QThread(this);
m_maxThreadsCount = QThread::idealThreadCount();
if (m_maxThreadsCount < MIN_THREADS_COUNT)
m_maxThreadsCount = MIN_THREADS_COUNT;
else
m_maxThreadsCount += 2;
moveToThread(m_serverThread);
m_serverThread->moveToThread(m_serverThread);
m_serverThread->start();
Abstract Server implementation
void AbstractRestServer::startListen()
{
if (!PrObject::call(this, &AbstractRestServer::startListen)) {
fillMethods();
bool isListen = listen(QHostAddress::Any, m_port);
}
}
void AbstractRestServer::stopListen()
{
if (!PrObject::call(this, &AbstractRestServer::stopListen, Proof::Call::Block))
close();
}
Make route tree
void AbstractRestServer::fillMethods() {
m_methodsTreeRoot.clear();
for (int i = 0; i < metaObject()->methodCount(); ++i) {
QMetaMethod method = metaObject()->method(i);
if (method.methodType() == QMetaMethod::Slot) {
QString currentMethod = QString(method.name());
if (currentMethod.startsWith(REST_METHOD_PREFIX))
addMethodToTree(currentMethod, method.tag());
}
}
}
Make route tree
void AbstractRestServer::addMethodToTree(const QString &realMethod,
const QString &tag)
{
QString method =
realMethod.mid(QString(REST_METHOD_PREFIX).length());
for (int i = 0; i < method.length(); ++i) {
if (method[i].isUpper()) {
method[i] = method[i].toLower();
if (i > 0 && method[i - 1] != '_')
method.insert(i++, '-');
}
} // rest_get_SourceList => get_source-list
Make route tree
QStringList splittedMethod = method.split("_");
MethodNode *currentNode = &m_methodsTreeRoot;
for (int i = 0; i < splittedMethod.count(); ++i) {
if (!currentNode->contains(splittedMethod[i]))
(*currentNode)[splittedMethod[i]] = MethodNode();
currentNode = &(*currentNode)[splittedMethod[i]];
}
currentNode->setValue(realMethod);
currentNode->setTag(tag);
}
Make route tree
class MethodNode {
public:
MethodNode();
bool contains(const QString &name) const;
void clear();
operator QString();
MethodNode &operator [](const QString &name);
const MethodNode operator [](const QString &name) const;
void setValue(const QString &value);
QString tag() const;
void setTag(const QString &tag);
private:
QHash<QString, MethodNode> m_nodes;
QString m_value = "";
QString m_tag;
};
New connection handling
void AbstractRestServer::incomingConnection(qintptr socketDescriptor) {
WorkerThread *worker = nullptr;
if (!m_threadPool.isEmpty()) {
auto iter = std::min_element(d->threadPool.begin(), d->threadPool.end(),
[](const WorkerThreadInfo &lhs, const WorkerThreadInfo &rhs)
{
return lhs.socketCount < rhs.socketCount;
});
if (iter->socketCount == 0 || m_threadPool.count() >= m_maxThreadsCount) {
worker = iter->thread;
++iter->socketCount;
}
}
New connection handling
if (worker == nullptr) {
worker = new WorkerThread(this);
worker->start();
m_threadPool << WorkerThreadInfo{worker, 1};
}
worker->handleNewConnection(socketDescriptor);
}
New connection handling
void WorkerThread::handleNewConnection(qintptr socketDescriptor) {
if (PrObject::call(this, &WorkerThread::handleNewConnection, socketDescriptor))
return;
QTcpSocket *tcpSocket = new QTcpSocket();
m_server->registerSocket(tcpSocket);
SocketInfo info;
info.readyReadConnection = connect(tcpSocket, &QTcpSocket::readyRead, this,
[tcpSocket, this] { onReadyRead(tcpSocket); }, Qt::QueuedConnection);
void (QTcpSocket:: *errorSignal)(QAbstractSocket::SocketError) =
&QTcpSocket::error;
info.errorConnection = connect(tcpSocket, errorSignal, this, [tcpSocket, this] {…},
Qt::QueuedConnection);
info.disconnectConnection = connect(tcpSocket, &QTcpSocket::disconnected, this,
[tcpSocket, this] {...}, Qt::QueuedConnection);
New connection handling
if (!tcpSocket->setSocketDescriptor(socketDescriptor)) {
m_server->deleteSocket(tcpSocket, this);
return;
}
sockets[tcpSocket] = info;
}
New connection handling
void WorkerThread::onReadyRead(QTcpSocket *socket) {
SocketInfo &info = m_sockets[socket];
HttpParser::Result result = info.parser.parseNextPart(socket->readAll());
switch (result) {
case HttpParser::Result::Success:
disconnect(info.readyReadConnection);
m_server->tryToCallMethod(socket, info.parser.method(), info.parser.uri(),
info.parser.headers(), info.parser.body());
break;
case HttpParser::Result::Error:
disconnect(info.readyReadConnection);
sendAnswer(socket, "", "text/plain; charset=utf-8", QHash<QString, QString>(), 400, "Bad
Request");
break;
case HttpParser::Result::NeedMore:
break;
}
}
Call method
void AbstractRestServer::tryToCallMethod(QTcpSocket *socket, const
QString &type, const QString &method, QStringList headers, const
QByteArray &body)
{
QStringList splittedByParamsMethod = method.split('?');
QStringList methodVariableParts;
QUrlQuery queryParams;
if (splittedByParamsMethod.count() > 1)
queryParams = QUrlQuery(splittedByParamsMethod.at(1));
MethodNode *methodNode = findMethod(makeMethodName(type,
splittedByParamsMethod.at(0)), methodVariableParts);
QString methodName = methodNode ? (*methodNode) : QString();
Call method
if (methodNode) {
bool isAuthenticationSuccessful = true;
if (methodNode->tag() != NO_AUTH_TAG) {
QString encryptedAuth;
for (int i = 0; i < headers.count(); ++i) {
if (headers.at(i).startsWith("Authorization", Qt::CaseInsensitive)) {
encryptedAuth = parseAuth(socket, headers.at(i));
break;
}
}
isAuthenticationSuccessful = (!encryptedAuth.isEmpty() && q-
>checkBasicAuth(encryptedAuth));
}
Call method
if (isAuthenticationSuccessful) {
QMetaObject::invokeMethod(this,
methodName.toLatin1().constData(), Qt::DirectConnection,
Q_ARG(QTcpSocket *,socket), Q_ARG(const
QStringList &, headers),
Q_ARG(const QStringList &, methodVariableParts),
Q_ARG(const QUrlQuery &, queryParams),
Q_ARG(const QByteArray &, body));
} else { sendNotAuthorized(socket); }
} else { sendNotFound(socket, "Wrong method"); }
}
Concrete Server
class RestServer : public Proof::AbstractRestServer
{
Q_OBJECT
public:
explicit RestServer(QObject *parent = 0);
protected slots:
NO_AUTH_REQUIRED void rest_get_Status(QTcpSocket *socket, const
QStringList &headers, const QStringList &methodVariableParts,
const QUrlQuery &query, const QByteArray &body);
void rest_get_Items_ValidList(...);
// GET /items/valid-list
}
Concrete Server implementation
void RestServer::rest_get_Items_ValidList(QTcpSocket *socket, const
QStringList &, const QStringList &, const QUrlQuery &, const
QByteArray &)
{
QJsonArray answerArray = m_somethingDataWorker->
getItems(ItemStatus::Valid);
sendAnswer(socket, QJsonDocument(answerArray).toJson(),
"text/json");
}
Сложности/Проблемы
● /press/123/start, /press/123/stop, item/321/transition
● Если нельзя вернуть данные сразу
Рефлексия в тестировании
TEST_F(AddressTest, updateFrom)
{
QList<QSignalSpy *> spies = spiesForObject(addressUT.data());
addressUT->updateFrom(addressUT2);
for (QSignalSpy *spy: spies)
EXPECT_EQ(1, spy->count()) << spy->signal().constData();
qDeleteAll(spies);
spies.clear();
EXPECT_EQ(addressUT2->city(), addressUT->city());
EXPECT_EQ(addressUT2->state(), addressUT->state());
EXPECT_EQ(addressUT2->postalCode(), addressUT->postalCode());
}
Рефлексия в тестировании
QList<QSignalSpy *> spiesForObject(QObject *obj, const QStringList &excludes)
{
QList<QSignalSpy *> spies;
for (int i = obj->metaObject()->methodOffset(); i < obj->metaObject()->methodCount(); ++i) {
if (obj->metaObject()->method(i).methodType() == QMetaMethod::Signal) {
QByteArray sign = obj->metaObject()->method(i).methodSignature();
if (excludes.contains(sign))
continue;
//Because QSignalSpy can't signals without SIGNAL() macros, but this hack cheating it
//# define SIGNAL(a) qFlagLocation("2"#a QLOCATION)
sign.prepend("2");
spies << new QSignalSpy(obj, qFlagLocation(sign.constData()));
}
}
return spies;
}
Заключение / Вопросы
Спасибо
vasiliy.a.sorokin@gmail.com

More Related Content

PDF
Clang tidy
PDF
Алексей Кутумов, Coroutines everywhere
PPT
Евгений Крутько, Многопоточные вычисления, современный подход.
PPTX
Дмитрий Демчук. Кроссплатформенный краш-репорт
PPTX
Evgeniy Muralev, Mark Vince, Working with the compiler, not against it
PDF
Qt Rest Server
PPTX
Lexical environment in ecma 262 5
PDF
Welcome to Modern C++
Clang tidy
Алексей Кутумов, Coroutines everywhere
Евгений Крутько, Многопоточные вычисления, современный подход.
Дмитрий Демчук. Кроссплатформенный краш-репорт
Evgeniy Muralev, Mark Vince, Working with the compiler, not against it
Qt Rest Server
Lexical environment in ecma 262 5
Welcome to Modern C++

What's hot (20)

PPTX
Pro typescript.ch03.Object Orientation in TypeScript
PDF
Работа с реляционными базами данных в C++
PDF
Windbg랑 친해지기
PDF
Антон Бикинеев, Reflection in C++Next
PPTX
модели акторов в с++ миф или реальность
PDF
Kirk Shoop, Reactive programming in C++
PDF
TensorFlow XLA RPC
PDF
Node.js flow control
PDF
How to make a large C++-code base manageable
PDF
Bridge TensorFlow to run on Intel nGraph backends (v0.4)
PDF
Architecture for Massively Parallel HDL Simulations
PDF
Bridge TensorFlow to run on Intel nGraph backends (v0.5)
PDF
201913001 khairunnisa progres_harian
DOCX
Fia fabila
PDF
Tugas 2
DOCX
Travel management
PPTX
Дмитрий Нестерук, Паттерны проектирования в XXI веке
PDF
Антон Наумович, Система автоматической крэш-аналитики своими средствами
PDF
Fuzzing: The New Unit Testing
Pro typescript.ch03.Object Orientation in TypeScript
Работа с реляционными базами данных в C++
Windbg랑 친해지기
Антон Бикинеев, Reflection in C++Next
модели акторов в с++ миф или реальность
Kirk Shoop, Reactive programming in C++
TensorFlow XLA RPC
Node.js flow control
How to make a large C++-code base manageable
Bridge TensorFlow to run on Intel nGraph backends (v0.4)
Architecture for Massively Parallel HDL Simulations
Bridge TensorFlow to run on Intel nGraph backends (v0.5)
201913001 khairunnisa progres_harian
Fia fabila
Tugas 2
Travel management
Дмитрий Нестерук, Паттерны проектирования в XXI веке
Антон Наумович, Система автоматической крэш-аналитики своими средствами
Fuzzing: The New Unit Testing
Ad

Viewers also liked (20)

PPTX
Евгений Рыжков, Андрей Карпов Как потратить 10 лет на разработку анализатора ...
PDF
Использование юнит-тестов для повышения качества разработки
PPTX
Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript
PDF
Догнать и перегнать boost::lexical_cast
PPTX
C++ Core Guidelines
PPTX
Алексей Кутумов, C++ без исключений, часть 3
PPTX
Фитнес для вашего кода: как держать его в форме
PDF
Для чего мы делали свой акторный фреймворк и что из этого вышло?
PPTX
Григорий Демченко, Универсальный адаптер
PDF
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
PDF
Parallel STL
PPTX
Quality assurance of large c++ projects
PPTX
Повседневный С++: алгоритмы и итераторы @ C++ Russia 2017
PPTX
Павел Беликов, Опыт мигрирования крупного проекта с Windows-only на Linux
PDF
Тененёв Анатолий, Boost.Asio в алгоритмической торговле
ODP
Антон Полухин. C++17
PPTX
Gor Nishanov, C++ Coroutines – a negative overhead abstraction
PPTX
Повседневный С++: алгоритмы и итераторы
PPTX
Григорий Демченко, Асинхронность и неблокирующая синхронизация
PDF
Дмитрий Кашицын, Вывод типов в динамических и не очень языках I
Евгений Рыжков, Андрей Карпов Как потратить 10 лет на разработку анализатора ...
Использование юнит-тестов для повышения качества разработки
Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript
Догнать и перегнать boost::lexical_cast
C++ Core Guidelines
Алексей Кутумов, C++ без исключений, часть 3
Фитнес для вашего кода: как держать его в форме
Для чего мы делали свой акторный фреймворк и что из этого вышло?
Григорий Демченко, Универсальный адаптер
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Parallel STL
Quality assurance of large c++ projects
Повседневный С++: алгоритмы и итераторы @ C++ Russia 2017
Павел Беликов, Опыт мигрирования крупного проекта с Windows-only на Linux
Тененёв Анатолий, Boost.Asio в алгоритмической торговле
Антон Полухин. C++17
Gor Nishanov, C++ Coroutines – a negative overhead abstraction
Повседневный С++: алгоритмы и итераторы
Григорий Демченко, Асинхронность и неблокирующая синхронизация
Дмитрий Кашицын, Вывод типов в динамических и не очень языках I
Ad

Similar to Василий Сорокин, Простой REST сервер на Qt с рефлексией (20)

PDF
Qt Network Explained (Portuguese)
PDF
Server Core with Morden C
PDF
The Ring programming language version 1.7 book - Part 101 of 196
PDF
Doing REST Right
PDF
The Ring programming language version 1.6 book - Part 176 of 189
PDF
L12: REST Service
KEY
I got 99 problems, but ReST ain't one
PDF
[Community Call] Ballerina Swan Lake HTTP Module Changes
PPTX
ASP.NET WEB API
PDF
Services web RESTful
PPTX
REST APIs and MQ
PDF
The Ring programming language version 1.3 book - Part 63 of 88
PDF
So you think you know REST - DPC11
PDF
Designing RESTful APIs
PPTX
Performance #4 network
PDF
Don't screw it up: how to build durable web apis
PDF
Don't screw it up: how to build durable web apis @ PHPDay 2014 in Verona (ITA)
PDF
Android App Development 06 : Network &amp; Web Services
Qt Network Explained (Portuguese)
Server Core with Morden C
The Ring programming language version 1.7 book - Part 101 of 196
Doing REST Right
The Ring programming language version 1.6 book - Part 176 of 189
L12: REST Service
I got 99 problems, but ReST ain't one
[Community Call] Ballerina Swan Lake HTTP Module Changes
ASP.NET WEB API
Services web RESTful
REST APIs and MQ
The Ring programming language version 1.3 book - Part 63 of 88
So you think you know REST - DPC11
Designing RESTful APIs
Performance #4 network
Don't screw it up: how to build durable web apis
Don't screw it up: how to build durable web apis @ PHPDay 2014 in Verona (ITA)
Android App Development 06 : Network &amp; Web Services

More from Sergey Platonov (18)

PPTX
Лев Казаркин, Удивительные приключения регистров SSE или в поисках одного бага
PDF
Антон Бикинеев, Writing good std::future&lt; C++ >
PDF
Павел Филонов, Разделяй и управляй вместе с Conan.io
PPTX
Павел Беликов, Как избежать ошибок, используя современный C++
PDF
Денис Кандров, Пушкова Евгения, QSpec: тестирование графических приложений на Qt
PPTX
Александр Тарасенко, Использование python для автоматизации отладки С/C++ код...
PDF
Павел Довгалюк, Обратная отладка
PPTX
Никита Глушков, К вопросу о реализации кроссплатформенных фреймворков
PPTX
Dori Exterman, Considerations for choosing the parallel computing strategy th...
PDF
Александр Гранин, Функциональная 'Жизнь': параллельные клеточные автоматы и к...
PPTX
Александр Фокин, Рефлексия в C++
PDF
Антон Нонко, Классические строки в C++
PPTX
Михаил Матросов, Повседневный С++: boost и STL
PDF
Борис Сазонов, RAII потоки и CancellationToken в C++
PPTX
Алексей Кутумов, Вектор с нуля
PPTX
Илья Шишков, Принципы создания тестируемого кода
PDF
Андрей Карпов, Приватные байки от разработчиков анализатора кода
PDF
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Лев Казаркин, Удивительные приключения регистров SSE или в поисках одного бага
Антон Бикинеев, Writing good std::future&lt; C++ >
Павел Филонов, Разделяй и управляй вместе с Conan.io
Павел Беликов, Как избежать ошибок, используя современный C++
Денис Кандров, Пушкова Евгения, QSpec: тестирование графических приложений на Qt
Александр Тарасенко, Использование python для автоматизации отладки С/C++ код...
Павел Довгалюк, Обратная отладка
Никита Глушков, К вопросу о реализации кроссплатформенных фреймворков
Dori Exterman, Considerations for choosing the parallel computing strategy th...
Александр Гранин, Функциональная 'Жизнь': параллельные клеточные автоматы и к...
Александр Фокин, Рефлексия в C++
Антон Нонко, Классические строки в C++
Михаил Матросов, Повседневный С++: boost и STL
Борис Сазонов, RAII потоки и CancellationToken в C++
Алексей Кутумов, Вектор с нуля
Илья Шишков, Принципы создания тестируемого кода
Андрей Карпов, Приватные байки от разработчиков анализатора кода
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++

Recently uploaded (20)

PDF
Product Update: Alluxio AI 3.7 Now with Sub-Millisecond Latency
PDF
Design an Analysis of Algorithms I-SECS-1021-03
PDF
Cost to Outsource Software Development in 2025
PPTX
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
PPTX
Reimagine Home Health with the Power of Agentic AI​
PPTX
history of c programming in notes for students .pptx
PPTX
Odoo POS Development Services by CandidRoot Solutions
PDF
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
PPTX
CHAPTER 2 - PM Management and IT Context
PDF
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
PDF
Wondershare Filmora 15 Crack With Activation Key [2025
PDF
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
PDF
Navsoft: AI-Powered Business Solutions & Custom Software Development
PDF
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
PPTX
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
PPTX
Introduction to Artificial Intelligence
PPTX
Why Generative AI is the Future of Content, Code & Creativity?
PDF
PTS Company Brochure 2025 (1).pdf.......
PDF
Odoo Companies in India – Driving Business Transformation.pdf
PPTX
Embracing Complexity in Serverless! GOTO Serverless Bengaluru
Product Update: Alluxio AI 3.7 Now with Sub-Millisecond Latency
Design an Analysis of Algorithms I-SECS-1021-03
Cost to Outsource Software Development in 2025
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
Reimagine Home Health with the Power of Agentic AI​
history of c programming in notes for students .pptx
Odoo POS Development Services by CandidRoot Solutions
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
CHAPTER 2 - PM Management and IT Context
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
Wondershare Filmora 15 Crack With Activation Key [2025
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
Navsoft: AI-Powered Business Solutions & Custom Software Development
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
Introduction to Artificial Intelligence
Why Generative AI is the Future of Content, Code & Creativity?
PTS Company Brochure 2025 (1).pdf.......
Odoo Companies in India – Driving Business Transformation.pdf
Embracing Complexity in Serverless! GOTO Serverless Bengaluru

Василий Сорокин, Простой REST сервер на Qt с рефлексией

  • 1. Простой REST сервер на Qt с рефлексией Василий Сорокин Москва 2017
  • 2. Введение ● Qt и moc ● Abstract Server ● Concrete Server ● Рефлексия ● Authorization (and tags) ● Сложности/Проблемы ● Рефлексия в тестировании ● Заключение
  • 3. Meta-Object Compiler ● Когда запускается ● Что делает ● Почему это важно ● Ограничения
  • 4. Что должен уметь сервер? ● Получать запросы ● Разбирать данные ● Возвращать ответы ● Обрабатывать ошибки ● Авторизация
  • 5. Abstract Server #ifndef Q_MOC_RUN # define NO_AUTH_REQUIRED #endif class AbstractRestServer : public QTcpServer { public: explicit AbstractRestServer(const QString &pathPrefix, int port, QObject *parent = 0); Q_INVOKABLE void startListen(); Q_INVOKABLE void stopListen(); protected: void incomingConnection(qintptr socketDescriptor) override;
  • 6. Abstract Server void tryToCallMethod(QTcpSocket *socket, const QString &type, const QString &method, QStringList headers, const QByteArray &body); QStringList makeMethodName(const QString &type, const QString &name); MethodNode *findMethod(const QStringList &splittedMethod, QStringList &methodVariableParts); void fillMethods(); void addMethodToTree(const QString &realMethod, const QString &tag);
  • 7. Abstract Server void sendAnswer(QTcpSocket *socket, const QByteArray &body, const QString &contentType, const QHash<QString, QString> &headers, int returnCode = 200, const QString &reason = QString()); void registerSocket(QTcpSocket *socket); void deleteSocket(QTcpSocket *socket, WorkerThread *worker);
  • 8. Abstract Server private: QThread *m_serverThread = nullptr; QList<WorkerThreadInfo> m_threadPool; QSet<QTcpSocket *> m_sockets; QMutex m_socketsMutex; MethodNode m_methodsTreeRoot; int m_maxThreadsCount;
  • 9. WorkerThread class WorkerThread: public QThread … public: WorkerThread(Proof::AbstractRestServer *const _server); void sendAnswer(QTcpSocket *socket, const QByteArray &body, const QString &contentType, const QHash<QString, QString> &headers, int returnCode, const QString &reason); void handleNewConnection(qintptr socketDescriptor); void deleteSocket(QTcpSocket *socket); void onReadyRead(QTcpSocket *socket); void stop();
  • 11. WorkerThreadInfo struct WorkerThreadInfo { explicit WorkerThreadInfo(WorkerThread *thread, quint32 socketCount) : thread(thread), socketCount(socketCount) {} WorkerThread *thread; quint32 socketCount; };
  • 12. SocketInfo struct SocketInfo { Proof::HttpParser parser; QMetaObject::Connection readyReadConnection; QMetaObject::Connection disconnectConnection; QMetaObject::Connection errorConnection; };
  • 13. Abstract Server implementation static const QString NO_AUTH_TAG = QString("NO_AUTH_REQUIRED"); AbstractRestServer::AbstractRestServer(...) : QTcpServer(parent) { m_serverThread = new QThread(this); m_maxThreadsCount = QThread::idealThreadCount(); if (m_maxThreadsCount < MIN_THREADS_COUNT) m_maxThreadsCount = MIN_THREADS_COUNT; else m_maxThreadsCount += 2; moveToThread(m_serverThread); m_serverThread->moveToThread(m_serverThread); m_serverThread->start();
  • 14. Abstract Server implementation void AbstractRestServer::startListen() { if (!PrObject::call(this, &AbstractRestServer::startListen)) { fillMethods(); bool isListen = listen(QHostAddress::Any, m_port); } } void AbstractRestServer::stopListen() { if (!PrObject::call(this, &AbstractRestServer::stopListen, Proof::Call::Block)) close(); }
  • 15. Make route tree void AbstractRestServer::fillMethods() { m_methodsTreeRoot.clear(); for (int i = 0; i < metaObject()->methodCount(); ++i) { QMetaMethod method = metaObject()->method(i); if (method.methodType() == QMetaMethod::Slot) { QString currentMethod = QString(method.name()); if (currentMethod.startsWith(REST_METHOD_PREFIX)) addMethodToTree(currentMethod, method.tag()); } } }
  • 16. Make route tree void AbstractRestServer::addMethodToTree(const QString &realMethod, const QString &tag) { QString method = realMethod.mid(QString(REST_METHOD_PREFIX).length()); for (int i = 0; i < method.length(); ++i) { if (method[i].isUpper()) { method[i] = method[i].toLower(); if (i > 0 && method[i - 1] != '_') method.insert(i++, '-'); } } // rest_get_SourceList => get_source-list
  • 17. Make route tree QStringList splittedMethod = method.split("_"); MethodNode *currentNode = &m_methodsTreeRoot; for (int i = 0; i < splittedMethod.count(); ++i) { if (!currentNode->contains(splittedMethod[i])) (*currentNode)[splittedMethod[i]] = MethodNode(); currentNode = &(*currentNode)[splittedMethod[i]]; } currentNode->setValue(realMethod); currentNode->setTag(tag); }
  • 18. Make route tree class MethodNode { public: MethodNode(); bool contains(const QString &name) const; void clear(); operator QString(); MethodNode &operator [](const QString &name); const MethodNode operator [](const QString &name) const; void setValue(const QString &value); QString tag() const; void setTag(const QString &tag); private: QHash<QString, MethodNode> m_nodes; QString m_value = ""; QString m_tag; };
  • 19. New connection handling void AbstractRestServer::incomingConnection(qintptr socketDescriptor) { WorkerThread *worker = nullptr; if (!m_threadPool.isEmpty()) { auto iter = std::min_element(d->threadPool.begin(), d->threadPool.end(), [](const WorkerThreadInfo &lhs, const WorkerThreadInfo &rhs) { return lhs.socketCount < rhs.socketCount; }); if (iter->socketCount == 0 || m_threadPool.count() >= m_maxThreadsCount) { worker = iter->thread; ++iter->socketCount; } }
  • 20. New connection handling if (worker == nullptr) { worker = new WorkerThread(this); worker->start(); m_threadPool << WorkerThreadInfo{worker, 1}; } worker->handleNewConnection(socketDescriptor); }
  • 21. New connection handling void WorkerThread::handleNewConnection(qintptr socketDescriptor) { if (PrObject::call(this, &WorkerThread::handleNewConnection, socketDescriptor)) return; QTcpSocket *tcpSocket = new QTcpSocket(); m_server->registerSocket(tcpSocket); SocketInfo info; info.readyReadConnection = connect(tcpSocket, &QTcpSocket::readyRead, this, [tcpSocket, this] { onReadyRead(tcpSocket); }, Qt::QueuedConnection); void (QTcpSocket:: *errorSignal)(QAbstractSocket::SocketError) = &QTcpSocket::error; info.errorConnection = connect(tcpSocket, errorSignal, this, [tcpSocket, this] {…}, Qt::QueuedConnection); info.disconnectConnection = connect(tcpSocket, &QTcpSocket::disconnected, this, [tcpSocket, this] {...}, Qt::QueuedConnection);
  • 22. New connection handling if (!tcpSocket->setSocketDescriptor(socketDescriptor)) { m_server->deleteSocket(tcpSocket, this); return; } sockets[tcpSocket] = info; }
  • 23. New connection handling void WorkerThread::onReadyRead(QTcpSocket *socket) { SocketInfo &info = m_sockets[socket]; HttpParser::Result result = info.parser.parseNextPart(socket->readAll()); switch (result) { case HttpParser::Result::Success: disconnect(info.readyReadConnection); m_server->tryToCallMethod(socket, info.parser.method(), info.parser.uri(), info.parser.headers(), info.parser.body()); break; case HttpParser::Result::Error: disconnect(info.readyReadConnection); sendAnswer(socket, "", "text/plain; charset=utf-8", QHash<QString, QString>(), 400, "Bad Request"); break; case HttpParser::Result::NeedMore: break; } }
  • 24. Call method void AbstractRestServer::tryToCallMethod(QTcpSocket *socket, const QString &type, const QString &method, QStringList headers, const QByteArray &body) { QStringList splittedByParamsMethod = method.split('?'); QStringList methodVariableParts; QUrlQuery queryParams; if (splittedByParamsMethod.count() > 1) queryParams = QUrlQuery(splittedByParamsMethod.at(1)); MethodNode *methodNode = findMethod(makeMethodName(type, splittedByParamsMethod.at(0)), methodVariableParts); QString methodName = methodNode ? (*methodNode) : QString();
  • 25. Call method if (methodNode) { bool isAuthenticationSuccessful = true; if (methodNode->tag() != NO_AUTH_TAG) { QString encryptedAuth; for (int i = 0; i < headers.count(); ++i) { if (headers.at(i).startsWith("Authorization", Qt::CaseInsensitive)) { encryptedAuth = parseAuth(socket, headers.at(i)); break; } } isAuthenticationSuccessful = (!encryptedAuth.isEmpty() && q- >checkBasicAuth(encryptedAuth)); }
  • 26. Call method if (isAuthenticationSuccessful) { QMetaObject::invokeMethod(this, methodName.toLatin1().constData(), Qt::DirectConnection, Q_ARG(QTcpSocket *,socket), Q_ARG(const QStringList &, headers), Q_ARG(const QStringList &, methodVariableParts), Q_ARG(const QUrlQuery &, queryParams), Q_ARG(const QByteArray &, body)); } else { sendNotAuthorized(socket); } } else { sendNotFound(socket, "Wrong method"); } }
  • 27. Concrete Server class RestServer : public Proof::AbstractRestServer { Q_OBJECT public: explicit RestServer(QObject *parent = 0); protected slots: NO_AUTH_REQUIRED void rest_get_Status(QTcpSocket *socket, const QStringList &headers, const QStringList &methodVariableParts, const QUrlQuery &query, const QByteArray &body); void rest_get_Items_ValidList(...); // GET /items/valid-list }
  • 28. Concrete Server implementation void RestServer::rest_get_Items_ValidList(QTcpSocket *socket, const QStringList &, const QStringList &, const QUrlQuery &, const QByteArray &) { QJsonArray answerArray = m_somethingDataWorker-> getItems(ItemStatus::Valid); sendAnswer(socket, QJsonDocument(answerArray).toJson(), "text/json"); }
  • 29. Сложности/Проблемы ● /press/123/start, /press/123/stop, item/321/transition ● Если нельзя вернуть данные сразу
  • 30. Рефлексия в тестировании TEST_F(AddressTest, updateFrom) { QList<QSignalSpy *> spies = spiesForObject(addressUT.data()); addressUT->updateFrom(addressUT2); for (QSignalSpy *spy: spies) EXPECT_EQ(1, spy->count()) << spy->signal().constData(); qDeleteAll(spies); spies.clear(); EXPECT_EQ(addressUT2->city(), addressUT->city()); EXPECT_EQ(addressUT2->state(), addressUT->state()); EXPECT_EQ(addressUT2->postalCode(), addressUT->postalCode()); }
  • 31. Рефлексия в тестировании QList<QSignalSpy *> spiesForObject(QObject *obj, const QStringList &excludes) { QList<QSignalSpy *> spies; for (int i = obj->metaObject()->methodOffset(); i < obj->metaObject()->methodCount(); ++i) { if (obj->metaObject()->method(i).methodType() == QMetaMethod::Signal) { QByteArray sign = obj->metaObject()->method(i).methodSignature(); if (excludes.contains(sign)) continue; //Because QSignalSpy can't signals without SIGNAL() macros, but this hack cheating it //# define SIGNAL(a) qFlagLocation("2"#a QLOCATION) sign.prepend("2"); spies << new QSignalSpy(obj, qFlagLocation(sign.constData())); } } return spies; }