std::string post_file()

in include/poac/io/network.hpp [147:237]


    std::string post_file(
            const std::string& token,
            const boost::filesystem::path& file_path,
            std::string_view target=POAC_UPLOAD_API,
            std::string_view host=POAC_UPLOAD_API_HOST)
    {
        namespace fs = boost::filesystem;

        const std::string CRLF = "\r\n";
        const std::string boundary = boost::lexical_cast<std::string>(boost::uuids::random_generator{}());
        const std::string boundary_footer = CRLF + "--" + boundary + "--" + CRLF; // footer
        const std::string content_disposition = "Content-Disposition: form-data; ";

        std::stringstream token_stream;
        token_stream << "--" << boundary << CRLF
            << content_disposition << "name=\"token\"" << CRLF << CRLF
            << token;

        std::stringstream file_stream;
        file_stream << CRLF << "--" << boundary << CRLF
            << content_disposition << "name=\"file\"; filename=\"" << file_path.filename().string() << "\"" << CRLF
            << "Content-Type: application/x-gzip" << CRLF
            << "Content-Transfer-Encoding: binary" << CRLF << CRLF;

        auto req = create_request<http::string_body>(http::verb::post, target, host);
        req.set(http::field::accept, "*/*");
        req.set(http::field::content_type, "multipart/form-data; boundary=" + boundary);
        const auto content_length = token_stream.str().size() + file_stream.str().size() + fs::file_size(file_path) + boundary_footer.size();
        req.set(http::field::content_length, content_length);


        // The io_context is required for all I/O
        boost::asio::io_context ioc;
        // The SSL context is required, and holds certificates
        ssl::context ctx{ ssl::context::sslv23 };
        // These objects perform our I/O
        boost::asio::ip::tcp::resolver resolver{ ioc };
        ssl::stream<boost::asio::ip::tcp::socket> stream{ ioc, ctx };

        // Set SNI Hostname (many hosts need this to handshake successfully)
        if(!SSL_set_tlsext_host_name(stream.native_handle(), std::string(host).c_str()))
        {
            boost::system::error_code ec{
                    static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category()
            };
            throw boost::system::system_error{ ec };
        }
        // Look up the domain name
        const auto port = "443";
        const auto results = resolver.resolve(host, port);
        // Make the connection on the IP address we get from a lookup
        boost::asio::connect(stream.next_layer(), results.begin(), results.end());
        // Perform the SSL handshake
        stream.handshake(ssl::stream_base::client);

        // Convert request to string
        std::stringstream reqss;
        reqss << req;
        // Send the HTTP request to the remote host
        stream.write_some(boost::asio::buffer(reqss.str()));
        stream.write_some(boost::asio::buffer(token_stream.str()));
        stream.write_some(boost::asio::buffer(file_stream.str()));
        // Read file and write to stream
        {
            std::ifstream file(file_path.string(), std::ios::in | std::ios::binary);
            char buf[512];
            while (!file.eof()) {
                file.read(buf, 512);
                stream.write_some(boost::asio::buffer(buf, file.gcount()));
            }
        }
        // Write footer to stream
        stream.write_some(boost::asio::buffer(boundary_footer));

        // This buffer is used for reading and must be persisted
        boost::beast::flat_buffer buffer;
        // Declare a container to hold the response
        http::response<http::string_body> res;
        // Receive the HTTP response
        http::read(stream, buffer, res);

        // Gracefully close the stream
        boost::system::error_code ec;
        stream.shutdown(ec);
        if (ec == boost::asio::error::eof) {
            // Rationale: https://stackoverflow.com/q/25587403
            ec.assign(0, ec.category());
        }

        return res.body().data();
    }