#include "socket.h" #include #include #include namespace fs = std::filesystem; using namespace std::string_literals; std::mutex& Socket::getMutex() { return m_mutex; } FCGI_ID& Socket::fcgi_id() { return m_fcgi_id; } SocketFactory::SocketFactory() { } std::shared_ptr 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(app_addr.substr(0, pos), app_addr.substr(pos + 1), m_io_context); } else if (fs::is_socket(fs::path{app_addr}, ec)) { // Unix domain socket return std::make_shared(app_addr, m_io_context); } else if (fs::is_regular_file(fs::path{app_addr}, ec)) { // Executable to start return std::make_shared(app_addr, m_io_context); } else { std::cerr << "FCGI Error: Invalid app_addr type." << std::endl; } return {}; } TCPSocket::TCPSocket(const std::string& host, const std::string& port, boost::asio::io_context& io_context) : m_io_context(io_context) , m_host(host) , m_port(port) , m_socket(io_context) { } TCPSocket::~TCPSocket() { } void TCPSocket::open() { boost::asio::ip::tcp::resolver resolver(m_io_context); auto endpoints{resolver.resolve(m_host, m_port)}; try { boost::asio::connect(m_socket, endpoints); } catch(const std::exception& ex) { std::cerr << "FCGI Error: Error on connecting to " << m_host << ":" << m_port << ": " << ex.what() << std::endl; return; } boost::asio::socket_base::keep_alive keepAlive(true); m_socket.set_option(keepAlive); struct timeval tv; tv.tv_sec = 0; // infinite tv.tv_usec = 0; if (setsockopt(m_socket.native_handle(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) std::cerr << "FCGI Error: SO_RCVTIMEO" << std::endl; if (setsockopt(m_socket.native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv))) std::cerr << "FCGI Error: SO_SNDTIMEO" << std::endl; int val{1}; if (setsockopt(m_socket.native_handle(), SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val))) std::cerr << "FCGI Error: SO_KEEPALIVE" << std::endl; } bool TCPSocket::is_open() { if (m_socket.is_open()) { std::vector inbuf; int error; socklen_t len = sizeof (error); int retval {getsockopt(m_socket.native_handle(), SOL_SOCKET, SO_ERROR, &error, &len)}; if (retval) { std::cerr << "FCGI Error: getsockopt() error in is_open(): " << strerror(errno) << std::endl; try { // for graceful shutdown, according to // https://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio/reference/basic_stream_socket/close/overload1.html m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both); m_socket.close(); // correct state } catch (...) { std::cerr << "Error on shutdown/close" << std::endl; } return false; } if (error != 0) { try { m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both); m_socket.close(); // correct state } catch (...) { std::cerr << "Error on shutdown/close" << std::endl; } return false; } return true; } else return false; } void TCPSocket::close() { m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both); m_socket.close(); } size_t TCPSocket::write(const std::vector& data) { try { return m_socket.write_some(boost::asio::buffer(data)); } catch (const boost::system::system_error& ex) { if (ex.code() == boost::asio::error::eof) { throw fcgi_eof_error("EOF on write"); } else if (ex.code() == boost::asio::error::broken_pipe) { throw fcgi_broken_pipe_error("Application server connection broken"); } else throw std::runtime_error("FCGI Error: Unknown boost asio exception on write: "s + ex.what()); } catch (const std::exception& ex) { throw std::runtime_error("FCGI Error: Unknown exception on write: "s + ex.what()); } } size_t TCPSocket::read(std::vector& data) { try { size_t result{0}; do { std::vector inbuf_part(1024); size_t got { m_socket.read_some(boost::asio::buffer(inbuf_part))}; data.insert(data.end(), inbuf_part.begin(), inbuf_part.begin() + got); result += got; } while (m_socket.available()); return result; } catch (const boost::system::system_error& ex) { if (ex.code() == boost::asio::error::eof) { throw fcgi_eof_error("EOF on read"); } else throw std::runtime_error("FCGI Error: Unknown boost asio exception on read: "s + ex.what()); } catch (const std::exception& ex) { throw std::runtime_error("FCGI Error: Unknown exception on read: "s + ex.what()); } } FileSocket::FileSocket(const std::string& app_addr, boost::asio::io_context& io_context) : m_app_addr(app_addr) , m_socket(io_context) { } FileSocket::~FileSocket() { } void FileSocket::open() { try { boost::asio::local::stream_protocol::endpoint ep(m_app_addr); m_socket.connect(ep); } catch(const std::exception& ex) { std::cerr << "FCGI Error: Error on connecting to " << m_app_addr << ": " << ex.what() << std::endl; return; } } bool FileSocket::is_open() { return m_socket.is_open(); } void FileSocket::close() { m_socket.close(); } size_t FileSocket::write(const std::vector& data) { try { return m_socket.write_some(boost::asio::buffer(data)); } catch (const boost::system::system_error& ex) { if (ex.code() == boost::asio::error::eof) { throw fcgi_eof_error("EOF on write"); } else if (ex.code() == boost::asio::error::broken_pipe) { throw fcgi_broken_pipe_error("Application server connection broken"); } else throw std::runtime_error("FCGI Error: Unknown boost asio exception on write: "s + ex.what()); } catch (const std::exception& ex) { throw std::runtime_error("FCGI Error: Unknown exception on write: "s + ex.what()); } } size_t FileSocket::read(std::vector& data) { try { size_t result{0}; do { std::vector inbuf_part(1024); size_t got { m_socket.read_some(boost::asio::buffer(inbuf_part))}; data.insert(data.end(), inbuf_part.begin(), inbuf_part.begin() + got); result += got; } while (m_socket.available()); return result; } catch (const boost::system::system_error& ex) { if (ex.code() == boost::asio::error::eof) { throw fcgi_eof_error("EOF on read"); } else throw std::runtime_error("FCGI Error: Unknown boost asio exception on read: "s + ex.what()); } catch (const std::exception& ex) { throw std::runtime_error("FCGI Error: Unknown exception on read: "s + ex.what()); } } 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(fs::exists("/var/lib/webserver") ? "/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& data) { return m_file_socket.write(data); } size_t FileSocketApp::read(std::vector& data) { return m_file_socket.read(data); }