Search code examples
androidqtopenssl

How do I fix HTTPS requests not working with 'android_openssl'?


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.

  • If I try to query the QNetworkReply object later (e.g. m_reply->errorString()), I get a segfault.
  • I've tried changing the Qt version I'm building with, only get a successful build on 6.6.3 and 6.7.2, but both exhibit the same problem.
  • I've tried changing the NDK to match the one that the SSL library was built with (25.2.*) to no avail.
  • I've tried turning on Qt's logging facility to see where it gets hung up, and it appears nothing happens (or at least nothing relevant appears in the logs) after it loads the library.

What can I do to find out what's at fault? Is it a broken android_openssl or am I doing something wrong?


Solution

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