#include "common.hh" #include "args.hh" #include "config.hh" #include "daemon.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 #include #include #include #include #include #include #include #include #ifndef VERSION # warning VERSION not set # define VERSION "" #endif namespace { class Server : public Daemon { public: Server(Option const* config_arg, Option const* log_arg, std::function()> default_logger_factory) : config_name_(config_arg->is_set() ? config_arg->arg() : "travel3.conf"), log_file_(log_arg->is_set() ? std::optional(log_arg->arg()) : std::nullopt), default_logger_factory_(default_logger_factory) { } ~Server() override { for (auto& fd : listen_) looper_->remove(fd.get()); } bool setup(Logger* logger) override { auto config = Config::create(logger, config_name_); if (!config) return false; { std::filesystem::path log_file; if (log_file_.has_value()) { log_file = log_file_.value(); } 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> 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() override { assert(logger_); assert(looper_); assert(transport_); if (listen_.empty()) return true; auto int_handler = SignalHandler::create( looper_, SignalHandler::Signal::INT, std::bind(&Server::quit, this, "INT")); auto term_handler = SignalHandler::create( looper_, SignalHandler::Signal::TERM, std::bind(&Server::quit, this, "TERM")); 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() { logger_->info("Caught HUP, starting reload."); travel_->reload(); site_->reload(); } void quit(std::string const& signal) { logger_->info("Caught %s, quitting.", signal.c_str()); looper_->quit(); } 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::string const config_name_; std::optional const log_file_; std::function()> default_logger_factory_; std::shared_ptr logger_; std::shared_ptr looper_; std::shared_ptr runner_; std::shared_ptr travel_; std::unique_ptr site_; std::unique_ptr handler_; std::unique_ptr transport_; std::vector 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 arguments; if (!args->run(argc, argv, "travel3-server", std::cerr, &arguments)) { std::cerr << 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 (!arguments.empty()) { std::cerr << "Unexpected arguments.\n" << kTryMessage << std::endl; return EXIT_FAILURE; } if (version_arg->is_set()) { std::cout << "Travel " VERSION " written by " "Joel Klinghed ." << std::endl; return EXIT_SUCCESS; } auto server = std::make_unique( config_arg, log_arg, [daemon_arg] () { return daemon_arg->is_set() ? Logger::create_syslog("travel3") : Logger::create_stdio(); }); auto logger = Logger::create_stdio(); bool ret; if (daemon_arg->is_set()) { ret = Daemon::fork_in_background(logger.get(), std::move(server)); } else { ret = Daemon::run_in_foreground(logger.get(), std::move(server)); } return ret ? EXIT_SUCCESS : EXIT_FAILURE; }