Skip to content

Commit

Permalink
Added another daemon option
Browse files Browse the repository at this point in the history
  • Loading branch information
mhekkel committed Aug 25, 2024
1 parent 101c00a commit d625d8c
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 21 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ debug/
release/
docs/api
docs/conf.py
src/revision.hpp
8 changes: 4 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

cmake_minimum_required(VERSION 3.22)

project(libzeep VERSION 6.0.13 LANGUAGES CXX)
project(libzeep VERSION 6.0.14 LANGUAGES CXX)

list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

Expand Down Expand Up @@ -247,9 +247,9 @@ if(ZEEP_BUILD_LIBRARY)
target_link_options(zeep PRIVATE -undefined dynamic_lookup)
endif()

# Build debug library with d postfix
set(CMAKE_DEBUG_POSTFIX d)
set_target_properties(zeep PROPERTIES DEBUG_POSTFIX "d")
# # Build debug library with d postfix
# set(CMAKE_DEBUG_POSTFIX d)
# set_target_properties(zeep PROPERTIES DEBUG_POSTFIX "d")

# Install rules
install(TARGETS zeep
Expand Down
5 changes: 5 additions & 0 deletions changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
Version 6.0.14
- Added variant of http::daemon with forking a daemon but
no preforked children. Simply a single instance with
multiple threads.

Version 6.0.13
- Flush access log after each request. (replacing all
instances of std::endl was a bit too drastic).
Expand Down
16 changes: 14 additions & 2 deletions include/zeep/http/daemon.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class daemon
m_restart_time_window = within_nr_of_seconds;
}

/// \brief Start the daemon, forking off in the background
/// \brief Start the daemon, forking off in the background with multiple preforked servers
///
/// \param address The address to bind to
/// \param port The port number to bind to
Expand All @@ -74,6 +74,17 @@ class daemon
int start(const std::string& address, uint16_t port, size_t nr_of_procs,
size_t nr_of_threads, const std::string& run_as_user);

/// \brief Start the daemon, forking off in the background with single process
///
/// \param address The address to bind to
/// \param port The port number to bind to
/// \param nr_of_threads The number of threads to pass to the server class
/// \param run_as_user The user to run the forked process. Daemons are usually
/// started as root and should drop their privileges as soon
/// as possible.
int start(const std::string& address, uint16_t port, size_t nr_of_threads,
const std::string& run_as_user);

/// \brief Stop a running daemon process. Returns 0 in case of successfully stopping a process.
int stop();

Expand All @@ -92,8 +103,9 @@ class daemon

private:

void daemonize();
int daemonize();
void open_log_file();

bool run_main_loop(const std::string& address, uint16_t port, size_t nr_of_procs,
size_t nr_of_threads, const std::string& run_as_user);

Expand Down
180 changes: 167 additions & 13 deletions lib-http/src/daemon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
// Utilitie routines to build daemon processes

#ifndef _WIN32
#include <grp.h>
#include <pwd.h>
#include <sys/wait.h>
# include <grp.h>
# include <pwd.h>
# include <sys/wait.h>
#endif

#include <filesystem>
Expand Down Expand Up @@ -124,7 +124,7 @@ bool daemon::pid_is_for_executable()
return false;
}

void daemon::daemonize()
int daemon::daemonize()
{
assert(false);
}
Expand All @@ -150,7 +150,7 @@ int daemon::start(const std::string &address, uint16_t port, size_t nr_of_procs,
fs::path pidDir = fs::path(m_pid_file).parent_path();
if (not fs::is_directory(pidDir, ec))
fs::create_directories(pidDir, ec);

if (ec)
std::clog << "Creating directory for pid file failed: " << ec.message() << '\n';

Expand Down Expand Up @@ -197,8 +197,10 @@ int daemon::start(const std::string &address, uint16_t port, size_t nr_of_procs,
throw std::runtime_error(std::string("Is server running already? ") + e.what());
}

daemonize();

int pid = daemonize();
if (pid != 0) // parent process, we can simply exit now
_exit(0);

for (;;)
{
bool hupped = run_main_loop(address, port, nr_of_procs, nr_of_threads, run_as_user);
Expand All @@ -207,21 +209,171 @@ int daemon::start(const std::string &address, uint16_t port, size_t nr_of_procs,
std::clog << "Server was interrupted, will attempt to resume\n";
continue;
}

break;
}


if (fs::exists(m_pid_file, ec))
fs::remove(m_pid_file, ec);

if (ec)
std::clog << "Removing pid file failed: " << ec.message() << '\n';
}

return result;
}

int daemon::start(const std::string &address, uint16_t port, size_t nr_of_threads, const std::string &run_as_user)
{
int result = 0;

if (pid_is_for_executable())
{
std::clog << "Server is already running.\n";
result = 1;
}
else
{
std::error_code ec;

if (fs::exists(m_pid_file, ec))
fs::remove(m_pid_file, ec);

fs::path pidDir = fs::path(m_pid_file).parent_path();
if (not fs::is_directory(pidDir, ec))
fs::create_directories(pidDir, ec);

if (ec)
std::clog << "Creating directory for pid file failed: " << ec.message() << '\n';

fs::path outLogDir = fs::path(m_stdout_log_file).parent_path();
if (not fs::is_directory(outLogDir, ec))
fs::create_directories(outLogDir, ec);

if (ec)
std::clog << "Creating directory " << outLogDir << " for log files failed: " << ec.message() << '\n';

fs::path errLogDir = fs::path(m_stderr_log_file).parent_path();
if (not fs::is_directory(errLogDir, ec))
fs::create_directories(errLogDir, ec);

if (ec)
std::clog << "Creating directory " << outLogDir << " for log files failed: " << ec.message() << '\n';

try
{
asio_ns::io_context io_context;

asio_ns::ip::tcp::endpoint endpoint;
try
{
endpoint = asio_ns::ip::tcp::endpoint(asio_ns::ip::make_address(address), port);
}
catch (const std::exception &e)
{
asio_ns::ip::tcp::resolver resolver(io_context);
asio_ns::ip::tcp::resolver::query query(address, std::to_string(port));
endpoint = *resolver.resolve(query);
}

asio_ns::ip::tcp::acceptor acceptor(io_context);
acceptor.open(endpoint.protocol());
acceptor.set_option(asio_ns::ip::tcp::acceptor::reuse_address(true));
acceptor.bind(endpoint);
acceptor.listen();

acceptor.close();
}
catch (exception &e)
{
throw std::runtime_error(std::string("Is server running already? ") + e.what());
}

int pid = daemonize();
if (pid == 0) // Child process
{
// Start listening now that we still have the rights

open_log_file();

std::clog << "starting server\n"
<< "Listening to " << address << ':' << port << '\n';

signal_catcher sc;
sc.block();

std::unique_ptr<basic_server> server;
try
{
server.reset(m_factory());
server->bind(address, port);
}
catch (const exception &e)
{
std::clog << "Failed to launch server: " << e.what() << '\n';
exit(1);
}

// Drop privileges
if (not run_as_user.empty())
{
struct passwd *pw = getpwnam(run_as_user.c_str());
if (pw == NULL)
{
std::clog << "Failed to set uid to " << run_as_user << ": " << strerror(errno) << '\n';
exit(1);
}

int ngroups = 0;
if (getgrouplist(pw->pw_name, pw->pw_gid, nullptr, &ngroups) == -1 and ngroups > 0)
{
std::vector<gid_t> groups(ngroups);
if (getgrouplist(pw->pw_name, pw->pw_gid, groups.data(), &ngroups) != -1 and
setgroups(ngroups, groups.data()) == -1)
{
std::clog << "Failed to set groups for " << run_as_user << ": " << strerror(errno) << '\n';
exit(1);
}
}

if (setgid(pw->pw_gid) < 0)
{
std::clog << "Failed to set gid for " << run_as_user << ": " << strerror(errno) << '\n';
exit(1);
}

if (setuid(pw->pw_uid) < 0)
{
std::clog << "Failed to set uid to " << run_as_user << ": " << strerror(errno) << '\n';
exit(1);
}
}

std::thread t([nr_of_threads, &server]()
{ server->run(nr_of_threads); });

sc.unblock();
sc.wait();

server->stop();

if (t.joinable())
t.join();

if (fs::exists(m_pid_file, ec))
fs::remove(m_pid_file, ec);

if (ec)
std::clog << "Removing pid file failed: " << ec.message() << '\n';

// We're done. Exit
_exit(0);
}
}

return result;
}

int daemon::stop()
{
int result = 1;
Expand Down Expand Up @@ -296,7 +448,7 @@ int daemon::reload()
return result;
}

void daemon::daemonize()
int daemon::daemonize()
{
int pid = fork();

Expand All @@ -308,7 +460,7 @@ void daemon::daemonize()

// exit the parent (=calling) process
if (pid != 0)
_exit(0);
return pid;

if (setsid() < 0)
{
Expand Down Expand Up @@ -348,6 +500,8 @@ void daemon::daemonize()
// close stdin
close(STDIN_FILENO);
open("/dev/null", O_RDONLY);

return 0;
}

void daemon::open_log_file()
Expand Down Expand Up @@ -407,7 +561,7 @@ bool daemon::run_main_loop(const std::string &address, uint16_t port, size_t nr_
sigfillset(&new_mask);
pthread_sigmask(SIG_BLOCK, &new_mask, &old_mask);

preforked_server server([=,this]()
preforked_server server([=, this]()
{
try
{
Expand Down
48 changes: 48 additions & 0 deletions lib-http/test/http-test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,54 @@ BOOST_AUTO_TEST_CASE(webapp_7)
t.join();
}

// Single process variant

BOOST_AUTO_TEST_CASE(webapp_8)
{
// start up a http server and stop it again

zh::daemon d([]() {
auto s = new zh::server;
s->add_controller(new my_controller());
return s;
}, "/tmp/libzeep-tests/zeep-http-test.pid",
"/tmp/libzeep-tests/zeep-http-test-access.log",
"/tmp/libzeep-tests/zeep-http-test-error.log");

std::random_device rng;
uint16_t port = 1024 + (rng() % 10240);

d.start("::", port, 1, "");

std::clog << "started daemon for test_8 at port " << port << '\n';

using namespace std::chrono_literals;
std::this_thread::sleep_for(1s);

try
{
auto reply = simple_request(port, "GET / HTTP/1.0\r\n\r\n");
BOOST_TEST(reply.get_status() == zh::not_found);

reply = simple_request(port, "XXX / HTTP/1.0\r\n\r\n");
BOOST_TEST(reply.get_status() == zh::bad_request);

reply = simple_request(port, "GET /test/one HTTP/1.0\r\n\r\n");
BOOST_TEST(reply.get_status() == zh::ok);

reply = simple_request(port, "GET /test/two HTTP/1.0\r\n\r\n");
BOOST_TEST(reply.get_status() == zh::not_found);
}
catch (const std::exception& e)
{
std::clog << e.what() << '\n';
BOOST_TEST_FAIL("Failed with exception");
throw;
}

d.stop();
}

// authentication test
BOOST_AUTO_TEST_CASE(server_with_security_1)
{
Expand Down
Loading

0 comments on commit d625d8c

Please sign in to comment.