diff options
| author | Joel Klinghed <the_jk@spawned.biz> | 2021-11-17 22:34:57 +0100 |
|---|---|---|
| committer | Joel Klinghed <the_jk@spawned.biz> | 2021-11-17 22:34:57 +0100 |
| commit | 6232d13f5321b87ddf12a1aa36b4545da45f173d (patch) | |
| tree | 23f3316470a14136debd9d02f9e920ca2b06f4cc /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.cc | 256 |
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; + } +} |
