Skip to content

Optionally listen on a UNIX socket instead of TCP #126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 31, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ addons:
- libboost-system-dev
- libboost-regex-dev
- libboost-date-time-dev
- libboost-filesystem-dev
- libboost-program-options-dev
- libboost-test-dev
- google-mock
Expand Down
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ else()
set(BOOST_MIN_VERSION "1.40")
endif()

set(CUKE_CORE_BOOST_LIBS thread system regex date_time program_options)
set(CUKE_CORE_BOOST_LIBS thread system regex date_time program_options filesystem)
if(NOT CUKE_DISABLE_BOOST_TEST)
set(CUKE_TEST_BOOST_LIBS unit_test_framework)
endif()
Expand Down Expand Up @@ -72,7 +72,7 @@ endif()

if(Boost_FOUND)
include_directories(${Boost_INCLUDE_DIRS})
set(CUKE_EXTRA_LIBRARIES ${CUKE_EXTRA_LIBRARIES} ${Boost_THREAD_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY})
set(CUKE_EXTRA_LIBRARIES ${CUKE_EXTRA_LIBRARIES} ${Boost_THREAD_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY})
endif()

#
Expand Down
81 changes: 68 additions & 13 deletions include/cucumber-cpp/internal/connectors/wire/WireServer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,41 @@ namespace internal {

using namespace boost::asio;
using namespace boost::asio::ip;
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
using namespace boost::asio::local;
#endif

/**
* Socket server that calls a protocol handler line by line
*/
class SocketServer {
public:
/**
* Constructor for DI
*/
SocketServer(const ProtocolHandler *protocolHandler);

/**
* Accept one connection
*/
virtual void acceptOnce() = 0;

protected:
const ProtocolHandler *protocolHandler;
io_service ios;

template <typename Protocol, typename Service>
void doListen(basic_socket_acceptor<Protocol, Service>& acceptor,
const typename Protocol::endpoint& endpoint);
template <typename Protocol, typename Service>
void doAcceptOnce(basic_socket_acceptor<Protocol, Service>& acceptor);
void processStream(std::iostream &stream);
};

/**
* Socket server that calls a protocol handler line by line
*/
class TCPSocketServer : public SocketServer {
public:
/**
* Type definition for TCP port
Expand All @@ -26,35 +56,60 @@ class SocketServer {
/**
* Constructor for DI
*/
SocketServer(const ProtocolHandler *protocolHandler);
TCPSocketServer(const ProtocolHandler *protocolHandler);

/**
* Bind and listen to a TCP port
*/
void listen(const port_type port);

/**
* Port number that this server is currently listening on.
* Endpoint (IP address and port number) that this server is currently
* listening on.
*
* @throw boost::system::system_error when not listening on any TCP port or
* the port cannot be determined.
* @throw boost::system::system_error when not listening on any socket or
* the endpoint cannot be determined.
*/
tcp::endpoint listenEndpoint() const;

virtual void acceptOnce();

private:
tcp::acceptor acceptor;
};

#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
/**
* Socket server that calls a protocol handler line by line
*/
class UnixSocketServer : public SocketServer {
public:
/**
* Constructor for DI
*/
UnixSocketServer(const ProtocolHandler *protocolHandler);

/**
* Bind and listen on a local stream socket
*/
port_type listenPort() const;
void listen(const std::string& unixPath);

/**
* Accept one connection
* Port number that this server is currently listening on.
*
* @throw boost::system::system_error when not listening on any socket or
* the endpoint cannot be determined.
*/
void acceptOnce();
stream_protocol::endpoint listenEndpoint() const;

~SocketServer() {}; // Forbid inheritance
virtual void acceptOnce();

private:
const ProtocolHandler *protocolHandler;
io_service ios;
tcp::acceptor acceptor;
~UnixSocketServer();

void processStream(tcp::iostream &stream);
private:
stream_protocol::acceptor acceptor;
};
#endif

}
}
Expand Down
79 changes: 65 additions & 14 deletions src/connectors/wire/WireServer.cpp
Original file line number Diff line number Diff line change
@@ -1,40 +1,91 @@
#include <cucumber-cpp/internal/connectors/wire/WireServer.hpp>
#include <boost/filesystem/operations.hpp>

namespace cucumber {
namespace internal {

using namespace boost::asio::ip;
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
using namespace boost::asio::local;
#endif

SocketServer::SocketServer(const ProtocolHandler *protocolHandler) :
protocolHandler(protocolHandler),
ios(),
acceptor(ios) {
ios() {
}

void SocketServer::listen(const port_type port) {
tcp::endpoint endpoint(tcp::v4(), port);
template <typename Protocol, typename Service>
void SocketServer::doListen(basic_socket_acceptor<Protocol, Service>& acceptor,
const typename Protocol::endpoint& endpoint) {
if (acceptor.is_open())
throw boost::system::system_error(boost::asio::error::already_open);
acceptor.open(endpoint.protocol());
acceptor.set_option(tcp::acceptor::reuse_address(true));
acceptor.set_option(tcp::no_delay(true));
acceptor.set_option(typename Protocol::acceptor::reuse_address(true));
acceptor.bind(endpoint);
acceptor.listen(1);
}

SocketServer::port_type SocketServer::listenPort() const {
const tcp::endpoint ep(acceptor.local_endpoint());
return ep.port();
}

void SocketServer::acceptOnce() {
tcp::iostream stream;
template <typename Protocol, typename Service>
void SocketServer::doAcceptOnce(basic_socket_acceptor<Protocol, Service>& acceptor) {
typename Protocol::iostream stream;
acceptor.accept(*stream.rdbuf());
processStream(stream);
}

void SocketServer::processStream(tcp::iostream &stream) {
void SocketServer::processStream(std::iostream& stream) {
std::string request;
while (getline(stream, request)) {
stream << protocolHandler->handle(request) << std::endl << std::flush;
}
}

TCPSocketServer::TCPSocketServer(const ProtocolHandler *protocolHandler) :
SocketServer(protocolHandler),
acceptor(ios) {
}

void TCPSocketServer::listen(const port_type port) {
doListen(acceptor, tcp::endpoint(tcp::v4(), port));
acceptor.set_option(tcp::no_delay(true));
}

tcp::endpoint TCPSocketServer::listenEndpoint() const {
return acceptor.local_endpoint();
}

void TCPSocketServer::acceptOnce() {
doAcceptOnce(acceptor);
}

#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
UnixSocketServer::UnixSocketServer(const ProtocolHandler *protocolHandler) :
SocketServer(protocolHandler),
acceptor(ios) {
}

void UnixSocketServer::listen(const std::string& unixPath) {
if (boost::filesystem::status(unixPath).type() == boost::filesystem::socket_file)
boost::filesystem::remove(unixPath);

doListen(acceptor, stream_protocol::endpoint(unixPath));
}

stream_protocol::endpoint UnixSocketServer::listenEndpoint() const {
return acceptor.local_endpoint();
}

void UnixSocketServer::acceptOnce() {
doAcceptOnce(acceptor);
}

UnixSocketServer::~UnixSocketServer() {
if (!acceptor.is_open())
return;
std::string path = acceptor.local_endpoint().path();
// NOTE: this will fail if this path got deleted manually or represents an abstract-namespace socket
boost::filesystem::remove(path);
}
#endif

}
}
40 changes: 33 additions & 7 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,35 @@
#include <cucumber-cpp/internal/connectors/wire/WireProtocol.hpp>
#include <iostream>
#include <boost/program_options.hpp>
#include <boost/scoped_ptr.hpp>

namespace {

void acceptWireProtocol(int port, bool verbose) {
void acceptWireProtocol(int port, const std::string& unixPath, bool verbose) {
using namespace ::cucumber::internal;
CukeEngineImpl cukeEngine;
JsonSpiritWireMessageCodec wireCodec;
WireProtocolHandler protocolHandler(&wireCodec, &cukeEngine);
SocketServer server(&protocolHandler);
server.listen(port);
if (verbose)
std::clog << "Listening on port " << server.listenPort() << std::endl;
server.acceptOnce();
boost::scoped_ptr<SocketServer> server;
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
if (!unixPath.empty())
{
UnixSocketServer* const unixServer = new UnixSocketServer(&protocolHandler);
server.reset(unixServer);
unixServer->listen(unixPath);
if (verbose)
std::clog << "Listening on socket " << unixServer->listenEndpoint() << std::endl;
}
else
#endif
{
TCPSocketServer* const tcpServer = new TCPSocketServer(&protocolHandler);
server.reset(tcpServer);
tcpServer->listen(port);
if (verbose)
std::clog << "Listening on port " << tcpServer->listenEndpoint() << std::endl;
}
server->acceptOnce();
}

}
Expand All @@ -27,6 +43,9 @@ int main(int argc, char **argv) {
("help,h", "help for cucumber-cpp")
("verbose,v", "verbose output")
("port,p", value<int>(), "listening port of wireserver, use '0' (zero) to select an ephemeral port")
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
("unix,u", value<std::string>(), "listening unix socket of wireserver (disables listening on port)")
#endif
;
boost::program_options::variables_map optionVariableMap;
boost::program_options::store(boost::program_options::parse_command_line(argc, argv, optionDescription), optionVariableMap);
Expand All @@ -42,13 +61,20 @@ int main(int argc, char **argv) {
port = optionVariableMap["port"].as<int>();
}

std::string unixPath;
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
if (optionVariableMap.count("unix")) {
unixPath = optionVariableMap["unix"].as<std::string>();
}
#endif

bool verbose = false;
if (optionVariableMap.count("verbose")) {
verbose = true;
}

try {
acceptWireProtocol(port, verbose);
acceptWireProtocol(port, unixPath, verbose);
} catch (std::exception &e) {
std::cerr << e.what() << std::endl;
exit(1);
Expand Down
Loading