Created
February 22, 2019 19:51
-
-
Save parbo/e0c74f9befa8228cafc6c77bf393545f to your computer and use it in GitHub Desktop.
Simple server to test network requests.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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