Skip to content
This repository has been archived by the owner on Jun 12, 2018. It is now read-only.

Commit

Permalink
Created non-templated base classes
Browse files Browse the repository at this point in the history
  • Loading branch information
eidheim committed Feb 17, 2018
1 parent 1056bd2 commit 080d6ba
Show file tree
Hide file tree
Showing 5 changed files with 328 additions and 290 deletions.
221 changes: 123 additions & 98 deletions client_http.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <unordered_set>
#include <vector>

; // <- libclang bug workaround

#ifdef USE_STANDALONE_ASIO
#include <asio.hpp>
#include <asio/steady_timer.hpp>
Expand All @@ -33,14 +35,11 @@ namespace SimpleWeb {
#endif

namespace SimpleWeb {
template <class socket_type>
class Client;

template <class socket_type>
class ClientBase {
public:
class Response;
class Content : public std::istream {
friend class ClientBase<socket_type>;
friend class Response;

public:
std::size_t size() noexcept {
Expand All @@ -64,8 +63,10 @@ namespace SimpleWeb {
};

class Response {
friend class ClientBase<socket_type>;
friend class Client<socket_type>;
template <typename SocketType>
friend class ClientTemplate;
template <typename SocketType>
friend class Client;

asio::streambuf streambuf;

Expand All @@ -80,7 +81,7 @@ namespace SimpleWeb {
};

class Config {
friend class ClientBase<socket_type>;
friend class ClientBase;

private:
Config() noexcept {}
Expand All @@ -97,67 +98,49 @@ namespace SimpleWeb {
std::string proxy_server;
};

protected:
class Connection : public std::enable_shared_from_this<Connection> {
public:
template <typename... Args>
Connection(std::shared_ptr<ScopeRunner> handler_runner, long timeout, Args &&... args) noexcept
: handler_runner(std::move(handler_runner)), timeout(timeout), socket(new socket_type(std::forward<Args>(args)...)) {}

std::shared_ptr<ScopeRunner> handler_runner;
long timeout;

std::unique_ptr<socket_type> socket; // Socket must be unique_ptr since asio::ssl::stream<asio::ip::tcp::socket> is not movable
bool in_use = false;
bool attempt_reconnect = true;

std::unique_ptr<asio::steady_timer> timer;

void set_timeout(long seconds = 0) noexcept {
if(seconds == 0)
seconds = timeout;
if(seconds == 0) {
timer = nullptr;
return;
}
timer = std::unique_ptr<asio::steady_timer>(new asio::steady_timer(socket->get_io_service()));
timer->expires_from_now(std::chrono::seconds(seconds));
auto self = this->shared_from_this();
timer->async_wait([self](const error_code &ec) {
if(!ec) {
error_code ec;
self->socket->lowest_layer().cancel(ec);
}
});
}

void cancel_timeout() noexcept {
if(timer) {
error_code ec;
timer->cancel(ec);
}
}
};

class Session {
public:
Session(std::size_t max_response_streambuf_size, std::shared_ptr<Connection> connection, std::unique_ptr<asio::streambuf> request_streambuf) noexcept
: connection(std::move(connection)), request_streambuf(std::move(request_streambuf)), response(new Response(max_response_streambuf_size)) {}

std::shared_ptr<Connection> connection;
std::unique_ptr<asio::streambuf> request_streambuf;
std::shared_ptr<Response> response;
std::function<void(const std::shared_ptr<Connection> &, const error_code &)> callback;
};

public:
/// Set before calling request
Config config;

/// If you have your own asio::io_service, store its pointer here before calling request().
/// When using asynchronous requests, running the io_service is up to the programmer.
std::shared_ptr<asio::io_service> io_service;

/// Asynchronous request where setting and/or running Client's io_service is required.
/// Do not use concurrently with the synchronous request functions.
virtual void request(const std::string &method, const std::string &path, string_view content, const CaseInsensitiveMultimap &header,
std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback_) = 0;
/// Asynchronous request where setting and/or running Client's io_service is required.
/// Do not use concurrently with the synchronous request functions.
virtual void request(const std::string &method, const std::string &path, std::istream &content, const CaseInsensitiveMultimap &header,
std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback_) = 0;

/// Asynchronous request where setting and/or running Client's io_service is required.
/// Do not use concurrently with the synchronous request functions.
void request(const std::string &method, const std::string &path, string_view content,
std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {
request(method, path, content, CaseInsensitiveMultimap(), std::move(request_callback));
}

/// Asynchronous request where setting and/or running Client's io_service is required.
/// Do not use concurrently with the synchronous request functions.
void request(const std::string &method, const std::string &path,
std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {
request(method, path, std::string(), CaseInsensitiveMultimap(), std::move(request_callback));
}

/// Asynchronous request where setting and/or running Client's io_service is required.
/// Do not use concurrently with the synchronous request functions.
void request(const std::string &method, std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {
request(method, std::string("/"), std::string(), CaseInsensitiveMultimap(), std::move(request_callback));
}

/// Asynchronous request where setting and/or running Client's io_service is required.
/// Do not use concurrently with the synchronous request functions.
void request(const std::string &method, const std::string &path, std::istream &content,
std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {
request(method, path, content, CaseInsensitiveMultimap(), std::move(request_callback));
}

/// Convenience function to perform synchronous request. The io_service is run within this function.
/// If reusing the io_service for other tasks, use the asynchronous request functions instead.
/// Do not use concurrently with the asynchronous request functions.
Expand Down Expand Up @@ -218,10 +201,78 @@ namespace SimpleWeb {
return response;
}

/// Close connections
virtual void stop() noexcept = 0;

virtual ~ClientBase() {}

protected:
std::size_t concurrent_synchronous_requests = 0;
std::mutex concurrent_synchronous_requests_mutex;
};

template <typename SocketType>
class ClientTemplate : public ClientBase {
protected:
class Connection : public std::enable_shared_from_this<Connection> {
public:
template <typename... Args>
Connection(std::shared_ptr<ScopeRunner> handler_runner, long timeout, Args &&... args) noexcept
: handler_runner(std::move(handler_runner)), timeout(timeout), socket(new SocketType(std::forward<Args>(args)...)) {}

std::shared_ptr<ScopeRunner> handler_runner;
long timeout;

std::unique_ptr<SocketType> socket; // Socket must be unique_ptr since asio::ssl::stream<asio::ip::tcp::socket> is not movable
bool in_use = false;
bool attempt_reconnect = true;

std::unique_ptr<asio::steady_timer> timer;

void set_timeout(long seconds = 0) noexcept {
if(seconds == 0)
seconds = timeout;
if(seconds == 0) {
timer = nullptr;
return;
}
timer = std::unique_ptr<asio::steady_timer>(new asio::steady_timer(socket->get_io_service()));
timer->expires_from_now(std::chrono::seconds(seconds));
auto self = this->shared_from_this();
timer->async_wait([self](const error_code &ec) {
if(!ec) {
error_code ec;
self->socket->lowest_layer().cancel(ec);
}
});
}

void cancel_timeout() noexcept {
if(timer) {
error_code ec;
timer->cancel(ec);
}
}
};

class Session {
public:
Session(std::size_t max_response_streambuf_size, std::shared_ptr<Connection> connection, std::unique_ptr<asio::streambuf> request_streambuf) noexcept
: connection(std::move(connection)), request_streambuf(std::move(request_streambuf)), response(new Response(max_response_streambuf_size)) {}

std::shared_ptr<Connection> connection;
std::unique_ptr<asio::streambuf> request_streambuf;
std::shared_ptr<Response> response;
std::function<void(const std::shared_ptr<Connection> &, const error_code &)> callback;
};

public:
using ClientBase::request;

/// Asynchronous request where setting and/or running Client's io_service is required.
/// Do not use concurrently with the synchronous request functions.
void request(const std::string &method, const std::string &path, string_view content, const CaseInsensitiveMultimap &header,
std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback_) {
std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback_) override {
auto session = std::make_shared<Session>(config.max_response_streambuf_size, get_connection(), create_request_header(method, path, header));
auto response = session->response;
auto request_callback = std::make_shared<std::function<void(std::shared_ptr<Response>, const error_code &)>>(std::move(request_callback_));
Expand Down Expand Up @@ -268,25 +319,8 @@ namespace SimpleWeb {

/// Asynchronous request where setting and/or running Client's io_service is required.
/// Do not use concurrently with the synchronous request functions.
void request(const std::string &method, const std::string &path, string_view content,
std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {
request(method, path, content, CaseInsensitiveMultimap(), std::move(request_callback));
}

/// Asynchronous request where setting and/or running Client's io_service is required.
void request(const std::string &method, const std::string &path,
std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {
request(method, path, std::string(), CaseInsensitiveMultimap(), std::move(request_callback));
}

/// Asynchronous request where setting and/or running Client's io_service is required.
void request(const std::string &method, std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {
request(method, std::string("/"), std::string(), CaseInsensitiveMultimap(), std::move(request_callback));
}

/// Asynchronous request where setting and/or running Client's io_service is required.
void request(const std::string &method, const std::string &path, std::istream &content, const CaseInsensitiveMultimap &header,
std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback_) {
std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback_) override {
auto session = std::make_shared<Session>(config.max_response_streambuf_size, get_connection(), create_request_header(method, path, header));
auto response = session->response;
auto request_callback = std::make_shared<std::function<void(std::shared_ptr<Response>, const error_code &)>>(std::move(request_callback_));
Expand Down Expand Up @@ -335,14 +369,8 @@ namespace SimpleWeb {
connect(session);
}

/// Asynchronous request where setting and/or running Client's io_service is required.
void request(const std::string &method, const std::string &path, std::istream &content,
std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {
request(method, path, content, CaseInsensitiveMultimap(), std::move(request_callback));
}

/// Close connections
void stop() noexcept {
void stop() noexcept override {
std::unique_lock<std::mutex> lock(connections_mutex);
for(auto it = connections.begin(); it != connections.end();) {
error_code ec;
Expand All @@ -351,7 +379,7 @@ namespace SimpleWeb {
}
}

virtual ~ClientBase() noexcept {
~ClientTemplate() {
handler_runner->stop();
stop();
}
Expand All @@ -370,10 +398,7 @@ namespace SimpleWeb {

std::shared_ptr<ScopeRunner> handler_runner;

std::size_t concurrent_synchronous_requests = 0;
std::mutex concurrent_synchronous_requests_mutex;

ClientBase(const std::string &host_port, unsigned short default_port) noexcept : default_port(default_port), handler_runner(new ScopeRunner()) {
ClientTemplate(const std::string &host_port, unsigned short default_port) noexcept : default_port(default_port), handler_runner(new ScopeRunner()) {
auto parsed_host_port = parse_host_port(host_port, default_port);
host = parsed_host_port.first;
port = parsed_host_port.second;
Expand Down Expand Up @@ -420,7 +445,7 @@ namespace SimpleWeb {
auto corrected_path = path;
if(corrected_path == "")
corrected_path = "/";
if(!config.proxy_server.empty() && std::is_same<socket_type, asio::ip::tcp::socket>::value)
if(!config.proxy_server.empty() && std::is_same<SocketType, asio::ip::tcp::socket>::value)
corrected_path = "http://" + host + ':' + std::to_string(port) + corrected_path;

std::unique_ptr<asio::streambuf> streambuf(new asio::streambuf());
Expand Down Expand Up @@ -638,15 +663,15 @@ namespace SimpleWeb {
}
};

template <class socket_type>
class Client : public ClientBase<socket_type> {};
template <typename = void>
class Client : public ClientBase {};

using HTTP = asio::ip::tcp::socket;

template <>
class Client<HTTP> : public ClientBase<HTTP> {
class Client<HTTP> : public ClientTemplate<HTTP> {
public:
Client(const std::string &server_port_path) noexcept : ClientBase<HTTP>::ClientBase(server_port_path, 80) {}
Client(const std::string &server_port_path) noexcept : ClientTemplate<HTTP>::ClientTemplate(server_port_path, 80) {}

protected:
std::shared_ptr<Connection> create_connection() noexcept override {
Expand Down
4 changes: 2 additions & 2 deletions client_https.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ namespace SimpleWeb {
using HTTPS = asio::ssl::stream<asio::ip::tcp::socket>;

template <>
class Client<HTTPS> : public ClientBase<HTTPS> {
class Client<HTTPS> : public ClientTemplate<HTTPS> {
public:
Client(const std::string &server_port_path, bool verify_certificate = true, const std::string &cert_file = std::string(),
const std::string &private_key_file = std::string(), const std::string &verify_file = std::string())
: ClientBase<HTTPS>::ClientBase(server_port_path, 443), context(asio::ssl::context::tlsv12) {
: ClientTemplate<HTTPS>::ClientTemplate(server_port_path, 443), context(asio::ssl::context::tlsv12) {
if(cert_file.size() > 0 && private_key_file.size() > 0) {
context.use_certificate_chain_file(cert_file);
context.use_private_key_file(private_key_file, asio::ssl::context::pem);
Expand Down
Loading

0 comments on commit 080d6ba

Please sign in to comment.