2016-11-20 56 views
0

我打算使用QNetworkAccessManager通過我的移動應用程序向服務器發送HTTP服務器的請求。問題是,如何將自定義數據鏈接到每個請求?我試圖繼承QNetworkReply,但是我發現我必須實現虛擬方法close()isSequential(),但我不知道那些應該返回的內容,所以我恐怕會打破網絡請求功能。如何使QNetworkReply返回自定義數據?

例如,當我的應用程序不會在日誌中的過程,它具有存儲帳戶的電子郵件地址:

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 
    .... 
} 

所以,我該如何實現我的情況下,電子郵件的存儲和檢索,什麼我應該繼承哪些類,並重新實現哪些方法?

+0

有幾種方法,但您可以簡單地將json結構中的數據與結構中的請求一起返回。所以你不需要子類化任何東西。您的網絡回覆處理器可以簡單地檢查請求,以查看剩餘消息中的數據。我這樣做;我返回一個包含三部分的json結構;請求,結果代碼和數據。該請求告訴我我做了哪個調用,結果代碼告訴我它是否工作,並且數據包含結果。 –

+0

@johnelemans,如果服務器不響應呢?如果在通話過程中連接中斷,並且必須計時,該怎麼辦?這就是爲什麼將更多數據與請求本身相關聯的原因。 – Nulik

+0

在這種情況下,您將在請求中獲得超時。 –

回答

2

您可以利用每個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. 以明文發送密碼:相反,發送它的鹽味散列。創建帳戶時,您的服務器應爲每個帳戶生成隨機鹽。現有的帳戶可以保持不變,但只要用戶更改密碼,他們就應該獲得鹽。

還要注意QStringQUrl轉換將自動%的編碼字符串,這樣做時明確是不必要的。

2

在這種情況下email是請求的URL的一部分,所以你也可以從那裏提取它(QNetworkReply可以訪問QNetworkRequest它正在處理,請參閱QNetworkReply::request())。

由於QNetworkReplyQObject派生類,因此您也可以存儲或多或少的任何類型的數據作爲動態屬性,請參閱QObject::setProperty()

+0

創建。感謝您的建議。這可能是最好的解決方案。如果我理解正確,我可以繼承QNetworkRequest並將其作爲成員添加到其中,然後將新類作爲參數傳遞給QNetworkAccessManager :: get(),是否正確?或者只是將一個動態屬性設置爲QNetworRequest(而不是QNetworkReply,因爲QNetworkReply只是在請求完成之後而不是之前獲得的對象) – Nulik

+1

@Nulik動態屬性確實意味着您的用例。你不需要子類化任何東西。只要使用它們! –

+1

@Nulik:我不認爲'QNetworkRequest'的子類化會起作用,我希望當你存儲在'QNetworkReply'中時你會得到「切片」。 我的意思是請求的URL是可用的,你的電子郵件是URL查詢的一部分,可以隨時檢索。 但是,設置動態屬性甚至可以處理不屬於請求/ URL的數據 –

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","[email protected]"); 
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(); 
} 
+0

感謝您的想法。但是QT文檔說,一個QNAM對象應該足夠用於整個應用程序。但是如果我使用單個對象的方法,如果我並行發送多個請求,則第二個電話將會覆蓋該電子郵件。 – Nulik