summaryrefslogtreecommitdiffhomepage
path: root/plugins
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2023-01-13 16:20:42 +0100
committerRoland Reichwein <mail@reichwein.it>2023-01-13 16:20:42 +0100
commitd14582a1d92e036780166a0b5ec0494d7353cc75 (patch)
treeb14c7d52f8bdbe511a2efb25aae45a565db202d0 /plugins
parentbde446bcc08483707dc20a0bbf85ad70bc9d1496 (diff)
Implemented and tested managed FCGI application start, separated out process check functions to libreichwein
Diffstat (limited to 'plugins')
-rw-r--r--plugins/fcgi/Makefile1
-rw-r--r--plugins/fcgi/fastcgiprocess.cpp151
-rw-r--r--plugins/fcgi/fastcgiprocess.h30
-rw-r--r--plugins/fcgi/socket.cpp59
-rw-r--r--plugins/fcgi/socket.h20
5 files changed, 257 insertions, 4 deletions
diff --git a/plugins/fcgi/Makefile b/plugins/fcgi/Makefile
index cd99cbe..e878c5d 100644
--- a/plugins/fcgi/Makefile
+++ b/plugins/fcgi/Makefile
@@ -20,6 +20,7 @@ LDLIBS=\
-ldl
PROGSRC=\
+ fastcgiprocess.cpp \
fcgi.cpp \
fcgiid.cpp \
socket.cpp
diff --git a/plugins/fcgi/fastcgiprocess.cpp b/plugins/fcgi/fastcgiprocess.cpp
new file mode 100644
index 0000000..dd51583
--- /dev/null
+++ b/plugins/fcgi/fastcgiprocess.cpp
@@ -0,0 +1,151 @@
+#include "fastcgiprocess.h"
+
+#include <chrono>
+#include <filesystem>
+#include <iostream>
+#include <string>
+#include <thread>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/beast/core.hpp>
+#include <boost/beast/http.hpp>
+#include <boost/beast/websocket.hpp>
+#include <boost/beast/websocket/ssl.hpp>
+#include <boost/beast/ssl.hpp>
+#include <boost/beast/version.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/buffers_iterator.hpp>
+#include <boost/asio/connect.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/asio/ssl/error.hpp>
+#include <boost/asio/ssl/stream.hpp>
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/xml_parser.hpp>
+#include <boost/asio/local/stream_protocol.hpp>
+
+#include <signal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#include <libreichwein/file.h>
+#include <libreichwein/process.h>
+
+using namespace std::string_literals;
+namespace fs = std::filesystem;
+namespace pt = boost::property_tree;
+using namespace Reichwein;
+
+#define FCGI_LISTENSOCK_FILENO 0
+
+FastCGIProcess::FastCGIProcess(const std::filesystem::path& exe_path, const std::string& host, unsigned short port):
+ m_pid{},
+ m_command{exe_path.generic_string()},
+ m_host{host},
+ m_port{port}
+{
+ start();
+}
+
+FastCGIProcess::FastCGIProcess(const std::filesystem::path& exe_path, const fs::path& socket_path):
+ m_pid{},
+ m_command{exe_path.generic_string()},
+ m_socket_path{socket_path}
+{
+ start();
+}
+
+FastCGIProcess::~FastCGIProcess()
+{
+ stop();
+}
+
+void FastCGIProcess::start()
+{
+ if (m_pid != 0)
+ throw std::runtime_error("Process already running, so it can't be started");
+
+ m_pid = fork();
+ if (m_pid < 0)
+ throw std::runtime_error("Fork unsuccessful.");
+
+ if (m_pid == 0) { // child process branch
+ try {
+ int fd{};
+ boost::asio::io_context ioc;
+ boost::asio::ip::tcp::resolver resolver(ioc);
+ boost::asio::ip::tcp::acceptor acceptor(ioc);
+ boost::asio::local::stream_protocol::acceptor file_acceptor(ioc);
+
+ if (m_socket_path.empty()) { // tcp connection
+ auto const results = resolver.resolve(m_host.c_str(), std::to_string(m_port).c_str());
+ if (results.begin() == results.end())
+ std::runtime_error("no resolve result");
+ boost::asio::ip::tcp::endpoint endpoint{*results.begin()};
+ acceptor.open(endpoint.protocol());
+ acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
+ acceptor.bind(endpoint);
+ acceptor.listen();
+ fd = acceptor.native_handle();
+ } else { // unix domain socket
+ std::error_code ec;
+ fs::remove(m_socket_path, ec); // otherwise we get: "bind: Address already in use"
+
+ boost::asio::local::stream_protocol::endpoint endpoint(m_socket_path);
+ file_acceptor.open(endpoint.protocol());
+ file_acceptor.set_option(boost::asio::local::stream_protocol::acceptor::reuse_address(true));
+ file_acceptor.bind(endpoint);
+ file_acceptor.listen();
+ fd = file_acceptor.native_handle();
+ }
+
+ if (fd != FCGI_LISTENSOCK_FILENO) {
+ close(FCGI_LISTENSOCK_FILENO);
+ dup2(fd, FCGI_LISTENSOCK_FILENO);
+ close(fd);
+ }
+
+ execl(m_command.c_str(), m_command.c_str(), (const char*)nullptr);
+ } catch (const std::exception& ex) {
+ std::cout << "FastCGI process error: " << ex.what() << std::endl;
+ }
+ exit(0);
+ }
+
+ // wait for server to start up
+ if (m_socket_path.empty()) { // tcp connection
+ Process::wait_for_pid_listening_on(m_pid, m_port);
+ } else { // unix domain socket
+ Process::wait_for_pid_listening_on(m_pid, m_socket_path);
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+}
+
+void FastCGIProcess::stop()
+{
+ if (m_pid == 0)
+ throw std::runtime_error("Process not running, so it can't be stopped");
+
+ if (kill(m_pid, SIGTERM) != 0)
+ throw std::runtime_error("Unable to kill process");
+
+ if (int result = waitpid(m_pid, NULL, 0); result != m_pid)
+ throw std::runtime_error("waitpid returned "s + std::to_string(result));
+
+ if (!m_socket_path.empty()) {
+ std::error_code ec;
+ fs::remove(m_socket_path, ec);
+ }
+
+ m_pid = 0;
+}
+
+bool FastCGIProcess::is_running()
+{
+ if (m_pid == 0)
+ return false;
+
+ return Process::is_running(m_pid);
+}
+
diff --git a/plugins/fcgi/fastcgiprocess.h b/plugins/fcgi/fastcgiprocess.h
new file mode 100644
index 0000000..07b6539
--- /dev/null
+++ b/plugins/fcgi/fastcgiprocess.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <filesystem>
+#include <string>
+
+#include <ext/stdio_filebuf.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+
+class FastCGIProcess
+{
+public:
+ FastCGIProcess(const std::filesystem::path& exe_path, const std::string& host, unsigned short port);
+ FastCGIProcess(const std::filesystem::path& exe_path, const std::filesystem::path& socket_path);
+ ~FastCGIProcess();
+ bool is_running();
+
+private:
+ void start();
+ void stop();
+
+ pid_t m_pid{};
+ std::string m_command;
+ std::string m_host;
+ unsigned short m_port{};
+ std::filesystem::path m_socket_path;
+};
diff --git a/plugins/fcgi/socket.cpp b/plugins/fcgi/socket.cpp
index 2b34bc3..c899de5 100644
--- a/plugins/fcgi/socket.cpp
+++ b/plugins/fcgi/socket.cpp
@@ -1,5 +1,7 @@
#include "socket.h"
+#include <fmt/core.h>
+
#include <filesystem>
#include <iostream>
@@ -22,16 +24,16 @@ SocketFactory::SocketFactory()
std::shared_ptr<Socket> SocketFactory::create(const std::string& app_addr)
{
+ std::error_code ec;
size_t pos { app_addr.find_last_of(':') };
if (pos != app_addr.npos) { // tcp socket: host:port
return std::make_shared<TCPSocket>(app_addr.substr(0, pos), app_addr.substr(pos + 1), m_io_context);
- } else if (fs::is_socket(fs::path{app_addr})) { // Unix domain socket
+ } else if (fs::is_socket(fs::path{app_addr}, ec)) { // Unix domain socket
return std::make_shared<FileSocket>(app_addr, m_io_context);
- } else if (fs::is_regular_file(fs::path{app_addr})) { // Executable to start
- // TODO
- std::cerr << "FCGI Error: Executable FCGI not yet implemented." << std::endl;
+ } else if (fs::is_regular_file(fs::path{app_addr}, ec)) { // Executable to start
+ return std::make_shared<FileSocketApp>(app_addr, m_io_context);
} else {
std::cerr << "FCGI Error: Invalid app_addr type." << std::endl;
}
@@ -232,3 +234,52 @@ size_t FileSocket::read(std::vector<char>& data)
}
}
+std::string generate_unix_domain_socket(const std::string& directory)
+{
+ for (int i = 0; i < 10000; i++) {
+ std::string path{fmt::format("{}/fcgi-socket{}", directory, i)};
+ if (fs::exists(path))
+ continue;
+ return path;
+ }
+ throw std::runtime_error("dynamic unix domain socket couldn't be generated.");
+}
+
+FileSocketApp::FileSocketApp(const std::string& app_addr, boost::asio::io_context& io_context):
+ m_socket_file{generate_unix_domain_socket("/var/lib/webserver")},
+ m_fcgi_process{app_addr, m_socket_file},
+ m_file_socket{m_socket_file, io_context}
+{
+}
+
+FileSocketApp::~FileSocketApp()
+{
+ std::error_code ec;
+ fs::remove(m_socket_file, ec);
+}
+
+void FileSocketApp::open()
+{
+ m_file_socket.open();
+}
+
+void FileSocketApp::close()
+{
+ m_file_socket.close();
+}
+
+bool FileSocketApp::is_open()
+{
+ return m_file_socket.is_open();
+}
+
+size_t FileSocketApp::write(const std::vector<char>& data)
+{
+ return m_file_socket.write(data);
+}
+
+size_t FileSocketApp::read(std::vector<char>& data)
+{
+ return m_file_socket.read(data);
+}
+
diff --git a/plugins/fcgi/socket.h b/plugins/fcgi/socket.h
index 272b844..ceee2a4 100644
--- a/plugins/fcgi/socket.h
+++ b/plugins/fcgi/socket.h
@@ -1,5 +1,6 @@
#pragma once
+#include "fastcgiprocess.h"
#include "fcgiid.h"
#include <boost/asio.hpp>
@@ -83,3 +84,22 @@ public:
size_t read(std::vector<char>& data) override;
};
+// File Socket, with Application started by us
+class FileSocketApp: public Socket
+{
+public:
+ FileSocketApp(const std::string& app_addr, boost::asio::io_context& io_context);
+ ~FileSocketApp() override;
+
+ void open() override;
+ void close() override;
+ bool is_open() override;
+ size_t write(const std::vector<char>& data) override;
+ size_t read(std::vector<char>& data) override;
+
+private:
+ std::string m_socket_file;
+ FastCGIProcess m_fcgi_process; // Application server
+ FileSocket m_file_socket; // Connection from client side
+};
+