* Copyright 2020 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Kyle Ambroff-Kao, kyle@ambroffkao.com
*/
#include "TestServer.h"
#include <errno.h>
#include <netinet/in.h>
#include <posix/libgen.h>
#include <sstream>
#include <string>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>
#include <AutoDeleter.h>
#include <TestShell.h>
namespace {
template<typename T>
std::string
to_string(T value)
{
std::ostringstream s;
s << value;
return s.str();
}
void
exec(const std::vector<std::string>& args)
{
const char** argv = new const char*[args.size() + 1];
ArrayDeleter<const char*> _(argv);
for (size_t i = 0; i < args.size(); ++i) {
argv[i] = args[i].c_str();
}
argv[args.size()] = NULL;
execv(args[0].c_str(), const_cast<char* const*>(argv));
}
std::string
TestFilePath(const std::string& relativePath)
{
char* testFileSource = strdup(__FILE__);
MemoryDeleter _(testFileSource);
std::string testSrcDir(::dirname(testFileSource));
return testSrcDir + "/" + relativePath;
}
}
RandomTCPServerPort::RandomTCPServerPort()
:
fInitStatus(B_NOT_INITIALIZED),
fSocketFd(-1),
fServerPort(0)
{
int socket_fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd == -1) {
fprintf(stderr, "ERROR: Unable to create socket: %s\n", strerror(errno));
fInitStatus = B_ERROR;
return;
}
fSocketFd = socket_fd;
{
int reuse = 1;
int result = ::setsockopt(socket_fd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse));
if (result == -1) {
fInitStatus = errno;
fprintf(stderr, "ERROR: Unable to set socket options on fd %d: %s\n", socket_fd,
strerror(fInitStatus));
return;
}
}
struct sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
int bind_result = ::bind(
socket_fd, reinterpret_cast<struct sockaddr*>(&server_address), sizeof(server_address));
if (bind_result == -1) {
fInitStatus = errno;
fprintf(stderr, "ERROR: Unable to bind to loopback interface: %s\n", strerror(fInitStatus));
return;
}
if (::listen(socket_fd, 32) == -1) {
fInitStatus = errno;
fprintf(stderr, "ERROR: listen() failed: %s\n", strerror(fInitStatus));
return;
}
socklen_t server_address_length = sizeof(server_address);
::getsockname(
socket_fd, reinterpret_cast<struct sockaddr*>(&server_address), &server_address_length);
fServerPort = ntohs(server_address.sin_port);
fInitStatus = B_OK;
}
RandomTCPServerPort::~RandomTCPServerPort()
{
if (fSocketFd != -1) {
::close(fSocketFd);
fSocketFd = -1;
fInitStatus = B_NOT_INITIALIZED;
}
}
status_t
RandomTCPServerPort::InitCheck() const
{
return fInitStatus;
}
int
RandomTCPServerPort::FileDescriptor() const
{
return fSocketFd;
}
uint16_t
RandomTCPServerPort::Port() const
{
return fServerPort;
}
ChildProcess::ChildProcess()
:
fChildPid(-1)
{
}
ChildProcess::~ChildProcess()
{
if (fChildPid != -1) {
::kill(fChildPid, SIGTERM);
pid_t result = -1;
while (result != fChildPid) {
result = ::waitpid(fChildPid, NULL, 0);
}
}
}
status_t
ChildProcess::Start(const std::vector<std::string>& args)
{
if (fChildPid != -1) {
return B_ALREADY_RUNNING;
}
pid_t child = ::fork();
if (child < 0)
return B_ERROR;
if (child > 0) {
fChildPid = child;
return B_OK;
}
exec(args);
std::ostringstream ostr;
for (std::vector<std::string>::const_iterator iter = args.begin(); iter != args.end(); ++iter) {
ostr << " " << *iter;
}
fprintf(stderr, "Unable to spawn `%s': %s\n", ostr.str().c_str(), strerror(errno));
exit(1);
}
TestServer::TestServer(TestServerMode mode)
:
fMode(mode)
{
}
status_t
TestServer::Start()
{
if (fPort.InitCheck() != B_OK) {
return fPort.InitCheck();
}
auto testFilePath = TestFilePath("testserver.py");
if (::access(testFilePath.data(), R_OK) != 0) {
fprintf(stderr, "ERROR: No access to the test server script at: %s\n", testFilePath.data());
return B_IO_ERROR;
}
std::vector<std::string> child_process_args;
child_process_args.push_back("/bin/python3");
child_process_args.push_back(testFilePath);
child_process_args.push_back("--port");
child_process_args.push_back(to_string(fPort.Port()));
child_process_args.push_back("--fd");
child_process_args.push_back(to_string(fPort.FileDescriptor()));
if (fMode == TestServerMode::Https) {
child_process_args.push_back("--use-tls");
}
return fChildProcess.Start(child_process_args);
}
BUrl
TestServer::BaseUrl() const
{
std::string scheme;
switch (fMode) {
case TestServerMode::Http:
scheme = "http://";
break;
case TestServerMode::Https:
scheme = "https://";
break;
}
std::string port_string = to_string(fPort.Port());
std::string baseUrl = scheme + "127.0.0.1:" + port_string + "/";
return BUrl(baseUrl.c_str());
}
status_t
TestProxyServer::Start()
{
if (fPort.InitCheck() != B_OK) {
return fPort.InitCheck();
}
auto testFilePath = TestFilePath("proxy.py");
if (::access(testFilePath.data(), R_OK) != 0) {
fprintf(stderr, "ERROR: No access to the test server script at: %s\n", testFilePath.data());
return B_IO_ERROR;
}
std::vector<std::string> child_process_args;
child_process_args.push_back("/bin/python3");
child_process_args.push_back(testFilePath);
child_process_args.push_back("--port");
child_process_args.push_back(to_string(fPort.Port()));
child_process_args.push_back("--fd");
child_process_args.push_back(to_string(fPort.FileDescriptor()));
return fChildProcess.Start(child_process_args);
}
uint16_t
TestProxyServer::Port() const
{
return fPort.Port();
}