Fork FCGI process

FossilOrigin-Name: 89820a01105e1a7ee10efbb5e072c1d0807a1cd730eaf00f6ed6b0a290b39c69
This commit is contained in:
nekobit 2022-10-30 17:09:32 +00:00
parent 96a8adbb20
commit ecc0d91cb5
5 changed files with 121 additions and 28 deletions

View file

@ -18,7 +18,11 @@ database:
db_file: "wormhole.sqlite"
# frontend:
# # If you specify multiple frontends configs and want to easily switch,
# # fe_type just ensures which one to use (but if you use just one, it's not required)
# fe_type: "fcgi"
#
# fcgi:
# exec: "/usr/bin/treebird"
# # Note: If you aren't building on GLIBC, this must be the
# # full path to the executable.
# exec: "treebird"

View file

@ -17,9 +17,14 @@
*/
#include <iostream>
#include <array>
#include <string>
#include <unistd.h>
#include "fcgi.h"
#include "config/config_loader.h"
#include "logger.h"
constexpr int FCGI_LISTENSOCK_FILENO = STDIN_FILENO;
HTTP::Response Route::fcgi_request(std::any& args, const HTTP::Request& req, const HTTP::RequestArgs_t& arg)
{
@ -27,16 +32,71 @@ HTTP::Response Route::fcgi_request(std::any& args, const HTTP::Request& req, con
return HTTP::Response{ "Hello FCGI"s + req.get_url(), HTTP::MIME::HTML };
}
namespace {
void setup_routes(HTTP::Server* server)
{
// TODO Use bitmasks for this, fucking hell...
server->add_route({{HTTP::Request::Type::GET, "/:"}, Route::fcgi_request});
server->add_route({{HTTP::Request::Type::PUT, "/:"}, Route::fcgi_request});
server->add_route({{HTTP::Request::Type::POST, "/:"}, Route::fcgi_request});
server->add_route({{HTTP::Request::Type::DELETE, "/:"}, Route::fcgi_request});
server->add_route({{HTTP::Request::Type::PATCH, "/:"}, Route::fcgi_request});
server->add_route({{HTTP::Request::Type::OTHER, "/:"}, Route::fcgi_request});
}
}
void Frontend::init_fcgi(HTTP::Server* server)
{
if (Config::instance().config.frontend.type != Type::FCGI)
return;
// TODO Use bitmasks for this, fucking hell...
server->add_route({{HTTP::Request::Type::GET, "/:"}, Route::fcgi_request});
server->add_route({{HTTP::Request::Type::PUT, "/:"}, Route::fcgi_request});
server->add_route({{HTTP::Request::Type::POST, "/:"}, Route::fcgi_request});
server->add_route({{HTTP::Request::Type::DELETE, "/:"}, Route::fcgi_request});
server->add_route({{HTTP::Request::Type::PATCH, "/:"}, Route::fcgi_request});
server->add_route({{HTTP::Request::Type::OTHER, "/:"}, Route::fcgi_request});
setup_routes(server);
}
void Frontend::poll_data(std::array<int, 2> data)
{
}
std::array<int, 2> Frontend::fork_fcgi_process()
{
if (Config::instance().config.frontend.type != Type::FCGI)
return {-1, -1};
std::array<int, 2> fd;
const std::string& exec = Config::instance().config.frontend.exec;
pipe(fd.data());
int pid = fork();
bool is_parent = pid != 0;
if (is_parent)
{
Logger::instance() << "Executing frontend \"" + exec + "\" in process " + std::to_string(pid) + "...";
return fd;
}
// We are the child process from here onwards
// FCGI says these aren't open
close(STDOUT_FILENO);
close(STDERR_FILENO);
char* argv[] = {
nullptr
};
int rc;
rc =
#ifdef _GNU_SOURCE
// Checks PATH as well
execvpe
#else
execve
#endif
(exec.data(), argv, nullptr);
Logger::instance() << "Child process \"" + exec + "\" exited with code " + std::to_string(rc);
exit(0);
}

View file

@ -18,6 +18,7 @@
#pragma once
#include <unistd.h>
#include "http/httpserver.h"
namespace Route
@ -28,4 +29,7 @@ namespace Route
namespace Frontend
{
void init_fcgi(HTTP::Server* server);
void poll_data(std::array<int, 2> data);
std::array<int, 2> fork_fcgi_process();
}

View file

@ -18,20 +18,20 @@
#include <cstdint>
#include <cstring>
#include <array>
#include <iostream>
#include <string_view>
#include <utility>
#include <microhttpd.h>
#include "logger.h"
#include "microhttpd_server.h"
#include "http/mime.h"
#include "http/request.h"
#include <microhttpd.h>
#include "http/response.h"
constexpr int POSTBUFFERSIZE = 2048;
using namespace std::string_literals;
using namespace HTTP;
constexpr int POSTBUFFERSIZE = 2048;
// Struct we define to hold onto data
struct MHDConnectionState
{
@ -173,16 +173,26 @@ MicroHttpdServer::MicroHttpdServer(std::uint16_t port, std::any callback_args)
void MicroHttpdServer::start()
{
serv.reset(MHD_start_daemon(MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD,
get_port(),
nullptr,
0,
&::new_connection,
this,
MHD_OPTION_NOTIFY_CONNECTION,
request_completed,
this,
MHD_OPTION_END));
MHD_Daemon* dm = MHD_start_daemon(MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD,
get_port(),
nullptr,
0,
&::new_connection,
this,
MHD_OPTION_NOTIFY_CONNECTION,
request_completed,
this,
MHD_OPTION_END);
if (!dm)
{
// MicroHTTPD seems to keep the errors in errno, but doesn't really have a good error system
Logger::instance().log("Couldn't start MicroHTTPD daemon at port "s + std::to_string(get_port()) +
": " + strerror(errno),
Logger::Level::ERROR);
exit(1);
}
serv.reset(dm);
}

View file

@ -71,6 +71,7 @@ void init_routes(const std::unique_ptr<HTTP::Server>& server)
Protocol::MastoAPI::init_masto_api(server.get());
#endif // MODULE_MASTO_API
// Global routes
#ifdef MODULE_FCGI
Frontend::init_fcgi(server.get());
#endif // MODULE_FCGI
@ -83,6 +84,11 @@ int start_wormhole()
Logger::instance() << "Loading config...";
std::shared_ptr<DB::Database> database = DB::load_db_from_cfg();
#ifdef MODULE_FCGI
// Config loaded, fork main FCGI process early if set in config
auto pipe_fds = Frontend::fork_fcgi_process();
#endif
// Passed as std::any to each route function
RouteArgs args{database.get()};
@ -133,12 +139,21 @@ int start_wormhole()
} },
});
// Start the server!
// Start the server! This forks in the background
server->start();
Logger::instance() << "HTTP server running on port "s + std::to_string(Config::instance().config.http.port);
getchar();
Logger::instance() << "HTTP server running on port "s +
std::to_string(Config::instance().config.http.port);
/* So... now what?
* Well, if we are building with FCGI, we can use this thread
* to listen to our pipes */
#ifdef MODULE_FCGI
Frontend::poll_data(pipe_fds);
#endif
Logger::instance() << "^D or enter 'q' to stop";
char c;
while ((c = getchar()) != EOF && c != 'q' && c != 'Q');
return EXIT_SUCCESS;
}