Search code examples
c++jsonqtqnetworkaccessmanagerqnetworkreply

Qt get external IP address using QNetworkReply


Good day

Intro:

My application requires getting the external IP address and matching it with an internally acquired address, thus allowing the application to proceed.

For this, I am using a QNetworkAccessManager and QNetworkReply for this purpose.

My code was built using this example as a reference.

What I have tried:

Acquiring an external IP can be done by getting a JSon object from the ipify API.

I confirmed this by:

curl "https://api.ipify.org?format=json"

which in turn responds with my current IP address in the format:

{"ip":"255.255.255.255"}

which is a JSonObject. Using this, I created the code below.

Problem:

The problem is quite simple, I get no response. The post request is executed but simply no response (or finished) signal is ever triggered.

  • POST -> GET request

I have changed the code for a get request as this solved this no response issue, found on this thread.

I did this by specifying the whole url with query parameters in the URL:

QNetworkRequest request(QUrl("https://api.ipify.org?format=json"));

including the header content type and size (as in the example below, finally calling the QNetworkAccessManager::get() with:

replyExternalAddress = networkManager->get(request);

but this too gave no response.

I figured that it is something small that I am missing, but I simply cannot see it.

Advice?


Code for querying external IP:

// public callable method, starting network request
void APICommunicator::requestExternalAddress(){
    qInfo(apicommunicator) << "Requesting external IP address from ipify.org";

    // creates network request
    // specifies "format=json"
    QUrlQuery postData;
    postData.addQueryItem("format", "json");
    QByteArray encodedQuery = postData.toString(QUrl::FullyEncoded).toUtf8();
    QNetworkRequest request(QUrl("https://api.ipify.org"));

    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
    request.setHeader(QNetworkRequest::ContentLengthHeader, QString::number(encodedQuery.size()));

    // creates merged URL from URL and query items and sends a post:
    //    https://api.ipify.org?format=json
    replyExternalAddress = networkManager->post(request, encodedQuery);

    // Creates QMetaObject::Connection connection for finished signal from QNetworkReply
    conExternalAddress = QObject::connect(replyExternalAddress, SIGNAL(finished()), this, SLOT(externalAddressResponse()));

    // attach error listener to reply
    addErrorListener(replyExternalAddress, conExternalAddress);


}

void APICommunicator::externalAddressResponse(){
    qDebug(apicommunicator) << "External Address response recieved";

    // disconnect signals
    QObject::disconnect(conExternalAddress);
    QObject::disconnect(conErrorListener);

    // read all output from JSon object
    QByteArray ba = replyExternalAddress->readAll();

    // delete QNetworkReply
    replyExternalAddress->deleteLater();

    LogMessageHandler::writeToApiLog(QString("\n\nCALL EXTERNAL [" + replyExternalAddress->request().url().toString() + "]\n" + QString(ba)));

    QJsonObject doc = QJsonDocument::fromJson(ba).object();
    QString ip = doc.value("ip").toString();    
    QHostAddress address = QHostAddress();

    if (ip.isEmpty()) {
        qWarning(apicommunicator) << "External Address: no data received";
    }
    else {
        address = QHostAddress(version);
    }

    // replies with address to external slot (in main application)
    emit ExternalAddressReply(address);
}

Solution

  • The problem is that you are sending a POST request, while ipify.org expects only GET requests. You seem to have the misconception that you need to be sending a POST request in order to be able to send parameters (format=json) along with your request, this is not true. In your code, you are sending the parameters as POST data, this is the same technique that is used when you submit a web form in your browser (because you are setting the content type header to application/x-www-form-urlencoded).

    You absolutely don't need to mimic the request of a web browser sending a form in order to be able to talk to the API ipify.org provides. ipify.org provides a much simpler interface; you just need to send your query string in your get request. Qt makes the job even easier by providing the class QUrlQuery that provides a way to build url queries. Here is a working example:

    #include <QtCore>
    #include <QtNetwork>
    
    int main(int argc, char *argv[]) {
        QCoreApplication a(argc, argv);
    
        QNetworkAccessManager networkManager;
    
        QUrl url("https://api.ipify.org");
        //the query used to add the parameter "format=json" to the request
        QUrlQuery query;
        query.addQueryItem("format", "json");
        //set the query on the url
        url.setQuery(query);
    
        //make a *get* request using the above url
        QNetworkReply* reply = networkManager.get(QNetworkRequest(url));
    
        QObject::connect(reply, &QNetworkReply::finished,
                         [&](){
            if(reply->error() != QNetworkReply::NoError) {
                //failure
                qDebug() << "error: " << reply->error();
            } else { //success
                //parse the json reply to extract the IP address
                QJsonObject jsonObject= QJsonDocument::fromJson(reply->readAll()).object();
                QHostAddress ip(jsonObject["ip"].toString());
                //do whatever you want with the ip
                qDebug() << "external ip: " << ip;
            }
            //delete reply later to prevent memory leak
            reply->deleteLater();
            a.quit();
        });
        return a.exec(); 
    }