Open evanugarte opened 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;
}
}
g++ http_server_small.cpp -lboost_system -lpthread && ./a.out 0.0.0.0 8000
Part of #114, we can use the
<<
operator forserved::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)