summaryrefslogtreecommitdiff
path: root/src/server.cc
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2021-11-17 22:34:57 +0100
committerJoel Klinghed <the_jk@spawned.biz>2021-11-17 22:34:57 +0100
commit6232d13f5321b87ddf12a1aa36b4545da45f173d (patch)
tree23f3316470a14136debd9d02f9e920ca2b06f4cc /src/server.cc
Travel3: Simple image and video display site
Reads the images and videos from filesystem and builds a site in memroy.
Diffstat (limited to 'src/server.cc')
-rw-r--r--src/server.cc256
1 files changed, 256 insertions, 0 deletions
diff --git a/src/server.cc b/src/server.cc
new file mode 100644
index 0000000..b2d03fa
--- /dev/null
+++ b/src/server.cc
@@ -0,0 +1,256 @@
+#include "common.hh"
+
+#include "args.hh"
+#include "config.hh"
+#include "inet.hh"
+#include "logger.hh"
+#include "looper.hh"
+#include "signal_handler.hh"
+#include "site.hh"
+#include "task_runner.hh"
+#include "transport.hh"
+#include "transport_fastcgi.hh"
+#include "transport_http.hh"
+#include "travel.hh"
+
+#include <algorithm>
+#include <errno.h>
+#include <iostream>
+#include <map>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+#include <utility>
+#include <vector>
+
+#ifndef VERSION
+# warning VERSION not set
+# define VERSION ""
+#endif
+
+namespace {
+
+class Server {
+public:
+ ~Server() {
+ for (auto& fd : listen_)
+ looper_->remove(fd.get());
+ }
+
+ bool setup(Logger* logger, Option const* config_arg, Option const* log_arg,
+ std::function<std::unique_ptr<Logger>()> default_logger_factory) {
+ auto config = Config::create(logger, config_arg->is_set()
+ ? config_arg->arg() : "travel3.conf");
+ if (!config)
+ return false;
+
+ {
+ std::filesystem::path log_file;
+ if (log_arg->is_set()) {
+ log_file = log_arg->arg();
+ } else {
+ log_file = config->get_path("log_file", "");
+ }
+ if (!log_file.empty()) {
+ logger_ = Logger::create_file(log_file, logger);
+ // If a log_file was requested and we can't append to it, treat as
+ // fatal error instead of falling back to default.
+ if (!logger_)
+ return false;
+ } else {
+ logger_ = default_logger_factory();
+ }
+ }
+
+ // EPIPE is handled so SIGPIPE is no help.
+ signal(SIGPIPE, SIG_IGN);
+
+ looper_ = Looper::create();
+ runner_ = TaskRunner::create(looper_);
+ travel_ = Travel::create(logger_, runner_);
+ if (!travel_->setup(logger, config.get()))
+ return false;
+ site_ = Site::create(logger_, runner_, travel_);
+ if (!site_->setup(logger, config.get()))
+ return false;
+
+ {
+ std::map<std::string, std::unique_ptr<Transport::Factory>>
+ transport_factories;
+ transport_factories.emplace("http", create_transport_factory_http());
+ transport_factories.emplace("fastcgi",
+ create_transport_factory_fastcgi());
+
+ auto* transport_factory_name = config->get("transport", "http");
+ auto it = transport_factories.find(transport_factory_name);
+ if (it == transport_factories.end()) {
+ logger->err("Unknown or unsupported transport: `%s'",
+ transport_factory_name);
+ return false;
+ }
+ handler_ = Transport::create_default_handler(logger_, site_->handler());
+ transport_ = it->second->create(logger_, looper_, runner_,
+ logger, config.get(), handler_.get());
+
+ if (!transport_)
+ return false;
+ }
+
+ if (!inet::bind_and_listen(logger,
+ config->get("bind", ""),
+ config->get("port", "5555"),
+ &listen_))
+ return false;
+
+ assert(!listen_.empty());
+
+ for (auto& fd : listen_)
+ looper_->add(fd.get(), Looper::EVENT_READ,
+ std::bind(&Server::accept, this,
+ fd.get(), std::placeholders::_1));
+
+ return true;
+ }
+
+ bool run() {
+ assert(logger_);
+ assert(looper_);
+ assert(transport_);
+
+ if (listen_.empty())
+ return true;
+
+ auto int_handler = SignalHandler::create(
+ looper_, SignalHandler::Signal::INT,
+ std::bind(&Looper::quit, looper_));
+
+ auto term_handler = SignalHandler::create(
+ looper_, SignalHandler::Signal::TERM,
+ std::bind(&Looper::quit, looper_));
+
+ auto hup_handler = SignalHandler::create(
+ looper_, SignalHandler::Signal::HUP,
+ std::bind(&Server::reload, this));
+
+ travel_->start();
+ site_->start();
+
+ return looper_->run(logger_.get());
+ }
+
+private:
+ void reload() {
+ travel_->reload();
+ site_->reload();
+ }
+
+ void accept(int listen_fd, uint8_t event) {
+ if (event & Looper::EVENT_READ) {
+ auto fd = inet::accept(logger_.get(), listen_fd, true);
+ if (fd) {
+ transport_->add_client(std::move(fd));
+ }
+ return;
+ }
+ if (event & Looper::EVENT_ERROR) {
+ looper_->remove(listen_fd);
+ auto it = std::find_if(listen_.begin(), listen_.end(),
+ [listen_fd] (auto& unique_fd) -> bool {
+ return unique_fd.get() == listen_fd;
+ });
+ if (it == listen_.end()) {
+ assert(false);
+ } else {
+ listen_.erase(it);
+ }
+ if (listen_.empty())
+ looper_->quit();
+ return;
+ }
+ // Event should be either read or error
+ assert(false);
+ }
+
+ std::shared_ptr<Logger> logger_;
+ std::shared_ptr<Looper> looper_;
+ std::shared_ptr<TaskRunner> runner_;
+ std::shared_ptr<Travel> travel_;
+ std::unique_ptr<Site> site_;
+ std::unique_ptr<Transport::Handler> handler_;
+ std::unique_ptr<Transport> transport_;
+ std::vector<unique_fd> listen_;
+};
+
+constexpr const char kTryMessage[] = "Try `travel3-server --help` for usage.";
+
+} // namespace
+
+int main(int argc, char** argv) {
+ auto args = Args::create();
+ auto* help_arg = args->add_option('h', "help", "display this text and exit.");
+ auto* version_arg = args->add_option('V', "version",
+ "display version and exit.");
+ auto* daemon_arg = args->add_option(
+ 'D', "daemon", "fork a daemon process, logging to syslog per default");
+ auto* log_arg = args->add_option_with_arg(
+ 'L', "log", "log to FILE instead of default (stdio or syslog)", "FILE");
+ auto* config_arg = args->add_option_with_arg(
+ 'C', "config", "load config from CONFIG instead of default travel3.conf"
+ " in current directory.", "CONFIG");
+ std::vector<std::string> arguments;
+ if (!args->run(argc, argv, "travel3-server", std::cerr, &arguments)) {
+ std::cerr << kTryMessage << std::endl;
+ return EXIT_FAILURE;
+ }
+ if (!arguments.empty()) {
+ std::cerr << "Unexpected arguments.\n"
+ << kTryMessage << std::endl;
+ return EXIT_FAILURE;
+ }
+ if (help_arg->is_set()) {
+ std::cout << "Usage: `travel3-servers [OPTIONS...]'\n"
+ << "Starts the travel server to receive and handle requests.\n"
+ << "\n"
+ << "Options:\n";
+ args->print_descriptions(std::cout, 80);
+ return EXIT_SUCCESS;
+ }
+ if (version_arg->is_set()) {
+ std::cout << "Travel " VERSION " written by "
+ "Joel Klinghed <the_jk@spawned.biz>." << std::endl;
+ return EXIT_SUCCESS;
+ }
+
+ Server server;
+
+ // Setup errors will always be logged to stdio to make them more visible.
+ auto logger = Logger::create_stdio();
+ if (!server.setup(logger.get(), config_arg, log_arg,
+ [daemon_arg] () {
+ return daemon_arg->is_set()
+ ? Logger::create_syslog("travel3")
+ : Logger::create_stdio();
+ }))
+ return EXIT_FAILURE;
+
+ if (daemon_arg->is_set()) {
+ auto pid = fork();
+ if (pid == -1) {
+ logger->err("Failed to fork(): %s", strerror(errno));
+ return EXIT_FAILURE;
+ }
+ if (pid == 0) {
+ // Daemon process
+ chdir("/");
+ setpgrp();
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ return server.run() ? EXIT_SUCCESS : EXIT_FAILURE;
+ } else {
+ return EXIT_SUCCESS;
+ }
+ } else {
+ return server.run() ? EXIT_SUCCESS : EXIT_FAILURE;
+ }
+}