evanugarte / LAN-File-Transfer

Using React.js, C++ and MongoDB, this application allows files to be hosted and transferred over local networks.
9 stars 3 forks source link

Stream Data back to User #124

Open evanugarte opened 2 years ago

evanugarte commented 2 years ago

Part of #114, we can use the << operator for served::response to stream the file back to the user in chunks. I'm not sure if we can manually set the response type back to the user with it but it's worth a shot. What I mean is having this as the first response (as specified by mozilla's chunked transfer encoding docs)

HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
evanugarte commented 2 years ago
/**
 * mp3 streaming github issue https://github.com/boostorg/beast/issues/1545
 * 
 * mentions beast chunked transfer encoding:
 * https://www.boost.org/doc/libs/1_78_0/libs/beast/doc/html/beast/using_http/chunked_encoding.html
 * 
 * g++ beast_server/http_server_small.cpp -lboost_system -lpthread
 * ./a.out 0.0.0.0 8000
 * 
 * reading cpp from js https://stackoverflow.com/a/64253294\
 * 
 * delivering large files http https://cabulous.medium.com/how-http-delivers-a-large-file-78af8840aad5
 * 
 * http::make_chunk
 * 
 * more chunked encoding stuff, might need to do a server from scratch then move on
 * https://www.boost.org/doc/libs/1_69_0/libs/beast/doc/html/beast/using_http/chunked_encoding.html
 * 
 * 
 * reading a file in chunks https://stackoverflow.com/a/20911639
 * 
 * boost creating a test chunk!!!!!
 * https://github.com/ceph/Beast/blob/master/test/doc/http_examples.cpp#L321
 * 
 * 
 * 
 * 2/26/22
 * https://github.com/boostorg/beast/issues/1740
 * above link is some other way to stream, directly setting the repsonse
 * 
 * i want to stream i want to stream
 * 
 * remove all other code in this file besides the bare minimum to run
 */

#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio.hpp>
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <memory>
#include <string>
#include <fstream>

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 = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp>

class http_connection : public std::enable_shared_from_this<http_connection>
{
public:
    http_connection(tcp::socket socket)
        : socket_(std::move(socket))
    {
    }

    // Initiate the asynchronous operations associated with the connection.
    void
    start()
    {
        read_request();
        check_deadline();
    }

private:
    // The socket for the currently connected client.
    tcp::socket socket_;

    // The buffer for performing reads.
    beast::flat_buffer buffer_{8192};

    // The request message.
    http::request<http::dynamic_body> request_;

    // The response message.
    http::response<http::dynamic_body> response_;

    // The timer for putting a deadline on connection processing.
    net::steady_timer deadline_{
        socket_.get_executor(), std::chrono::seconds(60)};

    // Asynchronously receive a complete request message.
    void
    read_request()
    {
        auto self = shared_from_this();

        http::async_read(
            socket_,
            buffer_,
            request_,
            [self](beast::error_code ec,
                std::size_t bytes_transferred)
            {
                boost::ignore_unused(bytes_transferred);
                if(!ec)
                    self->process_request();
            });
    }

    // Determine what needs to be done with the request message.
    void
    process_request()
    {
        response_.version(request_.version());
        response_.keep_alive(false);

        switch(request_.method())
        {
        case http::verb::get:
            response_.result(http::status::ok);
            response_.set(http::field::server, "Beast");
            create_response();
            break;

        default:
            // We return responses indicating an error if
            // we do not recognize the request method.
            response_.result(http::status::bad_request);
            response_.set(http::field::content_type, "text/plain");
            beast::ostream(response_.body())
                << "Invalid request-method '"
                << std::string(request_.method_string())
                << "'";
            break;
        }

        write_response();
    }

    // Construct a response message based on the program state.
    void
    create_response()
    {
        if(request_.target() == "/count")
        {
            response_.set(http::field::content_type, "text/html");
            beast::ostream(response_.body())
                << "<html>\n"
                <<  "<head><title>Request count</title></head>\n"
                <<  "<body>\n"
                <<  "<h1>Request count</h1>\n"
                <<  "</body>\n"
                <<  "</html>\n";
        }
        else if(request_.target() == "/time")
        {
            response_.set(http::field::content_type, "text/html");
            beast::ostream(response_.body())
                <<  "<html>\n"
                <<  "<head><title>Current time</title></head>\n"
                <<  "<body>\n"
                <<  "<h1>Current time</h1>\n"
                <<  "</body>\n"
                <<  "</html>\n";
        }
        else if(request_.target() == "/file")
        {
            response_.chunked(true);
            std::ifstream fin("/home/evan/Documents/fun/LAN-File-Transfer/beast_server/stream-me.mp3", std::ifstream::binary);
            std::vector<char> buffer (1024,0); //reads only the first 1024 bytes
            // make_chunk, then make_chunk_last
            // socket_.set_option(
            response_.set(http::field::content_type, "audio/mpeg");
            // http::response_serializer<http::empty_body> sr{response_};
            // http::write_header(socket_, sr);
            while(!fin.eof()) {
                fin.read(buffer.data(), buffer.size());
                std::streamsize s=fin.gcount();
                ///do with buffer
                // s is size, buffer is the raw mp3 bytes
                // std::cout << buffer.data() << std::endl;
                // std::cout <<s << std::endl;
                // from https://github.com/ceph/Beast/blob/dca65932a8055dd3e240633c6a058db8aa7f7915/test/doc/http_examples.cpp#L323
                socket_.non_blocking(false);
                auto const buf =
                    [](std::string s)
                    {
                        return boost::asio::const_buffer{
                            s.data(), s.size()};
                    };
                std::string lol(buffer.begin(), buffer.end());
                std::cout << "there i am gary there i am" << std::endl;
                response_.set(http::field::content_length, lol.size());
                http::async_write(socket_, response_,
                    [](beast::error_code ec, std::size_t)
                    {
                    // self->socket_.shutdown(tcp::socket::shutdown_send, ec);
                    // self->deadline_.cancel();
                    });
                boost::asio::write(socket_, http::make_chunk(buf(lol.c_str())));
            }
            boost::asio::write(socket_, http::make_chunk_last());
        }
        else if(request_.target() == "/file2")
        {
            response_.chunked(true);
            std::ifstream fin("/home/evan/Documents/fun/LAN-File-Transfer/beast_server/stream-me.mp3", std::ifstream::binary);
            std::vector<char> buffer (1024,0); //reads only the first 1024 bytes
            // make_chunk, then make_chunk_last
            // socket_.set_option(
            response_.set(http::field::content_type, "audio/mpeg");
            // http::response_serializer<http::empty_body> sr{response_};
            // http::write_header(socket_, sr);
            while(!fin.eof()) {
                fin.read(buffer.data(), buffer.size());
                std::streamsize s=fin.gcount();
                ///do with buffer
                // s is size, buffer is the raw mp3 bytes
                // std::cout << buffer.data() << std::endl;
                // std::cout <<s << std::endl;
                // from https://github.com/ceph/Beast/blob/dca65932a8055dd3e240633c6a058db8aa7f7915/test/doc/http_examples.cpp#L323
                socket_.non_blocking(false);
                auto const buf =
                    [](std::string s)
                    {
                        return boost::asio::const_buffer{
                            s.data(), s.size()};
                    };
                std::string lol(buffer.begin(), buffer.end());
                std::cout << "there i am gary there i am" << std::endl;
                response_.set(http::field::content_length, lol.size());
                http::async_write(socket_, response_,
                    [](beast::error_code ec, std::size_t)
                    {
                    // self->socket_.shutdown(tcp::socket::shutdown_send, ec);
                    // self->deadline_.cancel();
                    });
                boost::asio::write(socket_, http::make_chunk(buf(lol.c_str())));
            }
            boost::asio::write(socket_, http::make_chunk_last());
        }
        else
        {
            response_.result(http::status::not_found);
            response_.set(http::field::content_type, "text/plain");
            beast::ostream(response_.body()) << "File not found\r\n";
        }
    }

    // Asynchronously transmit the response message.
    void
    write_response()
    {
        auto self = shared_from_this();

        response_.content_length(response_.body().size());

        http::async_write(
            socket_,
            response_,
            [self](beast::error_code ec, std::size_t)
            {
                self->socket_.shutdown(tcp::socket::shutdown_send, ec);
                self->deadline_.cancel();
            });
    }

    // Check whether we have spent enough time on this connection.
    void
    check_deadline()
    {
        auto self = shared_from_this();

        deadline_.async_wait(
            [self](beast::error_code ec)
            {
                if(!ec)
                {
                    // Close socket to cancel any outstanding operation.
                    self->socket_.close(ec);
                }
            });
    }
};

// "Loop" forever accepting new connections.
void
http_server(tcp::acceptor& acceptor, tcp::socket& socket)
{
  acceptor.async_accept(socket,
      [&](beast::error_code ec)
      {
          if(!ec)
              std::make_shared<http_connection>(std::move(socket))->start();
          http_server(acceptor, socket);
      });
}

int
main(int argc, char* argv[])
{
    try
    {
        // Check command line arguments.
        if(argc != 3)
        {
            std::cerr << "Usage: " << argv[0] << " <address> <port>\n";
            std::cerr << "  For IPv4, try:\n";
            std::cerr << "    receiver 0.0.0.0 80\n";
            std::cerr << "  For IPv6, try:\n";
            std::cerr << "    receiver 0::0 80\n";
            return EXIT_FAILURE;
        }

        auto const address = net::ip::make_address(argv[1]);
        unsigned short port = static_cast<unsigned short>(std::atoi(argv[2]));

        net::io_context ioc{1};

        tcp::acceptor acceptor{ioc, {address, port}};
        tcp::socket socket{ioc};
        http_server(acceptor, socket);

        ioc.run();
    }
    catch(std::exception const& e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
        return EXIT_FAILURE;
    }
}
evanugarte commented 2 years ago
g++ http_server_small.cpp -lboost_system -lpthread && ./a.out 0.0.0.0 8000