boostboost-asioboost-beast

Sending file to a server through HTTP with Boost Beast


I'm trying to send a file with POST request but every time I get an empty body and absolutely no information about the file on the server side.

beast::http::request<beast::http::file_body> req;
                
req.method(beast::http::verb::post);
req.target(target);
req.set(beast::http::field::host, host);
req.set(beast::http::field::user_agent, BOOST_BEAST_VERSION_STRING);
req.body().open(inputFilePath.c_str(), beast::file_mode::scan, ec);

write(_stream, req);

From what I see this opens a file and uses write_some to send a data. The number of bytes written is non-null... but where is it writing them to if the body on the server side is absolutely empty?


Solution

  • Multipart uploads are not a feature of Beast.

    You can look at some inspiration here, though: https://github.com/boostorg/beast/pull/2381, e.g. example/http/client/sync/http_client_sync_post.cpp

    Live On Coliru

    #include <boost/asio.hpp>
    #include <boost/beast.hpp>
    #include <filesystem>
    #include <fstream>
    #include <iostream>
    #include <sstream>
    
    namespace beast = boost::beast; // from <boost/beast.hpp>
    namespace http  = beast::http;  // from <boost/beast/http.hpp>
    namespace net   = boost::asio;  // from <boost/asio.hpp>
    using tcp       = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
    
    auto multi_part_post(std::filesystem::path fileName)
    {
    #define MULTI_PART_BOUNDARY                                           \
        "AaB03x" // This is the boundary to limit the start/end of a
                 // part. It may be any string. More info on the RFC
                 // 2388
                 // (https://datatracker.ietf.org/doc/html/rfc2388)
    #define CRLF                                                          \
        "\r\n" // Line ends must be CRLF
               // https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.1.4
    
        http::request<http::string_body> req{
            http::verb::post, "/post", 11};
        req.set(http::field::host, "httpbin.org");
        req.set(http::field::user_agent, "stackoverflow/sehe");
        req.set(
            http::field::content_type,
            "multipart/form-data; boundary=" MULTI_PART_BOUNDARY);
    
        // Prepare the multipart/form-data message
        std::ostringstream payload;
        payload
            << "--" MULTI_PART_BOUNDARY CRLF                       //
            << R"(Content-Disposition: form-data; name="comment")" //
            << CRLF CRLF "Multipart POST demo" CRLF <<             //
            "--" MULTI_PART_BOUNDARY CRLF                          //
            << R"(Content-Disposition: form-data; name="files"; filename=)" //
            << fileName.filename() << CRLF                        //
            << "Content-Type: application/octet-stream" CRLF CRLF //
            << std::ifstream(fileName, std::ios::binary).rdbuf()  //
            << CRLF <<                                            //
            "--" MULTI_PART_BOUNDARY << "--" CRLF;                //
    
        // Allow Beast to set headers depending on HTTP version, verb and
        // body
        req.body() = std::move(payload).str();
        req.prepare_payload();
        return req;
    
    #undef MULTI_PART_BOUNDARY
    #undef CRLF
    }
    
    int main() {
        auto req = multi_part_post("test.cpp");
        std::cout << "Sending " << req << std::endl;
    
        { // will not work on COLIRU
            net::io_context ioc;
    
            beast::tcp_stream stream(ioc);
            stream.connect(tcp::resolver(ioc).resolve("httpbin.org", "http"));
    
            write(stream, req);
    
            http::response<http::string_body> res;
            beast::flat_buffer buffer;
            read(stream, buffer, res);
    
            std::cout << res << std::endl;
        }
    }
    

    enter image description here