How to set http_requset body in an HTTP POST with content type "multipart/form-data"

Sep 30, 2014 at 5:53 AM
Edited Sep 30, 2014 at 5:56 AM
Hello,

I'm trying to send HTTP POST request, and it's content type is "multipart/form-data".
    concurrency::streams::basic_istream<unsigned char> bodyStream;

    ...... // fill bodyStream

    http_request msg;
    msg.set_body(bodyStream);
    msg.set_method(methods::POST);
    msg.headers().set_content_type(U("multipart/form-data; boundary=---0123456789"));
My question is the bodyStream, which contains one more data. For example, the first data is a text string. The second data may be a text file. The third data may be a picture file. I think I should compose the bodyStream like string concatenation. But I have no idea how to compose it. For example, I have some std::string data and some concurrency::streams::basic_istream<unsigned char> file stream data, how to combine them into single bodyStream?

Thanks.

Best regards,
Mew
Oct 1, 2014 at 9:20 PM
I recently did this, but instead of using an istream I used a vector<unsigned char>.
You can add strings into the vector byte by byte. Files can be a bit more involved (like if you end up posting a binary file), but this is a good reference of how to get started.

Make sure that when you are adding every part to the vector, you delimit them properly with boundaries and have internal headers (like the content-type and the content-disposition).
Coordinator
Oct 2, 2014 at 1:42 AM
Hi fantasymew,

One option definitely is what connordavidson mentions.

Another possibility is you could create a stream backed by a producer_consumer_buffer. Write all your data to the buffer, or a basic_ostream constructed from it. When you are done close the output head of the stream. Then pass an input stream from the same producer_consumer_buffer to the http_request::set_body method.

In fact can call http_request::set_body and the http_client::request method before you finish writing all your data to the producer_consumer_buffer. This will allow for you to start streaming up the request body while you still are producing the data, if that is valuable for your scenario.

Another option somewhat like connordavidson mentions could be to use a container_buffer backed by a std::vector. If you decide to take this route you will have to write all your data to the buffer, then reconstruct a new buffer for input moving the vector out of the buffer you wrote to. This is because container_buffer doesn't support simultaneous reading and writing.

Steve
Marked as answer by fantasymew on 10/1/2014 at 7:33 PM
Oct 2, 2014 at 2:31 AM
Thanks connordavidson and stevetgates for quick reply. Your reply help me a lot.

In my scenario, I use file stream to open my file, and use concurrency::streams::basic_istream<unsigned char>::read() to read my file. After combining them to a string, I create a producer_comsumer_buffer to put the text into the buffer. And set the buffer as my http_request body. It works for me. Thank you very much. ^^
        string textBoundary = "--0123456789";
        string textBody = "";

        ...... // fill textBody

        concurrency::streams::producer_consumer_buffer<unsigned char> buffer;
        buffer.putn((const unsigned char *)textBody.c_str(), textBody.length());
        buffer.close(std::ios_base::out);

        http_request msg;
        msg.set_body(concurrency::streams::istream(buffer));
        msg.set_method(methods::POST);
        msg.headers().set_content_type(to_string_t("multipart/form-data; boundary=" + textBoundary));
        msg.headers().set_content_length(textBody.length());
Nov 5, 2014 at 4:53 PM
I'm a bit confused about how to read a file and add it to the body. Can you please tell how do you read a picture file and send it in the multipart request ???
Coordinator
Nov 6, 2014 at 12:19 AM
Hi mohamedselim,

The C++ Rest SDK can take the body of a HTTP message as a variety of different types and formats, for example a stream as is done in this discussion thread or a string. We don't have any high level multipart support. You will need to build up each portion yourself. This is probably easiest done by writing your request body to a stream and passing that to http_request::set_body(...).

Steve
Nov 7, 2014 at 5:20 AM
Hi mohamedselim,

This is the way I read a text file and add it to the body. I think other file does the same way except for content type. Hope it helps.
string fileName = "file.txt";
string filePath = "...\\" + textFileName;

// Open file stream
concurrency::streams::basic_istream<unsigned char> fileStream = file_stream<unsigned char>::open_istream(to_string_t(filePath)).get();

// Read file stream to string
concurrency::streams::stringstreambuf streamBuffer;
fileStream.read_to_end(streamBuffer).get();
string textFile = move(streamBuffer.collection());
fileStream.close();
streamBuffer.close();

// Make body text
string textBoundary = "--0123456789";
string textBody = "";

// Note: form data and content type depend on your application
textBody += "--" + textBoundary + "\r\n";
textBody += "Content-Disposition: form-data; name=\"file[]\"; filename=\"" + fileName + "\"\r\n";
textBody += "Content-Type: application/octet-stream\r\n\r\n";
textBody += textFile + "\r\n";

// Last boundary
textBody += "--" + textBoundary + "--\r\n";

// Put text to the buffer
concurrency::streams::producer_consumer_buffer<unsigned char> buffer;
buffer.putn((const unsigned char *)textBody.c_str(), textBody.length());
buffer.close(std::ios_base::out);

// Set website
string_t website = "your_website";
http_client client(website);

// Create HTTP request
http_request msg;
msg.set_body(concurrency::streams::istream(buffer));
msg.set_method(methods::POST);
msg.headers().set_content_type(to_string_t("multipart/form-data; boundary=" + textBoundary));
msg.headers().set_content_length(textBody.length());

// Send HTTP request
http_response response = client.request(msg).get();
Mew
Oct 15, 2015 at 9:43 AM
How can one post multiple files/parts in a single request? Like if I want to post a request with has two parts, 1st a JSON string and second a binary file.
--Boundary
Content-Disposition: form-data; name="myJsonString"
Content-Type: application/json

{"foo": "bar"}
--Boundary
Content-Disposition: form-data; name="photo"
Content-Type: image/jpeg

<file binary here..>
--Boundary--
Also, is there any way I can do this without reading the binary file into a string?

Thanks.