GET-Request mit Boost? oder was sonst?



  • Hallo zusammen!

    Ich möchte per HTTP GET-Request Daten abrufen und habe dazu den Beispielcode von boost.asio hergenommen.

    //
    // sync_client.cpp
    // ~~~~~~~~~~~~~~~
    //
    // Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
    //
    // Distributed under the Boost Software License, Version 1.0. (See accompanying
    // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
    //
    
    #include <iostream>
    #include <istream>
    #include <ostream>
    #include <string>
    #include <boost/asio.hpp>
    
    using boost::asio::ip::tcp;
    
    int main(int argc, char* argv[])
    {
      try
      {
        if (argc != 3)
        {
          std::cout << "Usage: sync_client <server> <path>\n";
          std::cout << "Example:\n";
          std::cout << "  sync_client www.boost.org /LICENSE_1_0.txt\n";
          return 1;
        }
    
        boost::asio::io_service io_service;
    
        // Get a list of endpoints corresponding to the server name.
        tcp::resolver resolver(io_service);
        tcp::resolver::query query(argv[1], "http");
        tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
    
        // Try each endpoint until we successfully establish a connection.
        tcp::socket socket(io_service);
        boost::asio::connect(socket, endpoint_iterator);
    
        // Form the request. We specify the "Connection: close" header so that the
        // server will close the socket after transmitting the response. This will
        // allow us to treat all data up until the EOF as the content.
        boost::asio::streambuf request;
        std::ostream request_stream(&request);
        request_stream << "GET " << argv[2] << " HTTP/1.0\r\n";
        request_stream << "Host: " << argv[1] << "\r\n";
        request_stream << "Accept: */*\r\n";
        request_stream << "Connection: close\r\n\r\n";
    
        // Send the request.
        boost::asio::write(socket, request);
    
        // Read the response status line. The response streambuf will automatically
        // grow to accommodate the entire line. The growth may be limited by passing
        // a maximum size to the streambuf constructor.
        boost::asio::streambuf response;
        boost::asio::read_until(socket, response, "\r\n");
    
        // Check that response is OK.
        std::istream response_stream(&response);
        std::string http_version;
        response_stream >> http_version;
        unsigned int status_code;
        response_stream >> status_code;
        std::string status_message;
        std::getline(response_stream, status_message);
        if (!response_stream || http_version.substr(0, 5) != "HTTP/")
        {
          std::cout << "Invalid response\n";
          return 1;
        }
        if (status_code != 200)
        {
          std::cout << "Response returned with status code " << status_code << "\n";
          return 1;
        }
    
        // Read the response headers, which are terminated by a blank line.
        boost::asio::read_until(socket, response, "\r\n\r\n");
    
        // Process the response headers.
        std::string header;
        while (std::getline(response_stream, header) && header != "\r")
          std::cout << header << "\n";
        std::cout << "\n";
    
        // Write whatever content we already have to output.
        if (response.size() > 0)
          std::cout << &response;
    
        // Read until EOF, writing data to output as we go.
        boost::system::error_code error;
        while (boost::asio::read(socket, response,
              boost::asio::transfer_at_least(1), error))
          std::cout << &response;
        if (error != boost::asio::error::eof)
          throw boost::system::system_error(error);
      }
      catch (std::exception& e)
      {
        std::cout << "Exception: " << e.what() << "\n";
      }
    
      return 0;
    }
    

    Leider haut mir der Code eine ganze Ladung von build-Fehlern um die Ohren, die irgendwo im Boost-Quellcode liegen und mit denen ich nichts anfangen kann.

    /usr/include/boost/system/error_code.hpp:221: Fehler: undefined reference to `boost::system::generic_category()'

    Keine Ahnung was da falsch läuft. Das Beispiel stammt von den asio-Examples C++ 03.

    Gibt es eine andere empfehlenswerte Lib, die mich zum Ziel bringt, falls das mit boost nix wird?



  • HTTP geht wohl einfacher mit zB. libcurl.



  • Betreffend dem Linker-Error: Du musst noch Boost.System dazulinken.
    Zeige zusätzlich noch, wie du dein Program baust (make, CMake...), dann kann dir besser geholfen werden.

    HTTP Client Libraries gibt es, wie Sand am Meer.

    Hier eine Auswahl:



  • theta schrieb:

    Betreffend dem Linker-Error: Du musst noch Boost.System dazulinken.
    Zeige zusätzlich noch, wie du dein Program baust (make, CMake...), dann kann dir besser geholfen werden.

    Hm, da muss ich wohl die Hose runterlassen. Ich verwende QtCreator als IDE und klicke im Prinzip einfach nur auf "RUN" 😞
    QtCreator deshalb, weil ich später schon mal auch mal mit GUI programmieren will, wenn ich die Grundlagen einigermaßen hinbekomme.

    Langer Rede kurzer Sinn, was das richtige Einbinden von externen Bibliotheken betrifft, bin ich noch ein unbeschriebenes Blatt. Boost habe ich einfach mit "apt-get install..." installiert.

    Das habe ich in den Einstellungen gefunden, falls es hilft:

    qmake /home/guido/c++/TEST_BOOST_CLIENT/TEST_BOOST_CLIENT.pro -r -spec linux-g++ CONFIG+=debug CONFIG+=qml_debug

    und in der TEST..CLIENT.pro:

    TEMPLATE = app
    CONFIG += console c++11
    CONFIG -= app_bundle
    CONFIG -= qt

    SOURCES += main.cpp



  • Ok, ich habe jetzt folgendes der .pro hinzugefügt:

    LIBS += /usr/lib/x86_64-linux-gnu/libboost_system.a

    Es sind jetzt deutlich weniger Fehler, die alle etwas mit posix_thread oder Pthread zu tun haben:

    /usr/include/boost/asio/detail/impl/posix_thread.ipp:35: Fehler: undefined reference to `pthread_detach'

    Ehrlich gesagt, ich habe keine Ahnung, was ich da überhaupt gemacht hab. 😕

    Gibt es irgendwo eine Anleitung "Einbinden externer Bibliotheken für Dummis"?



  • Ich bin mit dem Qt Addin für Visual Studio relativ verwöhnt, da werden alle Abhängigkeiten automatisch angegeben.
    Mit Linux bin ich nicht vertraut, aber dir fehlt wohl noch die pthreads-lib.
    Vversuch's mal mit libpthread.a oder so (wo die liegt, musst du schauen).
    Oder einfach mal die Suchmaschine bemühen, bist sicher nicht der Einzige mit diesen Problemen.



  • Füge noch

    LIBS += -L/<path>/boost/lib/ -lboost_thread -lboost_system
    

    zur Projektdatei hinzu, s.a. Link Boost in Qt Linux.



  • Es hat jetzt kompiliert, nachdem ich dies gemacht habe:

    LIBS += /usr/lib/x86_64-linux-gnu/libboost_system.a
    LIBS += -lpthread

    Leider habe ich weiterhin keine Ahnung, was ich überhaupt gemacht habe.



  • Th69 schrieb:

    Füge noch

    LIBS += -L/<path>/boost/lib/ -lboost_thread -lboost_system
    

    zur Projektdatei hinzu, s.a. Link Boost in Qt Linux.

    Das funktioniert nicht. Es gibt kein Verzeichnis "boost/lib". Es gibt ein Verzeichnis "usr/include/boost/" darin liegen .h und in Unterverzeichnissen .cpp Dateien.

    Außerdem gibt es noch "usr/lib/" mit Dateien wie "libboost_system.a" "libboost_system.so" "libboost_system.so.1.58.0"



  • temi schrieb:

    Leider habe ich weiterhin keine Ahnung, was ich überhaupt gemacht habe.

    temi schrieb:

    Gibt es irgendwo eine Anleitung "Einbinden externer Bibliotheken für Dummis"?

    Genau das hast du gemacht. Bibliotheken eingebunden.

    Du hast nun mal gewisse Abhängigkeiten im Code (was absolut üblich ist). Funktionen (bzw. ihre Prototypen), die du in deinem Code (indirekt) aufrufst, sind in diesen Bibliotheken enthalten (implementiert).
    Deshalb müssen diese libs eingebunden werden, damit sich am Ende alles Nötige im binary findet.



  • Ah, ok. Handelt es sich bei den eingebundenen Bibliotheken dann um bereits kompilierte Dateien?

    Wenn doch der Quelltext von boost (.h und .cpp) vorhanden ist, warum reicht es dann nicht aus, die Header einzubinden und alles wird aus dem Quelltext kompiliert?

    Das ist doch nicht viel anders, als wenn ich Header und Quelltextdateien schreibe und in der main include.



  • temi schrieb:

    Ah, ok. Handelt es sich bei den eingebundenen Bibliotheken dann um bereits kompilierte Dateien?

    Ja, siehe Objektcode.

    temi schrieb:

    Wenn doch der Quelltext von boost (.h und .cpp) vorhanden ist, warum reicht es dann nicht aus, die Header einzubinden und alles wird aus dem Quelltext kompiliert

    Es kommt einfach darauf an, wie die Lib (Boost) aufgebaut ist bzw. erstellt wird.

    Bei SQLite zB. gibt es eine "sqlite-amalgamation", das ist der komplette Source in nur einem .c/.h-Dateipaar.



  • Was ist denn der Unterschied zwischen *.a und *.so und wann benutze ich welche?

    .a steht laut BREYMANN für "..zusammengehörige Klassen und Funktionen zu Bibliotheksmodulen..".

    .so sind laut Google "shared objects"???



  • In Linux ist eine Lib .a static oder .so dynamic (shared).
    Statisch wird mit ins binary eingebaut, dynamisch liegt als extra Datei daneben.
    Hat alles Vor- und Nachteile. Einfach mal die Suchmaschine bedienen, ist sicher schon 100000x beantwortet.



  • Danke, ich versuche mich schlau zu machen...



  • Ich hab es jetzt doch mit libcurl gemacht, was mit dem Beispielcode auch sofort funktioniert hat. Ich habe auch grundsätzlich verstanden, bzw. aus der libcurl Dokumentation ermittelt, was da passiert. Ein oder zwei Fragen hätte ich dennoch:

    Diese Option habe ich in der Doku nicht gefunden.

    curl_easy_setopt(curl, CURLOPT_FILE, &os)
    

    Da es die einzige Verbindung zwischen dem ostream und der callback-Funktion ist wird dadurch vermutlich "userp" gesetzt, oder?

    Was bewirkt void*? Ein Zeiger auf beliebige Typen, oder so?

    Die callback-Funktion ist static. Muss das so sein oder gibt es noch eine andere Möglichkeit?

    Danke,
    temi

    --------------

    Hier noch der komplette Code:

    #include <curl/curl.h>
    #include <fstream>
    #include <sstream>
    #include <iostream>
    
    // callback function writes data to a std::ostream
    static size_t data_write(void* buf, size_t size, size_t nmemb, void* userp)
    {
        if(userp)
        {
            std::ostream& os = *static_cast<std::ostream*>(userp);
            std::streamsize len = size * nmemb;
            if(os.write(static_cast<char*>(buf), len))
                return len;
        }
    
        return 0;
    }
    
    /**
     * timeout is in seconds
     **/
    CURLcode curl_read(const std::string& url, std::ostream& os, long timeout = 30)
    {
        CURLcode code(CURLE_FAILED_INIT);
        CURL* curl = curl_easy_init();
    
        if(curl)
        {
            if(CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &data_write))
            && CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L))
            && CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L))
            && CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_FILE, &os))
            && CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout))
            && CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_URL, url.c_str())))
            {
                code = curl_easy_perform(curl);
            }
            curl_easy_cleanup(curl);
        }
        return code;
    }
    
    int main()
    {
        curl_global_init(CURL_GLOBAL_ALL);
    
    //    std::ofstream ofs("output.html");
    //    if(CURLE_OK == curl_read("http://google.com", ofs))
    //    {
    //        // Web page successfully written to file
    //    }
    
    //    std::ostringstream oss;
    //    if(CURLE_OK == curl_read("http://google.com", oss))
    //    {
    //        // Web page successfully written to string
    //        std::string html = oss.str();
    //    }
    
        if(CURLE_OK == curl_read("http://google.com", std::cout))
        {
            // Web page successfully written to standard output (console?)
        }
    
        curl_global_cleanup();
    }
    

  • Mod

    Das ist, wie man in C generische Daten übergibt. In C++ würde man das ganz anders machen, aber da du hier eine C-Schnittstelle benutzt, siehst du die ganzen schmutzigen Tricks 🙂

    void* ist dabei ein Zeiger auf irgendwelche Daten. Da die Funktion je nach Wert der anderen Parameter einen anderen Datentyp als drittes Argument erwartet, geht man hier den Weg über void*, da dies - neben Ellipsen (wie bei scanf/printf) - eine der wenigen Möglichkeiten ist, wie das in C geht. Genau genommen ist es nicht legal, Funktionszeiger per void* herum zu reichen, aber es wird auf den meisten Systemen trotzdem funktionieren. Der über CURLOPT_WRITEFUNCTION gesetzte Funktionszeiger wird von Curl bei bedarf aufgerufen und zwar mit den Argumenten, die vorher per CURLOPT_FILE gesetzt wurden. Und aus den gleichen Gründen wie oben, geht alles zwischendurch über void*, weil die C-Schnittstelle von Curl nicht wissen kann, was für Typen die Argumente haben.

    Die Funktion muss in dem Fall auch etwas sein, dass man über einen einfachen Funktionszeiger aufrufen kann. Da C-Schnittstellen mit Klassen und Objekten nichts anfangen können, können dies nur freie oder statische Funktionen sein, die man aufrufen kann, ohne sie an ein Objekt zu binden. Du kannst natürlich eine freie Funktion schreiben, die ein Objekt als Parameter hat, dieses per void* übergeben bekommt, den Zeiger castet, und dann eine Memberfunktion des Objekts aufruft. Das ist ziemlich genau, was hier gemacht wurde, um Curl indirekt dazu zu bringen, eine Memberfunktion eines Ostreams aufzurufen.

    PS: Und falls das nicht klar geworden sein sollte: Das würde in C++ ganz anders aussehen. Das brauchst du nur bei C-Schnittstellen. Diese kommen aber recht häufig vor, von daher ist es recht wichtig, dieses Schema zu kennen.



  • Freie Funktion würde ja im Beispiel bedeuten einfach das "static" wegzulassen?

    Das funkioniert auch, hab es grad probiert.

    Klar, "libcurl" ist eine C-Bibliothek. Ich könnte jetzt noch "libcurlpp" installieren, aber intern wird diese wohl etwas ähnliches machen, nehme ich an?

    Das würde in C++ ganz anders aussehen. Das brauchst du nur bei C-Schnittstellen.

    Liebe Kinder, macht das nicht zuhause nach! 😃 Kleiner Scherz!

    Mit das ist void* gemeint? Was wäre dann das ganz anders? Templates?



  • Wieso nutzt du boost wenn du QTCreator benutzt? Ob du später boost einbinden musst oder die QT Libs ist doch egal. Weil Qt mächtige und einfach Libs hat, um das zu bewerkstelligen. Verstehe ich irgendwie nicht.



  • Bennisen schrieb:

    Wieso nutzt du boost wenn du QTCreator benutzt? Ob du später boost einbinden musst oder die QT Libs ist doch egal. Weil Qt mächtige und einfach Libs hat, um das zu bewerkstelligen. Verstehe ich irgendwie nicht.

    Guter Einwand.

    Ich verwende QtCreator, weil ich irgendwann auch mal GUIs programmieren möchte und mich für Qt als Framework entschieden habe. Allerdings beginne ich erst mit C++ und wollte mich, bevor ich irgendwas mit Qt mache, erst mal mit den Grundlagen beschäftigen. Die IDE möchte ich aber sofort einsetzen.

    Derzeit programmiere ich also ausschließlich ohne Qt und nur Konsolenanwendungen, bis ich mich ausreichend sicher fühle, um auch die Konzepte von Qt verstehen und anwenden zu können.

    Keine Ahnung, ob das eine gute oder eine schlechte Entscheidung ist. Es ist jedenfalls eine Entscheidung. 😉

    Natürlich könnte ich trotzdem die Qt-Bibliothek verwenden. Ich hatte nur kurz gesucht und bin bei QHtml auf den Hinweis "deprecated" gestoßen, da hab ich es bleiben lassen.

    Immerhin habe ich jetzt gelernt, wie man eine externe C-Bibliothek "anwendet". Ist doch auch schon was.


Log in to reply