1

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?

Latawiec
  • 341
  • 2
  • 10
  • use `req.prepare_payload()` and set the http version. What is the server expecting? Multipart? Chunked? How is it implemented? – sehe Apr 21 '23 at 01:39
  • I'm using Node.js Express as backend. WIth express-fileupload middleware it works good with `multipart/form-data`. I was testing it with `curl -F`, so ideally I'd like to recreate same behaviour as curl has with Beast. – Latawiec Apr 21 '23 at 10:20

1 Answers1

1

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

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Does Boost support "chunked" or I have to manually define it just like for multipart? – Latawiec Apr 21 '23 at 13:39
  • 1
    Chunked encoding is builtin and mostly automatic (`req.chunked(true)`). It's also a transfer encoding, so independent of the body encoding: here's [multipart content chunked](http://coliru.stacked-crooked.com/a/4c45c7e40f285c9d) - note the chunk starting with **`72bbb`** (that's 469947 in hex) – sehe Apr 21 '23 at 16:32
  • Note that if you need just chunked encoding, no multipart, `file_body` already does it better: https://imgur.com/a/Vt2qoe1 – sehe Apr 21 '23 at 16:50