Article directory
1 Introduction to Beast
Beast is a library about http(s)/websocket(s) in Boost. It was first released in boost 1.66 (2016). It is a relatively new library. It mainly includes the parsing (deserialization) and encapsulation (deserialization) of http and websocket protocols ( Serialization) and regarding network operations, it is based on asio, but seems to want to isolate Asio. This article describes the construction steps and ideas in detail from 0 to 1.
2 Build process
2.1 Download and installation of boost and jsoncpp
Download these two in advance, download and preparation work, refer to the following two articles,
Installation and use of Boost library under Windows
Instructions for using Json on C++ (from downloading to generating library to using)
2.2 Project creation and configuration
1. Open visual studio and create a console application. An empty project can also be used. The project name and location are arbitrary.
2. Configure the project, right-click the project, open properties, click the VC++ directory on the left, select the include directory option, edit the configuration as follows,
3. Same as above, edit the configuration library directory again, see the figure below
4. Configure the jsoncpp library file in the following order Configure it and replace it with your own jsoncpp path in the fourth step.
At this point, all configurations are complete.
2.3 Code
#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<json/json.h>
#include<json/value.h>
#include<json/reader.h>
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>
namespace my_program_state
{
std::size_t
request_count()
{
static std::size_t count = 0;
return ++count;
}
std::time_t
now()
{
return std::time(0);
}
}
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;
case http::verb::post:
response_.result(http::status::ok);
response_.set(http::field::server, "Beast");
create_post_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"
<< "<p>There have been "
<< my_program_state::request_count()
<< " requests so far.</p>\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"
<< "<p>The current time is "
<< my_program_state::now()
<< " seconds since the epoch.</p>\n"
<< "</body>\n"
<< "</html>\n";
}
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);
}
});
}
void create_post_response() {
if (request_.target() == "/email")
{
auto& body = this->request_.body();
auto body_str = boost::beast::buffers_to_string(body.data());
std::cout << "receive body is " << body_str << std::endl;
this->response_.set(http::field::content_type, "text/json");
Json::Value root;
Json::Reader reader;
Json::Value src_root;
bool parse_success = reader.parse(body_str, src_root);
if (!parse_success) {
std::cout << "Failed to parse JSON data!" << std::endl;
root["error"] = 1001;
std::string jsonstr = root.toStyledString();
beast::ostream(this->response_.body()) << jsonstr;
return;
}
auto email = src_root["email"].asString();
std::cout << "email is " << email << std::endl;
root["error"] = 0;
root["email"] = src_root["email"];
root["msg"] = "recevie email post success";
std::string jsonstr = root.toStyledString();
beast::ostream(this->response_.body()) << jsonstr;
}
else
{
response_.result(http::status::not_found);
response_.set(http::field::content_type, "text/plain");
beast::ostream(response_.body()) << "File not found\r\n";
}
}
};
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("127.0.0.1");
unsigned short port = static_cast<unsigned short>(8080);
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;
}
}
2.4 Testing
get request, the url is localhost:8080/time
post request, the url is localhost:8080/email