Qt-ohjelmointi
Qt HTTP

Kun Qt-sovelluksessa halutaan käyttää HTTP-protokollaa, projektiin tulee lisätä Qt Network -moduuli seuraavasti:
qmake:
pro-tiedostoon

QT +=network

CMake:
CMakeLists-tiedostoon
  1. find_package osioon Network
  2. target_link_libraries osioon Network
Tällöin sinulla on jotain tämän kaltaista CMakeLists-tiedostossa (tämä on konsoliprojektista):
find_package(Qt6 6.5 REQUIRED COMPONENTS Core Network)

qt_standard_project_setup()

qt_add_executable(projektin_nimi
    main.cpp
)

target_link_libraries(projektin_nimi
    PRIVATE
        Qt::Core
        Qt::Network
)

QtNetwork on Qt-kirjaston moduuli, joka tarjoaa kehittäjille työkaluja verkko-ohjelmointiin ja verkkoyhteyksien hallintaan Qt-sovelluksissa. Moduuli sisältää muun muassa seuraavat luokat:

LuokkaSelite
QNetworkAccessManagerQNetworkAccessManager on QtNetwork-moduulin luokka, joka tarjoaa pääsyn verkkoresursseihin ja hallinnoi verkkopyyntöjä. Sen avulla voidaan helposti luoda ja käsitellä HTTP-pyyntöjä ja -vastauksia Qt-sovelluksissa.
QNetworkRequestQNetworkRequest on QtNetwork-moduulin luokka, joka edustaa HTTP- tai muuta verkko-pyyntöä.
QNetworkReplyQNetworkReply on QtNetwork-moduulin luokka, josta luodun olion avulla päästään käsiksi http-response dataan.
QJsonDocumentQJsonDocument on Qt:n luokka, jota käytetään JSON-objektien ja -taulukoiden (QJsonObject ja QJsonArray) lukemiseen ja kirjoittamiseen.
QJsonArrayQJsonArray-luokan avulla voidaan käsitellä JSON-array-tyyppistä dataa.
QJsonObjectQJsonObject luokan avulla voidaan käsitellä JSON-object tyyppistä dataa.

QByteArray on Qt-frameworkin luokka, joka on tarkoitettu binaaridatan ja tavujonojen käsittelyyn. Se tallentaa dataa peräkkäisinä tavuina (char) ja sopii erityisesti tilanteisiin, joissa käsitellään raakadataa, kuten tiedostoja, verkkoviestintää tai binääriprotokollia.

HTTP-responsen data luetaan tämän sivun esimerkeissä QByteArray-luokan olioon.

HTTP request
HTTP liikenne toimii asynkronisesti, joten käytetään Signal-Slot systeemiä. Alla lähetetään HTTP GET metodilla request.
HTTP Request/Response -prosessi Qt:ssa
Qt-sovellus
QNetworkAccessManager
manager
QNetworkReply
reply
lambda-funktio
vastauksen käsittely
⚡ finished signal

GET request

HTTP response
Palvelin
REST API

📡
1 manager (QNetworkAccessManager) aktivoituu
2 reply (QNetworkReply) -olio luodaan
3 HTTP GET request lähtee palvelimelle
4 HTTP response palaa takaisin
5 finished signal laukeaa
6 lambda-funktio käsittelee vastauksen

Seuraavissa esimerkeissä tarvitaan alla olevat include-rivit:

#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>

Huom! Seuraavissa esimerkeissä manager on luokan jäsenmuuttuja. Se tulee määritellä header-tiedostossa seuraavasti:

private:
    QNetworkAccessManager *manager;
manager on QNetworkAccessManager-tyyppinen osoitin, joka hallinnoi kaikkia verkkopyyntöjä sovelluksessa. Sen tehtävänä on lähettää HTTP-pyynnöt ja palauttaa niihin liittyvät vastaukset.

Huom! Qt suosittelee, että QNetworkAccessManager luodaan vain kerran sovelluksen elinkaaren aikana, esimerkiksi luokan konstruktorissa.

Esimerkki

Seuraava esimerkki näyttää kuinka lähetetään HTTPP-request GET-metodia käyttäen ja luetaan vastaus QByteArray tyyppiseen muuttujaan.

QString site_url="http://localhost:3000/book";
QNetworkRequest request(site_url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

//luo manager konstruktorissa, jos sinulla on monta requestia sovelluksessa
manager = new QNetworkAccessManager(this);

QNetworkReply *reply = manager->get(request);
connect(reply, &QNetworkReply::finished, this, [reply, this]() {
    // Tarkistetaan verkkovirheet
    if (reply->error() != QNetworkReply::NoError) {
        qWarning() << "Network error:" << reply->errorString();
        reply->deleteLater();
        return;
    }

    QByteArray response_data = reply->readAll();
    // Käsittele vastaus tässä

    reply->deleteLater();
});
Kun HTTP response saapuu, QNetworkAccessManager emittoi finished signaalin, joka on edellä kytketty lambda-funktioon. Lambda-funktio käsittelee vastauksen suoraan siinä missä pyyntö tehdään, mikä tekee koodista selkeämpää ja helpommin luettavaa.

📚 Lisätietoa Lambdasta (Olet opiskellut REST APIn tekoa JavaScriptillä, joten huomaa, että C++:n Lambda vastaa siellä oppimiasi JavaScriptin anonyymifunktioita ja nuolifunktioita.)

deleteLater()-metodia käytetään yleensä Qt-sovelluksissa tilanteissa, joissa halutaan poistaa dynaamisesti luotu olio turvallisesti. Tämä metodi asettaa olion poistettavaksi myöhemmin Qt:n tapahtumakäsittelyjärjestelmän kautta, mikä varmistaa, että olio poistetaan oikeassa kohtaa ohjelman suoritusjärjestystä. Tämä on erityisen tärkeää, jos olio on sidottu muihin osiin ohjelmaa, kuten signaaleihin.

Webtoken autentikointi
Edellinen esimerkki, jos API vaatii webtokenia
QString site_url="http://localhost:3000/book";
QNetworkRequest request(site_url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

//WEBTOKEN ALKU
    //Onnistuneen loginin seurauksena saadaan arvo muuttujalle webToken, jonka
    //tietotyyppi on QByteArray ja sen eteen asetetaan merkkijono "Bearer "
QByteArray myToken="Bearer "+webToken.toUtf8();
request.setRawHeader(QByteArray("Authorization"),(myToken));
//WEBTOKEN LOPPU

//luo manager konstruktorissa, jos sinulla on monta requestia sovelluksessa
manager = new QNetworkAccessManager(this);

QNetworkReply *reply = manager->get(request);
connect(reply, &QNetworkReply::finished, this, [reply, this]() {
    // Tarkistetaan verkkovirheet
    if (reply->error() != QNetworkReply::NoError) {
        qWarning() << "Network error:" << reply->errorString();
        reply->deleteLater();
        return;
    }

    QByteArray response_data = reply->readAll();
    // Käsittele vastaus tässä

    reply->deleteLater();
});
Huom! Jos käytetään Bearer-token menetelmää, niin varsinaisen tokenin (eli webToken) eteen on siis kirjoitettava sana Bearer.

Huom! Metodi setRawHeader on määritelty siten, että sekä avaimen ("Authorization") että arvon (Bearer-token) pitää olla QByteArray. Jos sinulla token on tyypiltään QString, voit muuttaa sen QByteArrayksi näin:

QByteArray myToken="Bearer "+webToken.toUtf8();

HTTP GET Request -rakenne (webtoken-autentikoinnilla)

HTTP Request Line: GET http://localhost:3000/book HTTP/1.1 Headers: Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6... ← webToken Body: (tyhjä - GET-pyynnöissä ei yleensä ole body:ä) 💡 Bearer-token lisätään Authorization-headeriin muodossa: "Bearer " + webToken
Http responsen käsittely

Edellä siis tehtiin http-request ja finished-signaali kytkettiin lambda-funktioon. Lambda-funktio käsittelee responsen suoraan connect-kutsun yhteydessä.

HTTP status koodi

HTTP resonsen status koodi voidaan lukea seuraavalla koodilla

int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();

Responsena JSON-objekti
Mikäli HTTP response sisältää JSON objektin, voidaan se käsitellä lambda-funktion sisällä seuraavalla koodilla:
QNetworkReply *reply = manager->get(request);
connect(reply, &QNetworkReply::finished, this, [reply, this]() {
    // Tarkistetaan verkkovirheet
    if (reply->error() != QNetworkReply::NoError) {
        qWarning() << "Network error:" << reply->errorString();
        reply->deleteLater();
        return;
    }

    QByteArray response_data = reply->readAll();
    // Muunnetaan vastaus QByteArray-tyyppisestä JSON-dokumentiksi
    QJsonDocument json_doc = QJsonDocument::fromJson(response_data);
    // Muunnetaan JSON-dokumentti JSON-objektiksi
    QJsonObject json_obj = json_doc.object();
    // Käsitellään JSON-objektia

    reply->deleteLater();
});
Responsena JSON-array
Mikäli HTTP response sisältää JSON-arrayn, voidaan se käsitellä lambda-funktion sisällä seuraavalla koodilla:
QNetworkReply *reply = manager->get(request);
connect(reply, &QNetworkReply::finished, this, [reply, this]() {
    // Tarkistetaan verkkovirheet
    if (reply->error() != QNetworkReply::NoError) {
        qWarning() << "Network error:" << reply->errorString();
        reply->deleteLater();
        return;
    }

    QByteArray response_data = reply->readAll();
    // Muunnetaan vastaus QByteArray-tyyppisestä JSON-dokumentiksi
    QJsonDocument json_doc = QJsonDocument::fromJson(response_data);
    // Muunnetaan JSON-dokumentti JSON-arrayksi
    QJsonArray json_array = json_doc.array();
    // Käsitellään JSON-arraytä

    reply->deleteLater();
});
fromJson-metodi

fromJson metodia, voidaan kutsua myös antamalla QJsonParseError-viite seuraavasti:

QJsonParseError parseError;
QJsonDocument json_doc = QJsonDocument::fromJson(response_data, &parseError);
Silloin voidaan tarkistaa tapahtuiko virhe seuraavasti:
if (parseError.error != QJsonParseError::NoError) {
    qWarning() << "JSON parsing error:" << parseError.errorString();
    // Tässä vaiheessa json_doc ei ole kelvollinen
}

JSON-objektin kenttiin päästään käsiksi seuraavasti:

int book_id = json_obj["id_book"].toInt();
QString book_name = json_obj["name"].toString();

JSON-array:tä voidaan käsitellä seuraavalla foreach-loopilla:

foreach (const QJsonValue &value, json_array) {
    // Toimenpiteet
}

Jos haluat päästä käsiksi JSON-arrayn yksittäisen objektin kenttään, voit käyttää seuraavaa rakennetta

QString book_name = json_array.at(0)["name"].toString();
Edellä QStringiin nimeltään book_name sijoitetaan ensimmäisen objektin (at(0)) name kentän arvo.

JSON-datan käsittely C++-olioiden avulla

Käytännössä JSON-data kannattaa usein mäpätä C++-olioiksi, jolloin sitä on helpompi käsitellä. Esimerkiksi edellisten esimerkkien tapauksessa voisi luoda Book-luokan seuraavilla koodeilla:

#ifndef BOOK_H
#define BOOK_H

#include <qjsonobject.h>

class Book
{
public:
    int id;
    QString name;
    QString author;

    static Book mapJson(const QJsonObject &json);
};

#endif // BOOK_H
#include "book.h"

Book Book::mapJson(const QJsonObject &json) {
    Book book;
    book.id = json["id_book"].toInt();
    book.name = json["name"].toString();
    book.author = json["author"].toString();
    return book;
}

Jos http-responsena saadaan JSON-objekti voidaan luoda Book-luokan olio seuraavasti:

Book book = Book::mapJson(json_obj);

Jos http-responsena saadaan json array voidaan luoda Book-olioita sisältävä QVector seuraavasti:

QVector<Book> bookList;

    for (const QJsonValue &value : json_array) {
        if (value.isObject()) {
            Book book = Book::mapJson(value.toObject());
            bookList.append(book);
        }
    }

Edellä metodi mapJson määriteltiin staattiseksi, jotta:

  1. Se ei tarvitse olemassaolevaa oliota toimiakseen
  2. Sen on tarkoitus luoda uusi Book-olio annetusta json-objektista
Kyseessä on siis ns. factory-metodi.

🚀 Lambda-funktiot C++:ssa

×

Mikä on lambda-funktio?

Lambda-funktio on anonyymi funktio, jonka voi määritellä suoraan koodin keskellä ilman erillistä funktio-määrittelyä. Lambda-funktiot ovat erityisen hyödyllisiä Qt:n signal-slot -yhteyksissä, koska vastauksen käsittely voidaan kirjoittaa samaan paikkaan missä pyyntö tehdään.

Lambda-funktion syntaksi

[kaappauslista](parametrit) { funktio-runko }

Esimerkki Qt:n HTTP-vastauksesta:

connect(reply, &QNetworkReply::finished, this, [reply, this]() { QByteArray data = reply->readAll(); qDebug() << "Vastaus:" << data; reply->deleteLater(); });

Kaappauslista (Capture List)

Kaappauslista määrittää, mitkä ulkopuoliset muuttujat ovat käytettävissä lambda-funktion sisällä. Tämä on lambda-funktioiden tärkein ominaisuus!

Kaappauslista Selitys Esimerkki
[] Ei kaappaa mitään muuttujia []() { qDebug() << "Hei"; }
[reply, this] Kaappaa reply ja this osoittimet arvon mukaan [reply, this]() { reply->deleteLater(); }
[=] Kaappaa kaikki käytetyt muuttujat arvon mukaan (copy) [=]() { qDebug() << x << y; }
[&] Kaappaa kaikki käytetyt muuttujat viitteen mukaan (reference) [&]() { x = 10; y = 20; }
[&x, y] Kaappaa x viitteenä ja y arvona [&x, y]() { x = y + 1; }
[this] Kaappaa this-osoittimen (pääsee käsiksi jäsenmuuttujiin) [this]() { this->manager->get(req); }
💡 Qt:ssa yleisimmät kaappauslistat:
  • [reply, this] - HTTP-vastauksissa
  • [this] - Kun tarvitaan vain luokan jäsenmuuttujia
  • [=] - Kun halutaan kaappaa kaikki paikalliset muuttujat

Miksi käyttää lambda-funktiota?

❌ Vanha tapa (slot-funktio):

// mainwindow.h private slots: void handleGetBook(); // mainwindow.cpp - button click handler QNetworkReply *reply = manager->get(request); connect(reply, &QNetworkReply::finished, this, &MainWindow::handleGetBook); // mainwindow.cpp - erillinen funktio void MainWindow::handleGetBook() { QByteArray data = reply->readAll(); // käsittely... reply->deleteLater(); }

Ongelma: Koodi on hajallaan useassa paikassa. Täytyy hyppiä tiedostojen välillä ymmärtääkseen mitä tapahtuu.

✅ Uusi tapa (lambda-funktio):

// mainwindow.cpp - button click handler QNetworkReply *reply = manager->get(request); connect(reply, &QNetworkReply::finished, this, [reply, this]() { if (reply->error() != QNetworkReply::NoError) { qWarning() << "Virhe:" << reply->errorString(); reply->deleteLater(); return; } QByteArray data = reply->readAll(); // käsittely... reply->deleteLater(); });

Edut:

  • ✅ Kaikki koodi samassa paikassa - helpompi lukea ja ymmärtää
  • ✅ Ei tarvitse määritellä slot-funktiota header-tiedostoon
  • ✅ Vähemmän koodia kirjoitettavaksi
  • ✅ Modernimpi C++-tyyli (C++11 ja uudemmat)

Esimerkkejä Qt:n HTTP-yhteyksissä

Esimerkki 1: GET-pyyntö lambda-funktiolla

QString url = "http://localhost:3000/api/users"; QNetworkRequest request(url); QNetworkReply *reply = manager->get(request); connect(reply, &QNetworkReply::finished, this, [reply, this]() { if (reply->error() == QNetworkReply::NoError) { QJsonDocument doc = QJsonDocument::fromJson(reply->readAll()); QJsonArray users = doc.array(); // Käsittele käyttäjälista } reply->deleteLater(); });

Esimerkki 2: POST-pyyntö lambda-funktiolla

QJsonObject data; data["username"] = "matti"; data["password"] = "salasana123"; QNetworkRequest request(QUrl("http://localhost:3000/login")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QNetworkReply *reply = manager->post(request, QJsonDocument(data).toJson()); connect(reply, &QNetworkReply::finished, this, [reply, this]() { if (reply->error() == QNetworkReply::NoError) { QJsonObject response = QJsonDocument::fromJson(reply->readAll()).object(); QString token = response["token"].toString(); qDebug() << "Kirjautuminen onnistui! Token:" << token; } else { qWarning() << "Kirjautuminen epäonnistui:" << reply->errorString(); } reply->deleteLater(); });

Esimerkki 3: Paikallisen muuttujan kaappaaminen

int requestId = 42; // Paikallinen muuttuja QString username = "matti"; QNetworkReply *reply = manager->get(request); connect(reply, &QNetworkReply::finished, this, [reply, this, requestId, username]() { qDebug() << "Vastaus pyyntöön #" << requestId << "käyttäjälle" << username; // Voit käyttää requestId ja username muuttujia täällä! reply->deleteLater(); });

Tärkeät huomiot

⚠️ Muista aina:
  • Kaappaa reply lambda-funktioon, jotta voit käsitellä vastausta
  • Kaappaa this jos tarvitset luokan jäsenmuuttujia tai -funktioita
  • Kutsu aina reply->deleteLater() lambda-funktion lopussa
  • Tarkista aina verkkovirheet reply->error() avulla
💡 Pro-vinkki: Lambda-funktiot tukevat myös paluuarvoja ja parametreja:
// Lambda parametreilla auto sum = [](int a, int b) { return a + b; }; int result = sum(5, 3); // result = 8 // Lambda paluuarvolla auto getToken = [this]() -> QString { return this->authToken; }; QString token = getToken();



Toggle Menu