summaryrefslogtreecommitdiffhomepage
path: root/whiteboard.cpp
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2023-01-21 19:05:43 +0100
committerRoland Reichwein <mail@reichwein.it>2023-01-21 19:05:43 +0100
commitc464265f60ddd367786b08f5d49cd7a6d650b7d6 (patch)
treed2c747cc041a92d38ac1d25eb47fc7e398d5af7b /whiteboard.cpp
parent3d0592e9238a59df54b3e3b757a38fa2e7f0ccfb (diff)
First websocket connection
Diffstat (limited to 'whiteboard.cpp')
-rw-r--r--whiteboard.cpp286
1 files changed, 167 insertions, 119 deletions
diff --git a/whiteboard.cpp b/whiteboard.cpp
index 6466635..be8bcb0 100644
--- a/whiteboard.cpp
+++ b/whiteboard.cpp
@@ -7,8 +7,6 @@
#include <dirent.h>
#include <sys/types.h>
-#include <fcgiapp.h>
-
#include <chrono>
#include <iostream>
#include <functional>
@@ -22,6 +20,15 @@
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/property_tree/xml_parser.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/beast/core.hpp>
+#include <boost/beast/http.hpp>
+#include <boost/beast/websocket.hpp>
+#include <boost/beast/version.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/buffers_iterator.hpp>
+#include <boost/asio/connect.hpp>
+#include <boost/asio/ip/tcp.hpp>
#include "libreichwein/file.h"
@@ -38,20 +45,20 @@ namespace {
void usage() {
std::cout <<
"Usage: \n"
- " whiteboard [-c]\n"
+ " whiteboard [options]\n"
"\n"
"Options:\n"
- " -c : Cleanup database according to timeout rules (config: maxage)\n"
+ " -c <path> : specify configuration file including path\n"
+ " -C : clean up database according to timeout rules (config: maxage)\n"
+ " -h : this help\n"
"\n"
- "Without options, whiteboard will be started as FCGI application"
+ "Without options, whiteboard will be started as websocket application"
<< std::endl;
}
} // namespace
-Whiteboard::Whiteboard():
- m_config(),
- m_storage(m_config)
+Whiteboard::Whiteboard()
{
}
@@ -61,136 +68,177 @@ void Whiteboard::storage_cleanup()
while(true) {
{
std::lock_guard<std::mutex> lock(m_storage_mutex);
- m_storage.cleanup();
+ if (!m_storage)
+ throw std::runtime_error("Storage not initialized");
+ m_storage->cleanup();
}
std::this_thread::sleep_for(std::chrono::minutes(10));
}
}
+std::string Whiteboard::handle_request(const std::string& request)
+{
+ try {
+ std::lock_guard<std::mutex> lock(m_storage_mutex);
+ if (!m_storage)
+ throw std::runtime_error("Storage not initialized");
+
+ pt::ptree xml;
+ std::istringstream ss{request};
+ pt::xml_parser::read_xml(ss, xml);
+
+ std::string command {xml.get<std::string>("request.command")};
+
+ if (command == "modify") {
+ std::string id {xml.get<std::string>("request.id")};
+ std::string data {xml.get<std::string>("request.data")};
+ m_storage->setDocument(id, data);
+ return {};
+ } else if (command == "getfile") {
+ std::string id {xml.get<std::string>("request.id")};
+
+ std::string filedata {m_storage->getDocument(id)};
+
+ if (filedata.size() > 30000000)
+ throw std::runtime_error("File too big");
+
+ return filedata;
+ } else if (command == "checkupdate") {
+ std::string id {xml.get<std::string>("request.id")};
+ std::string checksum_s {xml.get<std::string>("request.checksum")};
+ uint32_t checksum{static_cast<uint32_t>(stoul(checksum_s))};
+
+ std::string filedata {m_storage->getDocument(id)};
+ if (checksum != m_storage->checksum32(filedata)) {
+ return filedata;
+ } else {
+ return {};
+ }
+ } else if (command == "newid") {
+ return m_storage->generate_id();
+ } else if (command == "qrcode") {
+ std::string url{xml.get<std::string>("request.url")};
+
+ if (url.size() > 1000)
+ throw std::runtime_error("URL too big");
+
+ std::string pngdata {QRCode::getQRCode(url)};
+
+ return pngdata;
+ } else {
+ throw std::runtime_error("Bad command: "s + command);
+ }
+
+ } catch (const std::exception& ex) {
+ return "Message handling error: "s + ex.what();
+ }
+}
+
+void Whiteboard::do_session(boost::asio::ip::tcp::socket socket)
+{
+ try {
+ // Construct the stream by moving in the socket
+ boost::beast::websocket::stream<boost::asio::ip::tcp::socket> ws{std::move(socket)};
+
+ // Set a decorator to change the Server of the handshake
+ ws.set_option(boost::beast::websocket::stream_base::decorator(
+ [](boost::beast::websocket::response_type& res)
+ {
+ res.set(boost::beast::http::field::server,
+ std::string("Reichwein.IT Whiteboard"));
+ }));
+
+ boost::beast::http::request_parser<boost::beast::http::string_body> parser;
+ boost::beast::http::request<boost::beast::http::string_body> req;
+ boost::beast::flat_buffer buffer;
+
+ boost::beast::http::read(ws.next_layer(), buffer, parser);
+ req = parser.get();
+
+ ws.accept(req);
+
+ while (true) {
+ boost::beast::flat_buffer buffer;
+
+ ws.read(buffer);
+
+ ws.text(ws.got_text());
+ std::string data(boost::asio::buffers_begin(buffer.data()), boost::asio::buffers_end(buffer.data()));
+ data = handle_request(data);
+ buffer.consume(buffer.size());
+ boost::beast::ostream(buffer) << data;
+ ws.write(buffer.data());
+ }
+ } catch (boost::beast::system_error const& se) {
+ // This indicates that the session was closed
+ if (se.code() != boost::beast::websocket::error::closed)
+ std::cerr << "Boost system_error in session: " << se.code().message() << std::endl;
+ } catch (std::exception const& ex) {
+ std::cerr << "Error in session: " << ex.what() << std::endl;
+ }
+}
+
// the actual main() for testability
int Whiteboard::run(int argc, char* argv[])
{
- if (argc == 2) {
- if (argv[1] == "-h"s || argv[1] == "-?"s) {
- usage();
- exit(0);
- } else if (argv[1] == "-c"s) {
- m_storage.cleanup();
+ try {
+ bool flag_cleanup{};
+ fs::path configFile;
+
+ if (argc == 2) {
+ if (argv[1] == "-h"s || argv[1] == "-?"s) {
+ usage();
+ exit(0);
+ } else if (argv[1] == "-C"s) {
+ flag_cleanup = true;
+ }
+ } else if (argc == 3) {
+ if (argv[1] == "-c"s) {
+ configFile = argv[2];
+ }
+ }
+
+ if (configFile.empty())
+ m_config = std::make_unique<Config>();
+ else
+ m_config = std::make_unique<Config>(configFile);
+
+ m_storage = std::make_unique<Storage>(*m_config);
+
+ if (flag_cleanup) {
+ m_storage->cleanup();
exit(0);
}
- }
- std::thread storage_cleanup_thread(std::bind(&Whiteboard::storage_cleanup, this));
+ std::thread storage_cleanup_thread(std::bind(&Whiteboard::storage_cleanup, this));
- QRCode::init();
+ QRCode::init();
- int result = FCGX_Init();
- if (result != 0) { // error on init
- fprintf(stderr, "Error: FCGX_Init()\n");
- return 1;
- }
+ auto const address = boost::asio::ip::make_address(m_config->getListenAddress());
+ auto const port = static_cast<unsigned short>(m_config->getListenPort());
- result = FCGX_IsCGI();
- if (result) {
- fprintf(stderr, "Error: No FCGI environment available.\n");
- return 1;
- }
+ // The io_context is required for all I/O
+ boost::asio::io_context ioc{m_config->getThreads()};
- FCGX_Request request;
- result = FCGX_InitRequest(&request, 0, 0);
- if (result != 0) {
- fprintf(stderr, "Error: FCGX_InitRequest()\n");
- return 1;
- }
+ // The acceptor receives incoming connections
+ boost::asio::ip::tcp::acceptor acceptor{ioc, {address, port}};
+ while (true) {
+ // This will receive the new connection
+ boost::asio::ip::tcp::socket socket{ioc};
- while (FCGX_Accept_r(&request) >= 0) {
- try {
- std::lock_guard<std::mutex> lock(m_storage_mutex);
- char* method = FCGX_GetParam("REQUEST_METHOD", request.envp);
-
- // POST for server actions, changes
- if (!strcmp(method, "POST")) {
- size_t contentLength { std::stoul(FCGX_GetParam("CONTENT_LENGTH", request.envp)) };
- std::string postData(contentLength, '\0'); // contentLength number of bytes, initialize with 0
- if (FCGX_GetStr(postData.data(), contentLength, request.in) != static_cast<int>(contentLength)) {
- throw std::runtime_error("Bad data read: Content length mismatch.\r\n");
- }
- // postData contains POST data
- std::string contentType(FCGX_GetParam("CONTENT_TYPE", request.envp));
-
- std::string xmlData = postData; // default: interpret whole POST data as xml request
-
- pt::ptree xml;
- std::istringstream ss{xmlData};
- pt::xml_parser::read_xml(ss, xml);
-
- std::string command {xml.get<std::string>("request.command")};
-
- if (command == "modify") {
- std::string id {xml.get<std::string>("request.id")};
- std::string data {xml.get<std::string>("request.data")};
- m_storage.setDocument(id, data);
- FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out);
- } else if (command == "getfile") {
- std::string id {xml.get<std::string>("request.id")};
-
- std::string filedata {m_storage.getDocument(id)};
-
- if (filedata.size() > 30000000)
- throw std::runtime_error("File too big");
-
- FCGX_PutS("Content-Type: application/octet-stream\r\n", request.out);
- FCGX_FPrintF(request.out, "Content-Length: %d\r\n\r\n", filedata.size());
- FCGX_PutStr(filedata.c_str(), filedata.size(), request.out);
- } else if (command == "checkupdate") {
- std::string id {xml.get<std::string>("request.id")};
- std::string checksum_s {xml.get<std::string>("request.checksum")};
- uint32_t checksum{static_cast<uint32_t>(stoul(checksum_s))};
-
- std::string filedata {m_storage.getDocument(id)};
- if (checksum != m_storage.checksum32(filedata)) {
- //std::cout << "Sending change..." << std::endl;
- FCGX_PutS("Content-Type: application/octet-stream\r\n", request.out);
- FCGX_FPrintF(request.out, "Content-Length: %d\r\n\r\n", filedata.size());
- FCGX_PutStr(filedata.c_str(), filedata.size(), request.out);
- } else {
- //std::cout << "No change..." << std::endl;
- FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out);
- FCGX_PutS("No change.\r\n", request.out);
- }
- } else if (command == "newid") {
- FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out);
- FCGX_PutS(m_storage.generate_id().c_str(), request.out);
- } else if (command == "qrcode") {
- std::string url{xml.get<std::string>("request.url")};
-
- if (url.size() > 1000)
- throw std::runtime_error("URL too big");
-
- std::string pngdata {QRCode::getQRCode(url)};
-
- FCGX_PutS("Content-Type: image/png\r\n", request.out);
- FCGX_FPrintF(request.out, "Content-Length: %d\r\n\r\n", pngdata.size());
- FCGX_PutStr(pngdata.c_str(), pngdata.size(), request.out);
- } else {
- throw std::runtime_error("Bad command: "s + command);
- }
+ // Block until we get a connection
+ acceptor.accept(socket);
- } else {
- throw std::runtime_error("Unsupported method.\r\n");
- }
- } catch (const std::runtime_error& ex) {
- FCGX_PutS("Status: 500 Internal Server Error\r\n", request.out);
- FCGX_PutS("Content-Type: text/html\r\n\r\n", request.out);
- FCGX_FPrintF(request.out, "Error: %s\r\n", ex.what());
- } catch (const std::exception& ex) {
- FCGX_PutS("Status: 500 Internal Server Error\r\n", request.out);
- FCGX_PutS("Content-Type: text/html\r\n\r\n", request.out);
- FCGX_FPrintF(request.out, "Unknown exception: %s\r\n", ex.what());
+ // Launch the session, transferring ownership of the socket
+ std::thread(
+ &Whiteboard::do_session, this,
+ std::move(socket)).detach();
}
- }
- storage_cleanup_thread.join();
+ storage_cleanup_thread.join();
+ } catch (const std::exception& ex) {
+ std::cerr << "Error: " << ex.what() << std::endl;
+ }
return 0;
}