summaryrefslogtreecommitdiffhomepage
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
parentbde446bcc08483707dc20a0bbf85ad70bc9d1496 (diff)
Implemented and tested managed FCGI application start, separated out process check functions to libreichwein
-rw-r--r--TODO3
-rw-r--r--common.mk4
-rw-r--r--debian/changelog1
-rw-r--r--plugins/fcgi/Makefile1
-rw-r--r--plugins/fcgi/fastcgiprocess.cpp (renamed from tests/fastcgiprocess.cpp)68
-rw-r--r--plugins/fcgi/fastcgiprocess.h (renamed from tests/fastcgiprocess.h)8
-rw-r--r--plugins/fcgi/socket.cpp59
-rw-r--r--plugins/fcgi/socket.h20
-rw-r--r--tests/Makefile8
-rw-r--r--tests/helper.cpp62
-rw-r--r--tests/helper.h3
-rw-r--r--tests/test-webserver.cpp91
-rw-r--r--tests/webserverprocess.cpp2
-rw-r--r--tests/websocketserverprocess.cpp2
14 files changed, 238 insertions, 94 deletions
diff --git a/TODO b/TODO
index 21c9b79..5cc50fd 100644
--- a/TODO
+++ b/TODO
@@ -1,7 +1,5 @@
example conf files:
- php
-test:
-- FCGI
Big file bug
- dynamic plugin interface (file buffer, ...)
@@ -11,6 +9,7 @@ cgi unhandled headers
git via smart http / cgi
git via web interface
php
+SCGI?
weblog: link consistency check (cron?)
Integrate into Debian: WNPP
support alternative SSL libs: mbedtls, gnutls, wolfssl, botan, linux kernel TLS (matrixssl, libressl, cryptlib: not in debian)
diff --git a/common.mk b/common.mk
index 5770d7d..5041c9b 100644
--- a/common.mk
+++ b/common.mk
@@ -100,3 +100,7 @@ endif
SRC_ROOT=$(shell echo $(MAKEFILE_LIST) | tr " " "\n" | grep common.mk | sed -e 's/\([^ ]*\)common.mk/\1/g')
VERSION=$(shell dpkg-parsechangelog --show-field Version --file $(SRC_ROOT)/debian/changelog)
CXXFLAGS+=-DVERSION=\"$(VERSION)\"
+
+LIBS+=$(shell pkg-config --libs fmt)
+CXXFLAGS+=$(shell pkg-config --cflags fmt)
+
diff --git a/debian/changelog b/debian/changelog
index 0ebbed9..3f3b762 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -4,6 +4,7 @@ webserver (1.18~pre1) UNRELEASED; urgency=medium
* CGI bugfix: Executable execute bits check
* Added example configurations
* FCGI bugfix: IPv6 support
+ * FCGI: Implemented direct application start
-- Roland Reichwein <mail@reichwein.it> Sun, 08 Jan 2023 15:26:48 +0100
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/tests/fastcgiprocess.cpp b/plugins/fcgi/fastcgiprocess.cpp
index 53b9d04..dd51583 100644
--- a/tests/fastcgiprocess.cpp
+++ b/plugins/fcgi/fastcgiprocess.cpp
@@ -1,5 +1,11 @@
#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>
@@ -15,6 +21,7 @@
#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>
@@ -25,25 +32,30 @@
#include <libreichwein/file.h>
#include <libreichwein/process.h>
-#include "helper.h"
-
using namespace std::string_literals;
namespace fs = std::filesystem;
namespace pt = boost::property_tree;
-using namespace boost::unit_test;
using namespace Reichwein;
#define FCGI_LISTENSOCK_FILENO 0
-FastCGIProcess::FastCGIProcess(const std::filesystem::path& path, const std::string& host, unsigned short port):
+FastCGIProcess::FastCGIProcess(const std::filesystem::path& exe_path, const std::string& host, unsigned short port):
m_pid{},
- m_command{path.generic_string()},
+ 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();
@@ -60,18 +72,33 @@ void FastCGIProcess::start()
if (m_pid == 0) { // child process branch
try {
+ int fd{};
boost::asio::io_context ioc;
boost::asio::ip::tcp::resolver resolver(ioc);
- 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()};
boost::asio::ip::tcp::acceptor acceptor(ioc);
- acceptor.open(endpoint.protocol());
- acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
- acceptor.bind(endpoint);
- acceptor.listen();
- int fd{acceptor.native_handle()};
+ 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);
@@ -87,7 +114,11 @@ void FastCGIProcess::start()
}
// wait for server to start up
- wait_for_pid_listening_on(m_pid, m_port);
+ 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));
}
@@ -102,6 +133,11 @@ void FastCGIProcess::stop()
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;
}
@@ -110,6 +146,6 @@ bool FastCGIProcess::is_running()
if (m_pid == 0)
return false;
- return Reichwein::Process::is_running(m_pid);
+ return Process::is_running(m_pid);
}
diff --git a/tests/fastcgiprocess.h b/plugins/fcgi/fastcgiprocess.h
index ce7bf74..07b6539 100644
--- a/tests/fastcgiprocess.h
+++ b/plugins/fcgi/fastcgiprocess.h
@@ -13,7 +13,8 @@
class FastCGIProcess
{
public:
- FastCGIProcess(const std::filesystem::path& path, const std::string& host, unsigned short port);
+ 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();
@@ -21,8 +22,9 @@ private:
void start();
void stop();
- pid_t m_pid;
+ pid_t m_pid{};
std::string m_command;
std::string m_host;
- unsigned short m_port;
+ 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
+};
+
diff --git a/tests/Makefile b/tests/Makefile
index 8df5a45..898afbf 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -17,7 +17,7 @@ CXXFLAGS+= -I. -I.. -fPIE
CXXTESTFLAGS=
-CXXFLAGS+=$(shell pkg-config --cflags fmt fcgi)
+CXXFLAGS+=$(shell pkg-config --cflags fcgi)
LIBS+=\
-lreichwein \
@@ -29,7 +29,7 @@ LIBS+=\
-lpthread \
-lssl -lcrypto \
-ldl \
-$(shell pkg-config --libs fmt fcgi)
+$(shell pkg-config --libs fcgi)
LDFLAGS+=-pie
@@ -37,6 +37,7 @@ UNITS=\
auth.cpp \
config.cpp \
error.cpp \
+ fastcgiprocess.cpp \
http.cpp \
plugin.cpp \
privileges.cpp \
@@ -57,7 +58,6 @@ TESTSRC=\
test-server.cpp \
test-statistics.cpp \
test-webserver.cpp \
- fastcgiprocess.cpp \
helper.cpp \
webserverprocess.cpp \
websocketserverprocess.cpp
@@ -93,6 +93,8 @@ config.o: ../config.cpp
$(CXX) $(CXXFLAGS) $(CXXTESTFLAGS) -c $< -o $@
error.o: ../error.cpp
$(CXX) $(CXXFLAGS) $(CXXTESTFLAGS) -c $< -o $@
+fastcgiprocess.o: ../plugins/fcgi/fastcgiprocess.cpp
+ $(CXX) $(CXXFLAGS) $(CXXTESTFLAGS) -c $< -o $@
http.o: ../http.cpp
$(CXX) $(CXXFLAGS) $(CXXTESTFLAGS) -c $< -o $@
plugin.o: ../plugin.cpp
diff --git a/tests/helper.cpp b/tests/helper.cpp
index 644b9ca..66045fb 100644
--- a/tests/helper.cpp
+++ b/tests/helper.cpp
@@ -1,5 +1,7 @@
#include "helper.h"
+#include <libreichwein/stringhelper.h>
+
using namespace std::string_literals;
namespace fs = std::filesystem;
namespace pt = boost::property_tree;
@@ -10,66 +12,6 @@ const fs::path testConfigFilename{"./webserver.conf"};
const fs::path testCertFilename{"./testchain.pem"};
const fs::path testKeyFilename{"./testkey.pem"};
-// tcp: tcp or tcp6
-bool tcp_is_pid_listening_on(const std::string& tcp, pid_t pid, int port)
-{
- std::string filename{fmt::format("/proc/{}/net/{}", pid, tcp)};
- std::ifstream f{filename, std::ios::in};
- // e.g.:
- // sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
- // 0: 00000000:C799 00000000:0000 0A 00000000:00000000 00:00000000 00000000 107 0 21869 1 00000000335416a4 100 0 0 10 0
- std::string s;
- std::getline(f, s); // skip head line
- while (std::getline(f, s)) {
- boost::algorithm::trim_left(s);
-
- size_t pos_space1{s.find(' ')};
- if (pos_space1 == std::string::npos)
- throw std::runtime_error("Expected first space in " + filename);
-
- size_t pos_colon1{s.find(':', pos_space1 + 1)};
- if (pos_colon1 == std::string::npos)
- throw std::runtime_error("Expected first colon in " + filename);
-
- size_t pos_space2{s.find(' ', pos_colon1 + 1)};
- if (pos_space2 == std::string::npos)
- throw std::runtime_error("Expected second space in " + filename);
-
- std::string port_s{s.substr(pos_colon1 + 1, pos_space2 - (pos_colon1 + 1))};
- auto current_port{std::stoul(port_s, nullptr, 16)};
- if (current_port != port)
- continue;
-
- // now, we are in a line related to matching local port
-
- size_t pos_space3{s.find(' ', pos_space2 + 1)};
- if (pos_space3 == std::string::npos)
- throw std::runtime_error("Expected third space in " + filename);
-
- size_t pos_space4{s.find(' ', pos_space3 + 1)};
- if (pos_space4 == std::string::npos)
- throw std::runtime_error("Expected fourth space in " + filename);
-
- std::string state_s{s.substr(pos_space3 + 1, pos_space4 - (pos_space3 + 1))};
- if (state_s == "0A") // listening state TCP_LISTEN, from net/tcp_states.h
- return true;
- }
-
- return false; // not found
-}
-
-bool is_pid_listening_on(pid_t pid, int port)
-{
- return tcp_is_pid_listening_on("tcp", pid, port) || tcp_is_pid_listening_on("tcp6", pid, port);
-}
-
-void wait_for_pid_listening_on(pid_t pid, int port)
-{
- while (!is_pid_listening_on(pid, port)) {
- std::this_thread::sleep_for(std::chrono::milliseconds(10));
- }
-}
-
// returns -1 if no port found in config
int port_from_config(const std::string& config)
{
diff --git a/tests/helper.h b/tests/helper.h
index eba74cd..be443c6 100644
--- a/tests/helper.h
+++ b/tests/helper.h
@@ -47,9 +47,6 @@ extern const std::filesystem::path testConfigFilename;
extern const std::filesystem::path testCertFilename;
extern const std::filesystem::path testKeyFilename;
-bool tcp_is_pid_listening_on(const std::string& tcp, pid_t pid, int port);
-bool is_pid_listening_on(pid_t pid, int port);
-void wait_for_pid_listening_on(pid_t pid, int port);
int port_from_config(const std::string& config);
void load_root_certificates(boost::asio::ssl::context& ctx);
std::pair<std::string,std::string> HTTP(const std::string& target, bool ipv6 = true, bool HTTP11 = true, boost::beast::http::verb method = boost::beast::http::verb::get);
diff --git a/tests/test-webserver.cpp b/tests/test-webserver.cpp
index bd89aab..5353c6a 100644
--- a/tests/test-webserver.cpp
+++ b/tests/test-webserver.cpp
@@ -18,6 +18,7 @@
#include <boost/asio/buffers_iterator.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
+#include <boost/asio/local/stream_protocol.hpp>
#include <boost/asio/ssl/error.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/property_tree/ptree.hpp>
@@ -48,7 +49,8 @@
#include "webserver.h"
#include "response.h"
-#include "fastcgiprocess.h"
+#include "plugins/fcgi/fastcgiprocess.h"
+
#include "helper.h"
#include "webserverprocess.h"
#include "websocketserverprocess.h"
@@ -694,3 +696,90 @@ BOOST_FIXTURE_TEST_CASE(http_fcgi_ip6, Fixture)
BOOST_CHECK_EQUAL(result.second, "returning data of : ");
}
+BOOST_FIXTURE_TEST_CASE(http_fcgi_unix_socket, Fixture)
+{
+ std::string webserver_config{R"CONFIG(<webserver>
+ <user>www-data</user>
+ <group>www-data</group>
+ <threads>10</threads>
+ <statisticspath>stats.db</statisticspath>
+ <plugin-directory>../plugins</plugin-directory>
+ <sites>
+ <site>
+ <name>localhost</name>
+ <host>localhost</host>
+ <host>[::1]</host>
+ <path requested="/fcgi">
+ <plugin>fcgi</plugin>
+ <target>fcgi-socket</target>
+ </path>
+ </site>
+ </sites>
+ <sockets>
+ <socket>
+ <address>::1</address>
+ <port>8080</port>
+ <protocol>http</protocol>
+ <site>localhost</site>
+ </socket>
+ </sockets>
+</webserver>)CONFIG"};
+ WebserverProcess serverProcess{webserver_config};
+ BOOST_REQUIRE(serverProcess.is_running());
+
+ FastCGIProcess fcgiProcess("./fcgi1", "fcgi-socket");
+ BOOST_REQUIRE(fcgiProcess.is_running());
+
+ auto result {HTTP("/fcgi/abc")};
+ BOOST_CHECK_EQUAL(result.first, fmt::format(
+"HTTP/1.1 200 OK\r\n"
+"Server: Reichwein.IT Webserver {}\r\n"
+"Content-Type: text/plain\r\n"
+"Content-Length: {}\r\n"
+"\r\n"
+ , VERSION, result.second.size()));
+ BOOST_CHECK_EQUAL(result.second, "returning data of : ");
+}
+
+BOOST_FIXTURE_TEST_CASE(http_fcgi_executable, Fixture)
+{
+ std::string webserver_config{R"CONFIG(<webserver>
+ <user>www-data</user>
+ <group>www-data</group>
+ <threads>10</threads>
+ <statisticspath>stats.db</statisticspath>
+ <plugin-directory>../plugins</plugin-directory>
+ <sites>
+ <site>
+ <name>localhost</name>
+ <host>localhost</host>
+ <host>[::1]</host>
+ <path requested="/fcgi">
+ <plugin>fcgi</plugin>
+ <target>fcgi1</target>
+ </path>
+ </site>
+ </sites>
+ <sockets>
+ <socket>
+ <address>::1</address>
+ <port>8080</port>
+ <protocol>http</protocol>
+ <site>localhost</site>
+ </socket>
+ </sockets>
+</webserver>)CONFIG"};
+ WebserverProcess serverProcess{webserver_config};
+ BOOST_REQUIRE(serverProcess.is_running());
+
+ auto result {HTTP("/fcgi/abc")};
+ BOOST_CHECK_EQUAL(result.first, fmt::format(
+"HTTP/1.1 200 OK\r\n"
+"Server: Reichwein.IT Webserver {}\r\n"
+"Content-Type: text/plain\r\n"
+"Content-Length: {}\r\n"
+"\r\n"
+ , VERSION, result.second.size()));
+ BOOST_CHECK_EQUAL(result.second, "returning data of : ");
+}
+
diff --git a/tests/webserverprocess.cpp b/tests/webserverprocess.cpp
index c91275b..edafaaa 100644
--- a/tests/webserverprocess.cpp
+++ b/tests/webserverprocess.cpp
@@ -193,7 +193,7 @@ void WebserverProcess::start()
// wait for server to start up
if (int port{port_from_config(m_config)}; port >= 0)
- wait_for_pid_listening_on(m_pid, port);
+ Process::wait_for_pid_listening_on(m_pid, port);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
diff --git a/tests/websocketserverprocess.cpp b/tests/websocketserverprocess.cpp
index 89a50ee..a0ec13d 100644
--- a/tests/websocketserverprocess.cpp
+++ b/tests/websocketserverprocess.cpp
@@ -174,7 +174,7 @@ void WebsocketServerProcess::start()
exit(0);
}
- wait_for_pid_listening_on(m_pid, 8765);
+ Process::wait_for_pid_listening_on(m_pid, 8765);
}
void WebsocketServerProcess::stop()