Захват вывода из libcurl в std :: string в c ++ под Windows - PullRequest
0 голосов
/ 20 июня 2019

Я хочу записать вывод ошибки libcurl, который обычно отправляется stderr в std::string.

Это код, который я до сих пор придумал:

#include <windows.h>
#include <streambuf>
#include <iostream>
#include <io.h>
#include <sstream>
#include <fstream>
#include <stdlib.h>
#include <stdio.h>
#include <fstream>
#include <string>
#include "curl\curl.h"

std::string data; //will hold the url's contents

// callback function for curl

size_t writeCallback(char* buf, size_t size, size_t nmemb, void* up)
{ //callback must have this declaration
    //buf is a pointer to the data that curl has for us
    //size*nmemb is the size of the buffer

    for (int c = 0; c < size * nmemb; c++)
    {
        data.push_back(buf[c]);
    }
    return size * nmemb; //tell curl how many bytes we handled
}

int main()
{
    CURL* curl; //our curl object

    curl_global_init(CURL_GLOBAL_ALL); //pretty obvious
    curl = curl_easy_init();
    CURLcode res;
    char errbuf[CURL_ERROR_SIZE];

    curl_easy_setopt(curl, CURLOPT_URL, "http://ww.example.com/path");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &writeCallback);
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); //tell curl to output its progress

    int old;
    FILE* DataFile;

    old = _dup(2); // redirect stderr to "old"


    if (fopen_s(&DataFile, "data", "w") != 0)
    {
        puts("Can't open file 'Datafile'\n");
        exit(1);
    }

    // stderr now refers to file "Datafile"
    if (-1 == _dup2(_fileno(DataFile), 2))
    {
        perror("Can't _dup2 stderr");
        exit(1);
    }

    res = curl_easy_perform(curl);

    // Flush stderr stream buffer so it goes to correct file
    fflush(stderr);
    fclose(DataFile);

    // Restore original stderr
    _dup2(old, 2);


    curl_easy_cleanup(curl);
    curl_global_cleanup();

    std::ifstream filetoread(DataFile);

    if (filetoread)
    {
        // get length of file:
        filetoread.seekg(0, filetoread.end);
        int length = filetoread.tellg();
        filetoread.seekg(0, filetoread.beg);

        char* buffer = new (std::nothrow) char[length];
        if (buffer == nullptr)
        {
            std::cout << "Error allocating buffer.\nvariable 'length' is: " << length << std::endl;
            return 0;
        }

        std::cout << "Reading " << length << " characters... ";
        // read data as a block:
        filetoread.read(buffer, length);

        if (filetoread)
            std::cout << "all characters read successfully.";
        else
            std::cout << "error: only " << filetoread.gcount() << " could be read";
        filetoread.close();

        // ...buffer contains the entire file...
        std::string buffertext(buffer);
        delete[] buffer;
    }
    _flushall();
}

Мне кажется, что файл Datafile пуст, поскольку переменная длина равна -1.Кто-нибудь может помочь с этим кодом?Примеры из перенаправления stdout / stderr на строку до сих пор не решили моего вопроса.

1 Ответ

0 голосов
/ 30 июня 2019

Я бы использовал пары FUNCTION/DATA в классе-обертке, чтобы иметь возможность создавать обработчики событий, которые вызываются автоматически для событий, которые вы хотите обработать. Вот пример с комментариями в коде:

#include "curl/curl.h"
#include <iostream>
#include <memory>
#include <stdexcept>
#include <utility>

// A simple wrapper class for the C functions in libcurl
class EasyCurly {
public:
    // default constructor
    EasyCurly() : handle(curl_easy_init()) {
        if(handle == nullptr) throw std::runtime_error("curl_easy_init failed");

        // Set "this" as data pointer in callbacks to be able to make a call to the
        // correct EasyCurly object. There are a lot more callback functions you
        // could add here if you need them.
        setopt(CURLOPT_WRITEDATA, this);
        setopt(CURLOPT_DEBUGDATA, this);
        setopt(CURLOPT_XFERINFODATA, this);

        // Setup of proxy/callback functions. There should be one for each function
        // above.
        setopt(CURLOPT_WRITEFUNCTION, write_callback);
        setopt(CURLOPT_DEBUGFUNCTION, debug_callback);
        setopt(CURLOPT_XFERINFOFUNCTION, progress_callback);

        // some default options, remove those you usually don't want
        setopt(CURLOPT_NOPROGRESS, 0);       // turn on progress callbacks
        setopt(CURLOPT_FOLLOWLOCATION, 1L);  // redirects
        setopt(CURLOPT_HTTPPROXYTUNNEL, 1L); // corp. proxies etc.
        // setopt(CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
        setopt(CURLOPT_VERBOSE, 1L); // we want it all
    }

    // copy constructor
    EasyCurly(const EasyCurly& other) : handle(curl_easy_duphandle(other.handle)) {
        if(handle == nullptr) throw std::runtime_error("curl_easy_duphandle failed");
        // State information is not shared when using curl_easy_duphandle. Only the
        // options you've set (so you can create one CURL object, set its options and
        // then use as a template for other objects. The document and debug data are
        // therefor also not copied.
    }

    // move constructor
    EasyCurly(EasyCurly&& other) :
        handle(std::exchange(other.handle, nullptr)),
        m_document(std::move(other.m_document)), m_debug(std::move(other.m_debug)) {}

    // copy assignment
    EasyCurly& operator=(const EasyCurly& other) {
        CURL* tmp_handle = curl_easy_duphandle(other.handle);
        if(handle == nullptr) throw std::runtime_error("curl_easy_duphandle failed");
        // dup succeeded, now destroy any handle we might have and copy the tmp
        curl_easy_cleanup(handle);
        handle = tmp_handle;
        return *this;
    }

    // move assignment
    EasyCurly& operator=(EasyCurly&& other) {
        std::swap(handle, other.handle);
        std::swap(m_document, other.m_document);
        std::swap(m_debug, other.m_debug);
        return *this;
    }

    virtual ~EasyCurly() { curl_easy_cleanup(handle); }

    // To be able to use an instance of EasyCurly with C interfaces if you don't add
    // a function to this class for it, this operator will help
    operator CURL*() { return handle; }

    // generic curl_easy_setopt wrapper
    template<typename T>
    CURLcode setopt(CURLoption option, T v) {
        return curl_easy_setopt(handle, option, v);
    }

    // perform by supplying url
    CURLcode perform(std::string_view url) {
        setopt(CURLOPT_URL, url.data());
        return perform();
    }

    // perform with a previously supplied url
    CURLcode perform() {
        m_document.clear();
        m_debug.clear();
        return curl_easy_perform(handle);
    }

    // get collected data
    std::string const& document() const { return m_document; }
    std::string const& debug() const { return m_debug; }

    // callbacks from proxy functions
    virtual size_t on_write(char* ptr, size_t total_size) {
        m_document.insert(m_document.end(), ptr, ptr + total_size);

        // std::cout << "<write_callback> size " << total_size << "\n";
        return total_size;
    }
    virtual int on_debug(curl_infotype /*type*/, char* data, size_t size) {
        m_debug.insert(m_debug.end(), data, data + size);
        // std::cout << "<debug>\n";
        return 0; // must return 0
    }
    // override this to make a progress bar ...
    virtual int on_progress(curl_off_t /*dltotal*/, curl_off_t /*dlnow*/,
                            curl_off_t /*ultotal*/, curl_off_t /*ulnow*/) {
        // std::cout << "<progress>\n";
        return 0;
    }

private:
    // a private class to initialize and cleanup once
    class CurlGlobalInit {
    public:
        CurlGlobalInit() {
            // std::cout << "CurlGlobalInit\n";
            CURLcode res = curl_global_init(CURL_GLOBAL_ALL);
            if(res) throw std::runtime_error("curl_global_init failed");
        }
        ~CurlGlobalInit() {
            // std::cout << "~CurlGlobalInit\n";
            curl_global_cleanup();
        }
    };

    // callback functions - has to be static to work with the C interface in curl
    // use the data pointer (this) that we set in the constructor and cast it back
    // to a EasyCurly* and call the event handler in the correct object.
    static size_t write_callback(char* ptr, size_t size, size_t nmemb,
                                 void* userdata) {
        EasyCurly* ecurly = static_cast<EasyCurly*>(userdata);
        return ecurly->on_write(ptr, nmemb * size); // size==1 really
    }
    static int debug_callback(CURL* /*handle*/, curl_infotype type, char* data,
                              size_t size, void* userptr) {
        EasyCurly* ecurly = static_cast<EasyCurly*>(userptr);
        return ecurly->on_debug(type, data, size);
    }
    static int progress_callback(void* clientp, curl_off_t dltotal, curl_off_t dlnow,
                                 curl_off_t ultotal, curl_off_t ulnow) {
        EasyCurly* ecurly = static_cast<EasyCurly*>(clientp);
        return ecurly->on_progress(dltotal, dlnow, ultotal, ulnow);
    }

    // resources
    CURL* handle;
    std::string m_document{};
    std::string m_debug{};

    // a static initializer object
    static CurlGlobalInit setup_and_teardown;
};

// This must be defined in your .cpp file if you use this and split it into header
// and implementation parts.
EasyCurly::CurlGlobalInit EasyCurly::setup_and_teardown{};

// end of wrapper class
// --------------------------------------------------------------------------

// with that in place, the rest becomes something like this:

int main() {
    EasyCurly curl; // our curl object

    CURLcode res = curl.perform("http://www.google.com/");
    std::cout << "result: " << res << "\n";

    std::cout << "--- document size " << curl.document().size() << " captured ---\n"
              << curl.document() << "---------\n\n";
    std::cout << "--- debug size " << curl.debug().size() << " captured ---\n"
              << curl.debug() << "---------\n";
}
...