如何让QNetworkReply返回自定义数据?

时间:2016-11-20 17:24:02

标签: c++ qt

我将使用QNetworkAccessManager通过我的移动应用程序向服务器发出HTTP服务器请求。问题是,如何将自定义数据链接到每个请求?我尝试将QNetworkReply子类化,但我发现我必须实现虚拟方法close()isSequential(),但我不知道应该返回什么,所以我担心我会打破网络请求功能。

例如,当我的应用程序执行登录过程时,它必须存储帐户的电子邮件地址:

class MyApp : public QObject
{
    Q_OBJECT

private:
    QNetworkRequest             request;
    QNetworkReply               *reply;
    QNetworkAccessManager       *manager;

    ...

}

void MyApp::do_log_in(QString email, QString password) {
    QString s;

    someobject.email=email; // <-- I have to store email address before sending request to server, but where do I store it?
    s.append("http://myapp.com/do-auth.php?email=");
    s.append(QUrl::toPercentEncoding(email));
    s.append("&password=");
    s.append(QUrl::toPercentEncoding(password));
    connect(manager,SIGNAL(finished(QNetworkReply*)),this,SLOT(login_finished(QNetworkReply*)));
    request.setUrl(QUrl(s));
    manager->get(request);

}

void MyApp::login_finished(QNetworkReply *rep) {
    DepservReply *reply;
    QString email;
    ....
    email= ...... // <-- I need to get the email address from QNetworkReply object somehow
    ///my code here handling server reply
    ....
} 

那么,在我的案例中,如何实现电子邮件的存储和检索,我应该将哪些类子类化以及应该重新实现哪些方法?

3 个答案:

答案 0 :(得分:3)

您可以利用每个QObject中提供的动态属性系统,并在回复中保存数据:

// https://github.com/KubaO/stackoverflown/tree/master/questions/network-reply-tracking-40707025
#include <QtNetwork>

class MyCtl : public QObject
{
    Q_OBJECT
    QNetworkAccessManager manager{this};
    // ...
    void reply_finished(QNetworkReply *reply);
public:
    MyCtl(QObject *parent = nullptr);
    void do_log_in(const QString &email, const QString &password);
};

static const char kAuthGetSalt[] = "req_auth-get-salt";
static const char kDoAuth[] = "req_do-auth";
static const char kEmail[] = "req_email";
static const char kPassword[] = "req_password";

static const auto authGetSaltUrl = QStringLiteral("https://myapp.com/auth-get-salt.php?email=%1");
static const auto doAuthUrl = QStringLiteral("https://myapp.com/do-auth.php?email=%1&passwordHash=%2");

MyCtl::MyCtl(QObject *parent) : QObject{parent}
{
    connect(&manager, &QNetworkAccessManager::finished, this, &MyCtl::reply_finished);
}

void MyCtl::do_log_in(const QString &email, const QString &password) {
    auto url = authGetSaltUrl.arg(email);
    auto reply = manager.get(QNetworkRequest{url});
    reply->setProperty(kAuthGetSalt, true);
    reply->setProperty(kEmail, email);
    reply->setProperty(kPassword, password);
}

void MyCtl::reply_finished(QNetworkReply *reply) {
    if (!reply->property(kAuthGetSalt).isNull()) {
        reply->deleteLater(); // let's not leak the reply
        if (reply->error() == QNetworkReply::NoError) {
            auto salt = reply->readAll();
            auto email = reply->property(kEmail).toString();
            auto password = reply->property(kPassword).toString();
            Q_ASSERT(!password.isEmpty() && !email.isEmpty());
            QCryptographicHash hasher{QCryptographicHash::Sha1};
            hasher.addData(salt); // the server must hash the same way
            hasher.addData("----");
            hasher.addData(password.toUtf8());
            auto hash = hasher.result().toBase64(QByteArray::Base64UrlEncoding);
            auto url = doAuthUrl.arg(email).arg(QString::fromLatin1(hash));

            auto reply = manager.get(QNetworkRequest{url});
            reply->setProperty(kDoAuth, true);
            reply->setProperty(kEmail, email);
        }
    }
    else if (!reply->property(kDoAuth).isNull()) {
        if (reply->error() == QNetworkReply::NoError) {
            auto email = reply->property(kEmail).toString();
            // ...
        }
    }
}

使用常量作为属性名称,以避免打字错误,让编译器检查您是否使用了有效的标识符。

上述示例纠正了代码中的以下严重安全问题:

  1. 通过明确的连接发送安全凭据:使用https://,而不是http://

  2. 以明文形式发送密码:改为发送盐渍哈希值。创建帐户时,您的服务器应为每个帐户生成随机盐。现有帐户可以保持不变,但是一旦用户更改密码,他们就应该获得一个盐。

  3. 另请注意,QStringQUrl转换会自动对字符串进行百分比编码,因此无需明确地执行此操作。

答案 1 :(得分:2)

在这种情况下,email是请求网址的一部分,因此您可以从那里提取它(QNetworkReply可以访问它正在处理的QNetworkRequest,请参阅QNetworkReply::request())。

您还可以将或多或少的任何类型的数据存储为动态属性,因为QNetworkReplyQObject派生类,请参阅QObject::setProperty()

答案 2 :(得分:0)

您可以将QNAM子类化以获得对它的更多控制。

network.h

class QNAMNetwork : public QNetworkAccessManager
{
    Q_OBJECT
public:
    explicit QNAMNetwork(QObject *parent = 0);
    ~QNAMNetwork();
    inline void insertUserValue(const QString & key, const QString & value){this->m_user_values.insert(key,value);}
    inline QString getUserValue(const QString & key){return this->m_user_values.value(key);}


signals:
    void requestFinished(ExNetwork *, QNetworkReply *);

private slots:
    void _sslErrors(QNetworkReply *, const QList<QSslError> &);
    void _finished(QNetworkReply *);

private:
    QMap<QString, QString>           m_user_values;
 };

network.cpp

QNAMNetwork::QNAMNetwork(QObject *parent):QNetworkAccessManager(parent)
{
    connect(this, &QNAMNetwork::sslErrors, this, &QNAMNetwork::_sslErrors);
    connect(this, &QNAMNetwork::finished, this, &QNAMNetwork::_finished);
}

QNAMNetwork::~QNAMNetwork()
{
    //qDebug() << __FUNCTION__ << QString::number((long)this,16);
}

void QNAMNetwork::_sslErrors(QNetworkReply * reply, const QList<QSslError> & errors)
{
    reply->ignoreSslErrors(errors);
}

void QNAMNetwork::_finished(QNetworkReply * reply)
{
    emit requestFinished(this, reply);
}

用例:

QNAMNetwork * network = new QNAMNetwork(this);
network->insertUserValue("email","yourmail@mail.com");
connect(network, SIGNAL(requestFinished(QNAMNetwork*,QNetworkReply*)), this, SLOT(requestFinished(QNAMNetwork*,QNetworkReply*)));

QNetworkRequest req(QUrl::fromUserInput(query)); //get url

network->get(req);

...

void YourClass::requestFinished(QNAMNetwork * net, QNetworkReply * rep)
{
   QString email = net->getUserValue("email");
   net->deleteLater();
   rep->deleteLater();
}