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>
This commit is contained in:
Mårten Nordheim 2020-10-21 18:31:13 +02:00
parent cf03874dbb
commit dfca01e186
3 changed files with 122 additions and 71 deletions

View File

@ -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.
*/

View File

@ -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()));
// 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);
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());
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

View File

@ -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