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

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

Date post:03-Mar-2017
Category:
View:1,555 times
Download:8 times
Share this document with a friend
Transcript:
  • 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 &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 m_threadPool;

    QSet 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 &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 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 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 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(), 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;

Embed Size (px)
Recommended