Qt Rest Server
-
Upload
vasiliy-sorokin -
Category
Software
-
view
99 -
download
2
Transcript of Qt Rest Server
![Page 1: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/1.jpg)
Простой REST сервер на Qt с рефлексией
Василий СорокинМосква 2017
![Page 2: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/2.jpg)
Введение
● Qt и moc
● Abstract Server
● Concrete Server
● Рефлексия
● Authorization (and tags)
● Сложности/Проблемы
● Рефлексия в тестировании
● Заключение
![Page 3: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/3.jpg)
Meta-Object Compiler
● Когда запускается
● Что делает
● Почему это важно
● Ограничения
![Page 4: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/4.jpg)
Что должен уметь сервер?
● Получать запросы
● Разбирать данные
● Возвращать ответы
● Обрабатывать ошибки
● Авторизация
![Page 5: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/5.jpg)
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;
![Page 6: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/6.jpg)
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);
![Page 7: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/7.jpg)
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);
![Page 8: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/8.jpg)
Abstract Server
private:
QThread *m_serverThread = nullptr;
QList<WorkerThreadInfo> m_threadPool;
QSet<QTcpSocket *> m_sockets;
QMutex m_socketsMutex;
MethodNode m_methodsTreeRoot;
int m_maxThreadsCount;
![Page 9: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/9.jpg)
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();
![Page 10: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/10.jpg)
WorkerThread
private:
Proof::AbstractRestServer* const m_server;
QHash<QTcpSocket *, SocketInfo> m_sockets;
![Page 11: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/11.jpg)
WorkerThreadInfo
struct WorkerThreadInfo
{
explicit WorkerThreadInfo(WorkerThread *thread, quint32 socketCount)
: thread(thread), socketCount(socketCount) {}
WorkerThread *thread;
quint32 socketCount;
};
![Page 12: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/12.jpg)
SocketInfo
struct SocketInfo
{
Proof::HttpParser parser;
QMetaObject::Connection readyReadConnection;
QMetaObject::Connection disconnectConnection;
QMetaObject::Connection errorConnection;
};
![Page 13: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/13.jpg)
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();
![Page 14: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/14.jpg)
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();
}
![Page 15: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/15.jpg)
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());
}
}
}
![Page 16: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/16.jpg)
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
![Page 17: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/17.jpg)
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);
}
![Page 18: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/18.jpg)
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;
};
![Page 19: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/19.jpg)
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;
}
}
![Page 20: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/20.jpg)
New connection handling
if (worker == nullptr) {
worker = new WorkerThread(this);
worker->start();
m_threadPool << WorkerThreadInfo{worker, 1};
}
worker->handleNewConnection(socketDescriptor);
}
![Page 21: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/21.jpg)
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);
![Page 22: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/22.jpg)
New connection handling
if (!tcpSocket->setSocketDescriptor(socketDescriptor)) {
m_server->deleteSocket(tcpSocket, this);
return;
}
sockets[tcpSocket] = info;
}
![Page 23: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/23.jpg)
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;
}
}
![Page 24: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/24.jpg)
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();
![Page 25: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/25.jpg)
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));
}
![Page 26: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/26.jpg)
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"); }
}
![Page 27: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/27.jpg)
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
}
![Page 28: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/28.jpg)
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");
}
![Page 29: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/29.jpg)
Сложности/Проблемы
● /press/123/start, /press/123/stop, item/321/transition
● Если нельзя вернуть данные сразу
![Page 30: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/30.jpg)
Рефлексия в тестировании
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());
}
![Page 31: Qt Rest Server](https://reader031.fdocuments.net/reader031/viewer/2022021416/58ed12ff1a28abb86a8b45c3/html5/thumbnails/31.jpg)
Рефлексия в тестировании
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;
}