Skip to content

Instantly share code, notes, and snippets.

@parbo
Created February 22, 2019 19:51
Show Gist options
  • Save parbo/e0c74f9befa8228cafc6c77bf393545f to your computer and use it in GitHub Desktop.
Save parbo/e0c74f9befa8228cafc6c77bf393545f to your computer and use it in GitHub Desktop.
Simple server to test network requests.
#include <atomic>
#include <boost/asio.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/test/unit_test.hpp>
#include <chrono>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
using boost::asio::ip::tcp;
class ServerFixture {
public:
ServerFixture() : _started(false) {}
using ResponderFunction =
std::function<std::string(const std::vector<uint8_t> &)>;
void setup(const std::vector<ResponderFunction> &responders) {
_started = false;
_thread = std::thread([this, responders]() { serve(responders); });
while (true) {
std::unique_lock<std::mutex> lock{_mutex};
if (_started) {
break;
}
_condition.wait(lock);
}
}
void setup(const ResponderFunction &responder) {
setup(std::vector<ResponderFunction>{responder});
}
void onDone() { _io_context.stop(); }
void wait() { _thread.join(); }
void stopServer() { _io_context.stop(); }
private:
void serve(const std::vector<ResponderFunction> &responders) {
class Server {
public:
Server(boost::asio::io_context &io_context,
const std::vector<ResponderFunction> &responders)
: _io_context(io_context),
_acceptor(_io_context, tcp::endpoint(tcp::v4(), 0), true),
_responders(responders), _current(0) {
accept();
}
void accept() {
auto sock = std::make_shared<tcp::socket>(_io_context);
_acceptor.async_accept(*sock,
[this, sock](boost::system::error_code error) {
handle_accept(error, sock);
});
}
void handle_accept(boost::system::error_code error,
std::shared_ptr<tcp::socket> socket) {
if (error) {
// A cancel() call can lead to the system connection closing the
// socket prematurely.
return;
}
// Read the data.
std::vector<uint8_t> data(65536);
size_t length = socket->read_some(boost::asio::buffer(data), error);
if (error == boost::asio::error::eof) {
// Connection closed.
} else {
if (error) {
// A cancel() call can lead to the system connection closing the
// socket prematurely.
return;
}
}
// Let the test decide the response.
const auto &responder = _responders[_current++];
const auto response = responder(data);
boost::asio::write(*socket, boost::asio::buffer(response), error);
// Setup how to handle the next connection
if (_current < _responders.size()) {
accept();
} else {
// We're done, set up an accept to check that no new connections are
// made. And so that
// _io_context.run() doesn't stop until onDone is called or the
// timeout expires.
auto sock = std::make_shared<tcp::socket>(_io_context);
_acceptor.async_accept(*sock, [](boost::system::error_code error) {
if (error) {
return;
}
BOOST_CHECK(false);
});
}
}
int port() const {
const auto endpoint = _acceptor.local_endpoint();
return endpoint.port();
}
private:
boost::asio::io_context &_io_context;
tcp::acceptor _acceptor;
std::vector<ResponderFunction> _responders;
int _current{0};
};
// Serve up all responses
Server server(_io_context, responders);
// Now that the server has started on a random port, we can set up a dummy
// request for the host.
_url = "http://127.0.0.1:" + std::to_string(server.port());
// Stop in one second
boost::asio::deadline_timer timer(_io_context);
timer.expires_from_now(boost::posix_time::seconds(1));
timer.async_wait(
[this](const boost::system::error_code &error) { _io_context.stop(); });
// Release the setup call
{
std::unique_lock<std::mutex> lock{_mutex};
_started = true;
}
_condition.notify_all();
// Process event loop.
_io_context.reset();
_io_context.run();
}
boost::asio::io_context _io_context;
std::thread _thread;
std::mutex _mutex;
std::condition_variable _condition;
bool _started;
std::string _url;
};
BOOST_FIXTURE_TEST_CASE(TestSimpleRequest, ServerFixture) {
setup([](const std::vector<uint8_t> &data) -> std::string {
// validate request, return data
return "data";
});
// some code that connects to _url, and stops the server in the callback
// request(_url, [this] { onDone(); });
wait();
// BOOST_TEST(something());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment