I'm working on an Android app using Qt. I need to download a file from an HTTPS URL. App developers must now ship their own copy of OpenSSL. The easiest way I've found to do this is with the android_openssl project. This theoretically provides Qt with a usable TLS backend to use for downloading files from HTTPS servers.
To clone the minimal reproducible example:
git clone https://github.com/ftab/QuickDownloadTest --recursive
CMakeLists.txt (modified from what was generated by the Qt Creator new project wizard with the necessary lines to add android_openssl
):
cmake_minimum_required(VERSION 3.16)
project(QuickDownloadTest VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 6.5 REQUIRED COMPONENTS Quick Network)
qt_standard_project_setup(REQUIRES 6.5)
include(android_openssl/android_openssl.cmake)
qt_add_executable(appQuickDownloadTest
main.cpp
)
add_android_openssl_libraries(appQuickDownloadTest)
qt_add_qml_module(appQuickDownloadTest
URI QuickDownloadTest
VERSION 1.0
QML_FILES
Main.qml
)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
set_target_properties(appQuickDownloadTest PROPERTIES
# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appQuickDownloadTest
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
target_link_libraries(appQuickDownloadTest
PRIVATE Qt6::Quick Qt6::Network
)
include(GNUInstallDirs)
install(TARGETS appQuickDownloadTest
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QObject>
#include <QNetworkReply>
class Downloader : public QObject
{
Q_OBJECT
public:
explicit Downloader(QObject* parent = nullptr) : QObject(parent)
{
};
Q_INVOKABLE void downloadFile(const QString &url)
{
QNetworkAccessManager manager;
QNetworkRequest request((QUrl(url)));
m_reply = manager.get(request);
QObject::connect(m_reply, &QNetworkReply::finished, this, &Downloader::on_fileDownloaded);
QObject::connect(m_reply, &QNetworkReply::errorOccurred, this, &Downloader::on_errorOccurred);
};
Q_INVOKABLE QByteArray data()
{
return m_data;
};
Q_INVOKABLE QString toString()
{
return QString(m_data);
};
public slots:
void on_fileDownloaded()
{
if (m_reply->error() == QNetworkReply::NoError)
{
m_data = m_reply->readAll();
qDebug() << "Download success";
emit success();
}
else
{
qDebug() << "Download error:" << m_reply->errorString();
emit error();
}
m_reply->deleteLater();
m_reply = nullptr;
};
void on_errorOccurred(QNetworkReply::NetworkError error)
{
qDebug() << "Download error occurred:" << error;
};
signals:
void error();
void success();
private:
QByteArray m_data;
QNetworkReply *m_reply{nullptr};
};
#include "main.moc"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
auto context = engine.rootContext();
auto downloader = new Downloader();
context->setContextProperty("Downloader", downloader);
engine.loadFromModule("QuickDownloadTest", "Main");
return app.exec();
}
Main.qml:
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Window
ApplicationWindow {
visible: true
flags: Qt.Window | Qt.FramelessWindowHint
ColumnLayout {
Button {
text: "Download File"
onClicked: Downloader.downloadFile("https://gist.githubusercontent.com/ftab/818fbe5080a18ecb7332c7fe4542eeb3/raw/82a9750622a5a4db4c5250068cf2f6bba117ff51/test.txt");
}
Label {
id: label
text: "Result"
}
}
Connections {
target: Downloader
function onSuccess()
{
label.text = Downloader.toString();
}
function onError()
{
label.text = "Error";
}
}
}
(Configure the Qt project to build for Android on your desired test device)
I'm expecting the download to work and the label to be updated with the contents of my test.txt
file ("v1.0.1").
What actually happens is...nothing. Apparently. Once I've installed android_openssl
, I don't get any errors on trying to download, but the request just never seems to actually do anything. I don't get any finished signal, error signal, or any indication that anything is actually happening.
m_reply->errorString()
), I get a segfault.What can I do to find out what's at fault? Is it a broken android_openssl or am I doing something wrong?
The reason may be that you are destroying QNetworkAccessManager
before the reply has finished. It must remain alive until the answer has arrived. Citing from the specs:
QNetworkAccessManager::~QNetworkAccessManager()
Destroys the QNetworkAccessManager object and frees up any resources. Note that QNetworkReply objects that are returned from this class have this object set as their parents, which means that they will be deleted along with it if you don't call QObject::setParent() on them.
I use android_openssl and it works properly for me.