summaryrefslogtreecommitdiffhomepage
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
parent3d0592e9238a59df54b3e3b757a38fa2e7f0ccfb (diff)
First websocket connection
-rwxr-xr-xMakefile10
-rw-r--r--common.mk2
-rw-r--r--config.cpp37
-rw-r--r--config.h8
-rw-r--r--debian/README.Debian4
-rw-r--r--debian/changelog6
-rw-r--r--debian/control4
-rw-r--r--debian/whiteboard.whiteboard.service2
-rwxr-xr-xstart.sh5
-rw-r--r--tests/test-whiteboard.cpp120
-rw-r--r--webserver.conf.example4
-rw-r--r--whiteboard.conf12
-rw-r--r--whiteboard.cpp286
-rw-r--r--whiteboard.h10
14 files changed, 369 insertions, 141 deletions
diff --git a/Makefile b/Makefile
index 6dba940..cb5ef3e 100755
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,7 @@ INCLUDES=-I.
HEADERS=config.h qrcode.h storage.h whiteboard.h compiledsql.h
SOURCES=$(HEADERS:.h=.cpp)
OBJECTS=$(HEADERS:.h=.o)
-TARGETS=whiteboard.fcgi
+TARGETS=whiteboard
build: $(TARGETS)
@@ -24,8 +24,8 @@ all: build
./start.sh
install:
- mkdir -p $(DESTDIR)/usr/lib/whiteboard
- cp whiteboard.fcgi $(DESTDIR)/usr/lib/whiteboard/
+ mkdir -p $(DESTDIR)/usr/bin
+ cp whiteboard $(DESTDIR)/usr/bin
mkdir -p $(DESTDIR)/usr/lib/whiteboard/html
cp -r html/* $(DESTDIR)/usr/lib/whiteboard/html/
@@ -38,7 +38,7 @@ install:
cp whiteboard.conf $(DESTDIR)/etc
# link
-whiteboard.fcgi: $(OBJECTS) main.o
+whiteboard: $(OBJECTS) main.o
$(CXX) $(LDFLAGS) $^ $(LDLIBS) $(LIBS) -o $@
# .cpp -> .o
@@ -49,7 +49,7 @@ test:
$(MAKE) -C tests
clean:
- -rm -f *.o *.fcgi *.gcov
+ -rm -f *.o $(TARGETS) *.gcov
$(MAKE) -C tests clean
deb:
diff --git a/common.mk b/common.mk
index 2dba055..1f4c2df 100644
--- a/common.mk
+++ b/common.mk
@@ -57,7 +57,7 @@ CXXTYPE=g++
endif
CXXFLAGS+=$(shell pkg-config --cflags qrcodegencpp GraphicsMagick++ fmt sqlite3)
-LIBS+=-lfcgi -lboost_filesystem -lpthread
+LIBS+=-lboost_filesystem -lpthread
LIBS+=-lSQLiteCpp $(shell pkg-config --libs qrcodegencpp GraphicsMagick++ fmt sqlite3)
LIBS+=-lreichwein
diff --git a/config.cpp b/config.cpp
index 4488e84..d59156f 100644
--- a/config.cpp
+++ b/config.cpp
@@ -4,15 +4,24 @@
#include <boost/property_tree/xml_parser.hpp>
#include <iostream>
+#include <string>
namespace pt = boost::property_tree;
+using namespace std::string_literals;
namespace {
const std::string default_datapath {"/var/lib/whiteboard"};
const uint64_t default_maxage{0}; // timeout in seconds; 0 = no timeout
+ const std::string default_listen {"::1:9000"};
+ const int default_threads{1};
}
-Config::Config(const std::string& config_filename): m_dataPath{default_datapath}, m_maxage{default_maxage}
+Config::Config(const std::string& config_filename):
+ m_dataPath{default_datapath},
+ m_maxage{default_maxage},
+ m_listenAddress{"::1"},
+ m_listenPort{9000},
+ m_threads{default_threads}
{
try {
@@ -22,6 +31,17 @@ Config::Config(const std::string& config_filename): m_dataPath{default_datapath}
m_dataPath = tree.get<std::string>("config.datapath", default_datapath);
m_maxage = tree.get<uint64_t>("config.maxage", default_maxage);
+ std::string listen {tree.get<std::string>("config.port", default_listen)};
+ auto pos{listen.find_last_of(':')};
+ if (pos == std::string::npos)
+ throw std::runtime_error("Bad port address: "s + listen);
+
+ m_listenAddress = listen.substr(0, pos);
+ m_listenPort = std::stoi(listen.substr(pos + 1));
+ if (m_listenPort < 0 || m_listenPort > 65535)
+ throw std::runtime_error("Bad listen port: "s + std::to_string(m_listenPort));
+
+ m_threads = tree.get<int>("config.threads", default_threads);
} catch (const std::exception& ex) {
std::cerr << "Error reading config file " << config_filename << ". Using defaults." << std::endl;
}
@@ -36,3 +56,18 @@ uint64_t Config::getMaxage() const
{
return m_maxage;
}
+
+std::string Config::getListenAddress() const
+{
+ return m_listenAddress;
+}
+
+int Config::getListenPort() const
+{
+ return m_listenPort;
+}
+
+int Config::getThreads() const
+{
+ return m_threads;
+}
diff --git a/config.h b/config.h
index 4f589ff..01310aa 100644
--- a/config.h
+++ b/config.h
@@ -9,9 +9,17 @@ class Config
private:
std::string m_dataPath;
uint64_t m_maxage;
+ std::string m_listenAddress; // ip address v4/v6
+ int m_listenPort;
+ int m_threads;
public:
Config(const std::string& config_filename = default_config_filename);
std::string getDataPath() const;
uint64_t getMaxage() const;
+
+ std::string getListenAddress() const;
+ int getListenPort() const;
+
+ int getThreads() const;
};
diff --git a/debian/README.Debian b/debian/README.Debian
index 07ab4d6..c740e12 100644
--- a/debian/README.Debian
+++ b/debian/README.Debian
@@ -17,8 +17,8 @@ Configuration
<plugin>static-files</plugin>
<target>/usr/lib/whiteboard/html</target>
</path>
- <path requested="/whiteboard/whiteboard.fcgi">
- <plugin>fcgi</plugin>
+ <path requested="/whiteboard/websocket">
+ <plugin>websocket</plugin>
<target>127.0.0.1:9014</target>
</path>
diff --git a/debian/changelog b/debian/changelog
index 0b9c82c..8373568 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+whiteboard (1.5) UNRELEASED; urgency=medium
+
+ * Move from FCGI to websocket interface
+
+ -- Roland Reichwein <mail@reichwein.it> Sat, 21 Jan 2023 18:18:37 +0100
+
whiteboard (1.4) unstable; urgency=medium
* Move from filesystem based documents to sqlite
diff --git a/debian/control b/debian/control
index 6b0db0e..0dce8fc 100644
--- a/debian/control
+++ b/debian/control
@@ -2,13 +2,13 @@ Source: whiteboard
Section: web
Priority: optional
Maintainer: Roland Reichwein <mail@reichwein.it>
-Build-Depends: debhelper (>= 12), libboost-all-dev | libboost1.71-all-dev, clang | g++-9, llvm | g++-9, lld | g++-9, uglifyjs, python3-pkg-resources, htmlmin, cleancss, libfcgi-dev, libqrcodegencpp-dev, libgraphicsmagick++-dev, pkg-config, libfmt-dev, libsqlitecpp-dev, googletest, gcovr, webserver, libreichwein-dev
+Build-Depends: debhelper (>= 12), libboost-all-dev | libboost1.71-all-dev, clang | g++-9, llvm | g++-9, lld | g++-9, uglifyjs, python3-pkg-resources, htmlmin, cleancss, libqrcodegencpp-dev, libgraphicsmagick++-dev, pkg-config, libfmt-dev, libsqlitecpp-dev, googletest, gcovr, webserver, libreichwein-dev
Standards-Version: 4.5.0
Homepage: http://www.reichwein.it/whiteboard/
Package: whiteboard
Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, spawn-fcgi, libxml2-utils
+Depends: ${shlibs:Depends}, ${misc:Depends}, libxml2-utils
Recommends: webserver
Homepage: http://www.reichwein.it/whiteboard/
Description: Web application for an collaborative editor
diff --git a/debian/whiteboard.whiteboard.service b/debian/whiteboard.whiteboard.service
index c60f3f0..b41e655 100644
--- a/debian/whiteboard.whiteboard.service
+++ b/debian/whiteboard.whiteboard.service
@@ -5,7 +5,7 @@ After=network.target
[Service]
Type=simple
# Restart=always
-ExecStart=spawn-fcgi -a 127.0.0.1 -p 9014 -n -- /usr/lib/whiteboard/whiteboard.fcgi
+ExecStart=/usr/bin/whiteboard
Restart=always
diff --git a/start.sh b/start.sh
deleted file mode 100755
index b42c33e..0000000
--- a/start.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/bash
-#
-# Test script for debugging
-#
-spawn-fcgi -a 127.0.0.1 -p 9014 -n -- ./whiteboard.fcgi
diff --git a/tests/test-whiteboard.cpp b/tests/test-whiteboard.cpp
index 46c4bae..b183021 100644
--- a/tests/test-whiteboard.cpp
+++ b/tests/test-whiteboard.cpp
@@ -1,21 +1,39 @@
#include <gtest/gtest.h>
+#include <cstring>
#include <filesystem>
#include <memory>
#include <string>
#include <system_error>
+#include <boost/process/child.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/beast/core.hpp>
+#include <boost/beast/http.hpp>
+#include <boost/beast/websocket.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/buffers_iterator.hpp>
+#include <boost/asio/connect.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/asio/local/stream_protocol.hpp>
+
+#include <unistd.h>
+
#include "libreichwein/file.h"
+#include "libreichwein/process.h"
#include "config.h"
#include "storage.h"
#include "whiteboard.h"
+namespace bp = boost::process;
namespace fs = std::filesystem;
using namespace Reichwein;
+using namespace std::string_literals;
namespace {
const fs::path webserverConfigFilename{"./webserver.conf"};
+
const fs::path testConfigFilename{"./whiteboard.conf"};
const fs::path testDbFilename{"./whiteboard.db3"};
}
@@ -25,20 +43,55 @@ class Webserver
public:
Webserver()
{
+ File::setFile(webserverConfigFilename, R"CONFIG(
+<webserver>
+ <user>www-data</user>
+ <group>www-data</group>
+ <threads>10</threads>
+ <statisticspath>stats.db</statisticspath>
+ <plugin-directory>../plugins</plugin-directory>
+ <sites>
+ <site>
+ <host>[::1]</host>
+ <path requested="/">
+ <plugin>websocket</plugin>
+ <target>::1:9876</target>
+ </path>
+ </site>
+ </sites>
+ <sockets>
+ <socket>
+ <address>::1</address>
+ <port>8080</port>
+ <protocol>http</protocol>
+ <site>localhost</site>
+ </socket>
+ </sockets>
+</webserver>
+)CONFIG");
+ start();
}
~Webserver()
{
stop();
+ fs::remove(webserverConfigFilename);
}
- void runInBackground()
+ void start()
{
+ m_child = bp::child("/usr/bin/webserver"s, "-c"s, webserverConfigFilename.generic_string());
+ Process::wait_for_pid_listening_on(m_child.id(), 8080);
+ std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
void stop()
{
+ m_child.terminate();
}
+
+private:
+ bp::child m_child;
};
class WhiteboardTest: public ::testing::Test
@@ -54,27 +107,92 @@ protected:
{
File::setFile(testConfigFilename, R"CONFIG(
<config>
+ <port>::1:9876</port>
<datapath>.</datapath>
<maxage>2592000</maxage>
+ <threads>4</threads>
</config>
)CONFIG");
std::error_code ec;
fs::remove(testDbFilename, ec);
m_config = std::make_shared<Config>(testConfigFilename);
+
+ //m_webserver = std::make_shared<Webserver>(webserverConfigFilename);
+
+ m_pid = fork();
+ if (m_pid == -1) {
+ throw std::runtime_error("Error on fork(): "s + strerror(errno));
+ } else if (m_pid == 0) { // child
+ Whiteboard whiteboard;
+ std::vector<std::string> argvv{{"whiteboard", "-c", testConfigFilename.generic_string()}};
+ char* argv[] = {argvv[0].data(), argvv[1].data(), argvv[2].data(), nullptr};
+ whiteboard.run(argvv.size(), argv);
+ exit(0);
+ }
+ Process::wait_for_pid_listening_on(m_pid, 9876);
+ std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
void TearDown() override
{
+ if (m_pid == 0)
+ throw std::runtime_error("Whiteboard not running on requesting SIGTERM");
+
+ if (kill(m_pid, SIGTERM) != 0)
+ throw std::runtime_error("Unable to SIGTERM Whiteboard");
+
+ if (int result = waitpid(m_pid, NULL, 0); result != m_pid)
+ throw std::runtime_error("waitpid returned "s + std::to_string(result));
+
std::error_code ec;
fs::remove(testDbFilename, ec);
fs::remove(testConfigFilename, ec);
}
std::shared_ptr<Config> m_config;
+ //std::shared_ptr<Webserver> m_webserver;
+ pid_t m_pid{};
};
TEST_F(WhiteboardTest, connection)
{
+ std::string host = "::1";
+ auto const port = "9876" ;
+
+ // The io_context is required for all I/O
+ boost::asio::io_context ioc;
+
+ // These objects perform our I/O
+ boost::asio::ip::tcp::resolver resolver{ioc};
+ boost::beast::websocket::stream<boost::asio::ip::tcp::socket> ws{ioc};
+
+ // Look up the domain name
+ auto const results = resolver.resolve(host, port);
+
+ // Make the connection on the IP address we get from a lookup
+ auto ep = boost::asio::connect(boost::beast::get_lowest_layer(ws), results);
+
+ // Update the host_ string. This will provide the value of the
+ // Host HTTP header during the WebSocket handshake.
+ // See https://tools.ietf.org/html/rfc7230#section-5.4
+ if (host == "::1")
+ host = "[" + host + "]";
+ host += ':' + std::to_string(ep.port());
+
+ // Set a decorator to change the User-Agent of the handshake
+ ws.set_option(boost::beast::websocket::stream_base::decorator(
+ [](boost::beast::websocket::request_type& req)
+ {
+ req.set(boost::beast::http::field::user_agent,
+ std::string("Reichwein.IT Test Websocket Client"));
+ }));
+
+ // Perform the websocket handshake
+ ws.handshake(host, "/");
+}
+
+TEST_F(WhiteboardTest, getfile)
+{
}
diff --git a/webserver.conf.example b/webserver.conf.example
index eeb48a1..9193a5b 100644
--- a/webserver.conf.example
+++ b/webserver.conf.example
@@ -2,7 +2,7 @@
<plugin>static-files</plugin>
<target>/usr/lib/whiteboard/html</target>
</path>
- <path requested="/whiteboard/whiteboard.fcgi">
- <plugin>fcgi</plugin>
+ <path requested="/whiteboard/websocket">
+ <plugin>websocket</plugin>
<target>127.0.0.1:9014</target>
</path>
diff --git a/whiteboard.conf b/whiteboard.conf
index 80ae173..126bef5 100644
--- a/whiteboard.conf
+++ b/whiteboard.conf
@@ -6,9 +6,21 @@
<datapath>/var/lib/whiteboard</datapath>
<!--
+ port: socket to listen on for websocket
+ Example: ::1:8765
+ -->
+ <port>::1:8765</port>
+
+ <!--
Maximum age of individual whiteboard pages in seconds.
Older pages will be removed by whiteboard-cleanup and crond.
Example: 2592000 (~30 days)
-->
<maxage>2592000</maxage>
+
+ <!--
+ Number of threads to use
+ Example: 4
+ -->
+ <threads>4</threads>
</config>
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;
}
diff --git a/whiteboard.h b/whiteboard.h
index f50f455..d645115 100644
--- a/whiteboard.h
+++ b/whiteboard.h
@@ -1,8 +1,12 @@
#pragma once
+#include <filesystem>
+#include <memory>
#include <mutex>
#include <string>
+#include <boost/asio/ip/tcp.hpp>
+
#include "config.h"
#include "storage.h"
@@ -13,10 +17,12 @@ public:
int run(int argc, char* argv[]);
private:
- Config m_config;
- Storage m_storage;
+ std::unique_ptr<Config> m_config;
+ std::unique_ptr<Storage> m_storage;
std::mutex m_storage_mutex;
+ std::string handle_request(const std::string& request);
+ void do_session(boost::asio::ip::tcp::socket socket);
void storage_cleanup();
};