Added .clang-format file and applied style to source files

This commit is contained in:
eidheim 2017-06-26 15:19:52 +02:00
commit e50d2fc63a
13 changed files with 2552 additions and 2538 deletions

9
.clang-format Normal file
View file

@ -0,0 +1,9 @@
IndentWidth: 2
AccessModifierOffset: -2
UseTab: Never
ColumnLimit: 0
MaxEmptyLinesToKeep: 2
SpaceBeforeParens: Never
BreakBeforeBraces: Custom
BraceWrapping: {BeforeElse: true, BeforeCatch: true}
NamespaceIndentation: All

View file

@ -2,9 +2,9 @@
#define CLIENT_HTTP_HPP #define CLIENT_HTTP_HPP
#include "utility.hpp" #include "utility.hpp"
#include <vector>
#include <random>
#include <mutex> #include <mutex>
#include <random>
#include <vector>
#ifdef USE_STANDALONE_ASIO #ifdef USE_STANDALONE_ASIO
#include <asio.hpp> #include <asio.hpp>
@ -13,8 +13,8 @@ namespace SimpleWeb {
using errc = std::errc; using errc = std::errc;
using system_error = std::system_error; using system_error = std::system_error;
namespace make_error_code = std; namespace make_error_code = std;
using string_view = const std::string&; // TODO c++17: use std::string_view using string_view = const std::string &; // TODO c++17: use std::string_view
} } // namespace SimpleWeb
#else #else
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/utility/string_ref.hpp> #include <boost/utility/string_ref.hpp>
@ -25,7 +25,7 @@ namespace SimpleWeb {
using system_error = boost::system::system_error; using system_error = boost::system::system_error;
namespace make_error_code = boost::system::errc; namespace make_error_code = boost::system::errc;
using string_view = boost::string_ref; using string_view = boost::string_ref;
} } // namespace SimpleWeb
#endif #endif
namespace SimpleWeb { namespace SimpleWeb {
@ -37,6 +37,7 @@ namespace SimpleWeb {
public: public:
class Content : public std::istream { class Content : public std::istream {
friend class ClientBase<socket_type>; friend class ClientBase<socket_type>;
public: public:
size_t size() { size_t size() {
return streambuf.size(); return streambuf.size();
@ -47,14 +48,16 @@ namespace SimpleWeb {
ss << rdbuf(); ss << rdbuf();
return ss.str(); return ss.str();
} }
private: private:
asio::streambuf &streambuf; asio::streambuf &streambuf;
Content(asio::streambuf &streambuf): std::istream(&streambuf), streambuf(streambuf) {} Content(asio::streambuf &streambuf) : std::istream(&streambuf), streambuf(streambuf) {}
}; };
class Response { class Response {
friend class ClientBase<socket_type>; friend class ClientBase<socket_type>;
friend class Client<socket_type>; friend class Client<socket_type>;
public: public:
std::string http_version, status_code; std::string http_version, status_code;
@ -65,18 +68,20 @@ namespace SimpleWeb {
private: private:
asio::streambuf content_buffer; asio::streambuf content_buffer;
Response(): content(content_buffer) {} Response() : content(content_buffer) {}
}; };
class Config { class Config {
friend class ClientBase<socket_type>; friend class ClientBase<socket_type>;
private: private:
Config() {} Config() {}
public: public:
/// Set timeout on requests in seconds. Default value: 0 (no timeout). /// Set timeout on requests in seconds. Default value: 0 (no timeout).
size_t timeout=0; size_t timeout = 0;
/// Set connect timeout in seconds. Default value: 0 (Config::timeout is then used instead). /// Set connect timeout in seconds. Default value: 0 (Config::timeout is then used instead).
size_t timeout_connect=0; size_t timeout_connect = 0;
/// Set proxy server (server:port) /// Set proxy server (server:port)
std::string proxy_server; std::string proxy_server;
}; };
@ -84,13 +89,13 @@ namespace SimpleWeb {
protected: protected:
class Connection { class Connection {
public: public:
Connection(const std::string &host, unsigned short port, const Config &config, std::unique_ptr<socket_type> &&socket) : Connection(const std::string &host, unsigned short port, const Config &config, std::unique_ptr<socket_type> &&socket)
host(host), port(port), config(config), socket(std::move(socket)) { : host(host), port(port), config(config), socket(std::move(socket)) {
if(config.proxy_server.empty()) if(config.proxy_server.empty())
query=std::unique_ptr<asio::ip::tcp::resolver::query>(new asio::ip::tcp::resolver::query(host, std::to_string(port))); query = std::unique_ptr<asio::ip::tcp::resolver::query>(new asio::ip::tcp::resolver::query(host, std::to_string(port)));
else { else {
auto proxy_host_port=parse_host_port(config.proxy_server, 8080); auto proxy_host_port = parse_host_port(config.proxy_server, 8080);
query=std::unique_ptr<asio::ip::tcp::resolver::query>(new asio::ip::tcp::resolver::query(proxy_host_port.first, std::to_string(proxy_host_port.second))); query = std::unique_ptr<asio::ip::tcp::resolver::query>(new asio::ip::tcp::resolver::query(proxy_host_port.first, std::to_string(proxy_host_port.second)));
} }
} }
@ -99,21 +104,21 @@ namespace SimpleWeb {
Config config; Config config;
std::unique_ptr<socket_type> socket; std::unique_ptr<socket_type> socket;
bool in_use=false; bool in_use = false;
bool reconnecting=false; bool reconnecting = false;
std::unique_ptr<asio::ip::tcp::resolver::query> query; std::unique_ptr<asio::ip::tcp::resolver::query> query;
}; };
class Session { class Session {
public: public:
Session(const std::shared_ptr<asio::io_service> &io_service, const std::shared_ptr<Connection> &connection, std::unique_ptr<asio::streambuf> &&request_buffer) : Session(const std::shared_ptr<asio::io_service> &io_service, const std::shared_ptr<Connection> &connection, std::unique_ptr<asio::streambuf> &&request_buffer)
io_service(io_service), connection(connection), request_buffer(std::move(request_buffer)), response(new Response()) {} : io_service(io_service), connection(connection), request_buffer(std::move(request_buffer)), response(new Response()) {}
std::shared_ptr<asio::io_service> io_service; std::shared_ptr<asio::io_service> io_service;
std::shared_ptr<Connection> connection; std::shared_ptr<Connection> connection;
std::unique_ptr<asio::streambuf> request_buffer; std::unique_ptr<asio::streambuf> request_buffer;
std::shared_ptr<Response> response; std::shared_ptr<Response> response;
std::function<void(const error_code&)> callback; std::function<void(const error_code &)> callback;
}; };
public: public:
@ -128,10 +133,11 @@ namespace SimpleWeb {
/// Convenience function to perform synchronous request. The io_service is run within this function. /// Convenience function to perform synchronous request. The io_service is run within this function.
/// If reusing the io_service for other tasks, please use the asynchronous request functions instead. /// If reusing the io_service for other tasks, please use the asynchronous request functions instead.
std::shared_ptr<Response> request(const std::string& method, const std::string& path=std::string("/"), string_view content="", const CaseInsensitiveMultimap& header=CaseInsensitiveMultimap()) { std::shared_ptr<Response> request(const std::string &method, const std::string &path = std::string("/"),
string_view content = "", const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
std::shared_ptr<Response> response; std::shared_ptr<Response> response;
request(method, path, content, header, [&response](std::shared_ptr<Response> response_, const error_code &ec) { request(method, path, content, header, [&response](std::shared_ptr<Response> response_, const error_code &ec) {
response=response_; response = response_;
if(ec) if(ec)
throw system_error(ec); throw system_error(ec);
}); });
@ -144,10 +150,11 @@ namespace SimpleWeb {
/// Convenience function to perform synchronous request. The io_service is run within this function. /// Convenience function to perform synchronous request. The io_service is run within this function.
/// If reusing the io_service for other tasks, please use the asynchronous request functions instead. /// If reusing the io_service for other tasks, please use the asynchronous request functions instead.
std::shared_ptr<Response> request(const std::string& method, const std::string& path, std::istream& content, const CaseInsensitiveMultimap& header=CaseInsensitiveMultimap()) { std::shared_ptr<Response> request(const std::string &method, const std::string &path, std::istream &content,
const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
std::shared_ptr<Response> response; std::shared_ptr<Response> response;
request(method, path, content, header, [&response](std::shared_ptr<Response> response_, const error_code &ec) { request(method, path, content, header, [&response](std::shared_ptr<Response> response_, const error_code &ec) {
response=response_; response = response_;
if(ec) if(ec)
throw system_error(ec); throw system_error(ec);
}); });
@ -159,28 +166,28 @@ namespace SimpleWeb {
} }
/// Asynchronous request where setting and/or running Client's io_service is required. /// Asynchronous request where setting and/or running Client's io_service is required.
void request(const std::string &method, const std::string &path, string_view content, const CaseInsensitiveMultimap& header, 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_) {
auto session=std::make_shared<Session>(io_service, get_connection(), create_request_header(method, path, header)); auto session = std::make_shared<Session>(io_service, get_connection(), create_request_header(method, path, header));
auto connection=session->connection; auto connection = session->connection;
auto response=session->response; auto response = session->response;
auto request_callback=std::make_shared<std::function<void(std::shared_ptr<Response>, const error_code&)>>(std::move(request_callback_)); auto request_callback = std::make_shared<std::function<void(std::shared_ptr<Response>, const error_code &)>>(std::move(request_callback_));
auto connections=this->connections; auto connections = this->connections;
auto connections_mutex=this->connections_mutex; auto connections_mutex = this->connections_mutex;
session->callback=[connection, response, request_callback, connections, connections_mutex](const error_code &ec) { session->callback = [connection, response, request_callback, connections, connections_mutex](const error_code &ec) {
{ {
std::lock_guard<std::mutex> lock(*connections_mutex); std::lock_guard<std::mutex> lock(*connections_mutex);
connection->in_use=false; connection->in_use = false;
// Remove unused connections, but keep one open for HTTP persistent connection: // Remove unused connections, but keep one open for HTTP persistent connection:
size_t unused_connections=0; size_t unused_connections = 0;
for(auto it=connections->begin();it!=connections->end();) { for(auto it = connections->begin(); it != connections->end();) {
if((*it)->in_use) if((*it)->in_use)
++it; ++it;
else { else {
++unused_connections; ++unused_connections;
if(unused_connections>1) if(unused_connections > 1)
it=connections->erase(it); it = connections->erase(it);
else else
++it; ++it;
} }
@ -192,53 +199,54 @@ namespace SimpleWeb {
}; };
std::ostream write_stream(session->request_buffer.get()); std::ostream write_stream(session->request_buffer.get());
if(content.size()>0) if(content.size() > 0)
write_stream << "Content-Length: " << content.size() << "\r\n"; write_stream << "Content-Length: " << content.size() << "\r\n";
write_stream << "\r\n" << content; write_stream << "\r\n"
<< content;
Client<socket_type>::connect(session); Client<socket_type>::connect(session);
} }
/// Asynchronous request where setting and/or running Client's io_service is required. /// Asynchronous request where setting and/or running Client's io_service is required.
void request(const std::string &method, const std::string &path, string_view content, 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) { std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {
request(method, path, content, CaseInsensitiveMultimap(), std::move(request_callback)); request(method, path, content, CaseInsensitiveMultimap(), std::move(request_callback));
} }
/// Asynchronous request where setting and/or running Client's io_service is required. /// Asynchronous request where setting and/or running Client's io_service is required.
void request(const std::string &method, const std::string &path, void request(const std::string &method, const std::string &path,
std::function<void(std::shared_ptr<Response>, const error_code&)> &&request_callback) { std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {
request(method, path, std::string(), CaseInsensitiveMultimap(), std::move(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. /// 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) { 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)); request(method, std::string("/"), std::string(), CaseInsensitiveMultimap(), std::move(request_callback));
} }
/// Asynchronous request where setting and/or running Client's io_service is required. /// 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, 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_) {
auto session=std::make_shared<Session>(io_service, get_connection(), create_request_header(method, path, header)); auto session = std::make_shared<Session>(io_service, get_connection(), create_request_header(method, path, header));
auto connection=session->connection; auto connection = session->connection;
auto response=session->response; auto response = session->response;
auto request_callback=std::make_shared<std::function<void(std::shared_ptr<Response>, const error_code&)>>(std::move(request_callback_)); auto request_callback = std::make_shared<std::function<void(std::shared_ptr<Response>, const error_code &)>>(std::move(request_callback_));
auto connections=this->connections; auto connections = this->connections;
auto connections_mutex=this->connections_mutex; auto connections_mutex = this->connections_mutex;
session->callback=[connection, response, request_callback, connections, connections_mutex](const error_code &ec) { session->callback = [connection, response, request_callback, connections, connections_mutex](const error_code &ec) {
{ {
std::lock_guard<std::mutex> lock(*connections_mutex); std::lock_guard<std::mutex> lock(*connections_mutex);
connection->in_use=false; connection->in_use = false;
// Remove unused connections, but keep one open for HTTP persistent connection: // Remove unused connections, but keep one open for HTTP persistent connection:
size_t unused_connections=0; size_t unused_connections = 0;
for(auto it=connections->begin();it!=connections->end();) { for(auto it = connections->begin(); it != connections->end();) {
if((*it)->in_use) if((*it)->in_use)
++it; ++it;
else { else {
++unused_connections; ++unused_connections;
if(unused_connections>1) if(unused_connections > 1)
it=connections->erase(it); it = connections->erase(it);
else else
++it; ++it;
} }
@ -250,21 +258,21 @@ namespace SimpleWeb {
}; };
content.seekg(0, std::ios::end); content.seekg(0, std::ios::end);
auto content_length=content.tellg(); auto content_length = content.tellg();
content.seekg(0, std::ios::beg); content.seekg(0, std::ios::beg);
std::ostream write_stream(session->request_buffer.get()); std::ostream write_stream(session->request_buffer.get());
if(content_length>0) if(content_length > 0)
write_stream << "Content-Length: " << content_length << "\r\n"; write_stream << "Content-Length: " << content_length << "\r\n";
write_stream << "\r\n"; write_stream << "\r\n";
if(content_length>0) if(content_length > 0)
write_stream << content.rdbuf(); write_stream << content.rdbuf();
Client<socket_type>::connect(session); Client<socket_type>::connect(session);
} }
/// Asynchronous request where setting and/or running Client's io_service is required. /// 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, 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) { std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {
request(method, path, content, CaseInsensitiveMultimap(), std::move(request_callback)); request(method, path, content, CaseInsensitiveMultimap(), std::move(request_callback));
} }
@ -275,71 +283,72 @@ namespace SimpleWeb {
std::shared_ptr<std::vector<std::shared_ptr<Connection>>> connections; std::shared_ptr<std::vector<std::shared_ptr<Connection>>> connections;
std::shared_ptr<std::mutex> connections_mutex; std::shared_ptr<std::mutex> connections_mutex;
ClientBase(const std::string& host_port, unsigned short default_port) : io_service(new asio::io_service()), connections(new std::vector<std::shared_ptr<Connection>>()), connections_mutex(new std::mutex()) { ClientBase(const std::string &host_port, unsigned short default_port)
auto parsed_host_port=parse_host_port(host_port, default_port); : io_service(new asio::io_service()), connections(new std::vector<std::shared_ptr<Connection>>()), connections_mutex(new std::mutex()) {
host=parsed_host_port.first; auto parsed_host_port = parse_host_port(host_port, default_port);
port=parsed_host_port.second; host = parsed_host_port.first;
port = parsed_host_port.second;
} }
std::shared_ptr<Connection> get_connection() { std::shared_ptr<Connection> get_connection() {
std::shared_ptr<Connection> connection; std::shared_ptr<Connection> connection;
std::lock_guard<std::mutex> lock(*connections_mutex); std::lock_guard<std::mutex> lock(*connections_mutex);
for(auto it=connections->begin();it!=connections->end();++it) { for(auto it = connections->begin(); it != connections->end(); ++it) {
if(!(*it)->in_use && !connection) { if(!(*it)->in_use && !connection) {
connection=*it; connection = *it;
break; break;
} }
} }
if(!connection) { if(!connection) {
connection=create_connection(); connection = create_connection();
connections->emplace_back(connection); connections->emplace_back(connection);
} }
connection->reconnecting=false; connection->reconnecting = false;
connection->in_use=true; connection->in_use = true;
return connection; return connection;
} }
virtual std::shared_ptr<Connection> create_connection()=0; virtual std::shared_ptr<Connection> create_connection() = 0;
std::unique_ptr<asio::streambuf> create_request_header(const std::string& method, const std::string& path, const CaseInsensitiveMultimap& header) const { std::unique_ptr<asio::streambuf> create_request_header(const std::string &method, const std::string &path, const CaseInsensitiveMultimap &header) const {
auto corrected_path=path; auto corrected_path = path;
if(corrected_path=="") if(corrected_path == "")
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<socket_type, asio::ip::tcp::socket>::value)
corrected_path="http://"+host+':'+std::to_string(port)+corrected_path; corrected_path = "http://" + host + ':' + std::to_string(port) + corrected_path;
std::unique_ptr<asio::streambuf> request_buffer(new asio::streambuf()); std::unique_ptr<asio::streambuf> request_buffer(new asio::streambuf());
std::ostream write_stream(request_buffer.get()); std::ostream write_stream(request_buffer.get());
write_stream << method << " " << corrected_path << " HTTP/1.1\r\n"; write_stream << method << " " << corrected_path << " HTTP/1.1\r\n";
write_stream << "Host: " << host << "\r\n"; write_stream << "Host: " << host << "\r\n";
for(auto& h: header) for(auto &h : header)
write_stream << h.first << ": " << h.second << "\r\n"; write_stream << h.first << ": " << h.second << "\r\n";
return request_buffer; return request_buffer;
} }
static std::pair<std::string, unsigned short> parse_host_port(const std::string &host_port, unsigned short default_port) { static std::pair<std::string, unsigned short> parse_host_port(const std::string &host_port, unsigned short default_port) {
std::pair<std::string, unsigned short> parsed_host_port; std::pair<std::string, unsigned short> parsed_host_port;
size_t host_end=host_port.find(':'); size_t host_end = host_port.find(':');
if(host_end==std::string::npos) { if(host_end == std::string::npos) {
parsed_host_port.first=host_port; parsed_host_port.first = host_port;
parsed_host_port.second=default_port; parsed_host_port.second = default_port;
} }
else { else {
parsed_host_port.first=host_port.substr(0, host_end); parsed_host_port.first = host_port.substr(0, host_end);
parsed_host_port.second=static_cast<unsigned short>(stoul(host_port.substr(host_end+1))); parsed_host_port.second = static_cast<unsigned short>(stoul(host_port.substr(host_end + 1)));
} }
return parsed_host_port; return parsed_host_port;
} }
static std::shared_ptr<asio::deadline_timer> get_timeout_timer(const std::shared_ptr<Session> &session, size_t timeout=0) { static std::shared_ptr<asio::deadline_timer> get_timeout_timer(const std::shared_ptr<Session> &session, size_t timeout = 0) {
if(timeout==0) if(timeout == 0)
timeout=session->connection->config.timeout; timeout = session->connection->config.timeout;
if(timeout==0) if(timeout == 0)
return nullptr; return nullptr;
auto timer=std::make_shared<asio::deadline_timer>(*session->io_service); auto timer = std::make_shared<asio::deadline_timer>(*session->io_service);
timer->expires_from_now(boost::posix_time::seconds(timeout)); timer->expires_from_now(boost::posix_time::seconds(timeout));
timer->async_wait([session](const error_code& ec) { timer->async_wait([session](const error_code &ec) {
if(!ec) if(!ec)
close(session); close(session);
}); });
@ -349,22 +358,22 @@ namespace SimpleWeb {
static void parse_response_header(const std::shared_ptr<Response> &response) { static void parse_response_header(const std::shared_ptr<Response> &response) {
std::string line; std::string line;
getline(response->content, line); getline(response->content, line);
size_t version_end=line.find(' '); size_t version_end = line.find(' ');
if(version_end!=std::string::npos) { if(version_end != std::string::npos) {
if(5<line.size()) if(5 < line.size())
response->http_version=line.substr(5, version_end-5); response->http_version = line.substr(5, version_end - 5);
if((version_end+1)<line.size()) if((version_end + 1) < line.size())
response->status_code=line.substr(version_end+1, line.size()-(version_end+1)-1); response->status_code = line.substr(version_end + 1, line.size() - (version_end + 1) - 1);
getline(response->content, line); getline(response->content, line);
size_t param_end; size_t param_end;
while((param_end=line.find(':'))!=std::string::npos) { while((param_end = line.find(':')) != std::string::npos) {
size_t value_start=param_end+1; size_t value_start = param_end + 1;
if((value_start)<line.size()) { if((value_start) < line.size()) {
if(line[value_start]==' ') if(line[value_start] == ' ')
value_start++; value_start++;
if(value_start<line.size()) if(value_start < line.size())
response->header.insert(std::make_pair(line.substr(0, param_end), line.substr(value_start, line.size()-value_start-1))); response->header.insert(std::make_pair(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - 1)));
} }
getline(response->content, line); getline(response->content, line);
@ -373,7 +382,7 @@ namespace SimpleWeb {
} }
static void write(const std::shared_ptr<Session> &session) { static void write(const std::shared_ptr<Session> &session) {
auto timer=get_timeout_timer(session); auto timer = get_timeout_timer(session);
asio::async_write(*session->connection->socket, session->request_buffer->data(), [session, timer](const error_code &ec, size_t /*bytes_transferred*/) { asio::async_write(*session->connection->socket, session->request_buffer->data(), [session, timer](const error_code &ec, size_t /*bytes_transferred*/) {
if(timer) if(timer)
timer->cancel(); timer->cancel();
@ -387,25 +396,23 @@ namespace SimpleWeb {
} }
static void read(const std::shared_ptr<Session> &session) { static void read(const std::shared_ptr<Session> &session) {
auto timer=get_timeout_timer(session); auto timer = get_timeout_timer(session);
asio::async_read_until(*session->connection->socket, session->response->content_buffer, "\r\n\r\n", asio::async_read_until(*session->connection->socket, session->response->content_buffer, "\r\n\r\n", [session, timer](const error_code &ec, size_t bytes_transferred) {
[session, timer](const error_code& ec, size_t bytes_transferred) {
if(timer) if(timer)
timer->cancel(); timer->cancel();
if(!ec) { if(!ec) {
session->connection->reconnecting=false; session->connection->reconnecting = false;
size_t num_additional_bytes=session->response->content_buffer.size()-bytes_transferred; size_t num_additional_bytes = session->response->content_buffer.size() - bytes_transferred;
parse_response_header(session->response); parse_response_header(session->response);
auto header_it=session->response->header.find("Content-Length"); auto header_it = session->response->header.find("Content-Length");
if(header_it!=session->response->header.end()) { if(header_it != session->response->header.end()) {
auto content_length=stoull(header_it->second); auto content_length = stoull(header_it->second);
if(content_length>num_additional_bytes) { if(content_length > num_additional_bytes) {
auto timer=get_timeout_timer(session); auto timer = get_timeout_timer(session);
asio::async_read(*session->connection->socket, session->response->content_buffer, asio::transfer_exactly(content_length-num_additional_bytes), asio::async_read(*session->connection->socket, session->response->content_buffer, asio::transfer_exactly(content_length - num_additional_bytes), [session, timer](const error_code &ec, size_t /*bytes_transferred*/) {
[session, timer](const error_code& ec, size_t /*bytes_transferred*/) {
if(timer) if(timer)
timer->cancel(); timer->cancel();
if(!ec) if(!ec)
@ -419,20 +426,20 @@ namespace SimpleWeb {
else else
session->callback(ec); session->callback(ec);
} }
else if((header_it=session->response->header.find("Transfer-Encoding"))!=session->response->header.end() && header_it->second=="chunked") { else if((header_it = session->response->header.find("Transfer-Encoding")) != session->response->header.end() && header_it->second == "chunked") {
auto tmp_streambuf=std::make_shared<asio::streambuf>(); auto tmp_streambuf = std::make_shared<asio::streambuf>();
read_chunked(session, tmp_streambuf); read_chunked(session, tmp_streambuf);
} }
else if(session->response->http_version<"1.1" || ((header_it=session->response->header.find("Session"))!=session->response->header.end() && header_it->second=="close")) { else if(session->response->http_version < "1.1" || ((header_it = session->response->header.find("Session")) != session->response->header.end() && header_it->second == "close")) {
auto timer=get_timeout_timer(session); auto timer = get_timeout_timer(session);
asio::async_read(*session->connection->socket, session->response->content_buffer, [session, timer](const error_code& ec, size_t /*bytes_transferred*/) { asio::async_read(*session->connection->socket, session->response->content_buffer, [session, timer](const error_code &ec, size_t /*bytes_transferred*/) {
if(timer) if(timer)
timer->cancel(); timer->cancel();
if(!ec) if(!ec)
session->callback(ec); session->callback(ec);
else { else {
close(session); close(session);
if(ec==asio::error::eof) { if(ec == asio::error::eof) {
error_code ec; error_code ec;
session->callback(ec); session->callback(ec);
} }
@ -446,7 +453,7 @@ namespace SimpleWeb {
} }
else { else {
if(!session->connection->reconnecting) { if(!session->connection->reconnecting) {
session->connection->reconnecting=true; session->connection->reconnecting = true;
close(session); close(session);
Client<socket_type>::connect(session); Client<socket_type>::connect(session);
} }
@ -459,22 +466,22 @@ namespace SimpleWeb {
} }
static void read_chunked(const std::shared_ptr<Session> &session, const std::shared_ptr<asio::streambuf> &tmp_streambuf) { static void read_chunked(const std::shared_ptr<Session> &session, const std::shared_ptr<asio::streambuf> &tmp_streambuf) {
auto timer=get_timeout_timer(session); auto timer = get_timeout_timer(session);
asio::async_read_until(*session->connection->socket, session->response->content_buffer, "\r\n", [session, tmp_streambuf, timer](const error_code& ec, size_t bytes_transferred) { asio::async_read_until(*session->connection->socket, session->response->content_buffer, "\r\n", [session, tmp_streambuf, timer](const error_code &ec, size_t bytes_transferred) {
if(timer) if(timer)
timer->cancel(); timer->cancel();
if(!ec) { if(!ec) {
std::string line; std::string line;
getline(session->response->content, line); getline(session->response->content, line);
bytes_transferred-=line.size()+1; bytes_transferred -= line.size() + 1;
line.pop_back(); line.pop_back();
std::streamsize length=stol(line, 0, 16); std::streamsize length = stol(line, 0, 16);
auto num_additional_bytes=static_cast<std::streamsize>(session->response->content_buffer.size()-bytes_transferred); auto num_additional_bytes = static_cast<std::streamsize>(session->response->content_buffer.size() - bytes_transferred);
auto post_process=[session, tmp_streambuf, length] { auto post_process = [session, tmp_streambuf, length] {
std::ostream tmp_stream(tmp_streambuf.get()); std::ostream tmp_stream(tmp_streambuf.get());
if(length>0) { if(length > 0) {
std::vector<char> buffer(static_cast<size_t>(length)); std::vector<char> buffer(static_cast<size_t>(length));
session->response->content.read(&buffer[0], length); session->response->content.read(&buffer[0], length);
tmp_stream.write(&buffer[0], length); tmp_stream.write(&buffer[0], length);
@ -484,7 +491,7 @@ namespace SimpleWeb {
session->response->content.get(); session->response->content.get();
session->response->content.get(); session->response->content.get();
if(length>0) if(length > 0)
read_chunked(session, tmp_streambuf); read_chunked(session, tmp_streambuf);
else { else {
std::ostream response_stream(&session->response->content_buffer); std::ostream response_stream(&session->response->content_buffer);
@ -494,10 +501,9 @@ namespace SimpleWeb {
} }
}; };
if((2+length)>num_additional_bytes) { if((2 + length) > num_additional_bytes) {
auto timer=get_timeout_timer(session); auto timer = get_timeout_timer(session);
asio::async_read(*session->connection->socket, session->response->content_buffer, asio::transfer_exactly(2+length-num_additional_bytes), asio::async_read(*session->connection->socket, session->response->content_buffer, asio::transfer_exactly(2 + length - num_additional_bytes), [session, post_process, timer](const error_code &ec, size_t /*bytes_transferred*/) {
[session, post_process, timer](const error_code& ec, size_t /*bytes_transferred*/) {
if(timer) if(timer)
timer->cancel(); timer->cancel();
if(!ec) if(!ec)
@ -525,17 +531,17 @@ namespace SimpleWeb {
} }
}; };
template<class socket_type> template <class socket_type>
class Client : public ClientBase<socket_type> {}; class Client : public ClientBase<socket_type> {};
typedef asio::ip::tcp::socket HTTP; typedef asio::ip::tcp::socket HTTP;
template<> template <>
class Client<HTTP> : public ClientBase<HTTP> { class Client<HTTP> : public ClientBase<HTTP> {
public: public:
friend ClientBase<HTTP>; friend ClientBase<HTTP>;
Client(const std::string& server_port_path) : ClientBase<HTTP>::ClientBase(server_port_path, 80) {} Client(const std::string &server_port_path) : ClientBase<HTTP>::ClientBase(server_port_path, 80) {}
protected: protected:
std::shared_ptr<Connection> create_connection() override { std::shared_ptr<Connection> create_connection() override {
@ -544,14 +550,14 @@ namespace SimpleWeb {
static void connect(const std::shared_ptr<Session> &session) { static void connect(const std::shared_ptr<Session> &session) {
if(!session->connection->socket->lowest_layer().is_open()) { if(!session->connection->socket->lowest_layer().is_open()) {
auto resolver=std::make_shared<asio::ip::tcp::resolver>(*session->io_service); auto resolver = std::make_shared<asio::ip::tcp::resolver>(*session->io_service);
auto timer=get_timeout_timer(session, session->connection->config.timeout_connect); auto timer = get_timeout_timer(session, session->connection->config.timeout_connect);
resolver->async_resolve(*session->connection->query, [session, timer, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator it){ resolver->async_resolve(*session->connection->query, [session, timer, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator it) {
if(timer) if(timer)
timer->cancel(); timer->cancel();
if(!ec) { if(!ec) {
auto timer=get_timeout_timer(session, session->connection->config.timeout_connect); auto timer = get_timeout_timer(session, session->connection->config.timeout_connect);
asio::async_connect(*session->connection->socket, it, [session, timer, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator /*it*/){ asio::async_connect(*session->connection->socket, it, [session, timer, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator /*it*/) {
if(timer) if(timer)
timer->cancel(); timer->cancel();
if(!ec) { if(!ec) {
@ -575,6 +581,6 @@ namespace SimpleWeb {
write(session); write(session);
} }
}; };
} } // namespace SimpleWeb
#endif /* CLIENT_HTTP_HPP */ #endif /* CLIENT_HTTP_HPP */

View file

@ -12,16 +12,15 @@
namespace SimpleWeb { namespace SimpleWeb {
typedef asio::ssl::stream<asio::ip::tcp::socket> HTTPS; typedef asio::ssl::stream<asio::ip::tcp::socket> HTTPS;
template<> template <>
class Client<HTTPS> : public ClientBase<HTTPS> { class Client<HTTPS> : public ClientBase<HTTPS> {
public: public:
friend ClientBase<HTTPS>; friend ClientBase<HTTPS>;
Client(const std::string& server_port_path, bool verify_certificate=true, Client(const std::string &server_port_path, bool verify_certificate = true, const std::string &cert_file = std::string(),
const std::string& cert_file=std::string(), const std::string& private_key_file=std::string(), const std::string &private_key_file = std::string(), const std::string &verify_file = std::string())
const std::string& verify_file=std::string()) : : ClientBase<HTTPS>::ClientBase(server_port_path, 443), context(asio::ssl::context::tlsv12) {
ClientBase<HTTPS>::ClientBase(server_port_path, 443), context(asio::ssl::context::tlsv12) { if(cert_file.size() > 0 && private_key_file.size() > 0) {
if(cert_file.size()>0 && private_key_file.size()>0) {
context.use_certificate_chain_file(cert_file); context.use_certificate_chain_file(cert_file);
context.use_private_key_file(private_key_file, asio::ssl::context::pem); context.use_private_key_file(private_key_file, asio::ssl::context::pem);
} }
@ -29,12 +28,12 @@ namespace SimpleWeb {
if(verify_certificate) if(verify_certificate)
context.set_verify_callback(asio::ssl::rfc2818_verification(host)); context.set_verify_callback(asio::ssl::rfc2818_verification(host));
if(verify_file.size()>0) if(verify_file.size() > 0)
context.load_verify_file(verify_file); context.load_verify_file(verify_file);
else else
context.set_default_verify_paths(); context.set_default_verify_paths();
if(verify_file.size()>0 || verify_certificate) if(verify_file.size() > 0 || verify_certificate)
context.set_verify_mode(asio::ssl::verify_peer); context.set_verify_mode(asio::ssl::verify_peer);
else else
context.set_verify_mode(asio::ssl::verify_none); context.set_verify_mode(asio::ssl::verify_none);
@ -49,11 +48,11 @@ namespace SimpleWeb {
static void connect(const std::shared_ptr<Session> &session) { static void connect(const std::shared_ptr<Session> &session) {
if(!session->connection->socket->lowest_layer().is_open()) { if(!session->connection->socket->lowest_layer().is_open()) {
auto resolver=std::make_shared<asio::ip::tcp::resolver>(*session->io_service); auto resolver = std::make_shared<asio::ip::tcp::resolver>(*session->io_service);
resolver->async_resolve(*session->connection->query, [session, resolver] (const error_code &ec, asio::ip::tcp::resolver::iterator it){ resolver->async_resolve(*session->connection->query, [session, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator it) {
if(!ec) { if(!ec) {
auto timer=get_timeout_timer(session, session->connection->config.timeout_connect); auto timer = get_timeout_timer(session, session->connection->config.timeout_connect);
asio::async_connect(session->connection->socket->lowest_layer(), it, [session, resolver, timer] (const error_code &ec, asio::ip::tcp::resolver::iterator /*it*/){ asio::async_connect(session->connection->socket->lowest_layer(), it, [session, resolver, timer](const error_code &ec, asio::ip::tcp::resolver::iterator /*it*/) {
if(timer) if(timer)
timer->cancel(); timer->cancel();
if(!ec) { if(!ec) {
@ -61,23 +60,24 @@ namespace SimpleWeb {
session->connection->socket->lowest_layer().set_option(option); session->connection->socket->lowest_layer().set_option(option);
if(!session->connection->config.proxy_server.empty()) { if(!session->connection->config.proxy_server.empty()) {
auto write_buffer=std::make_shared<asio::streambuf>(); auto write_buffer = std::make_shared<asio::streambuf>();
std::ostream write_stream(write_buffer.get()); std::ostream write_stream(write_buffer.get());
auto host_port=session->connection->host+':'+std::to_string(session->connection->port); auto host_port = session->connection->host + ':' + std::to_string(session->connection->port);
write_stream << "CONNECT "+host_port+" HTTP/1.1\r\n" << "Host: " << host_port << "\r\n\r\n"; write_stream << "CONNECT " + host_port + " HTTP/1.1\r\n"
auto timer=get_timeout_timer(session, session->connection->config.timeout_connect); << "Host: " << host_port << "\r\n\r\n";
auto timer = get_timeout_timer(session, session->connection->config.timeout_connect);
asio::async_write(session->connection->socket->next_layer(), *write_buffer, [session, write_buffer, timer](const error_code &ec, size_t /*bytes_transferred*/) { asio::async_write(session->connection->socket->next_layer(), *write_buffer, [session, write_buffer, timer](const error_code &ec, size_t /*bytes_transferred*/) {
if(timer) if(timer)
timer->cancel(); timer->cancel();
if(!ec) { if(!ec) {
std::shared_ptr<Response> response(new Response()); std::shared_ptr<Response> response(new Response());
auto timer=get_timeout_timer(session, session->connection->config.timeout_connect); auto timer = get_timeout_timer(session, session->connection->config.timeout_connect);
asio::async_read_until(session->connection->socket->next_layer(), response->content_buffer, "\r\n\r\n", [session, response, timer](const error_code& ec, size_t /*bytes_transferred*/) { asio::async_read_until(session->connection->socket->next_layer(), response->content_buffer, "\r\n\r\n", [session, response, timer](const error_code &ec, size_t /*bytes_transferred*/) {
if(timer) if(timer)
timer->cancel(); timer->cancel();
if(!ec) { if(!ec) {
parse_response_header(response); parse_response_header(response);
if (response->status_code.empty() || response->status_code.compare(0, 3, "200") != 0) { if(response->status_code.empty() || response->status_code.compare(0, 3, "200") != 0) {
close(session); close(session);
session->callback(make_error_code::make_error_code(errc::permission_denied)); session->callback(make_error_code::make_error_code(errc::permission_denied));
} }
@ -116,8 +116,8 @@ namespace SimpleWeb {
} }
static void handshake(const std::shared_ptr<Session> &session) { static void handshake(const std::shared_ptr<Session> &session) {
auto timer=get_timeout_timer(session, session->connection->config.timeout_connect); auto timer = get_timeout_timer(session, session->connection->config.timeout_connect);
session->connection->socket->async_handshake(asio::ssl::stream_base::client, [session, timer](const error_code& ec) { session->connection->socket->async_handshake(asio::ssl::stream_base::client, [session, timer](const error_code &ec) {
if(timer) if(timer)
timer->cancel(); timer->cancel();
if(!ec) if(!ec)
@ -129,6 +129,6 @@ namespace SimpleWeb {
}); });
} }
}; };
} } // namespace SimpleWeb
#endif /* CLIENT_HTTPS_HPP */ #endif /* CLIENT_HTTPS_HPP */

View file

@ -1,29 +1,30 @@
#ifndef SIMPLE_WEB_CRYPTO_HPP #ifndef SIMPLE_WEB_CRYPTO_HPP
#define SIMPLE_WEB_CRYPTO_HPP #define SIMPLE_WEB_CRYPTO_HPP
#include <string>
#include <cmath> #include <cmath>
#include <sstream>
#include <iomanip> #include <iomanip>
#include <istream> #include <istream>
#include <sstream>
#include <string>
#include <vector> #include <vector>
//Moving these to a seperate namespace for minimal global namespace cluttering does not work with clang++ //Moving these to a seperate namespace for minimal global namespace cluttering does not work with clang++
#include <openssl/evp.h>
#include <openssl/buffer.h> #include <openssl/buffer.h>
#include <openssl/sha.h> #include <openssl/evp.h>
#include <openssl/md5.h> #include <openssl/md5.h>
#include <openssl/sha.h>
namespace SimpleWeb { namespace SimpleWeb {
//TODO 2017: remove workaround for MSVS 2012 //TODO 2017: remove workaround for MSVS 2012
#if _MSC_VER == 1700 //MSVS 2012 has no definition for round() #if _MSC_VER == 1700 //MSVS 2012 has no definition for round()
inline double round(double x) { //custom definition of round() for positive numbers inline double round(double x) { //custom definition of round() for positive numbers
return floor(x + 0.5); return floor(x + 0.5);
} }
#endif #endif
class Crypto { class Crypto {
const static size_t buffer_size=131072; const static size_t buffer_size = 131072;
public: public:
class Base64 { class Base64 {
public: public:
@ -31,7 +32,7 @@ namespace SimpleWeb {
std::string base64; std::string base64;
BIO *bio, *b64; BIO *bio, *b64;
BUF_MEM *bptr=BUF_MEM_new(); BUF_MEM *bptr = BUF_MEM_new();
b64 = BIO_new(BIO_f_base64()); b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
@ -40,19 +41,19 @@ namespace SimpleWeb {
BIO_set_mem_buf(b64, bptr, BIO_CLOSE); BIO_set_mem_buf(b64, bptr, BIO_CLOSE);
//Write directly to base64-buffer to avoid copy //Write directly to base64-buffer to avoid copy
int base64_length=static_cast<int>(round(4*ceil((double)ascii.size()/3.0))); int base64_length = static_cast<int>(round(4 * ceil((double)ascii.size() / 3.0)));
base64.resize(base64_length); base64.resize(base64_length);
bptr->length=0; bptr->length = 0;
bptr->max=base64_length+1; bptr->max = base64_length + 1;
bptr->data=(char*)&base64[0]; bptr->data = (char *)&base64[0];
BIO_write(b64, &ascii[0], static_cast<int>(ascii.size())); BIO_write(b64, &ascii[0], static_cast<int>(ascii.size()));
BIO_flush(b64); BIO_flush(b64);
//To keep &base64[0] through BIO_free_all(b64) //To keep &base64[0] through BIO_free_all(b64)
bptr->length=0; bptr->length = 0;
bptr->max=0; bptr->max = 0;
bptr->data=nullptr; bptr->data = nullptr;
BIO_free_all(b64); BIO_free_all(b64);
@ -63,12 +64,12 @@ namespace SimpleWeb {
std::string ascii; std::string ascii;
//Resize ascii, however, the size is a up to two bytes too large. //Resize ascii, however, the size is a up to two bytes too large.
ascii.resize((6*base64.size())/8); ascii.resize((6 * base64.size()) / 8);
BIO *b64, *bio; BIO *b64, *bio;
b64 = BIO_new(BIO_f_base64()); b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bio = BIO_new_mem_buf((char*)&base64[0], static_cast<int>(base64.size())); bio = BIO_new_mem_buf((char *)&base64[0], static_cast<int>(base64.size()));
bio = BIO_push(b64, bio); bio = BIO_push(b64, bio);
int decoded_length = BIO_read(bio, &ascii[0], static_cast<int>(ascii.size())); int decoded_length = BIO_read(bio, &ascii[0], static_cast<int>(ascii.size()));
@ -84,123 +85,123 @@ namespace SimpleWeb {
static std::string to_hex_string(const std::string &input) { static std::string to_hex_string(const std::string &input) {
std::stringstream hex_stream; std::stringstream hex_stream;
hex_stream << std::hex << std::internal << std::setfill('0'); hex_stream << std::hex << std::internal << std::setfill('0');
for (auto &byte : input) for(auto &byte : input)
hex_stream << std::setw(2) << static_cast<int>(static_cast<unsigned char>(byte)); hex_stream << std::setw(2) << static_cast<int>(static_cast<unsigned char>(byte));
return hex_stream.str(); return hex_stream.str();
} }
static std::string md5(const std::string &input, size_t iterations=1) { static std::string md5(const std::string &input, size_t iterations = 1) {
std::string hash; std::string hash;
hash.resize(128 / 8); hash.resize(128 / 8);
MD5(reinterpret_cast<const unsigned char*>(&input[0]), input.size(), reinterpret_cast<unsigned char*>(&hash[0])); MD5(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));
for (size_t c = 1; c < iterations; ++c) for(size_t c = 1; c < iterations; ++c)
MD5(reinterpret_cast<const unsigned char*>(&hash[0]), hash.size(), reinterpret_cast<unsigned char*>(&hash[0])); MD5(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
return hash; return hash;
} }
static std::string md5(std::istream &stream, size_t iterations=1) { static std::string md5(std::istream &stream, size_t iterations = 1) {
MD5_CTX context; MD5_CTX context;
MD5_Init(&context); MD5_Init(&context);
std::streamsize read_length; std::streamsize read_length;
std::vector<char> buffer(buffer_size); std::vector<char> buffer(buffer_size);
while((read_length=stream.read(&buffer[0], buffer_size).gcount())>0) while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0)
MD5_Update(&context, buffer.data(), read_length); MD5_Update(&context, buffer.data(), read_length);
std::string hash; std::string hash;
hash.resize(128 / 8); hash.resize(128 / 8);
MD5_Final(reinterpret_cast<unsigned char*>(&hash[0]), &context); MD5_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);
for (size_t c = 1; c < iterations; ++c) for(size_t c = 1; c < iterations; ++c)
MD5(reinterpret_cast<const unsigned char*>(&hash[0]), hash.size(), reinterpret_cast<unsigned char*>(&hash[0])); MD5(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
return hash; return hash;
} }
static std::string sha1(const std::string &input, size_t iterations=1) { static std::string sha1(const std::string &input, size_t iterations = 1) {
std::string hash; std::string hash;
hash.resize(160 / 8); hash.resize(160 / 8);
SHA1(reinterpret_cast<const unsigned char*>(&input[0]), input.size(), reinterpret_cast<unsigned char*>(&hash[0])); SHA1(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));
for (size_t c = 1; c < iterations; ++c) for(size_t c = 1; c < iterations; ++c)
SHA1(reinterpret_cast<const unsigned char*>(&hash[0]), hash.size(), reinterpret_cast<unsigned char*>(&hash[0])); SHA1(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
return hash; return hash;
} }
static std::string sha1(std::istream &stream, size_t iterations=1) { static std::string sha1(std::istream &stream, size_t iterations = 1) {
SHA_CTX context; SHA_CTX context;
SHA1_Init(&context); SHA1_Init(&context);
std::streamsize read_length; std::streamsize read_length;
std::vector<char> buffer(buffer_size); std::vector<char> buffer(buffer_size);
while((read_length=stream.read(&buffer[0], buffer_size).gcount())>0) while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0)
SHA1_Update(&context, buffer.data(), read_length); SHA1_Update(&context, buffer.data(), read_length);
std::string hash; std::string hash;
hash.resize(160 / 8); hash.resize(160 / 8);
SHA1_Final(reinterpret_cast<unsigned char*>(&hash[0]), &context); SHA1_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);
for (size_t c = 1; c < iterations; ++c) for(size_t c = 1; c < iterations; ++c)
SHA1(reinterpret_cast<const unsigned char*>(&hash[0]), hash.size(), reinterpret_cast<unsigned char*>(&hash[0])); SHA1(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
return hash; return hash;
} }
static std::string sha256(const std::string &input, size_t iterations=1) { static std::string sha256(const std::string &input, size_t iterations = 1) {
std::string hash; std::string hash;
hash.resize(256 / 8); hash.resize(256 / 8);
SHA256(reinterpret_cast<const unsigned char*>(&input[0]), input.size(), reinterpret_cast<unsigned char*>(&hash[0])); SHA256(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));
for (size_t c = 1; c < iterations; ++c) for(size_t c = 1; c < iterations; ++c)
SHA256(reinterpret_cast<const unsigned char*>(&hash[0]), hash.size(), reinterpret_cast<unsigned char*>(&hash[0])); SHA256(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
return hash; return hash;
} }
static std::string sha256(std::istream &stream, size_t iterations=1) { static std::string sha256(std::istream &stream, size_t iterations = 1) {
SHA256_CTX context; SHA256_CTX context;
SHA256_Init(&context); SHA256_Init(&context);
std::streamsize read_length; std::streamsize read_length;
std::vector<char> buffer(buffer_size); std::vector<char> buffer(buffer_size);
while((read_length=stream.read(&buffer[0], buffer_size).gcount())>0) while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0)
SHA256_Update(&context, buffer.data(), read_length); SHA256_Update(&context, buffer.data(), read_length);
std::string hash; std::string hash;
hash.resize(256 / 8); hash.resize(256 / 8);
SHA256_Final(reinterpret_cast<unsigned char*>(&hash[0]), &context); SHA256_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);
for (size_t c = 1; c < iterations; ++c) for(size_t c = 1; c < iterations; ++c)
SHA256(reinterpret_cast<const unsigned char*>(&hash[0]), hash.size(), reinterpret_cast<unsigned char*>(&hash[0])); SHA256(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
return hash; return hash;
} }
static std::string sha512(const std::string &input, size_t iterations=1) { static std::string sha512(const std::string &input, size_t iterations = 1) {
std::string hash; std::string hash;
hash.resize(512 / 8); hash.resize(512 / 8);
SHA512(reinterpret_cast<const unsigned char*>(&input[0]), input.size(), reinterpret_cast<unsigned char*>(&hash[0])); SHA512(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));
for (size_t c = 1; c < iterations; ++c) for(size_t c = 1; c < iterations; ++c)
SHA512(reinterpret_cast<const unsigned char*>(&hash[0]), hash.size(), reinterpret_cast<unsigned char*>(&hash[0])); SHA512(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
return hash; return hash;
} }
static std::string sha512(std::istream &stream, size_t iterations=1) { static std::string sha512(std::istream &stream, size_t iterations = 1) {
SHA512_CTX context; SHA512_CTX context;
SHA512_Init(&context); SHA512_Init(&context);
std::streamsize read_length; std::streamsize read_length;
std::vector<char> buffer(buffer_size); std::vector<char> buffer(buffer_size);
while((read_length=stream.read(&buffer[0], buffer_size).gcount())>0) while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0)
SHA512_Update(&context, buffer.data(), read_length); SHA512_Update(&context, buffer.data(), read_length);
std::string hash; std::string hash;
hash.resize(512 / 8); hash.resize(512 / 8);
SHA512_Final(reinterpret_cast<unsigned char*>(&hash[0]), &context); SHA512_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);
for (size_t c = 1; c < iterations; ++c) for(size_t c = 1; c < iterations; ++c)
SHA512(reinterpret_cast<const unsigned char*>(&hash[0]), hash.size(), reinterpret_cast<unsigned char*>(&hash[0])); SHA512(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
return hash; return hash;
} }
@ -210,8 +211,8 @@ namespace SimpleWeb {
std::string key; std::string key;
key.resize(key_size); key.resize(key_size);
PKCS5_PBKDF2_HMAC_SHA1(password.c_str(), password.size(), PKCS5_PBKDF2_HMAC_SHA1(password.c_str(), password.size(),
reinterpret_cast<const unsigned char*>(salt.c_str()), salt.size(), iterations, reinterpret_cast<const unsigned char *>(salt.c_str()), salt.size(), iterations,
key_size, reinterpret_cast<unsigned char*>(&key[0])); key_size, reinterpret_cast<unsigned char *>(&key[0]));
return key; return key;
} }
}; };

View file

@ -1,16 +1,16 @@
#include "server_http.hpp"
#include "client_http.hpp" #include "client_http.hpp"
#include "server_http.hpp"
//Added for the json-example //Added for the json-example
#define BOOST_SPIRIT_THREADSAFE #define BOOST_SPIRIT_THREADSAFE
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
//Added for the default_resource example //Added for the default_resource example
#include <fstream>
#include <boost/filesystem.hpp>
#include <vector>
#include <algorithm> #include <algorithm>
#include <boost/filesystem.hpp>
#include <fstream>
#include <vector>
#ifdef HAVE_OPENSSL #ifdef HAVE_OPENSSL
#include "crypto.hpp" #include "crypto.hpp"
#endif #endif
@ -23,27 +23,27 @@ typedef SimpleWeb::Server<SimpleWeb::HTTP> HttpServer;
typedef SimpleWeb::Client<SimpleWeb::HTTP> HttpClient; typedef SimpleWeb::Client<SimpleWeb::HTTP> HttpClient;
//Added for the default_resource example //Added for the default_resource example
void default_resource_send(const HttpServer &server, const shared_ptr<HttpServer::Response> &response, void default_resource_send(const HttpServer &server, const shared_ptr<HttpServer::Response> &response, const shared_ptr<ifstream> &ifs);
const shared_ptr<ifstream> &ifs);
int main() { int main() {
//HTTP-server at port 8080 using 1 thread //HTTP-server at port 8080 using 1 thread
//Unless you do more heavy non-threaded processing in the resources, //Unless you do more heavy non-threaded processing in the resources,
//1 thread is usually faster than several threads //1 thread is usually faster than several threads
HttpServer server; HttpServer server;
server.config.port=8080; server.config.port = 8080;
//Add resources using path-regex and method-string, and an anonymous function //Add resources using path-regex and method-string, and an anonymous function
//POST-example for the path /string, responds the posted string //POST-example for the path /string, responds the posted string
server.resource["^/string$"]["POST"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) { server.resource["^/string$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
//Retrieve string: //Retrieve string:
auto content=request->content.string(); auto content = request->content.string();
//request->content.string() is a convenience function for: //request->content.string() is a convenience function for:
//stringstream ss; //stringstream ss;
//ss << request->content.rdbuf(); //ss << request->content.rdbuf();
//auto content=ss.str(); //auto content=ss.str();
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n" << content; *response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n"
<< content;
// Alternatively, use one of the convenience functions, for instance: // Alternatively, use one of the convenience functions, for instance:
@ -58,12 +58,12 @@ int main() {
// "lastName": "Smith", // "lastName": "Smith",
// "age": 25 // "age": 25
//} //}
server.resource["^/json$"]["POST"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) { server.resource["^/json$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
try { try {
ptree pt; ptree pt;
read_json(request->content, pt); read_json(request->content, pt);
auto name=pt.get<string>("firstName")+" "+pt.get<string>("lastName"); auto name = pt.get<string>("firstName") + " " + pt.get<string>("lastName");
*response << "HTTP/1.1 200 OK\r\n" *response << "HTTP/1.1 200 OK\r\n"
<< "Content-Type: application/json\r\n" << "Content-Type: application/json\r\n"
@ -71,7 +71,8 @@ int main() {
<< name; << name;
} }
catch(const exception &e) { catch(const exception &e) {
*response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n" << e.what(); *response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n"
<< e.what();
} }
@ -90,17 +91,18 @@ int main() {
//GET-example for the path /info //GET-example for the path /info
//Responds with request-information //Responds with request-information
server.resource["^/info$"]["GET"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) { server.resource["^/info$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
stringstream stream; stringstream stream;
stream << "<h1>Request from " << request->remote_endpoint_address << " (" << request->remote_endpoint_port << ")</h1>"; stream << "<h1>Request from " << request->remote_endpoint_address << " (" << request->remote_endpoint_port << ")</h1>";
stream << request->method << " " << request->path << " HTTP/" << request->http_version << "<br>"; stream << request->method << " " << request->path << " HTTP/" << request->http_version << "<br>";
for(auto &header: request->header) for(auto &header : request->header)
stream << header.first << ": " << header.second << "<br>"; stream << header.first << ": " << header.second << "<br>";
//find length of content_stream (length received using content_stream.tellp()) //find length of content_stream (length received using content_stream.tellp())
stream.seekp(0, ios::end); stream.seekp(0, ios::end);
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << stream.tellp() << "\r\n\r\n" << stream.rdbuf(); *response << "HTTP/1.1 200 OK\r\nContent-Length: " << stream.tellp() << "\r\n\r\n"
<< stream.rdbuf();
// Alternatively, using a convenience function: // Alternatively, using a convenience function:
@ -114,9 +116,10 @@ int main() {
//GET-example for the path /match/[number], responds with the matched string in path (number) //GET-example for the path /match/[number], responds with the matched string in path (number)
//For instance a request GET /match/123 will receive: 123 //For instance a request GET /match/123 will receive: 123
server.resource["^/match/([0-9]+)$"]["GET"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) { server.resource["^/match/([0-9]+)$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
string number=request->path_match[1]; string number = request->path_match[1];
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n" << number; *response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n"
<< number;
// Alternatively, using a convenience function: // Alternatively, using a convenience function:
@ -124,7 +127,7 @@ int main() {
}; };
//Get example simulating heavy work in a separate thread //Get example simulating heavy work in a separate thread
server.resource["^/work$"]["GET"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) { server.resource["^/work$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
thread work_thread([response] { thread work_thread([response] {
this_thread::sleep_for(chrono::seconds(5)); this_thread::sleep_for(chrono::seconds(5));
response->write("Work done"); response->write("Work done");
@ -136,47 +139,47 @@ int main() {
//Will respond with content in the web/-directory, and its subdirectories. //Will respond with content in the web/-directory, and its subdirectories.
//Default file: index.html //Default file: index.html
//Can for instance be used to retrieve an HTML 5 client that uses REST-resources on this server //Can for instance be used to retrieve an HTML 5 client that uses REST-resources on this server
server.default_resource["GET"]=[&server](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) { server.default_resource["GET"] = [&server](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
try { try {
auto web_root_path=boost::filesystem::canonical("web"); auto web_root_path = boost::filesystem::canonical("web");
auto path=boost::filesystem::canonical(web_root_path/request->path); auto path = boost::filesystem::canonical(web_root_path / request->path);
//Check if path is within web_root_path //Check if path is within web_root_path
if(distance(web_root_path.begin(), web_root_path.end())>distance(path.begin(), path.end()) || if(distance(web_root_path.begin(), web_root_path.end()) > distance(path.begin(), path.end()) ||
!equal(web_root_path.begin(), web_root_path.end(), path.begin())) !equal(web_root_path.begin(), web_root_path.end(), path.begin()))
throw invalid_argument("path must be within root path"); throw invalid_argument("path must be within root path");
if(boost::filesystem::is_directory(path)) if(boost::filesystem::is_directory(path))
path/="index.html"; path /= "index.html";
SimpleWeb::CaseInsensitiveMultimap header; SimpleWeb::CaseInsensitiveMultimap header;
// Uncomment the following line to enable Cache-Control // Uncomment the following line to enable Cache-Control
// header.emplace("Cache-Control", "max-age=86400"); // header.emplace("Cache-Control", "max-age=86400");
#ifdef HAVE_OPENSSL #ifdef HAVE_OPENSSL
// Uncomment the following lines to enable ETag // Uncomment the following lines to enable ETag
// { // {
// ifstream ifs(path.string(), ifstream::in | ios::binary); // ifstream ifs(path.string(), ifstream::in | ios::binary);
// if(ifs) { // if(ifs) {
// auto hash=SimpleWeb::Crypto::to_hex_string(SimpleWeb::Crypto::md5(ifs)); // auto hash = SimpleWeb::Crypto::to_hex_string(SimpleWeb::Crypto::md5(ifs));
// header.emplace("ETag", "\""+hash+"\""); // header.emplace("ETag", "\"" + hash + "\"");
// auto it=request->header.find("If-None-Match"); // auto it = request->header.find("If-None-Match");
// if(it!=request->header.end()) { // if(it != request->header.end()) {
// if(!it->second.empty() && it->second.compare(1, hash.size(), hash)==0) { // if(!it->second.empty() && it->second.compare(1, hash.size(), hash) == 0) {
// response->write(SimpleWeb::StatusCode::redirection_not_modified, header); // response->write(SimpleWeb::StatusCode::redirection_not_modified, header);
// return; // return;
// } // }
// } // }
// } // }
// else // else
// throw invalid_argument("could not read file"); // throw invalid_argument("could not read file");
// } // }
#endif #endif
auto ifs=make_shared<ifstream>(); auto ifs = make_shared<ifstream>();
ifs->open(path.string(), ifstream::in | ios::binary | ios::ate); ifs->open(path.string(), ifstream::in | ios::binary | ios::ate);
if(*ifs) { if(*ifs) {
auto length=ifs->tellg(); auto length = ifs->tellg();
ifs->seekg(0, ios::beg); ifs->seekg(0, ios::beg);
header.emplace("Content-Length", to_string(length)); header.emplace("Content-Length", to_string(length));
@ -187,15 +190,15 @@ int main() {
throw invalid_argument("could not read file"); throw invalid_argument("could not read file");
} }
catch(const exception &e) { catch(const exception &e) {
response->write(SimpleWeb::StatusCode::client_error_bad_request, "Could not open path "+request->path+": "+e.what()); response->write(SimpleWeb::StatusCode::client_error_bad_request, "Could not open path " + request->path + ": " + e.what());
} }
}; };
server.on_error=[](std::shared_ptr<HttpServer::Request> /*request*/, const SimpleWeb::error_code &/*ec*/) { server.on_error = [](std::shared_ptr<HttpServer::Request> /*request*/, const SimpleWeb::error_code & /*ec*/) {
// handle errors here // handle errors here
}; };
thread server_thread([&server](){ thread server_thread([&server]() {
//Start server //Start server
server.start(); server.start();
}); });
@ -207,11 +210,11 @@ int main() {
HttpClient client("localhost:8080"); HttpClient client("localhost:8080");
// synchronous request examples // synchronous request examples
auto r1=client.request("GET", "/match/123"); auto r1 = client.request("GET", "/match/123");
cout << r1->content.rdbuf() << endl; // Alternatively, use the convenience function r1->content.string() cout << r1->content.rdbuf() << endl; // Alternatively, use the convenience function r1->content.string()
string json_string="{\"firstName\": \"John\",\"lastName\": \"Smith\",\"age\": 25}"; string json_string = "{\"firstName\": \"John\",\"lastName\": \"Smith\",\"age\": 25}";
auto r2=client.request("POST", "/string", json_string); auto r2 = client.request("POST", "/string", json_string);
cout << r2->content.rdbuf() << endl; cout << r2->content.rdbuf() << endl;
// asynchronous request example // asynchronous request example
@ -223,18 +226,15 @@ int main() {
client.io_service->run(); client.io_service->run();
server_thread.join(); server_thread.join();
return 0;
} }
void default_resource_send(const HttpServer &server, const shared_ptr<HttpServer::Response> &response, void default_resource_send(const HttpServer &server, const shared_ptr<HttpServer::Response> &response, const shared_ptr<ifstream> &ifs) {
const shared_ptr<ifstream> &ifs) {
//read and send 128 KB at a time //read and send 128 KB at a time
static vector<char> buffer(131072); // Safe when server is running on one thread static vector<char> buffer(131072); // Safe when server is running on one thread
streamsize read_length; streamsize read_length;
if((read_length=ifs->read(&buffer[0], buffer.size()).gcount())>0) { if((read_length = ifs->read(&buffer[0], buffer.size()).gcount()) > 0) {
response->write(&buffer[0], read_length); response->write(&buffer[0], read_length);
if(read_length==static_cast<streamsize>(buffer.size())) { if(read_length == static_cast<streamsize>(buffer.size())) {
server.send(response, [&server, response, ifs](const SimpleWeb::error_code &ec) { server.send(response, [&server, response, ifs](const SimpleWeb::error_code &ec) {
if(!ec) if(!ec)
default_resource_send(server, response, ifs); default_resource_send(server, response, ifs);

View file

@ -1,17 +1,17 @@
#include "server_https.hpp"
#include "client_https.hpp" #include "client_https.hpp"
#include "server_https.hpp"
//Added for the json-example //Added for the json-example
#define BOOST_SPIRIT_THREADSAFE #define BOOST_SPIRIT_THREADSAFE
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
//Added for the default_resource example //Added for the default_resource example
#include <fstream>
#include <boost/filesystem.hpp>
#include <vector>
#include <algorithm>
#include "crypto.hpp" #include "crypto.hpp"
#include <algorithm>
#include <boost/filesystem.hpp>
#include <fstream>
#include <vector>
using namespace std; using namespace std;
//Added for the json-example: //Added for the json-example:
@ -21,27 +21,27 @@ typedef SimpleWeb::Server<SimpleWeb::HTTPS> HttpsServer;
typedef SimpleWeb::Client<SimpleWeb::HTTPS> HttpsClient; typedef SimpleWeb::Client<SimpleWeb::HTTPS> HttpsClient;
//Added for the default_resource example //Added for the default_resource example
void default_resource_send(const HttpsServer &server, const shared_ptr<HttpsServer::Response> &response, void default_resource_send(const HttpsServer &server, const shared_ptr<HttpsServer::Response> &response, const shared_ptr<ifstream> &ifs);
const shared_ptr<ifstream> &ifs);
int main() { int main() {
//HTTPS-server at port 8080 using 1 thread //HTTPS-server at port 8080 using 1 thread
//Unless you do more heavy non-threaded processing in the resources, //Unless you do more heavy non-threaded processing in the resources,
//1 thread is usually faster than several threads //1 thread is usually faster than several threads
HttpsServer server("server.crt", "server.key"); HttpsServer server("server.crt", "server.key");
server.config.port=8080; server.config.port = 8080;
//Add resources using path-regex and method-string, and an anonymous function //Add resources using path-regex and method-string, and an anonymous function
//POST-example for the path /string, responds the posted string //POST-example for the path /string, responds the posted string
server.resource["^/string$"]["POST"]=[](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) { server.resource["^/string$"]["POST"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
//Retrieve string: //Retrieve string:
auto content=request->content.string(); auto content = request->content.string();
//request->content.string() is a convenience function for: //request->content.string() is a convenience function for:
//stringstream ss; //stringstream ss;
//ss << request->content.rdbuf(); //ss << request->content.rdbuf();
//auto content=ss.str(); //auto content=ss.str();
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n" << content; *response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n"
<< content;
// Alternatively, use one of the convenience functions, for instance: // Alternatively, use one of the convenience functions, for instance:
@ -56,12 +56,12 @@ int main() {
// "lastName": "Smith", // "lastName": "Smith",
// "age": 25 // "age": 25
//} //}
server.resource["^/json$"]["POST"]=[](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) { server.resource["^/json$"]["POST"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
try { try {
ptree pt; ptree pt;
read_json(request->content, pt); read_json(request->content, pt);
auto name=pt.get<string>("firstName")+" "+pt.get<string>("lastName"); auto name = pt.get<string>("firstName") + " " + pt.get<string>("lastName");
*response << "HTTP/1.1 200 OK\r\n" *response << "HTTP/1.1 200 OK\r\n"
<< "Content-Type: application/json\r\n" << "Content-Type: application/json\r\n"
@ -69,7 +69,8 @@ int main() {
<< name; << name;
} }
catch(const exception &e) { catch(const exception &e) {
*response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n" << e.what(); *response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n"
<< e.what();
} }
@ -88,17 +89,18 @@ int main() {
//GET-example for the path /info //GET-example for the path /info
//Responds with request-information //Responds with request-information
server.resource["^/info$"]["GET"]=[](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) { server.resource["^/info$"]["GET"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
stringstream stream; stringstream stream;
stream << "<h1>Request from " << request->remote_endpoint_address << " (" << request->remote_endpoint_port << ")</h1>"; stream << "<h1>Request from " << request->remote_endpoint_address << " (" << request->remote_endpoint_port << ")</h1>";
stream << request->method << " " << request->path << " HTTP/" << request->http_version << "<br>"; stream << request->method << " " << request->path << " HTTP/" << request->http_version << "<br>";
for(auto &header: request->header) for(auto &header : request->header)
stream << header.first << ": " << header.second << "<br>"; stream << header.first << ": " << header.second << "<br>";
//find length of content_stream (length received using content_stream.tellp()) //find length of content_stream (length received using content_stream.tellp())
stream.seekp(0, ios::end); stream.seekp(0, ios::end);
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << stream.tellp() << "\r\n\r\n" << stream.rdbuf(); *response << "HTTP/1.1 200 OK\r\nContent-Length: " << stream.tellp() << "\r\n\r\n"
<< stream.rdbuf();
// Alternatively, using a convenience function: // Alternatively, using a convenience function:
@ -112,9 +114,10 @@ int main() {
//GET-example for the path /match/[number], responds with the matched string in path (number) //GET-example for the path /match/[number], responds with the matched string in path (number)
//For instance a request GET /match/123 will receive: 123 //For instance a request GET /match/123 will receive: 123
server.resource["^/match/([0-9]+)$"]["GET"]=[](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) { server.resource["^/match/([0-9]+)$"]["GET"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
string number=request->path_match[1]; string number = request->path_match[1];
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n" << number; *response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n"
<< number;
// Alternatively, using a convenience function: // Alternatively, using a convenience function:
@ -122,7 +125,7 @@ int main() {
}; };
//Get example simulating heavy work in a separate thread //Get example simulating heavy work in a separate thread
server.resource["^/work$"]["GET"]=[](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> /*request*/) { server.resource["^/work$"]["GET"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> /*request*/) {
thread work_thread([response] { thread work_thread([response] {
this_thread::sleep_for(chrono::seconds(5)); this_thread::sleep_for(chrono::seconds(5));
response->write("Work done"); response->write("Work done");
@ -134,47 +137,47 @@ int main() {
//Will respond with content in the web/-directory, and its subdirectories. //Will respond with content in the web/-directory, and its subdirectories.
//Default file: index.html //Default file: index.html
//Can for instance be used to retrieve an HTML 5 client that uses REST-resources on this server //Can for instance be used to retrieve an HTML 5 client that uses REST-resources on this server
server.default_resource["GET"]=[&server](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) { server.default_resource["GET"] = [&server](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
try { try {
auto web_root_path=boost::filesystem::canonical("web"); auto web_root_path = boost::filesystem::canonical("web");
auto path=boost::filesystem::canonical(web_root_path/request->path); auto path = boost::filesystem::canonical(web_root_path / request->path);
//Check if path is within web_root_path //Check if path is within web_root_path
if(distance(web_root_path.begin(), web_root_path.end())>distance(path.begin(), path.end()) || if(distance(web_root_path.begin(), web_root_path.end()) > distance(path.begin(), path.end()) ||
!equal(web_root_path.begin(), web_root_path.end(), path.begin())) !equal(web_root_path.begin(), web_root_path.end(), path.begin()))
throw invalid_argument("path must be within root path"); throw invalid_argument("path must be within root path");
if(boost::filesystem::is_directory(path)) if(boost::filesystem::is_directory(path))
path/="index.html"; path /= "index.html";
SimpleWeb::CaseInsensitiveMultimap header; SimpleWeb::CaseInsensitiveMultimap header;
// Uncomment the following line to enable Cache-Control // Uncomment the following line to enable Cache-Control
// header.emplace("Cache-Control", "max-age=86400"); // header.emplace("Cache-Control", "max-age=86400");
#ifdef HAVE_OPENSSL #ifdef HAVE_OPENSSL
// Uncomment the following lines to enable ETag // Uncomment the following lines to enable ETag
// { // {
// ifstream ifs(path.string(), ifstream::in | ios::binary); // ifstream ifs(path.string(), ifstream::in | ios::binary);
// if(ifs) { // if(ifs) {
// auto hash=SimpleWeb::Crypto::to_hex_string(SimpleWeb::Crypto::md5(ifs)); // auto hash = SimpleWeb::Crypto::to_hex_string(SimpleWeb::Crypto::md5(ifs));
// header.emplace("ETag", "\""+hash+"\""); // header.emplace("ETag", "\"" + hash + "\"");
// auto it=request->header.find("If-None-Match"); // auto it = request->header.find("If-None-Match");
// if(it!=request->header.end()) { // if(it != request->header.end()) {
// if(!it->second.empty() && it->second.compare(1, hash.size(), hash)==0) { // if(!it->second.empty() && it->second.compare(1, hash.size(), hash) == 0) {
// response->write(SimpleWeb::StatusCode::redirection_not_modified, header); // response->write(SimpleWeb::StatusCode::redirection_not_modified, header);
// return; // return;
// } // }
// } // }
// } // }
// else // else
// throw invalid_argument("could not read file"); // throw invalid_argument("could not read file");
// } // }
#endif #endif
auto ifs=make_shared<ifstream>(); auto ifs = make_shared<ifstream>();
ifs->open(path.string(), ifstream::in | ios::binary | ios::ate); ifs->open(path.string(), ifstream::in | ios::binary | ios::ate);
if(*ifs) { if(*ifs) {
auto length=ifs->tellg(); auto length = ifs->tellg();
ifs->seekg(0, ios::beg); ifs->seekg(0, ios::beg);
header.emplace("Content-Length", to_string(length)); header.emplace("Content-Length", to_string(length));
@ -185,15 +188,15 @@ int main() {
throw invalid_argument("could not read file"); throw invalid_argument("could not read file");
} }
catch(const exception &e) { catch(const exception &e) {
response->write(SimpleWeb::StatusCode::client_error_bad_request, "Could not open path "+request->path+": "+e.what()); response->write(SimpleWeb::StatusCode::client_error_bad_request, "Could not open path " + request->path + ": " + e.what());
} }
}; };
server.on_error=[](std::shared_ptr<HttpsServer::Request> /*request*/, const SimpleWeb::error_code &/*ec*/) { server.on_error = [](std::shared_ptr<HttpsServer::Request> /*request*/, const SimpleWeb::error_code & /*ec*/) {
// handle errors here // handle errors here
}; };
thread server_thread([&server](){ thread server_thread([&server]() {
//Start server //Start server
server.start(); server.start();
}); });
@ -206,11 +209,11 @@ int main() {
HttpsClient client("localhost:8080", false); HttpsClient client("localhost:8080", false);
// synchronous request examples // synchronous request examples
auto r1=client.request("GET", "/match/123"); auto r1 = client.request("GET", "/match/123");
cout << r1->content.rdbuf() << endl; // Alternatively, use the convenience function r1->content.string() cout << r1->content.rdbuf() << endl; // Alternatively, use the convenience function r1->content.string()
string json_string="{\"firstName\": \"John\",\"lastName\": \"Smith\",\"age\": 25}"; string json_string = "{\"firstName\": \"John\",\"lastName\": \"Smith\",\"age\": 25}";
auto r2=client.request("POST", "/string", json_string); auto r2 = client.request("POST", "/string", json_string);
cout << r2->content.rdbuf() << endl; cout << r2->content.rdbuf() << endl;
// asynchronous request example // asynchronous request example
@ -222,18 +225,15 @@ int main() {
client.io_service->run(); client.io_service->run();
server_thread.join(); server_thread.join();
return 0;
} }
void default_resource_send(const HttpsServer &server, const shared_ptr<HttpsServer::Response> &response, void default_resource_send(const HttpsServer &server, const shared_ptr<HttpsServer::Response> &response, const shared_ptr<ifstream> &ifs) {
const shared_ptr<ifstream> &ifs) {
//read and send 128 KB at a time //read and send 128 KB at a time
static vector<char> buffer(131072); // Safe when server is running on one thread static vector<char> buffer(131072); // Safe when server is running on one thread
streamsize read_length; streamsize read_length;
if((read_length=ifs->read(&buffer[0], buffer.size()).gcount())>0) { if((read_length = ifs->read(&buffer[0], buffer.size()).gcount()) > 0) {
response->write(&buffer[0], read_length); response->write(&buffer[0], read_length);
if(read_length==static_cast<streamsize>(buffer.size())) { if(read_length == static_cast<streamsize>(buffer.size())) {
server.send(response, [&server, response, ifs](const SimpleWeb::error_code &ec) { server.send(response, [&server, response, ifs](const SimpleWeb::error_code &ec) {
if(!ec) if(!ec)
default_resource_send(server, response, ifs); default_resource_send(server, response, ifs);

View file

@ -2,11 +2,11 @@
#define SERVER_HTTP_HPP #define SERVER_HTTP_HPP
#include "utility.hpp" #include "utility.hpp"
#include <map>
#include <thread>
#include <functional> #include <functional>
#include <iostream> #include <iostream>
#include <map>
#include <sstream> #include <sstream>
#include <thread>
#ifdef USE_STANDALONE_ASIO #ifdef USE_STANDALONE_ASIO
#include <asio.hpp> #include <asio.hpp>
@ -14,7 +14,7 @@ namespace SimpleWeb {
using error_code = std::error_code; using error_code = std::error_code;
using errc = std::errc; using errc = std::errc;
namespace make_error_code = std; namespace make_error_code = std;
} } // namespace SimpleWeb
#else #else
#include <boost/asio.hpp> #include <boost/asio.hpp>
namespace SimpleWeb { namespace SimpleWeb {
@ -22,7 +22,7 @@ namespace SimpleWeb {
using error_code = boost::system::error_code; using error_code = boost::system::error_code;
namespace errc = boost::system::errc; namespace errc = boost::system::errc;
namespace make_error_code = boost::system::errc; namespace make_error_code = boost::system::errc;
} } // namespace SimpleWeb
#endif #endif
// Late 2017 TODO: remove the following checks and always use std::regex // Late 2017 TODO: remove the following checks and always use std::regex
@ -54,17 +54,17 @@ namespace SimpleWeb {
std::shared_ptr<socket_type> socket; std::shared_ptr<socket_type> socket;
Response(const std::shared_ptr<socket_type> &socket): std::ostream(&streambuf), socket(socket) {} Response(const std::shared_ptr<socket_type> &socket) : std::ostream(&streambuf), socket(socket) {}
template<class size_type> template <class size_type>
void write_header(const CaseInsensitiveMultimap &header, size_type size) { void write_header(const CaseInsensitiveMultimap &header, size_type size) {
bool content_length_written=false; bool content_length_written = false;
bool chunked_transfer_encoding=false; bool chunked_transfer_encoding = false;
for(auto &field: header) { for(auto &field : header) {
if(!content_length_written && case_insensitive_equal(field.first, "content-length")) if(!content_length_written && case_insensitive_equal(field.first, "content-length"))
content_length_written=true; content_length_written = true;
else if(!chunked_transfer_encoding && case_insensitive_equal(field.first, "transfer-encoding") && case_insensitive_equal(field.second, "chunked")) else if(!chunked_transfer_encoding && case_insensitive_equal(field.first, "transfer-encoding") && case_insensitive_equal(field.second, "chunked"))
chunked_transfer_encoding=true; chunked_transfer_encoding = true;
*this << field.first << ": " << field.second << "\r\n"; *this << field.first << ": " << field.second << "\r\n";
} }
@ -73,6 +73,7 @@ namespace SimpleWeb {
else else
*this << "\r\n"; *this << "\r\n";
} }
public: public:
size_t size() { size_t size() {
return streambuf.size(); return streambuf.size();
@ -84,13 +85,13 @@ namespace SimpleWeb {
} }
/// Convenience function for writing status line, potential header fields, and empty content /// Convenience function for writing status line, potential header fields, and empty content
void write(StatusCode status_code=StatusCode::success_ok, const CaseInsensitiveMultimap &header=CaseInsensitiveMultimap()) { void write(StatusCode status_code = StatusCode::success_ok, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n"; *this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
write_header(header, 0); write_header(header, 0);
} }
/// Convenience function for writing status line, header fields, and content /// Convenience function for writing status line, header fields, and content
void write(StatusCode status_code, const std::string &content, const CaseInsensitiveMultimap &header=CaseInsensitiveMultimap()) { void write(StatusCode status_code, const std::string &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n"; *this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
write_header(header, content.size()); write_header(header, content.size());
if(!content.empty()) if(!content.empty())
@ -98,10 +99,10 @@ namespace SimpleWeb {
} }
/// Convenience function for writing status line, header fields, and content /// Convenience function for writing status line, header fields, and content
void write(StatusCode status_code, std::istream &content, const CaseInsensitiveMultimap &header=CaseInsensitiveMultimap()) { void write(StatusCode status_code, std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n"; *this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
content.seekg(0, std::ios::end); content.seekg(0, std::ios::end);
auto size=content.tellg(); auto size = content.tellg();
content.seekg(0, std::ios::beg); content.seekg(0, std::ios::beg);
write_header(header, size); write_header(header, size);
if(size) if(size)
@ -109,12 +110,12 @@ namespace SimpleWeb {
} }
/// Convenience function for writing success status line, header fields, and content /// Convenience function for writing success status line, header fields, and content
void write(const std::string &content, const CaseInsensitiveMultimap &header=CaseInsensitiveMultimap()) { void write(const std::string &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
write(StatusCode::success_ok, content, header); write(StatusCode::success_ok, content, header);
} }
/// Convenience function for writing success status line, header fields, and content /// Convenience function for writing success status line, header fields, and content
void write(std::istream &content, const CaseInsensitiveMultimap &header=CaseInsensitiveMultimap()) { void write(std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
write(StatusCode::success_ok, content, header); write(StatusCode::success_ok, content, header);
} }
@ -132,6 +133,7 @@ namespace SimpleWeb {
class Content : public std::istream { class Content : public std::istream {
friend class ServerBase<socket_type>; friend class ServerBase<socket_type>;
public: public:
size_t size() { size_t size() {
return streambuf.size(); return streambuf.size();
@ -142,14 +144,16 @@ namespace SimpleWeb {
ss << rdbuf(); ss << rdbuf();
return ss.str(); return ss.str();
} }
private: private:
asio::streambuf &streambuf; asio::streambuf &streambuf;
Content(asio::streambuf &streambuf): std::istream(&streambuf), streambuf(streambuf) {} Content(asio::streambuf &streambuf) : std::istream(&streambuf), streambuf(streambuf) {}
}; };
class Request { class Request {
friend class ServerBase<socket_type>; friend class ServerBase<socket_type>;
friend class Server<socket_type>; friend class Server<socket_type>;
public: public:
std::string method, path, http_version; std::string method, path, http_version;
@ -165,19 +169,20 @@ namespace SimpleWeb {
/// Returns query keys with percent-decoded values. /// Returns query keys with percent-decoded values.
CaseInsensitiveMultimap parse_query_string() { CaseInsensitiveMultimap parse_query_string() {
auto pos = path.find('?'); auto pos = path.find('?');
if (pos != std::string::npos && pos + 1 < path.size()) if(pos != std::string::npos && pos + 1 < path.size())
return SimpleWeb::QueryString::parse(path.substr(pos + 1)); return SimpleWeb::QueryString::parse(path.substr(pos + 1));
else else
return CaseInsensitiveMultimap(); return CaseInsensitiveMultimap();
} }
private: private:
Request(const socket_type &socket): content(streambuf) { Request(const socket_type &socket) : content(streambuf) {
try { try {
remote_endpoint_address=socket.lowest_layer().remote_endpoint().address().to_string(); remote_endpoint_address = socket.lowest_layer().remote_endpoint().address().to_string();
remote_endpoint_port=socket.lowest_layer().remote_endpoint().port(); remote_endpoint_port = socket.lowest_layer().remote_endpoint().port();
}
catch(...) {
} }
catch(...) {}
} }
asio::streambuf streambuf; asio::streambuf streambuf;
@ -186,21 +191,22 @@ namespace SimpleWeb {
class Config { class Config {
friend class ServerBase<socket_type>; friend class ServerBase<socket_type>;
Config(unsigned short port): port(port) {} Config(unsigned short port) : port(port) {}
public: public:
/// Port number to use. Defaults to 80 for HTTP and 443 for HTTPS. /// Port number to use. Defaults to 80 for HTTP and 443 for HTTPS.
unsigned short port; unsigned short port;
/// Number of threads that the server will use when start() is called. Defaults to 1 thread. /// Number of threads that the server will use when start() is called. Defaults to 1 thread.
size_t thread_pool_size=1; size_t thread_pool_size = 1;
/// Timeout on request handling. Defaults to 5 seconds. /// Timeout on request handling. Defaults to 5 seconds.
size_t timeout_request=5; size_t timeout_request = 5;
/// Timeout on content handling. Defaults to 300 seconds. /// Timeout on content handling. Defaults to 300 seconds.
size_t timeout_content=300; size_t timeout_content = 300;
/// IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation. /// IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation.
/// If empty, the address will be any address. /// If empty, the address will be any address.
std::string address; std::string address;
/// Set to false to avoid binding the socket to an address that is already in use. Defaults to true. /// Set to false to avoid binding the socket to an address that is already in use. Defaults to true.
bool reuse_address=true; bool reuse_address = true;
}; };
///Set before calling start(). ///Set before calling start().
Config config; Config config;
@ -208,40 +214,40 @@ namespace SimpleWeb {
private: private:
class regex_orderable : public regex::regex { class regex_orderable : public regex::regex {
std::string str; std::string str;
public: public:
regex_orderable(const char *regex_cstr) : regex::regex(regex_cstr), str(regex_cstr) {} regex_orderable(const char *regex_cstr) : regex::regex(regex_cstr), str(regex_cstr) {}
regex_orderable(const std::string &regex_str) : regex::regex(regex_str), str(regex_str) {} regex_orderable(const std::string &regex_str) : regex::regex(regex_str), str(regex_str) {}
bool operator<(const regex_orderable &rhs) const { bool operator<(const regex_orderable &rhs) const {
return str<rhs.str; return str < rhs.str;
} }
}; };
public: public:
/// Warning: do not add or remove resources after start() is called /// Warning: do not add or remove resources after start() is called
std::map<regex_orderable, std::map<std::string, std::map<regex_orderable, std::map<std::string, std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)>>> resource;
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)> > > resource;
std::map<std::string, std::map<std::string, std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)>> default_resource;
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)> > default_resource;
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Request>, const error_code&)> on_error; std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Request>, const error_code &)> on_error;
std::function<void(std::shared_ptr<socket_type> socket, std::shared_ptr<typename ServerBase<socket_type>::Request>)> on_upgrade; std::function<void(std::shared_ptr<socket_type> socket, std::shared_ptr<typename ServerBase<socket_type>::Request>)> on_upgrade;
virtual void start() { virtual void start() {
if(!io_service) if(!io_service)
io_service=std::make_shared<asio::io_service>(); io_service = std::make_shared<asio::io_service>();
if(io_service->stopped()) if(io_service->stopped())
io_service->reset(); io_service->reset();
asio::ip::tcp::endpoint endpoint; asio::ip::tcp::endpoint endpoint;
if(config.address.size()>0) if(config.address.size() > 0)
endpoint=asio::ip::tcp::endpoint(asio::ip::address::from_string(config.address), config.port); endpoint = asio::ip::tcp::endpoint(asio::ip::address::from_string(config.address), config.port);
else else
endpoint=asio::ip::tcp::endpoint(asio::ip::tcp::v4(), config.port); endpoint = asio::ip::tcp::endpoint(asio::ip::tcp::v4(), config.port);
if(!acceptor) if(!acceptor)
acceptor=std::unique_ptr<asio::ip::tcp::acceptor>(new asio::ip::tcp::acceptor(*io_service)); acceptor = std::unique_ptr<asio::ip::tcp::acceptor>(new asio::ip::tcp::acceptor(*io_service));
acceptor->open(endpoint.protocol()); acceptor->open(endpoint.protocol());
acceptor->set_option(asio::socket_base::reuse_address(config.reuse_address)); acceptor->set_option(asio::socket_base::reuse_address(config.reuse_address));
acceptor->bind(endpoint); acceptor->bind(endpoint);
@ -251,31 +257,31 @@ namespace SimpleWeb {
//If thread_pool_size>1, start m_io_service.run() in (thread_pool_size-1) threads for thread-pooling //If thread_pool_size>1, start m_io_service.run() in (thread_pool_size-1) threads for thread-pooling
threads.clear(); threads.clear();
for(size_t c=1;c<config.thread_pool_size;c++) { for(size_t c = 1; c < config.thread_pool_size; c++) {
threads.emplace_back([this]() { threads.emplace_back([this]() {
io_service->run(); io_service->run();
}); });
} }
//Main thread //Main thread
if(config.thread_pool_size>0) if(config.thread_pool_size > 0)
io_service->run(); io_service->run();
//Wait for the rest of the threads, if any, to finish as well //Wait for the rest of the threads, if any, to finish as well
for(auto& t: threads) { for(auto &t : threads) {
t.join(); t.join();
} }
} }
void stop() { void stop() {
acceptor->close(); acceptor->close();
if(config.thread_pool_size>0) if(config.thread_pool_size > 0)
io_service->stop(); io_service->stop();
} }
///Use this function if you need to recursively send parts of a longer message ///Use this function if you need to recursively send parts of a longer message
void send(const std::shared_ptr<Response> &response, const std::function<void(const error_code&)>& callback=nullptr) const { void send(const std::shared_ptr<Response> &response, const std::function<void(const error_code &)> &callback = nullptr) const {
asio::async_write(*response->socket, response->streambuf, [this, response, callback](const error_code& ec, size_t /*bytes_transferred*/) { asio::async_write(*response->socket, response->streambuf, [this, response, callback](const error_code &ec, size_t /*bytes_transferred*/) {
if(callback) if(callback)
callback(ec); callback(ec);
}); });
@ -284,21 +290,22 @@ namespace SimpleWeb {
/// If you have your own asio::io_service, store its pointer here before running start(). /// If you have your own asio::io_service, store its pointer here before running start().
/// You might also want to set config.thread_pool_size to 0. /// You might also want to set config.thread_pool_size to 0.
std::shared_ptr<asio::io_service> io_service; std::shared_ptr<asio::io_service> io_service;
protected: protected:
std::unique_ptr<asio::ip::tcp::acceptor> acceptor; std::unique_ptr<asio::ip::tcp::acceptor> acceptor;
std::vector<std::thread> threads; std::vector<std::thread> threads;
ServerBase(unsigned short port) : config(port) {} ServerBase(unsigned short port) : config(port) {}
virtual void accept()=0; virtual void accept() = 0;
std::shared_ptr<asio::deadline_timer> get_timeout_timer(const std::shared_ptr<socket_type> &socket, long seconds) { std::shared_ptr<asio::deadline_timer> get_timeout_timer(const std::shared_ptr<socket_type> &socket, long seconds) {
if(seconds==0) if(seconds == 0)
return nullptr; return nullptr;
auto timer=std::make_shared<asio::deadline_timer>(*io_service); auto timer = std::make_shared<asio::deadline_timer>(*io_service);
timer->expires_from_now(boost::posix_time::seconds(seconds)); timer->expires_from_now(boost::posix_time::seconds(seconds));
timer->async_wait([socket](const error_code& ec){ timer->async_wait([socket](const error_code &ec) {
if(!ec) { if(!ec) {
error_code ec; error_code ec;
socket->lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ec); socket->lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ec);
@ -314,10 +321,9 @@ namespace SimpleWeb {
std::shared_ptr<Request> request(new Request(*socket)); std::shared_ptr<Request> request(new Request(*socket));
//Set timeout on the following asio::async-read or write function //Set timeout on the following asio::async-read or write function
auto timer=this->get_timeout_timer(socket, config.timeout_request); auto timer = this->get_timeout_timer(socket, config.timeout_request);
asio::async_read_until(*socket, request->streambuf, "\r\n\r\n", asio::async_read_until(*socket, request->streambuf, "\r\n\r\n", [this, socket, request, timer](const error_code &ec, size_t bytes_transferred) {
[this, socket, request, timer](const error_code& ec, size_t bytes_transferred) {
if(timer) if(timer)
timer->cancel(); timer->cancel();
if(!ec) { if(!ec) {
@ -325,30 +331,27 @@ namespace SimpleWeb {
//"After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter" //"After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter"
//The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the //The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the
//streambuf (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content). //streambuf (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content).
size_t num_additional_bytes=request->streambuf.size()-bytes_transferred; size_t num_additional_bytes = request->streambuf.size() - bytes_transferred;
if(!this->parse_request(request)) if(!this->parse_request(request))
return; return;
//If content, read that as well //If content, read that as well
auto it=request->header.find("Content-Length"); auto it = request->header.find("Content-Length");
if(it!=request->header.end()) { if(it != request->header.end()) {
unsigned long long content_length; unsigned long long content_length;
try { try {
content_length=stoull(it->second); content_length = stoull(it->second);
} }
catch(const std::exception &e) { catch(const std::exception &e) {
if(on_error) if(on_error)
on_error(request, make_error_code::make_error_code(errc::protocol_error)); on_error(request, make_error_code::make_error_code(errc::protocol_error));
return; return;
} }
if(content_length>num_additional_bytes) { if(content_length > num_additional_bytes) {
//Set timeout on the following asio::async-read or write function //Set timeout on the following asio::async-read or write function
auto timer=this->get_timeout_timer(socket, config.timeout_content); auto timer = this->get_timeout_timer(socket, config.timeout_content);
asio::async_read(*socket, request->streambuf, asio::async_read(*socket, request->streambuf, asio::transfer_exactly(content_length - num_additional_bytes), [this, socket, request, timer](const error_code &ec, size_t /*bytes_transferred*/) {
asio::transfer_exactly(content_length-num_additional_bytes),
[this, socket, request, timer]
(const error_code& ec, size_t /*bytes_transferred*/) {
if(timer) if(timer)
timer->cancel(); timer->cancel();
if(!ec) if(!ec)
@ -372,30 +375,30 @@ namespace SimpleWeb {
std::string line; std::string line;
getline(request->content, line); getline(request->content, line);
size_t method_end; size_t method_end;
if((method_end=line.find(' '))!=std::string::npos) { if((method_end = line.find(' ')) != std::string::npos) {
size_t path_end; size_t path_end;
if((path_end=line.find(' ', method_end+1))!=std::string::npos) { if((path_end = line.find(' ', method_end + 1)) != std::string::npos) {
request->method=line.substr(0, method_end); request->method = line.substr(0, method_end);
request->path=line.substr(method_end+1, path_end-method_end-1); request->path = line.substr(method_end + 1, path_end - method_end - 1);
size_t protocol_end; size_t protocol_end;
if((protocol_end=line.find('/', path_end+1))!=std::string::npos) { if((protocol_end = line.find('/', path_end + 1)) != std::string::npos) {
if(line.compare(path_end+1, protocol_end-path_end-1, "HTTP")!=0) if(line.compare(path_end + 1, protocol_end - path_end - 1, "HTTP") != 0)
return false; return false;
request->http_version=line.substr(protocol_end+1, line.size()-protocol_end-2); request->http_version = line.substr(protocol_end + 1, line.size() - protocol_end - 2);
} }
else else
return false; return false;
getline(request->content, line); getline(request->content, line);
size_t param_end; size_t param_end;
while((param_end=line.find(':'))!=std::string::npos) { while((param_end = line.find(':')) != std::string::npos) {
size_t value_start=param_end+1; size_t value_start = param_end + 1;
if((value_start)<line.size()) { if((value_start) < line.size()) {
if(line[value_start]==' ') if(line[value_start] == ' ')
value_start++; value_start++;
if(value_start<line.size()) if(value_start < line.size())
request->header.emplace(line.substr(0, param_end), line.substr(value_start, line.size()-value_start-1)); request->header.emplace(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - 1));
} }
getline(request->content, line); getline(request->content, line);
@ -412,50 +415,50 @@ namespace SimpleWeb {
void find_resource(const std::shared_ptr<socket_type> &socket, const std::shared_ptr<Request> &request) { void find_resource(const std::shared_ptr<socket_type> &socket, const std::shared_ptr<Request> &request) {
//Upgrade connection //Upgrade connection
if(on_upgrade) { if(on_upgrade) {
auto it=request->header.find("Upgrade"); auto it = request->header.find("Upgrade");
if(it!=request->header.end()) { if(it != request->header.end()) {
on_upgrade(socket, request); on_upgrade(socket, request);
return; return;
} }
} }
//Find path- and method-match, and call write_response //Find path- and method-match, and call write_response
for(auto &regex_method: resource) { for(auto &regex_method : resource) {
auto it=regex_method.second.find(request->method); auto it = regex_method.second.find(request->method);
if(it!=regex_method.second.end()) { if(it != regex_method.second.end()) {
regex::smatch sm_res; regex::smatch sm_res;
if(regex::regex_match(request->path, sm_res, regex_method.first)) { if(regex::regex_match(request->path, sm_res, regex_method.first)) {
request->path_match=std::move(sm_res); request->path_match = std::move(sm_res);
write_response(socket, request, it->second); write_response(socket, request, it->second);
return; return;
} }
} }
} }
auto it=default_resource.find(request->method); auto it = default_resource.find(request->method);
if(it!=default_resource.end()) { if(it != default_resource.end()) {
write_response(socket, request, it->second); write_response(socket, request, it->second);
} }
} }
void write_response(const std::shared_ptr<socket_type> &socket, const std::shared_ptr<Request> &request, void write_response(const std::shared_ptr<socket_type> &socket, const std::shared_ptr<Request> &request,
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)> &resource_function) {
std::shared_ptr<typename ServerBase<socket_type>::Request>)>& resource_function) {
//Set timeout on the following asio::async-read or write function //Set timeout on the following asio::async-read or write function
auto timer=this->get_timeout_timer(socket, config.timeout_content); auto timer = this->get_timeout_timer(socket, config.timeout_content);
auto response=std::shared_ptr<Response>(new Response(socket), [this, request, timer](Response *response_ptr) { auto response = std::shared_ptr<Response>(new Response(socket), [this, request, timer](Response *response_ptr) {
auto response=std::shared_ptr<Response>(response_ptr); auto response = std::shared_ptr<Response>(response_ptr);
this->send(response, [this, response, request, timer](const error_code& ec) { this->send(response, [this, response, request, timer](const error_code &ec) {
if(timer) if(timer)
timer->cancel(); timer->cancel();
if(!ec) { if(!ec) {
if (response->close_connection_after_response) if(response->close_connection_after_response)
return; return;
auto range=request->header.equal_range("Connection"); auto range = request->header.equal_range("Connection");
for(auto it=range.first;it!=range.second;it++) { for(auto it = range.first; it != range.second; it++) {
if(case_insensitive_equal(it->second, "close")) { if(case_insensitive_equal(it->second, "close")) {
return; return;
} else if (case_insensitive_equal(it->second, "keep-alive")) { }
else if(case_insensitive_equal(it->second, "keep-alive")) {
this->read_request_and_content(response->socket); this->read_request_and_content(response->socket);
return; return;
} }
@ -479,20 +482,19 @@ namespace SimpleWeb {
} }
}; };
template<class socket_type> template <class socket_type>
class Server : public ServerBase<socket_type> {}; class Server : public ServerBase<socket_type> {};
typedef asio::ip::tcp::socket HTTP; typedef asio::ip::tcp::socket HTTP;
template<> template <>
class Server<HTTP> : public ServerBase<HTTP> { class Server<HTTP> : public ServerBase<HTTP> {
public: public:
DEPRECATED Server(unsigned short port, size_t thread_pool_size=1, long timeout_request=5, long timeout_content=300) : DEPRECATED Server(unsigned short port, size_t thread_pool_size = 1, long timeout_request = 5, long timeout_content = 300) : Server() {
Server() { config.port = port;
config.port=port; config.thread_pool_size = thread_pool_size;
config.thread_pool_size=thread_pool_size; config.timeout_request = timeout_request;
config.timeout_request=timeout_request; config.timeout_content = timeout_content;
config.timeout_content=timeout_content;
} }
Server() : ServerBase<HTTP>::ServerBase(80) {} Server() : ServerBase<HTTP>::ServerBase(80) {}
@ -501,11 +503,11 @@ namespace SimpleWeb {
void accept() { void accept() {
//Create new socket for this connection //Create new socket for this connection
//Shared_ptr is used to pass temporary objects to the asynchronous functions //Shared_ptr is used to pass temporary objects to the asynchronous functions
auto socket=std::make_shared<HTTP>(*io_service); auto socket = std::make_shared<HTTP>(*io_service);
acceptor->async_accept(*socket, [this, socket](const error_code& ec){ acceptor->async_accept(*socket, [this, socket](const error_code &ec) {
//Immediately start accepting a new connection (if io_service hasn't been stopped) //Immediately start accepting a new connection (if io_service hasn't been stopped)
if (ec != asio::error::operation_aborted) if(ec != asio::error::operation_aborted)
accept(); accept();
if(!ec) { if(!ec) {
@ -519,5 +521,6 @@ namespace SimpleWeb {
}); });
} }
}; };
} } // namespace SimpleWeb
#endif /* SERVER_HTTP_HPP */ #endif /* SERVER_HTTP_HPP */

View file

@ -9,46 +9,45 @@
#include <boost/asio/ssl.hpp> #include <boost/asio/ssl.hpp>
#endif #endif
#include <openssl/ssl.h>
#include <algorithm> #include <algorithm>
#include <openssl/ssl.h>
namespace SimpleWeb { namespace SimpleWeb {
typedef asio::ssl::stream<asio::ip::tcp::socket> HTTPS; typedef asio::ssl::stream<asio::ip::tcp::socket> HTTPS;
template<> template <>
class Server<HTTPS> : public ServerBase<HTTPS> { class Server<HTTPS> : public ServerBase<HTTPS> {
std::string session_id_context; std::string session_id_context;
bool set_session_id_context=false; bool set_session_id_context = false;
public: public:
DEPRECATED Server(unsigned short port, size_t thread_pool_size, const std::string& cert_file, const std::string& private_key_file, DEPRECATED Server(unsigned short port, size_t thread_pool_size, const std::string &cert_file, const std::string &private_key_file,
long timeout_request=5, long timeout_content=300, long timeout_request = 5, long timeout_content = 300, const std::string &verify_file = std::string())
const std::string& verify_file=std::string()) : : Server(cert_file, private_key_file, verify_file) {
Server(cert_file, private_key_file, verify_file) { config.port = port;
config.port=port; config.thread_pool_size = thread_pool_size;
config.thread_pool_size=thread_pool_size; config.timeout_request = timeout_request;
config.timeout_request=timeout_request; config.timeout_content = timeout_content;
config.timeout_content=timeout_content;
} }
Server(const std::string& cert_file, const std::string& private_key_file, const std::string& verify_file=std::string()): Server(const std::string &cert_file, const std::string &private_key_file, const std::string &verify_file = std::string())
ServerBase<HTTPS>::ServerBase(443), context(asio::ssl::context::tlsv12) { : ServerBase<HTTPS>::ServerBase(443), context(asio::ssl::context::tlsv12) {
context.use_certificate_chain_file(cert_file); context.use_certificate_chain_file(cert_file);
context.use_private_key_file(private_key_file, asio::ssl::context::pem); context.use_private_key_file(private_key_file, asio::ssl::context::pem);
if(verify_file.size()>0) { if(verify_file.size() > 0) {
context.load_verify_file(verify_file); context.load_verify_file(verify_file);
context.set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert | context.set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert | asio::ssl::verify_client_once);
asio::ssl::verify_client_once); set_session_id_context = true;
set_session_id_context=true;
} }
} }
void start() { void start() {
if(set_session_id_context) { if(set_session_id_context) {
// Creating session_id_context from address:port but reversed due to small SSL_MAX_SSL_SESSION_ID_LENGTH // Creating session_id_context from address:port but reversed due to small SSL_MAX_SSL_SESSION_ID_LENGTH
session_id_context=std::to_string(config.port)+':'; session_id_context = std::to_string(config.port) + ':';
session_id_context.append(config.address.rbegin(), config.address.rend()); session_id_context.append(config.address.rbegin(), config.address.rend());
SSL_CTX_set_session_id_context(context.native_handle(), reinterpret_cast<const unsigned char*>(session_id_context.data()), SSL_CTX_set_session_id_context(context.native_handle(), reinterpret_cast<const unsigned char *>(session_id_context.data()),
std::min<size_t>(session_id_context.size(), SSL_MAX_SSL_SESSION_ID_LENGTH)); std::min<size_t>(session_id_context.size(), SSL_MAX_SSL_SESSION_ID_LENGTH));
} }
ServerBase::start(); ServerBase::start();
@ -60,11 +59,11 @@ namespace SimpleWeb {
void accept() { void accept() {
//Create new socket for this connection //Create new socket for this connection
//Shared_ptr is used to pass temporary objects to the asynchronous functions //Shared_ptr is used to pass temporary objects to the asynchronous functions
auto socket=std::make_shared<HTTPS>(*io_service, context); auto socket = std::make_shared<HTTPS>(*io_service, context);
acceptor->async_accept((*socket).lowest_layer(), [this, socket](const error_code& ec) { acceptor->async_accept((*socket).lowest_layer(), [this, socket](const error_code &ec) {
//Immediately start accepting a new connection (if io_service hasn't been stopped) //Immediately start accepting a new connection (if io_service hasn't been stopped)
if (ec != asio::error::operation_aborted) if(ec != asio::error::operation_aborted)
accept(); accept();
@ -73,9 +72,8 @@ namespace SimpleWeb {
socket->lowest_layer().set_option(option); socket->lowest_layer().set_option(option);
//Set timeout on the following asio::ssl::stream::async_handshake //Set timeout on the following asio::ssl::stream::async_handshake
auto timer=get_timeout_timer(socket, config.timeout_request); auto timer = get_timeout_timer(socket, config.timeout_request);
socket->async_handshake(asio::ssl::stream_base::server, [this, socket, timer] socket->async_handshake(asio::ssl::stream_base::server, [this, socket, timer](const error_code &ec) {
(const error_code& ec) {
if(timer) if(timer)
timer->cancel(); timer->cancel();
if(!ec) if(!ec)
@ -89,8 +87,6 @@ namespace SimpleWeb {
}); });
} }
}; };
} } // namespace SimpleWeb
#endif /* SERVER_HTTPS_HPP */ #endif /* SERVER_HTTPS_HPP */

View file

@ -5,7 +5,7 @@
#include <vector> #include <vector>
namespace SimpleWeb { namespace SimpleWeb {
enum class StatusCode { enum class StatusCode {
unknown = 0, unknown = 0,
information_continue = 100, information_continue = 100,
information_switching_protocols, information_switching_protocols,
@ -68,9 +68,9 @@ enum class StatusCode {
server_error_loop_detected, server_error_loop_detected,
server_error_not_extended = 510, server_error_not_extended = 510,
server_error_network_authentication_required server_error_network_authentication_required
}; };
inline const static std::vector<std::pair<StatusCode, std::string>> &status_codes() { inline const static std::vector<std::pair<StatusCode, std::string>> &status_codes() {
static std::vector<std::pair<StatusCode, std::string>> status_codes = { static std::vector<std::pair<StatusCode, std::string>> status_codes = {
{StatusCode::unknown, ""}, {StatusCode::unknown, ""},
{StatusCode::information_continue, "100 Continue"}, {StatusCode::information_continue, "100 Continue"},
@ -135,23 +135,23 @@ inline const static std::vector<std::pair<StatusCode, std::string>> &status_code
{StatusCode::server_error_not_extended, "510 Not Extended"}, {StatusCode::server_error_not_extended, "510 Not Extended"},
{StatusCode::server_error_network_authentication_required, "511 Network Authentication Required"}}; {StatusCode::server_error_network_authentication_required, "511 Network Authentication Required"}};
return status_codes; return status_codes;
} }
inline StatusCode status_code(const std::string &status_code_str) { inline StatusCode status_code(const std::string &status_code_str) {
for(auto &status_code : status_codes()) { for(auto &status_code : status_codes()) {
if(status_code.second == status_code_str) if(status_code.second == status_code_str)
return status_code.first; return status_code.first;
} }
return StatusCode::unknown; return StatusCode::unknown;
} }
inline const std::string &status_code(StatusCode status_code_enum) { inline const std::string &status_code(StatusCode status_code_enum) {
for(auto &status_code : status_codes()) { for(auto &status_code : status_codes()) {
if(status_code.first == status_code_enum) if(status_code.first == status_code_enum)
return status_code.second; return status_code.second;
} }
return status_codes()[0].second; return status_codes()[0].second;
} }
} // namespace SimpleWeb } // namespace SimpleWeb
#endif // SIMPLE_WEB_SERVER_STATUS_CODE_HPP #endif // SIMPLE_WEB_SERVER_STATUS_CODE_HPP

View file

@ -1,66 +1,61 @@
#include <vector>
#include <cassert> #include <cassert>
#include <vector>
#include "crypto.hpp" #include "crypto.hpp"
using namespace std; using namespace std;
using namespace SimpleWeb; using namespace SimpleWeb;
const vector<pair<string, string> > base64_string_tests = { const vector<pair<string, string>> base64_string_tests = {
{"", ""}, {"", ""},
{"f" , "Zg=="}, {"f", "Zg=="},
{"fo", "Zm8="}, {"fo", "Zm8="},
{"foo", "Zm9v"}, {"foo", "Zm9v"},
{"foob", "Zm9vYg=="}, {"foob", "Zm9vYg=="},
{"fooba", "Zm9vYmE="}, {"fooba", "Zm9vYmE="},
{"foobar", "Zm9vYmFy"} {"foobar", "Zm9vYmFy"}};
};
const vector<pair<string, string> > md5_string_tests = { const vector<pair<string, string>> md5_string_tests = {
{"", "d41d8cd98f00b204e9800998ecf8427e"}, {"", "d41d8cd98f00b204e9800998ecf8427e"},
{"The quick brown fox jumps over the lazy dog", "9e107d9d372bb6826bd81d3542a419d6"} {"The quick brown fox jumps over the lazy dog", "9e107d9d372bb6826bd81d3542a419d6"}};
};
const vector<pair<string, string> > sha1_string_tests = { const vector<pair<string, string>> sha1_string_tests = {
{"", "da39a3ee5e6b4b0d3255bfef95601890afd80709"}, {"", "da39a3ee5e6b4b0d3255bfef95601890afd80709"},
{"The quick brown fox jumps over the lazy dog", "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12"} {"The quick brown fox jumps over the lazy dog", "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12"}};
};
const vector<pair<string, string> > sha256_string_tests = { const vector<pair<string, string>> sha256_string_tests = {
{"", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, {"", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},
{"The quick brown fox jumps over the lazy dog", "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592"} {"The quick brown fox jumps over the lazy dog", "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592"}};
};
const vector<pair<string, string> > sha512_string_tests = { const vector<pair<string, string>> sha512_string_tests = {
{"", "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"}, {"", "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"},
{"The quick brown fox jumps over the lazy dog", "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6"} {"The quick brown fox jumps over the lazy dog", "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6"}};
};
int main() { int main() {
for(auto& string_test: base64_string_tests) { for(auto &string_test : base64_string_tests) {
assert(Crypto::Base64::encode(string_test.first)==string_test.second); assert(Crypto::Base64::encode(string_test.first) == string_test.second);
assert(Crypto::Base64::decode(string_test.second)==string_test.first); assert(Crypto::Base64::decode(string_test.second) == string_test.first);
} }
for(auto& string_test: md5_string_tests) { for(auto &string_test : md5_string_tests) {
assert(Crypto::to_hex_string(Crypto::md5(string_test.first)) == string_test.second); assert(Crypto::to_hex_string(Crypto::md5(string_test.first)) == string_test.second);
stringstream ss(string_test.first); stringstream ss(string_test.first);
assert(Crypto::to_hex_string(Crypto::md5(ss)) == string_test.second); assert(Crypto::to_hex_string(Crypto::md5(ss)) == string_test.second);
} }
for(auto& string_test: sha1_string_tests) { for(auto &string_test : sha1_string_tests) {
assert(Crypto::to_hex_string(Crypto::sha1(string_test.first)) == string_test.second); assert(Crypto::to_hex_string(Crypto::sha1(string_test.first)) == string_test.second);
stringstream ss(string_test.first); stringstream ss(string_test.first);
assert(Crypto::to_hex_string(Crypto::sha1(ss)) == string_test.second); assert(Crypto::to_hex_string(Crypto::sha1(ss)) == string_test.second);
} }
for(auto& string_test: sha256_string_tests) { for(auto &string_test : sha256_string_tests) {
assert(Crypto::to_hex_string(Crypto::sha256(string_test.first)) == string_test.second); assert(Crypto::to_hex_string(Crypto::sha256(string_test.first)) == string_test.second);
stringstream ss(string_test.first); stringstream ss(string_test.first);
assert(Crypto::to_hex_string(Crypto::sha256(ss)) == string_test.second); assert(Crypto::to_hex_string(Crypto::sha256(ss)) == string_test.second);
} }
for(auto& string_test: sha512_string_tests) { for(auto &string_test : sha512_string_tests) {
assert(Crypto::to_hex_string(Crypto::sha512(string_test.first)) == string_test.second); assert(Crypto::to_hex_string(Crypto::sha512(string_test.first)) == string_test.second);
stringstream ss(string_test.first); stringstream ss(string_test.first);
assert(Crypto::to_hex_string(Crypto::sha512(ss)) == string_test.second); assert(Crypto::to_hex_string(Crypto::sha512(ss)) == string_test.second);

View file

@ -1,5 +1,5 @@
#include "server_http.hpp"
#include "client_http.hpp" #include "client_http.hpp"
#include "server_http.hpp"
#include <cassert> #include <cassert>
@ -10,50 +10,54 @@ typedef SimpleWeb::Client<SimpleWeb::HTTP> HttpClient;
int main() { int main() {
HttpServer server; HttpServer server;
server.config.port=8080; server.config.port = 8080;
server.resource["^/string$"]["POST"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) { server.resource["^/string$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
auto content=request->content.string(); auto content = request->content.string();
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n" << content; *response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n"
<< content;
}; };
server.resource["^/string2$"]["POST"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) { server.resource["^/string2$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
response->write(request->content.string()); response->write(request->content.string());
}; };
server.resource["^/string3$"]["POST"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) { server.resource["^/string3$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
std::stringstream stream; std::stringstream stream;
stream << request->content.rdbuf(); stream << request->content.rdbuf();
response->write(stream); response->write(stream);
}; };
server.resource["^/string4$"]["POST"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) { server.resource["^/string4$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
response->write(SimpleWeb::StatusCode::client_error_forbidden, {{"Test1", "test2"}, {"tesT3", "test4"}}); response->write(SimpleWeb::StatusCode::client_error_forbidden, {{"Test1", "test2"}, {"tesT3", "test4"}});
}; };
server.resource["^/info$"]["GET"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) { server.resource["^/info$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
stringstream content_stream; stringstream content_stream;
content_stream << request->method << " " << request->path << " " << request->http_version << " "; content_stream << request->method << " " << request->path << " " << request->http_version << " ";
content_stream << request->header.find("test parameter")->second; content_stream << request->header.find("test parameter")->second;
content_stream.seekp(0, ios::end); content_stream.seekp(0, ios::end);
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << content_stream.tellp() << "\r\n\r\n" << content_stream.rdbuf(); *response << "HTTP/1.1 200 OK\r\nContent-Length: " << content_stream.tellp() << "\r\n\r\n"
<< content_stream.rdbuf();
}; };
server.resource["^/match/([0-9]+)$"]["GET"]=[&server](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) { server.resource["^/match/([0-9]+)$"]["GET"] = [&server](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
string number=request->path_match[1]; string number = request->path_match[1];
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n" << number; *response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n"
<< number;
}; };
server.resource["^/header$"]["GET"]=[](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) { server.resource["^/header$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
auto content=request->header.find("test1")->second+request->header.find("test2")->second; auto content = request->header.find("test1")->second + request->header.find("test2")->second;
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n" << content; *response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n"
<< content;
}; };
thread server_thread([&server](){ thread server_thread([&server]() {
//Start server //Start server
server.start(); server.start();
}); });
@ -64,67 +68,67 @@ int main() {
{ {
stringstream output; stringstream output;
auto r=client.request("POST", "/string", "A string"); auto r = client.request("POST", "/string", "A string");
assert(SimpleWeb::status_code(r->status_code)==SimpleWeb::StatusCode::success_ok); assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
output << r->content.rdbuf(); output << r->content.rdbuf();
assert(output.str()=="A string"); assert(output.str() == "A string");
} }
{ {
stringstream output; stringstream output;
auto r=client.request("POST", "/string", "A string"); auto r = client.request("POST", "/string", "A string");
assert(SimpleWeb::status_code(r->status_code)==SimpleWeb::StatusCode::success_ok); assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
assert(r->content.string()=="A string"); assert(r->content.string() == "A string");
} }
{ {
stringstream output; stringstream output;
auto r=client.request("POST", "/string2", "A string"); auto r = client.request("POST", "/string2", "A string");
assert(SimpleWeb::status_code(r->status_code)==SimpleWeb::StatusCode::success_ok); assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
output << r->content.rdbuf(); output << r->content.rdbuf();
assert(output.str()=="A string"); assert(output.str() == "A string");
} }
{ {
stringstream output; stringstream output;
auto r=client.request("POST", "/string3", "A string"); auto r = client.request("POST", "/string3", "A string");
assert(SimpleWeb::status_code(r->status_code)==SimpleWeb::StatusCode::success_ok); assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
output << r->content.rdbuf(); output << r->content.rdbuf();
assert(output.str()=="A string"); assert(output.str() == "A string");
} }
{ {
stringstream output; stringstream output;
auto r=client.request("POST", "/string4", "A string"); auto r = client.request("POST", "/string4", "A string");
assert(SimpleWeb::status_code(r->status_code)==SimpleWeb::StatusCode::client_error_forbidden); assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::client_error_forbidden);
assert(r->header.size()==3); assert(r->header.size() == 3);
assert(r->header.find("test1")->second=="test2"); assert(r->header.find("test1")->second == "test2");
assert(r->header.find("tEst3")->second=="test4"); assert(r->header.find("tEst3")->second == "test4");
assert(r->header.find("content-length")->second=="0"); assert(r->header.find("content-length")->second == "0");
output << r->content.rdbuf(); output << r->content.rdbuf();
assert(output.str()==""); assert(output.str() == "");
} }
{ {
stringstream output; stringstream output;
stringstream content("A string"); stringstream content("A string");
auto r=client.request("POST", "/string", content); auto r = client.request("POST", "/string", content);
output << r->content.rdbuf(); output << r->content.rdbuf();
assert(output.str()=="A string"); assert(output.str() == "A string");
} }
{ {
stringstream output; stringstream output;
auto r=client.request("GET", "/info", "", {{"Test Parameter", "test value"}}); auto r = client.request("GET", "/info", "", {{"Test Parameter", "test value"}});
output << r->content.rdbuf(); output << r->content.rdbuf();
assert(output.str()=="GET /info 1.1 test value"); assert(output.str() == "GET /info 1.1 test value");
} }
{ {
stringstream output; stringstream output;
auto r=client.request("GET", "/match/123"); auto r = client.request("GET", "/match/123");
output << r->content.rdbuf(); output << r->content.rdbuf();
assert(output.str()=="123"); assert(output.str() == "123");
} }
} }
{ {
@ -135,41 +139,41 @@ int main() {
// test performing the stream version of the request methods first // test performing the stream version of the request methods first
stringstream output; stringstream output;
stringstream content("A string"); stringstream content("A string");
auto r=client.request("POST", "/string", content); auto r = client.request("POST", "/string", content);
output << r->content.rdbuf(); output << r->content.rdbuf();
assert(output.str()=="A string"); assert(output.str() == "A string");
assert(client.connections->size()==1); assert(client.connections->size() == 1);
connection=client.connections->front().get(); connection = client.connections->front().get();
} }
{ {
stringstream output; stringstream output;
auto r=client.request("POST", "/string", "A string"); auto r = client.request("POST", "/string", "A string");
output << r->content.rdbuf(); output << r->content.rdbuf();
assert(output.str()=="A string"); assert(output.str() == "A string");
assert(client.connections->size()==1); assert(client.connections->size() == 1);
assert(connection==client.connections->front().get()); assert(connection == client.connections->front().get());
} }
{ {
stringstream output; stringstream output;
auto r=client.request("GET", "/header", "", {{"test1", "test"}, {"test2", "ing"}}); auto r = client.request("GET", "/header", "", {{"test1", "test"}, {"test2", "ing"}});
output << r->content.rdbuf(); output << r->content.rdbuf();
assert(output.str()=="testing"); assert(output.str() == "testing");
assert(client.connections->size()==1); assert(client.connections->size() == 1);
assert(connection==client.connections->front().get()); assert(connection == client.connections->front().get());
} }
} }
{ {
HttpClient client("localhost:8080"); HttpClient client("localhost:8080");
bool call=false; bool call = false;
client.request("GET", "/match/123", [&call](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) { client.request("GET", "/match/123", [&call](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
assert(!ec); assert(!ec);
stringstream output; stringstream output;
output << response->content.rdbuf(); output << response->content.rdbuf();
assert(output.str()=="123"); assert(output.str() == "123");
call=true; call = true;
}); });
client.io_service->run(); client.io_service->run();
assert(call); assert(call);
@ -177,37 +181,37 @@ int main() {
{ {
vector<int> calls(100); vector<int> calls(100);
vector<thread> threads; vector<thread> threads;
for(size_t c=0;c<100;++c) { for(size_t c = 0; c < 100; ++c) {
calls[c]=0; calls[c] = 0;
threads.emplace_back([c, &client, &calls] { threads.emplace_back([c, &client, &calls] {
client.request("GET", "/match/123", [c, &calls](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) { client.request("GET", "/match/123", [c, &calls](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
assert(!ec); assert(!ec);
stringstream output; stringstream output;
output << response->content.rdbuf(); output << response->content.rdbuf();
assert(output.str()=="123"); assert(output.str() == "123");
calls[c]=1; calls[c] = 1;
}); });
}); });
} }
for(auto &thread: threads) for(auto &thread : threads)
thread.join(); thread.join();
assert(client.connections->size()==100); assert(client.connections->size() == 100);
client.io_service->reset(); client.io_service->reset();
client.io_service->run(); client.io_service->run();
assert(client.connections->size()==1); assert(client.connections->size() == 1);
for(auto call: calls) for(auto call : calls)
assert(call); assert(call);
} }
} }
{ {
HttpClient client("localhost:8080"); HttpClient client("localhost:8080");
assert(client.connections->size()==0); assert(client.connections->size() == 0);
for(size_t c=0;c<5000;++c) { for(size_t c = 0; c < 5000; ++c) {
auto r1=client.request("POST", "/string", "A string"); auto r1 = client.request("POST", "/string", "A string");
assert(SimpleWeb::status_code(r1->status_code)==SimpleWeb::StatusCode::success_ok); assert(SimpleWeb::status_code(r1->status_code) == SimpleWeb::StatusCode::success_ok);
assert(r1->content.string()=="A string"); assert(r1->content.string() == "A string");
assert(client.connections->size()==1); assert(client.connections->size() == 1);
stringstream content("A string"); stringstream content("A string");
auto r2 = client.request("POST", "/string", content); auto r2 = client.request("POST", "/string", content);
@ -217,13 +221,13 @@ int main() {
} }
} }
for(size_t c=0;c<500;++c) { for(size_t c = 0; c < 500; ++c) {
{ {
HttpClient client("localhost:8080"); HttpClient client("localhost:8080");
auto r=client.request("POST", "/string", "A string"); auto r = client.request("POST", "/string", "A string");
assert(SimpleWeb::status_code(r->status_code)==SimpleWeb::StatusCode::success_ok); assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
assert(r->content.string()=="A string"); assert(r->content.string() == "A string");
assert(client.connections->size()==1); assert(client.connections->size() == 1);
} }
{ {

View file

@ -1,7 +1,7 @@
#include "server_http.hpp"
#include "client_http.hpp" #include "client_http.hpp"
#include <iostream> #include "server_http.hpp"
#include <cassert> #include <cassert>
#include <iostream>
using namespace std; using namespace std;
using namespace SimpleWeb; using namespace SimpleWeb;
@ -26,47 +26,47 @@ public:
assert(parse_request(request)); assert(parse_request(request));
assert(request->method=="GET"); assert(request->method == "GET");
assert(request->path=="/test/"); assert(request->path == "/test/");
assert(request->http_version=="1.1"); assert(request->http_version == "1.1");
assert(request->header.size()==4); assert(request->header.size() == 4);
auto header_it=request->header.find("TestHeader"); auto header_it = request->header.find("TestHeader");
assert(header_it!=request->header.end() && header_it->second=="test"); assert(header_it != request->header.end() && header_it->second == "test");
header_it=request->header.find("TestHeader2"); header_it = request->header.find("TestHeader2");
assert(header_it!=request->header.end() && header_it->second=="test2"); assert(header_it != request->header.end() && header_it->second == "test2");
header_it=request->header.find("testheader"); header_it = request->header.find("testheader");
assert(header_it!=request->header.end() && header_it->second=="test"); assert(header_it != request->header.end() && header_it->second == "test");
header_it=request->header.find("testheader2"); header_it = request->header.find("testheader2");
assert(header_it!=request->header.end() && header_it->second=="test2"); assert(header_it != request->header.end() && header_it->second == "test2");
auto range=request->header.equal_range("testheader3"); auto range = request->header.equal_range("testheader3");
auto first=range.first; auto first = range.first;
auto second=first; auto second = first;
++second; ++second;
assert(range.first!=request->header.end() && range.second!=request->header.end() && assert(range.first != request->header.end() && range.second != request->header.end() &&
((first->second=="test3a" && second->second=="test3b") || ((first->second == "test3a" && second->second == "test3b") ||
(first->second=="test3b" && second->second=="test3a"))); (first->second == "test3b" && second->second == "test3a")));
} }
}; };
class ClientTest : public ClientBase<HTTP> { class ClientTest : public ClientBase<HTTP> {
public: public:
ClientTest(const std::string& server_port_path) : ClientBase<HTTP>::ClientBase(server_port_path, 80) {} ClientTest(const std::string &server_port_path) : ClientBase<HTTP>::ClientBase(server_port_path, 80) {}
std::shared_ptr<Connection> create_connection() override { std::shared_ptr<Connection> create_connection() override {
return nullptr; return nullptr;
} }
void constructor_parse_test1() { void constructor_parse_test1() {
assert(host=="test.org"); assert(host == "test.org");
assert(port==8080); assert(port == 8080);
} }
void constructor_parse_test2() { void constructor_parse_test2() {
assert(host=="test.org"); assert(host == "test.org");
assert(port==80); assert(port == 80);
} }
void parse_response_header_test() { void parse_response_header_test() {
@ -82,27 +82,27 @@ public:
parse_response_header(response); parse_response_header(response);
assert(response->http_version=="1.1"); assert(response->http_version == "1.1");
assert(response->status_code=="200 OK"); assert(response->status_code == "200 OK");
assert(response->header.size()==4); assert(response->header.size() == 4);
auto header_it=response->header.find("TestHeader"); auto header_it = response->header.find("TestHeader");
assert(header_it!=response->header.end() && header_it->second=="test"); assert(header_it != response->header.end() && header_it->second == "test");
header_it=response->header.find("TestHeader2"); header_it = response->header.find("TestHeader2");
assert(header_it!=response->header.end() && header_it->second=="test2"); assert(header_it != response->header.end() && header_it->second == "test2");
header_it=response->header.find("testheader"); header_it = response->header.find("testheader");
assert(header_it!=response->header.end() && header_it->second=="test"); assert(header_it != response->header.end() && header_it->second == "test");
header_it=response->header.find("testheader2"); header_it = response->header.find("testheader2");
assert(header_it!=response->header.end() && header_it->second=="test2"); assert(header_it != response->header.end() && header_it->second == "test2");
auto range=response->header.equal_range("testheader3"); auto range = response->header.equal_range("testheader3");
auto first=range.first; auto first = range.first;
auto second=first; auto second = first;
++second; ++second;
assert(range.first!=response->header.end() && range.second!=response->header.end() && assert(range.first != response->header.end() && range.second != response->header.end() &&
((first->second=="test3a" && second->second=="test3b") || ((first->second == "test3a" && second->second == "test3b") ||
(first->second=="test3b" && second->second=="test3a"))); (first->second == "test3b" && second->second == "test3a")));
} }
}; };
@ -115,27 +115,27 @@ int main() {
assert(equal("tesT", "test")); assert(equal("tesT", "test"));
assert(!equal("test", "tset")); assert(!equal("test", "tset"));
CaseInsensitiveHash hash; CaseInsensitiveHash hash;
assert(hash("Test")==hash("tesT")); assert(hash("Test") == hash("tesT"));
assert(hash("tesT")==hash("test")); assert(hash("tesT") == hash("test"));
assert(hash("test")!=hash("tset")); assert(hash("test") != hash("tset"));
auto percent_decoded="testing æøå !#$&'()*+,/:;=?@[]"; auto percent_decoded = "testing æøå !#$&'()*+,/:;=?@[]";
auto percent_encoded="testing+æøå+%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D"; auto percent_encoded = "testing+æøå+%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D";
assert(Percent::encode(percent_decoded)==percent_encoded); assert(Percent::encode(percent_decoded) == percent_encoded);
assert(Percent::decode(percent_encoded)==percent_decoded); assert(Percent::decode(percent_encoded) == percent_decoded);
assert(Percent::decode(Percent::encode(percent_decoded))==percent_decoded); assert(Percent::decode(Percent::encode(percent_decoded)) == percent_decoded);
SimpleWeb::CaseInsensitiveMultimap fields={{"test1", "æøå"}, {"test2", "!#$&'()*+,/:;=?@[]"}}; SimpleWeb::CaseInsensitiveMultimap fields = {{"test1", "æøå"}, {"test2", "!#$&'()*+,/:;=?@[]"}};
auto query_string1="test1=æøå&test2=%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D"; auto query_string1 = "test1=æøå&test2=%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D";
auto query_string2="test2=%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D&test1=æøå"; auto query_string2 = "test2=%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D&test1=æøå";
auto query_string_result=QueryString::create(fields); auto query_string_result = QueryString::create(fields);
assert(query_string_result==query_string1 || query_string_result==query_string2); assert(query_string_result == query_string1 || query_string_result == query_string2);
auto fields_result1=QueryString::parse(query_string1); auto fields_result1 = QueryString::parse(query_string1);
auto fields_result2=QueryString::parse(query_string2); auto fields_result2 = QueryString::parse(query_string2);
assert(fields_result1==fields_result2 && fields_result1==fields); assert(fields_result1 == fields_result2 && fields_result1 == fields);
ServerTest serverTest; ServerTest serverTest;
serverTest.io_service=std::make_shared<asio::io_service>(); serverTest.io_service = std::make_shared<asio::io_service>();
serverTest.parse_request_test(); serverTest.parse_request_test();

View file

@ -20,21 +20,21 @@
namespace SimpleWeb { namespace SimpleWeb {
#ifndef CASE_INSENSITIVE_EQUAL_AND_HASH #ifndef CASE_INSENSITIVE_EQUAL_AND_HASH
#define CASE_INSENSITIVE_EQUAL_AND_HASH #define CASE_INSENSITIVE_EQUAL_AND_HASH
inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) { inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) {
return str1.size() == str2.size() && return str1.size() == str2.size() &&
std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) { std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) {
return tolower(a) == tolower(b); return tolower(a) == tolower(b);
}); });
} }
class CaseInsensitiveEqual { class CaseInsensitiveEqual {
public: public:
bool operator()(const std::string &str1, const std::string &str2) const { bool operator()(const std::string &str1, const std::string &str2) const {
return case_insensitive_equal(str1, str2); return case_insensitive_equal(str1, str2);
} }
}; };
// Based on https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226 // Based on https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226
class CaseInsensitiveHash { class CaseInsensitiveHash {
public: public:
size_t operator()(const std::string &str) const { size_t operator()(const std::string &str) const {
size_t h = 0; size_t h = 0;
std::hash<int> hash; std::hash<int> hash;
@ -42,14 +42,14 @@ public:
h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2); h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2);
return h; return h;
} }
}; };
#endif #endif
typedef std::unordered_multimap<std::string, std::string, CaseInsensitiveHash, CaseInsensitiveEqual> CaseInsensitiveMultimap; typedef std::unordered_multimap<std::string, std::string, CaseInsensitiveHash, CaseInsensitiveEqual> CaseInsensitiveMultimap;
/// Percent encoding and decoding /// Percent encoding and decoding
class Percent { class Percent {
public: public:
/// Returns percent-encoded string /// Returns percent-encoded string
static std::string encode(const std::string &value) { static std::string encode(const std::string &value) {
static auto hex_chars = "0123456789ABCDEF"; static auto hex_chars = "0123456789ABCDEF";
@ -90,11 +90,11 @@ public:
return result; return result;
} }
}; };
/// Query string creation and parsing /// Query string creation and parsing
class QueryString { class QueryString {
public: public:
/// Returns query string created from given field names and values /// Returns query string created from given field names and values
static std::string create(const CaseInsensitiveMultimap &fields) { static std::string create(const CaseInsensitiveMultimap &fields) {
std::string result; std::string result;
@ -144,12 +144,12 @@ public:
return result; return result;
} }
}; };
/// Returns query keys with percent-decoded values. /// Returns query keys with percent-decoded values.
DEPRECATED inline CaseInsensitiveMultimap parse_query_string(const std::string &query_string) { DEPRECATED inline CaseInsensitiveMultimap parse_query_string(const std::string &query_string) {
return QueryString::parse(query_string); return QueryString::parse(query_string);
} }
} // namespace SimpleWeb } // namespace SimpleWeb
#endif // SIMPLE_WEB_SERVER_UTILITY_HPP #endif // SIMPLE_WEB_SERVER_UTILITY_HPP