diff options
| author | Roland Reichwein <mail@reichwein.it> | 2023-01-13 16:20:42 +0100 | 
|---|---|---|
| committer | Roland Reichwein <mail@reichwein.it> | 2023-01-13 16:20:42 +0100 | 
| commit | d14582a1d92e036780166a0b5ec0494d7353cc75 (patch) | |
| tree | b14c7d52f8bdbe511a2efb25aae45a565db202d0 | |
| parent | bde446bcc08483707dc20a0bbf85ad70bc9d1496 (diff) | |
Implemented and tested managed FCGI application start, separated out process check functions to libreichwein
| -rw-r--r-- | TODO | 3 | ||||
| -rw-r--r-- | common.mk | 4 | ||||
| -rw-r--r-- | debian/changelog | 1 | ||||
| -rw-r--r-- | plugins/fcgi/Makefile | 1 | ||||
| -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.cpp | 59 | ||||
| -rw-r--r-- | plugins/fcgi/socket.h | 20 | ||||
| -rw-r--r-- | tests/Makefile | 8 | ||||
| -rw-r--r-- | tests/helper.cpp | 62 | ||||
| -rw-r--r-- | tests/helper.h | 3 | ||||
| -rw-r--r-- | tests/test-webserver.cpp | 91 | ||||
| -rw-r--r-- | tests/webserverprocess.cpp | 2 | ||||
| -rw-r--r-- | tests/websocketserverprocess.cpp | 2 | 
14 files changed, 238 insertions, 94 deletions
@@ -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) @@ -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()  | 
