libcpr / cpr

C++ Requests: Curl for People, a spiritual port of Python Requests.
https://docs.libcpr.org/
Other
6.29k stars 903 forks source link

cpr::Buffer data lifetime question #1025

Closed Chrys4lisfag closed 4 months ago

Chrys4lisfag commented 4 months ago

As I see cpr::Buffer only stores data ptr + data length. Is the buffer content being copied somewhere inside the request itself?

When using async\callback way to transfer file data is it possible that memory will be invalid due to scope exit? If the buffer is not being copied and I am leaving the scope without waiting for the response then what is the proper way to manage lifetime when using cpr::Buffer?

my usage example

void send_file( const std::string& message,
                const std::string& file_name,
                const std::vector<std::uint8_t>& buffer )
{
    const cpr::Url req_uri( "https://api.telegram.org/" + "_my_token_" + "/sendDocument");
    const cpr::Redirect red{ 52L, true, true, cpr::PostRedirectFlags::POST_ALL };
    const cpr::Timeout timeout{ 50000 };

    const cpr::Multipart payload{ { "chat_id", "my_chat_id"},
                                  { "caption", message },
                                  { "document", cpr::Buffer{ buffer.begin(), buffer.end(), std::string( file_name ) } } };

    cpr::PostCallback( []( const cpr::Response& resp )
    {
        if ( resp.status_code != 200 )
        {
            //
            // log error
            //
        }
    }, req_uri, payload, red, timeout );
}
COM8 commented 4 months ago

Yes, you have to ensure your memory stays valid when using cpr::Buffer. We only store a pointer but as soon as you call for example cpr::Get or an async version of it your memory location cpr::Buffer is pointing to is copied into curl (curl_mime_data).

From then on you are safe to reuse your memory cpr::Buffer is pointing to except if you want to call a further request on the same session. Every time you call cpr::Get or its async counterpart, cpr copies the data pointed to from your passed cpr::Buffer into curl again to prepare the next request.

Chrys4lisfag commented 4 months ago

Didn't quite get the meaning of this message " We only store a pointer but as soon as you call for example cpr::Get or an async version of it your memory location cpr::Buffer is pointing to is copied"

__declspec( noinline ) void send_file()
{
    std::ifstream file( "file.json", std::ios::binary );
    std::vector<char> buffer( std::istreambuf_iterator<char>( file ), {} );

    const cpr::Url req_uri( "https://api.telegram.org/" + token + "/sendDocument");
    const cpr::Redirect red{ 52L, true, true, cpr::PostRedirectFlags::POST_ALL };
    const cpr::Timeout timeout{ 50000 };

    const cpr::Multipart payload{ { "chat_id", chat },
                                  { "caption", "test"},
                                  { "document", cpr::Buffer{ buffer.begin(), buffer.end(), std::string( "test.json" ) } } };

    auto r = cpr::PostCallback( []( const cpr::Response& resp )
    {
        if ( resp.status_code != 200 )
        {
            std::cout << "Error: " << resp.status_code << std::endl;
            std::cout << "Error: " << resp.text << std::endl;
            std::cout << "Error: " << resp.error.message << std::endl;
        }
    }, req_uri, payload, red, timeout );

    r.get();
}

You say that when PostCallback is called, my buffer content should be copied.

In my test case if I do r.get(); (block until resp), I recv valid file. If I comment this then I get broken file (buffer memory destructed).

If the buffer content was copied right after PostCallback called, then it shouldn't have happened. Or you meant that buffer is copied somewhere later inside when task runner starts the request (at that moment buffer scope exited ) and passed ptr to curl and that is the thing I didn't understand in your answer.

COM8 commented 4 months ago

PostCallback does not perform the session preparation and therefore the copying directly. The request gets enqueued and performed asynchronously. It could get executed immediately, or it can be delayed.

In your case make sure, your buffer is valid until your callback gets invoked.

Chrys4lisfag commented 4 months ago

PostCallback does not perform the session preparation and therefore the copying directly. The request gets enqueued and performed asynchronously. It could get executed immediately, or it can be delayed.

In your case make sure, your buffer is valid until your callback gets invoked.

Thanks. Now it is clear for me.