diff options
author | Mårten Nordheim <marten.nordheim@qt.io> | 2020-10-21 18:31:13 +0200 |
---|---|---|
committer | Mårten Nordheim <marten.nordheim@qt.io> | 2020-10-28 08:48:26 +0200 |
commit | dfca01e18687987b247e33130dc86738f75aea90 (patch) | |
tree | d0d531ed90e994dbd0ba6d8e491a0fa597125fc5 /examples | |
parent | cf03874dbbaeefbc01a2ffcc73dd36bad2442bc6 (diff) |
Update the HTTP example
- Extended the documentation talking about it
- Initialize members in the header rather than in the ctor
- Some formatting changes
- Prefer connect-ing to QNetworkReply rather than QNAM for sslErrors
- Because we didn't use the QNetworkReply* argument
- Put the QNetworkReply pointer in a managed pointer
- Removed the code explicitly handling a redirect (it's the new default)
- Edited HttpWindow::httpFinished so that there're less places to reset
the reply pointer
- Updated some ifdefs I didn't update when I was revamping this example
3 years ago
Task-number: QTBUG-87306
Change-Id: I10a6f756c09908f199ac9c61e28b49625af10105
Reviewed-by: Paul Wicking <paul.wicking@qt.io>
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
Diffstat (limited to 'examples')
-rw-r--r-- | examples/network/doc/src/http.qdoc | 67 | ||||
-rw-r--r-- | examples/network/http/httpwindow.cpp | 111 | ||||
-rw-r--r-- | examples/network/http/httpwindow.h | 11 |
3 files changed, 120 insertions, 69 deletions
diff --git a/examples/network/doc/src/http.qdoc b/examples/network/doc/src/http.qdoc index 1a70187369..815775c51d 100644 --- a/examples/network/doc/src/http.qdoc +++ b/examples/network/doc/src/http.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the documentation of the Qt Toolkit. @@ -35,4 +35,69 @@ from remote hosts. \image http-example.png + + The main work of this example is done in the HttpWindow class. + Thus we will focus on that. + + \snippet http/httpwindow.cpp qnam-download + + Using QNetworkAccessManager, we begin the download of a resource as + pointed to by the \c url. If you are unfamiliar with it or the function used, + QNetworkAccessManager::get(), or simply want to look into it in more detail, + take a look at its documentation and the documentation for + QNetworkReply and QNetworkRequest. + + \snippet http/httpwindow.cpp connecting-reply-to-slots + + Above, we connect some of the reply's signals to slots in the class. + These slots will take care of both incoming data and finalizing the + download/handling errors. + + \snippet http/httpwindow.cpp networkreply-readyread-1 + + As for handling the incoming data, since we don't know the maximum + download size of any potential input and we don't want to exhaust + the memory of any computer which might run the example program, we + handle incoming data in QNetworkReply::readyRead() instead of in + QNetworkReply::finished(). + + \snippet http/httpwindow.cpp networkreply-readyread-2 + + Then we write the data to file as it arrives. It is less convenient, + but the application will consume less memory at its peak! + + \snippet http/httpwindow.cpp sslerrors-1 + + With the QNetworkReply::sslErrors() signal we can also handle errors that may + occur during the TLS handshake when connecting to secure websites (i.e. HTTPS). + + \snippet http/httpwindow.cpp sslerrors-2 + + In this example, we show a dialog to the user so that they can choose whether + or not to ignore the errors. + + \snippet http/httpwindow.cpp networkreply-error-handling-1 + \snippet http/httpwindow.cpp networkreply-error-handling-2 + + If an error occurs then QNetworkReply will emit the + QNetworkReply::errorOccurred() signal, followed by the + QNetworkReply::finished() signal. In this example, we only connect to the + latter. We handle any potential error(s) in the respective slot by deleting + the file we were writing to, and display the error with our status label. + + \snippet http/httpwindow.cpp qnam-auth-required-1 + + If you connect to a website that uses + \l{https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication}{HTTP authentication}, + assuming you didn't supply the credentials that should be used ahead of time, + you can handle missing credentials when the website requests it. With QNetworkAccessManager, + we do this in a slot connected to the signal + QNetworkAccessManager::authenticationRequired(). We make this connection once, + in the constructor. + + \snippet http/httpwindow.cpp qnam-auth-required-2 + + In this example, we show a dialog where the user can either insert a + username and password, or cancel. Canceling causes the request to fail. + */ diff --git a/examples/network/http/httpwindow.cpp b/examples/network/http/httpwindow.cpp index c7bf0c0dff..2790b8503e 100644 --- a/examples/network/http/httpwindow.cpp +++ b/examples/network/http/httpwindow.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the examples of the Qt Toolkit. @@ -56,6 +56,9 @@ #include <QtNetwork> #include <QUrl> +#include <algorithm> +#include <memory> + #if QT_CONFIG(ssl) const char defaultUrl[] = "https://www.qt.io/"; #else @@ -75,10 +78,6 @@ ProgressDialog::ProgressDialog(const QUrl &url, QWidget *parent) setMinimumSize(QSize(400, 75)); } -ProgressDialog::~ProgressDialog() -{ -} - void ProgressDialog::networkReplyProgress(qint64 bytesRead, qint64 totalBytes) { setMaximum(totalBytes); @@ -93,24 +92,18 @@ HttpWindow::HttpWindow(QWidget *parent) , launchCheckBox(new QCheckBox("Launch file")) , defaultFileLineEdit(new QLineEdit(defaultFileName)) , downloadDirectoryLineEdit(new QLineEdit) - , reply(nullptr) - , file(nullptr) - , httpRequestAborted(false) { setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowTitle(tr("HTTP")); + //! [qnam-auth-required-1] connect(&qnam, &QNetworkAccessManager::authenticationRequired, this, &HttpWindow::slotAuthenticationRequired); -#ifndef QT_NO_SSL - connect(&qnam, &QNetworkAccessManager::sslErrors, - this, &HttpWindow::sslErrors); -#endif + //! [qnam-auth-required-1] QFormLayout *formLayout = new QFormLayout; urlLineEdit->setClearButtonEnabled(true); - connect(urlLineEdit, &QLineEdit::textChanged, - this, &HttpWindow::enableDownloadButton); + connect(urlLineEdit, &QLineEdit::textChanged, this, &HttpWindow::enableDownloadButton); formLayout->addRow(tr("&URL:"), urlLineEdit); QString downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::TempLocation); if (downloadDirectory.isEmpty() || !QFileInfo(downloadDirectory).isDir()) @@ -141,25 +134,34 @@ HttpWindow::HttpWindow(QWidget *parent) urlLineEdit->setFocus(); } - -HttpWindow::~HttpWindow() -{ -} +HttpWindow::~HttpWindow() = default; void HttpWindow::startRequest(const QUrl &requestedUrl) { url = requestedUrl; httpRequestAborted = false; - reply = qnam.get(QNetworkRequest(url)); - connect(reply, &QNetworkReply::finished, this, &HttpWindow::httpFinished); - connect(reply, &QIODevice::readyRead, this, &HttpWindow::httpReadyRead); + //! [qnam-download] + reply.reset(qnam.get(QNetworkRequest(url))); + //! [qnam-download] + //! [connecting-reply-to-slots] + connect(reply.get(), &QNetworkReply::finished, this, &HttpWindow::httpFinished); + //! [networkreply-readyread-1] + connect(reply.get(), &QIODevice::readyRead, this, &HttpWindow::httpReadyRead); + //! [networkreply-readyread-1] +#if QT_CONFIG(ssl) + //! [sslerrors-1] + connect(reply.get(), &QNetworkReply::sslErrors, this, &HttpWindow::sslErrors); + //! [sslerrors-1] +#endif + //! [connecting-reply-to-slots] ProgressDialog *progressDialog = new ProgressDialog(url, this); progressDialog->setAttribute(Qt::WA_DeleteOnClose); connect(progressDialog, &QProgressDialog::canceled, this, &HttpWindow::cancelDownload); - connect(reply, &QNetworkReply::downloadProgress, progressDialog, &ProgressDialog::networkReplyProgress); - connect(reply, &QNetworkReply::finished, progressDialog, &ProgressDialog::hide); + connect(reply.get(), &QNetworkReply::downloadProgress, + progressDialog, &ProgressDialog::networkReplyProgress); + connect(reply.get(), &QNetworkReply::finished, progressDialog, &ProgressDialog::hide); progressDialog->show(); statusLabel->setText(tr("Downloading %1...").arg(url.toString())); @@ -215,7 +217,7 @@ void HttpWindow::downloadFile() std::unique_ptr<QFile> HttpWindow::openFileForWrite(const QString &fileName) { - std::unique_ptr<QFile> file(new QFile(fileName)); + std::unique_ptr<QFile> file = std::make_unique<QFile>(fileName); if (!file->open(QIODevice::WriteOnly)) { QMessageBox::information(this, tr("Error"), tr("Unable to save the file %1: %2.") @@ -243,67 +245,49 @@ void HttpWindow::httpFinished() file.reset(); } - if (httpRequestAborted) { - reply->deleteLater(); - reply = nullptr; - return; - } - - if (reply->error()) { + //! [networkreply-error-handling-1] + QNetworkReply::NetworkError error = reply->error(); + const QString &errorString = reply->errorString(); + //! [networkreply-error-handling-1] + reply.reset(); + //! [networkreply-error-handling-2] + if (error != QNetworkReply::NoError) { QFile::remove(fi.absoluteFilePath()); - statusLabel->setText(tr("Download failed:\n%1.").arg(reply->errorString())); - downloadButton->setEnabled(true); - reply->deleteLater(); - reply = nullptr; - return; - } - - const QVariant redirectionTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); - - reply->deleteLater(); - reply = nullptr; - - if (!redirectionTarget.isNull()) { - const QUrl redirectedUrl = url.resolved(redirectionTarget.toUrl()); - if (QMessageBox::question(this, tr("Redirect"), - tr("Redirect to %1 ?").arg(redirectedUrl.toString()), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { - QFile::remove(fi.absoluteFilePath()); + // For "request aborted" we handle the label and button in cancelDownload() + if (!httpRequestAborted) { + statusLabel->setText(tr("Download failed:\n%1.").arg(errorString)); downloadButton->setEnabled(true); - statusLabel->setText(tr("Download failed:\nRedirect rejected.")); - return; } - file = openFileForWrite(fi.absoluteFilePath()); - if (!file) { - downloadButton->setEnabled(true); - return; - } - startRequest(redirectedUrl); return; } + //! [networkreply-error-handling-2] statusLabel->setText(tr("Downloaded %1 bytes to %2\nin\n%3") - .arg(fi.size()).arg(fi.fileName(), QDir::toNativeSeparators(fi.absolutePath()))); + .arg(fi.size()) + .arg(fi.fileName(), QDir::toNativeSeparators(fi.absolutePath()))); if (launchCheckBox->isChecked()) QDesktopServices::openUrl(QUrl::fromLocalFile(fi.absoluteFilePath())); downloadButton->setEnabled(true); } +//! [networkreply-readyread-2] void HttpWindow::httpReadyRead() { - // this slot gets called every time the QNetworkReply has new data. + // This slot gets called every time the QNetworkReply has new data. // We read all of its new data and write it into the file. // That way we use less RAM than when reading it at the finished() // signal of the QNetworkReply if (file) file->write(reply->readAll()); } +//! [networkreply-readyread-2] void HttpWindow::enableDownloadButton() { downloadButton->setEnabled(!urlLineEdit->text().isEmpty()); } +//! [qnam-auth-required-2] void HttpWindow::slotAuthenticationRequired(QNetworkReply *, QAuthenticator *authenticator) { QDialog authenticationDialog; @@ -312,7 +296,7 @@ void HttpWindow::slotAuthenticationRequired(QNetworkReply *, QAuthenticator *aut authenticationDialog.adjustSize(); ui.siteDescription->setText(tr("%1 at %2").arg(authenticator->realm(), url.host())); - // Did the URL have information? Fill the UI + // Did the URL have information? Fill the UI. // This is only relevant if the URL-supplied credentials were wrong ui.userEdit->setText(url.userName()); ui.passwordEdit->setText(url.password()); @@ -322,9 +306,11 @@ void HttpWindow::slotAuthenticationRequired(QNetworkReply *, QAuthenticator *aut authenticator->setPassword(ui.passwordEdit->text()); } } +//! [qnam-auth-required-2] -#ifndef QT_NO_SSL -void HttpWindow::sslErrors(QNetworkReply *, const QList<QSslError> &errors) +#if QT_CONFIG(ssl) +//! [sslerrors-2] +void HttpWindow::sslErrors(const QList<QSslError> &errors) { QString errorString; for (const QSslError &error : errors) { @@ -339,4 +325,5 @@ void HttpWindow::sslErrors(QNetworkReply *, const QList<QSslError> &errors) reply->ignoreSslErrors(); } } +//! [sslerrors-2] #endif diff --git a/examples/network/http/httpwindow.h b/examples/network/http/httpwindow.h index f7bd0047de..4bb99611bb 100644 --- a/examples/network/http/httpwindow.h +++ b/examples/network/http/httpwindow.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the examples of the Qt Toolkit. @@ -74,7 +74,6 @@ class ProgressDialog : public QProgressDialog { public: explicit ProgressDialog(const QUrl &url, QWidget *parent = nullptr); - ~ProgressDialog(); public slots: void networkReplyProgress(qint64 bytesRead, qint64 totalBytes); @@ -97,8 +96,8 @@ private slots: void httpReadyRead(); void enableDownloadButton(); void slotAuthenticationRequired(QNetworkReply *, QAuthenticator *authenticator); -#ifndef QT_NO_SSL - void sslErrors(QNetworkReply *, const QList<QSslError> &errors); +#if QT_CONFIG(ssl) + void sslErrors(const QList<QSslError> &errors); #endif private: @@ -113,9 +112,9 @@ private: QUrl url; QNetworkAccessManager qnam; - QNetworkReply *reply; + QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> reply; std::unique_ptr<QFile> file; - bool httpRequestAborted; + bool httpRequestAborted = false; }; #endif |