Repository: teslamotors/Simple-Web-Server
Branch: master
Commit: e50d2fc63a9a
Files: 21
Total size: 114.4 KB
Directory structure:
gitextract_5e6ws4b2/
├── .clang-format
├── .gitignore
├── .travis.yml
├── CMakeLists.txt
├── LICENSE
├── README.md
├── client_http.hpp
├── client_https.hpp
├── crypto.hpp
├── http_examples.cpp
├── https_examples.cpp
├── server_http.hpp
├── server_https.hpp
├── status_code.hpp
├── tests/
│ ├── CMakeLists.txt
│ ├── crypto_test.cpp
│ ├── io_test.cpp
│ └── parse_test.cpp
├── utility.hpp
└── web/
├── index.html
└── test.html
================================================
FILE CONTENTS
================================================
================================================
FILE: .clang-format
================================================
IndentWidth: 2
AccessModifierOffset: -2
UseTab: Never
ColumnLimit: 0
MaxEmptyLinesToKeep: 2
SpaceBeforeParens: Never
BreakBeforeBraces: Custom
BraceWrapping: {BeforeElse: true, BeforeCatch: true}
NamespaceIndentation: All
================================================
FILE: .gitignore
================================================
# https://github.com/github/gitignore/blob/master/CMake.gitignore
CMakeCache.txt
CMakeFiles
CMakeScripts
Makefile
cmake_install.cmake
install_manifest.txt
*.cmake
#Additions to https://github.com/github/gitignore/blob/master/CMake.gitignore
Testing
compile_commands.json
# executables
http_examples
https_examples
io_test
parse_test
crypto_test
================================================
FILE: .travis.yml
================================================
sudo: required
services:
- docker
script:
- sudo docker run -it -v "$PWD:/repository" debian:testing sh -c "
apt-get update &&
apt-get install --yes cmake make g\+\+ clang perl libssl-dev libboost-thread-dev libboost-regex-dev libboost-date-time-dev libboost-filesystem-dev libasio-dev &&
cd /repository && mkdir build && cd build &&
scan-build cmake -DCMAKE_CXX_FLAGS=-Werror .. &&
scan-build --status-bugs make &&
rm -r * &&
CXX=clang++ cmake -DCMAKE_CXX_FLAGS=-Werror .. &&
make &&
rm -r * &&
CXX=g++ cmake -DCMAKE_CXX_FLAGS=-Werror .. &&
make &&
CTEST_OUTPUT_ON_FAILURE=1 make test &&
rm -r * &&
CXX=g++ cmake -DCMAKE_CXX_FLAGS=\"-Werror -O3 -DUSE_STANDALONE_ASIO\" .. &&
make &&
CTEST_OUTPUT_ON_FAILURE=1 make test
"
================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required (VERSION 2.8.8)
project (Simple-Web-Server)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra")
include_directories(.)
find_package(Threads REQUIRED)
set(BOOST_COMPONENTS system thread filesystem date_time)
# Late 2017 TODO: remove the following checks and always use std::regex
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9)
set(BOOST_COMPONENTS ${BOOST_COMPONENTS} regex)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUSE_BOOST_REGEX")
endif()
endif()
find_package(Boost 1.53.0 COMPONENTS ${BOOST_COMPONENTS} REQUIRED)
include_directories(SYSTEM ${Boost_INCLUDE_DIR})
if(APPLE)
set(OPENSSL_ROOT_DIR "/usr/local/opt/openssl")
endif()
add_executable(http_examples http_examples.cpp)
target_link_libraries(http_examples ${Boost_LIBRARIES})
target_link_libraries(http_examples ${CMAKE_THREAD_LIBS_INIT})
#TODO: add requirement for version 1.0.1g (can it be done in one line?)
find_package(OpenSSL)
if(OPENSSL_FOUND)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAVE_OPENSSL")
target_link_libraries(http_examples ${OPENSSL_LIBRARIES})
include_directories(SYSTEM ${OPENSSL_INCLUDE_DIR})
add_executable(https_examples https_examples.cpp)
target_link_libraries(https_examples ${Boost_LIBRARIES})
target_link_libraries(https_examples ${OPENSSL_LIBRARIES})
target_link_libraries(https_examples ${CMAKE_THREAD_LIBS_INIT})
endif()
if(MSYS) #TODO: Is MSYS true when MSVC is true?
target_link_libraries(http_examples ws2_32 wsock32)
if(OPENSSL_FOUND)
target_link_libraries(https_examples ws2_32 wsock32)
endif()
endif()
enable_testing()
add_subdirectory(tests)
install(FILES server_http.hpp client_http.hpp server_https.hpp client_https.hpp crypto.hpp utility.hpp status_code.hpp DESTINATION include/simple-web-server)
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2014-2016 Ole Christian Eidheim
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
Simple-Web-Server [](https://travis-ci.org/eidheim/Simple-Web-Server)
=================
A very simple, fast, multithreaded, platform independent HTTP and HTTPS server and client library implemented using C++11 and Boost.Asio. Created to be an easy way to make REST resources available from C++ applications.
See https://github.com/eidheim/Simple-WebSocket-Server for an easy way to make WebSocket/WebSocket Secure endpoints in C++. Also, feel free to check out the new C++ IDE supporting C++11/14/17: https://github.com/cppit/jucipp.
### Features
* Asynchronous request handling
* Thread pool if needed
* Platform independent
* HTTPS support
* HTTP persistent connection (for HTTP/1.1)
* Client supports chunked transfer encoding
* Timeouts, if any of Server::timeout_request and Server::timeout_content are >0 (default: Server::timeout_request=5 seconds, and Server::timeout_content=300 seconds)
* Simple way to add REST resources using regex for path, and anonymous functions
### Usage
See http_examples.cpp or https_examples.cpp for example usage.
See particularly the JSON-POST (using Boost.PropertyTree) and the GET /match/[number] examples, which are most relevant.
### Dependencies
* Boost C++ libraries
* For HTTPS: OpenSSL libraries
### Compile and run
Compile with a C++11 compliant compiler:
```sh
mkdir build
cd build
cmake ..
make
cd ..
```
#### HTTP
Run the server and client examples: `./build/http_examples`
Direct your favorite browser to for instance http://localhost:8080/
#### HTTPS
Before running the server, an RSA private key (server.key) and an SSL certificate (server.crt) must be created. Follow, for instance, the instructions given here (for a self-signed certificate): http://www.akadia.com/services/ssh_test_certificate.html
Run the server and client examples: `./build/https_examples`
Direct your favorite browser to for instance https://localhost:8080/
================================================
FILE: client_http.hpp
================================================
#ifndef CLIENT_HTTP_HPP
#define CLIENT_HTTP_HPP
#include "utility.hpp"
#include <mutex>
#include <random>
#include <vector>
#ifdef USE_STANDALONE_ASIO
#include <asio.hpp>
namespace SimpleWeb {
using error_code = std::error_code;
using errc = std::errc;
using system_error = std::system_error;
namespace make_error_code = std;
using string_view = const std::string &; // TODO c++17: use std::string_view
} // namespace SimpleWeb
#else
#include <boost/asio.hpp>
#include <boost/utility/string_ref.hpp>
namespace SimpleWeb {
namespace asio = boost::asio;
using error_code = boost::system::error_code;
namespace errc = boost::system::errc;
using system_error = boost::system::system_error;
namespace make_error_code = boost::system::errc;
using string_view = boost::string_ref;
} // namespace SimpleWeb
#endif
namespace SimpleWeb {
template <class socket_type>
class Client;
template <class socket_type>
class ClientBase {
public:
class Content : public std::istream {
friend class ClientBase<socket_type>;
public:
size_t size() {
return streambuf.size();
}
/// Convenience function to return std::string. Note that the stream buffer is emptied when this functions is used.
std::string string() {
std::stringstream ss;
ss << rdbuf();
return ss.str();
}
private:
asio::streambuf &streambuf;
Content(asio::streambuf &streambuf) : std::istream(&streambuf), streambuf(streambuf) {}
};
class Response {
friend class ClientBase<socket_type>;
friend class Client<socket_type>;
public:
std::string http_version, status_code;
Content content;
CaseInsensitiveMultimap header;
private:
asio::streambuf content_buffer;
Response() : content(content_buffer) {}
};
class Config {
friend class ClientBase<socket_type>;
private:
Config() {}
public:
/// Set timeout on requests in seconds. Default value: 0 (no timeout).
size_t timeout = 0;
/// Set connect timeout in seconds. Default value: 0 (Config::timeout is then used instead).
size_t timeout_connect = 0;
/// Set proxy server (server:port)
std::string proxy_server;
};
protected:
class Connection {
public:
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)) {
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)));
else {
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)));
}
}
std::string host;
unsigned short port;
Config config;
std::unique_ptr<socket_type> socket;
bool in_use = false;
bool reconnecting = false;
std::unique_ptr<asio::ip::tcp::resolver::query> query;
};
class Session {
public:
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()) {}
std::shared_ptr<asio::io_service> io_service;
std::shared_ptr<Connection> connection;
std::unique_ptr<asio::streambuf> request_buffer;
std::shared_ptr<Response> response;
std::function<void(const error_code &)> callback;
};
public:
/// Set before calling request
Config config;
/// If you have your own asio::io_service, store its pointer here before calling request().
/// When using asynchronous requests, running the io_service is up to the programmer.
std::shared_ptr<asio::io_service> io_service;
virtual ~ClientBase() {}
/// 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.
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;
request(method, path, content, header, [&response](std::shared_ptr<Response> response_, const error_code &ec) {
response = response_;
if(ec)
throw system_error(ec);
});
io_service->reset();
io_service->run();
return response;
}
/// 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.
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;
request(method, path, content, header, [&response](std::shared_ptr<Response> response_, const error_code &ec) {
response = response_;
if(ec)
throw system_error(ec);
});
io_service->reset();
io_service->run();
return response;
}
/// 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,
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 connection = session->connection;
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 connections = this->connections;
auto connections_mutex = this->connections_mutex;
session->callback = [connection, response, request_callback, connections, connections_mutex](const error_code &ec) {
{
std::lock_guard<std::mutex> lock(*connections_mutex);
connection->in_use = false;
// Remove unused connections, but keep one open for HTTP persistent connection:
size_t unused_connections = 0;
for(auto it = connections->begin(); it != connections->end();) {
if((*it)->in_use)
++it;
else {
++unused_connections;
if(unused_connections > 1)
it = connections->erase(it);
else
++it;
}
}
}
if(*request_callback)
(*request_callback)(response, ec);
};
std::ostream write_stream(session->request_buffer.get());
if(content.size() > 0)
write_stream << "Content-Length: " << content.size() << "\r\n";
write_stream << "\r\n"
<< content;
Client<socket_type>::connect(session);
}
/// Asynchronous request where setting and/or running Client's io_service is required.
void request(const std::string &method, const std::string &path, string_view content,
std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {
request(method, path, content, CaseInsensitiveMultimap(), std::move(request_callback));
}
/// Asynchronous request where setting and/or running Client's io_service is required.
void request(const std::string &method, const std::string &path,
std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {
request(method, path, std::string(), CaseInsensitiveMultimap(), std::move(request_callback));
}
/// Asynchronous request where setting and/or running Client's io_service is required.
void request(const std::string &method, std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {
request(method, std::string("/"), std::string(), CaseInsensitiveMultimap(), std::move(request_callback));
}
/// Asynchronous request where setting and/or running Client's io_service is required.
void request(const std::string &method, const std::string &path, std::istream &content, const CaseInsensitiveMultimap &header,
std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback_) {
auto session = std::make_shared<Session>(io_service, get_connection(), create_request_header(method, path, header));
auto connection = session->connection;
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 connections = this->connections;
auto connections_mutex = this->connections_mutex;
session->callback = [connection, response, request_callback, connections, connections_mutex](const error_code &ec) {
{
std::lock_guard<std::mutex> lock(*connections_mutex);
connection->in_use = false;
// Remove unused connections, but keep one open for HTTP persistent connection:
size_t unused_connections = 0;
for(auto it = connections->begin(); it != connections->end();) {
if((*it)->in_use)
++it;
else {
++unused_connections;
if(unused_connections > 1)
it = connections->erase(it);
else
++it;
}
}
}
if(*request_callback)
(*request_callback)(response, ec);
};
content.seekg(0, std::ios::end);
auto content_length = content.tellg();
content.seekg(0, std::ios::beg);
std::ostream write_stream(session->request_buffer.get());
if(content_length > 0)
write_stream << "Content-Length: " << content_length << "\r\n";
write_stream << "\r\n";
if(content_length > 0)
write_stream << content.rdbuf();
Client<socket_type>::connect(session);
}
/// Asynchronous request where setting and/or running Client's io_service is required.
void request(const std::string &method, const std::string &path, std::istream &content,
std::function<void(std::shared_ptr<Response>, const error_code &)> &&request_callback) {
request(method, path, content, CaseInsensitiveMultimap(), std::move(request_callback));
}
protected:
std::string host;
unsigned short port;
std::shared_ptr<std::vector<std::shared_ptr<Connection>>> connections;
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()) {
auto parsed_host_port = parse_host_port(host_port, default_port);
host = parsed_host_port.first;
port = parsed_host_port.second;
}
std::shared_ptr<Connection> get_connection() {
std::shared_ptr<Connection> connection;
std::lock_guard<std::mutex> lock(*connections_mutex);
for(auto it = connections->begin(); it != connections->end(); ++it) {
if(!(*it)->in_use && !connection) {
connection = *it;
break;
}
}
if(!connection) {
connection = create_connection();
connections->emplace_back(connection);
}
connection->reconnecting = false;
connection->in_use = true;
return connection;
}
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 {
auto corrected_path = path;
if(corrected_path == "")
corrected_path = "/";
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;
std::unique_ptr<asio::streambuf> request_buffer(new asio::streambuf());
std::ostream write_stream(request_buffer.get());
write_stream << method << " " << corrected_path << " HTTP/1.1\r\n";
write_stream << "Host: " << host << "\r\n";
for(auto &h : header)
write_stream << h.first << ": " << h.second << "\r\n";
return request_buffer;
}
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;
size_t host_end = host_port.find(':');
if(host_end == std::string::npos) {
parsed_host_port.first = host_port;
parsed_host_port.second = default_port;
}
else {
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)));
}
return parsed_host_port;
}
static std::shared_ptr<asio::deadline_timer> get_timeout_timer(const std::shared_ptr<Session> &session, size_t timeout = 0) {
if(timeout == 0)
timeout = session->connection->config.timeout;
if(timeout == 0)
return nullptr;
auto timer = std::make_shared<asio::deadline_timer>(*session->io_service);
timer->expires_from_now(boost::posix_time::seconds(timeout));
timer->async_wait([session](const error_code &ec) {
if(!ec)
close(session);
});
return timer;
}
static void parse_response_header(const std::shared_ptr<Response> &response) {
std::string line;
getline(response->content, line);
size_t version_end = line.find(' ');
if(version_end != std::string::npos) {
if(5 < line.size())
response->http_version = line.substr(5, version_end - 5);
if((version_end + 1) < line.size())
response->status_code = line.substr(version_end + 1, line.size() - (version_end + 1) - 1);
getline(response->content, line);
size_t param_end;
while((param_end = line.find(':')) != std::string::npos) {
size_t value_start = param_end + 1;
if((value_start) < line.size()) {
if(line[value_start] == ' ')
value_start++;
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)));
}
getline(response->content, line);
}
}
}
static void write(const std::shared_ptr<Session> &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*/) {
if(timer)
timer->cancel();
if(!ec)
read(session);
else {
close(session);
session->callback(ec);
}
});
}
static void read(const std::shared_ptr<Session> &session) {
auto timer = get_timeout_timer(session);
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) {
if(timer)
timer->cancel();
if(!ec) {
session->connection->reconnecting = false;
size_t num_additional_bytes = session->response->content_buffer.size() - bytes_transferred;
parse_response_header(session->response);
auto header_it = session->response->header.find("Content-Length");
if(header_it != session->response->header.end()) {
auto content_length = stoull(header_it->second);
if(content_length > num_additional_bytes) {
auto timer = get_timeout_timer(session);
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*/) {
if(timer)
timer->cancel();
if(!ec)
session->callback(ec);
else {
close(session);
session->callback(ec);
}
});
}
else
session->callback(ec);
}
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>();
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")) {
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*/) {
if(timer)
timer->cancel();
if(!ec)
session->callback(ec);
else {
close(session);
if(ec == asio::error::eof) {
error_code ec;
session->callback(ec);
}
else
session->callback(ec);
}
});
}
else
session->callback(ec);
}
else {
if(!session->connection->reconnecting) {
session->connection->reconnecting = true;
close(session);
Client<socket_type>::connect(session);
}
else {
close(session);
session->callback(ec);
}
}
});
}
static void read_chunked(const std::shared_ptr<Session> &session, const std::shared_ptr<asio::streambuf> &tmp_streambuf) {
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) {
if(timer)
timer->cancel();
if(!ec) {
std::string line;
getline(session->response->content, line);
bytes_transferred -= line.size() + 1;
line.pop_back();
std::streamsize length = stol(line, 0, 16);
auto num_additional_bytes = static_cast<std::streamsize>(session->response->content_buffer.size() - bytes_transferred);
auto post_process = [session, tmp_streambuf, length] {
std::ostream tmp_stream(tmp_streambuf.get());
if(length > 0) {
std::vector<char> buffer(static_cast<size_t>(length));
session->response->content.read(&buffer[0], length);
tmp_stream.write(&buffer[0], length);
}
//Remove "\r\n"
session->response->content.get();
session->response->content.get();
if(length > 0)
read_chunked(session, tmp_streambuf);
else {
std::ostream response_stream(&session->response->content_buffer);
response_stream << tmp_stream.rdbuf();
error_code ec;
session->callback(ec);
}
};
if((2 + length) > num_additional_bytes) {
auto timer = get_timeout_timer(session);
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*/) {
if(timer)
timer->cancel();
if(!ec)
post_process();
else {
close(session);
session->callback(ec);
}
});
}
else
post_process();
}
else {
close(session);
session->callback(ec);
}
});
}
static void close(const std::shared_ptr<Session> &session) {
error_code ec;
session->connection->socket->lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ec);
session->connection->socket->lowest_layer().close(ec);
}
};
template <class socket_type>
class Client : public ClientBase<socket_type> {};
typedef asio::ip::tcp::socket HTTP;
template <>
class Client<HTTP> : public ClientBase<HTTP> {
public:
friend ClientBase<HTTP>;
Client(const std::string &server_port_path) : ClientBase<HTTP>::ClientBase(server_port_path, 80) {}
protected:
std::shared_ptr<Connection> create_connection() override {
return std::make_shared<Connection>(host, port, config, std::unique_ptr<HTTP>(new HTTP(*io_service)));
}
static void connect(const std::shared_ptr<Session> &session) {
if(!session->connection->socket->lowest_layer().is_open()) {
auto resolver = std::make_shared<asio::ip::tcp::resolver>(*session->io_service);
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) {
if(timer)
timer->cancel();
if(!ec) {
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*/) {
if(timer)
timer->cancel();
if(!ec) {
asio::ip::tcp::no_delay option(true);
session->connection->socket->set_option(option);
write(session);
}
else {
close(session);
session->callback(ec);
}
});
}
else {
close(session);
session->callback(ec);
}
});
}
else
write(session);
}
};
} // namespace SimpleWeb
#endif /* CLIENT_HTTP_HPP */
================================================
FILE: client_https.hpp
================================================
#ifndef CLIENT_HTTPS_HPP
#define CLIENT_HTTPS_HPP
#include "client_http.hpp"
#ifdef USE_STANDALONE_ASIO
#include <asio/ssl.hpp>
#else
#include <boost/asio/ssl.hpp>
#endif
namespace SimpleWeb {
typedef asio::ssl::stream<asio::ip::tcp::socket> HTTPS;
template <>
class Client<HTTPS> : public ClientBase<HTTPS> {
public:
friend ClientBase<HTTPS>;
Client(const std::string &server_port_path, bool verify_certificate = true, const std::string &cert_file = std::string(),
const std::string &private_key_file = std::string(), const std::string &verify_file = std::string())
: ClientBase<HTTPS>::ClientBase(server_port_path, 443), context(asio::ssl::context::tlsv12) {
if(cert_file.size() > 0 && private_key_file.size() > 0) {
context.use_certificate_chain_file(cert_file);
context.use_private_key_file(private_key_file, asio::ssl::context::pem);
}
if(verify_certificate)
context.set_verify_callback(asio::ssl::rfc2818_verification(host));
if(verify_file.size() > 0)
context.load_verify_file(verify_file);
else
context.set_default_verify_paths();
if(verify_file.size() > 0 || verify_certificate)
context.set_verify_mode(asio::ssl::verify_peer);
else
context.set_verify_mode(asio::ssl::verify_none);
}
protected:
asio::ssl::context context;
std::shared_ptr<Connection> create_connection() override {
return std::make_shared<Connection>(host, port, config, std::unique_ptr<HTTPS>(new HTTPS(*io_service, context)));
}
static void connect(const std::shared_ptr<Session> &session) {
if(!session->connection->socket->lowest_layer().is_open()) {
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) {
if(!ec) {
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*/) {
if(timer)
timer->cancel();
if(!ec) {
asio::ip::tcp::no_delay option(true);
session->connection->socket->lowest_layer().set_option(option);
if(!session->connection->config.proxy_server.empty()) {
auto write_buffer = std::make_shared<asio::streambuf>();
std::ostream write_stream(write_buffer.get());
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";
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*/) {
if(timer)
timer->cancel();
if(!ec) {
std::shared_ptr<Response> response(new Response());
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*/) {
if(timer)
timer->cancel();
if(!ec) {
parse_response_header(response);
if(response->status_code.empty() || response->status_code.compare(0, 3, "200") != 0) {
close(session);
session->callback(make_error_code::make_error_code(errc::permission_denied));
}
else
handshake(session);
}
else {
close(session);
session->callback(ec);
}
});
}
else {
close(session);
session->callback(ec);
}
});
}
else
handshake(session);
}
else {
close(session);
session->callback(ec);
}
});
}
else {
close(session);
session->callback(ec);
}
});
}
else
write(session);
}
static void handshake(const std::shared_ptr<Session> &session) {
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) {
if(timer)
timer->cancel();
if(!ec)
write(session);
else {
close(session);
session->callback(ec);
}
});
}
};
} // namespace SimpleWeb
#endif /* CLIENT_HTTPS_HPP */
================================================
FILE: crypto.hpp
================================================
#ifndef SIMPLE_WEB_CRYPTO_HPP
#define SIMPLE_WEB_CRYPTO_HPP
#include <cmath>
#include <iomanip>
#include <istream>
#include <sstream>
#include <string>
#include <vector>
//Moving these to a seperate namespace for minimal global namespace cluttering does not work with clang++
#include <openssl/buffer.h>
#include <openssl/evp.h>
#include <openssl/md5.h>
#include <openssl/sha.h>
namespace SimpleWeb {
//TODO 2017: remove workaround for MSVS 2012
#if _MSC_VER == 1700 //MSVS 2012 has no definition for round()
inline double round(double x) { //custom definition of round() for positive numbers
return floor(x + 0.5);
}
#endif
class Crypto {
const static size_t buffer_size = 131072;
public:
class Base64 {
public:
static std::string encode(const std::string &ascii) {
std::string base64;
BIO *bio, *b64;
BUF_MEM *bptr = BUF_MEM_new();
b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bio = BIO_new(BIO_s_mem());
BIO_push(b64, bio);
BIO_set_mem_buf(b64, bptr, BIO_CLOSE);
//Write directly to base64-buffer to avoid copy
int base64_length = static_cast<int>(round(4 * ceil((double)ascii.size() / 3.0)));
base64.resize(base64_length);
bptr->length = 0;
bptr->max = base64_length + 1;
bptr->data = (char *)&base64[0];
BIO_write(b64, &ascii[0], static_cast<int>(ascii.size()));
BIO_flush(b64);
//To keep &base64[0] through BIO_free_all(b64)
bptr->length = 0;
bptr->max = 0;
bptr->data = nullptr;
BIO_free_all(b64);
return base64;
}
static std::string decode(const std::string &base64) {
std::string ascii;
//Resize ascii, however, the size is a up to two bytes too large.
ascii.resize((6 * base64.size()) / 8);
BIO *b64, *bio;
b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bio = BIO_new_mem_buf((char *)&base64[0], static_cast<int>(base64.size()));
bio = BIO_push(b64, bio);
int decoded_length = BIO_read(bio, &ascii[0], static_cast<int>(ascii.size()));
ascii.resize(decoded_length);
BIO_free_all(b64);
return ascii;
}
};
/// Return hex string from bytes in input string.
static std::string to_hex_string(const std::string &input) {
std::stringstream hex_stream;
hex_stream << std::hex << std::internal << std::setfill('0');
for(auto &byte : input)
hex_stream << std::setw(2) << static_cast<int>(static_cast<unsigned char>(byte));
return hex_stream.str();
}
static std::string md5(const std::string &input, size_t iterations = 1) {
std::string hash;
hash.resize(128 / 8);
MD5(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));
for(size_t c = 1; c < iterations; ++c)
MD5(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
return hash;
}
static std::string md5(std::istream &stream, size_t iterations = 1) {
MD5_CTX context;
MD5_Init(&context);
std::streamsize read_length;
std::vector<char> buffer(buffer_size);
while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0)
MD5_Update(&context, buffer.data(), read_length);
std::string hash;
hash.resize(128 / 8);
MD5_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);
for(size_t c = 1; c < iterations; ++c)
MD5(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
return hash;
}
static std::string sha1(const std::string &input, size_t iterations = 1) {
std::string hash;
hash.resize(160 / 8);
SHA1(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));
for(size_t c = 1; c < iterations; ++c)
SHA1(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
return hash;
}
static std::string sha1(std::istream &stream, size_t iterations = 1) {
SHA_CTX context;
SHA1_Init(&context);
std::streamsize read_length;
std::vector<char> buffer(buffer_size);
while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0)
SHA1_Update(&context, buffer.data(), read_length);
std::string hash;
hash.resize(160 / 8);
SHA1_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);
for(size_t c = 1; c < iterations; ++c)
SHA1(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
return hash;
}
static std::string sha256(const std::string &input, size_t iterations = 1) {
std::string hash;
hash.resize(256 / 8);
SHA256(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));
for(size_t c = 1; c < iterations; ++c)
SHA256(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
return hash;
}
static std::string sha256(std::istream &stream, size_t iterations = 1) {
SHA256_CTX context;
SHA256_Init(&context);
std::streamsize read_length;
std::vector<char> buffer(buffer_size);
while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0)
SHA256_Update(&context, buffer.data(), read_length);
std::string hash;
hash.resize(256 / 8);
SHA256_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);
for(size_t c = 1; c < iterations; ++c)
SHA256(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
return hash;
}
static std::string sha512(const std::string &input, size_t iterations = 1) {
std::string hash;
hash.resize(512 / 8);
SHA512(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));
for(size_t c = 1; c < iterations; ++c)
SHA512(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
return hash;
}
static std::string sha512(std::istream &stream, size_t iterations = 1) {
SHA512_CTX context;
SHA512_Init(&context);
std::streamsize read_length;
std::vector<char> buffer(buffer_size);
while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0)
SHA512_Update(&context, buffer.data(), read_length);
std::string hash;
hash.resize(512 / 8);
SHA512_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);
for(size_t c = 1; c < iterations; ++c)
SHA512(reinterpret_cast<const unsigned char *>(&hash[0]), hash.size(), reinterpret_cast<unsigned char *>(&hash[0]));
return hash;
}
/// key_size is number of bytes of the returned key.
static std::string pbkdf2(const std::string &password, const std::string &salt, int iterations, int key_size) {
std::string key;
key.resize(key_size);
PKCS5_PBKDF2_HMAC_SHA1(password.c_str(), password.size(),
reinterpret_cast<const unsigned char *>(salt.c_str()), salt.size(), iterations,
key_size, reinterpret_cast<unsigned char *>(&key[0]));
return key;
}
};
}
#endif /* SIMPLE_WEB_CRYPTO_HPP */
================================================
FILE: http_examples.cpp
================================================
#include "client_http.hpp"
#include "server_http.hpp"
//Added for the json-example
#define BOOST_SPIRIT_THREADSAFE
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
//Added for the default_resource example
#include <algorithm>
#include <boost/filesystem.hpp>
#include <fstream>
#include <vector>
#ifdef HAVE_OPENSSL
#include "crypto.hpp"
#endif
using namespace std;
//Added for the json-example:
using namespace boost::property_tree;
typedef SimpleWeb::Server<SimpleWeb::HTTP> HttpServer;
typedef SimpleWeb::Client<SimpleWeb::HTTP> HttpClient;
//Added for the default_resource example
void default_resource_send(const HttpServer &server, const shared_ptr<HttpServer::Response> &response, const shared_ptr<ifstream> &ifs);
int main() {
//HTTP-server at port 8080 using 1 thread
//Unless you do more heavy non-threaded processing in the resources,
//1 thread is usually faster than several threads
HttpServer server;
server.config.port = 8080;
//Add resources using path-regex and method-string, and an anonymous function
//POST-example for the path /string, responds the posted string
server.resource["^/string$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
//Retrieve string:
auto content = request->content.string();
//request->content.string() is a convenience function for:
//stringstream ss;
//ss << request->content.rdbuf();
//auto content=ss.str();
*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:
// response->write(content);
};
//POST-example for the path /json, responds firstName+" "+lastName from the posted json
//Responds with an appropriate error message if the posted json is not valid, or if firstName or lastName is missing
//Example posted json:
//{
// "firstName": "John",
// "lastName": "Smith",
// "age": 25
//}
server.resource["^/json$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
try {
ptree pt;
read_json(request->content, pt);
auto name = pt.get<string>("firstName") + " " + pt.get<string>("lastName");
*response << "HTTP/1.1 200 OK\r\n"
<< "Content-Type: application/json\r\n"
<< "Content-Length: " << name.length() << "\r\n\r\n"
<< name;
}
catch(const exception &e) {
*response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n"
<< e.what();
}
// Alternatively, using a convenience function:
// try {
// ptree pt;
// read_json(request->content, pt);
// auto name=pt.get<string>("firstName")+" "+pt.get<string>("lastName");
// response->write(name, {{"Content-Type", "application/json"}});
// }
// catch(const exception &e) {
// response->write(SimpleWeb::StatusCode::client_error_bad_request, e.what());
// }
};
//GET-example for the path /info
//Responds with request-information
server.resource["^/info$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
stringstream stream;
stream << "<h1>Request from " << request->remote_endpoint_address << " (" << request->remote_endpoint_port << ")</h1>";
stream << request->method << " " << request->path << " HTTP/" << request->http_version << "<br>";
for(auto &header : request->header)
stream << header.first << ": " << header.second << "<br>";
//find length of content_stream (length received using content_stream.tellp())
stream.seekp(0, ios::end);
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << stream.tellp() << "\r\n\r\n"
<< stream.rdbuf();
// Alternatively, using a convenience function:
// stringstream stream;
// stream << "<h1>Request from " << request->remote_endpoint_address << " (" << request->remote_endpoint_port << ")</h1>";
// stream << request->method << " " << request->path << " HTTP/" << request->http_version << "<br>";
// for(auto &header: request->header)
// stream << header.first << ": " << header.second << "<br>";
// response->write(stream);
};
//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
server.resource["^/match/([0-9]+)$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
string number = request->path_match[1];
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n"
<< number;
// Alternatively, using a convenience function:
// response->write(request->path_match[1]);
};
//Get example simulating heavy work in a separate thread
server.resource["^/work$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
thread work_thread([response] {
this_thread::sleep_for(chrono::seconds(5));
response->write("Work done");
});
work_thread.detach();
};
//Default GET-example. If no other matches, this anonymous function will be called.
//Will respond with content in the web/-directory, and its subdirectories.
//Default file: index.html
//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) {
try {
auto web_root_path = boost::filesystem::canonical("web");
auto path = boost::filesystem::canonical(web_root_path / request->path);
//Check if path is within web_root_path
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()))
throw invalid_argument("path must be within root path");
if(boost::filesystem::is_directory(path))
path /= "index.html";
SimpleWeb::CaseInsensitiveMultimap header;
// Uncomment the following line to enable Cache-Control
// header.emplace("Cache-Control", "max-age=86400");
#ifdef HAVE_OPENSSL
// Uncomment the following lines to enable ETag
// {
// ifstream ifs(path.string(), ifstream::in | ios::binary);
// if(ifs) {
// auto hash = SimpleWeb::Crypto::to_hex_string(SimpleWeb::Crypto::md5(ifs));
// header.emplace("ETag", "\"" + hash + "\"");
// auto it = request->header.find("If-None-Match");
// if(it != request->header.end()) {
// if(!it->second.empty() && it->second.compare(1, hash.size(), hash) == 0) {
// response->write(SimpleWeb::StatusCode::redirection_not_modified, header);
// return;
// }
// }
// }
// else
// throw invalid_argument("could not read file");
// }
#endif
auto ifs = make_shared<ifstream>();
ifs->open(path.string(), ifstream::in | ios::binary | ios::ate);
if(*ifs) {
auto length = ifs->tellg();
ifs->seekg(0, ios::beg);
header.emplace("Content-Length", to_string(length));
response->write(header);
default_resource_send(server, response, ifs);
}
else
throw invalid_argument("could not read file");
}
catch(const exception &e) {
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*/) {
// handle errors here
};
thread server_thread([&server]() {
//Start server
server.start();
});
//Wait for server to start so that the client can connect
this_thread::sleep_for(chrono::seconds(1));
//Client examples
HttpClient client("localhost:8080");
// synchronous request examples
auto r1 = client.request("GET", "/match/123");
cout << r1->content.rdbuf() << endl; // Alternatively, use the convenience function r1->content.string()
string json_string = "{\"firstName\": \"John\",\"lastName\": \"Smith\",\"age\": 25}";
auto r2 = client.request("POST", "/string", json_string);
cout << r2->content.rdbuf() << endl;
// asynchronous request example
client.request("POST", "/json", json_string, [](std::shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
if(!ec)
cout << response->content.rdbuf() << endl;
});
client.io_service->reset(); // needed because the io_service has been run already in the synchronous examples
client.io_service->run();
server_thread.join();
}
void default_resource_send(const HttpServer &server, const shared_ptr<HttpServer::Response> &response, const shared_ptr<ifstream> &ifs) {
//read and send 128 KB at a time
static vector<char> buffer(131072); // Safe when server is running on one thread
streamsize read_length;
if((read_length = ifs->read(&buffer[0], buffer.size()).gcount()) > 0) {
response->write(&buffer[0], read_length);
if(read_length == static_cast<streamsize>(buffer.size())) {
server.send(response, [&server, response, ifs](const SimpleWeb::error_code &ec) {
if(!ec)
default_resource_send(server, response, ifs);
else
cerr << "Connection interrupted" << endl;
});
}
}
}
================================================
FILE: https_examples.cpp
================================================
#include "client_https.hpp"
#include "server_https.hpp"
//Added for the json-example
#define BOOST_SPIRIT_THREADSAFE
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
//Added for the default_resource example
#include "crypto.hpp"
#include <algorithm>
#include <boost/filesystem.hpp>
#include <fstream>
#include <vector>
using namespace std;
//Added for the json-example:
using namespace boost::property_tree;
typedef SimpleWeb::Server<SimpleWeb::HTTPS> HttpsServer;
typedef SimpleWeb::Client<SimpleWeb::HTTPS> HttpsClient;
//Added for the default_resource example
void default_resource_send(const HttpsServer &server, const shared_ptr<HttpsServer::Response> &response, const shared_ptr<ifstream> &ifs);
int main() {
//HTTPS-server at port 8080 using 1 thread
//Unless you do more heavy non-threaded processing in the resources,
//1 thread is usually faster than several threads
HttpsServer server("server.crt", "server.key");
server.config.port = 8080;
//Add resources using path-regex and method-string, and an anonymous function
//POST-example for the path /string, responds the posted string
server.resource["^/string$"]["POST"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
//Retrieve string:
auto content = request->content.string();
//request->content.string() is a convenience function for:
//stringstream ss;
//ss << request->content.rdbuf();
//auto content=ss.str();
*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:
// response->write(content);
};
//POST-example for the path /json, responds firstName+" "+lastName from the posted json
//Responds with an appropriate error message if the posted json is not valid, or if firstName or lastName is missing
//Example posted json:
//{
// "firstName": "John",
// "lastName": "Smith",
// "age": 25
//}
server.resource["^/json$"]["POST"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
try {
ptree pt;
read_json(request->content, pt);
auto name = pt.get<string>("firstName") + " " + pt.get<string>("lastName");
*response << "HTTP/1.1 200 OK\r\n"
<< "Content-Type: application/json\r\n"
<< "Content-Length: " << name.length() << "\r\n\r\n"
<< name;
}
catch(const exception &e) {
*response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n"
<< e.what();
}
// Alternatively, using a convenience function:
// try {
// ptree pt;
// read_json(request->content, pt);
// auto name=pt.get<string>("firstName")+" "+pt.get<string>("lastName");
// response->write(name, {{"Content-Type", "application/json"}});
// }
// catch(const exception &e) {
// response->write(SimpleWeb::StatusCode::client_error_bad_request, e.what());
// }
};
//GET-example for the path /info
//Responds with request-information
server.resource["^/info$"]["GET"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
stringstream stream;
stream << "<h1>Request from " << request->remote_endpoint_address << " (" << request->remote_endpoint_port << ")</h1>";
stream << request->method << " " << request->path << " HTTP/" << request->http_version << "<br>";
for(auto &header : request->header)
stream << header.first << ": " << header.second << "<br>";
//find length of content_stream (length received using content_stream.tellp())
stream.seekp(0, ios::end);
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << stream.tellp() << "\r\n\r\n"
<< stream.rdbuf();
// Alternatively, using a convenience function:
// stringstream stream;
// stream << "<h1>Request from " << request->remote_endpoint_address << " (" << request->remote_endpoint_port << ")</h1>";
// stream << request->method << " " << request->path << " HTTP/" << request->http_version << "<br>";
// for(auto &header: request->header)
// stream << header.first << ": " << header.second << "<br>";
// response->write(stream);
};
//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
server.resource["^/match/([0-9]+)$"]["GET"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> request) {
string number = request->path_match[1];
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n"
<< number;
// Alternatively, using a convenience function:
// response->write(request->path_match[1]);
};
//Get example simulating heavy work in a separate thread
server.resource["^/work$"]["GET"] = [](shared_ptr<HttpsServer::Response> response, shared_ptr<HttpsServer::Request> /*request*/) {
thread work_thread([response] {
this_thread::sleep_for(chrono::seconds(5));
response->write("Work done");
});
work_thread.detach();
};
//Default GET-example. If no other matches, this anonymous function will be called.
//Will respond with content in the web/-directory, and its subdirectories.
//Default file: index.html
//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) {
try {
auto web_root_path = boost::filesystem::canonical("web");
auto path = boost::filesystem::canonical(web_root_path / request->path);
//Check if path is within web_root_path
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()))
throw invalid_argument("path must be within root path");
if(boost::filesystem::is_directory(path))
path /= "index.html";
SimpleWeb::CaseInsensitiveMultimap header;
// Uncomment the following line to enable Cache-Control
// header.emplace("Cache-Control", "max-age=86400");
#ifdef HAVE_OPENSSL
// Uncomment the following lines to enable ETag
// {
// ifstream ifs(path.string(), ifstream::in | ios::binary);
// if(ifs) {
// auto hash = SimpleWeb::Crypto::to_hex_string(SimpleWeb::Crypto::md5(ifs));
// header.emplace("ETag", "\"" + hash + "\"");
// auto it = request->header.find("If-None-Match");
// if(it != request->header.end()) {
// if(!it->second.empty() && it->second.compare(1, hash.size(), hash) == 0) {
// response->write(SimpleWeb::StatusCode::redirection_not_modified, header);
// return;
// }
// }
// }
// else
// throw invalid_argument("could not read file");
// }
#endif
auto ifs = make_shared<ifstream>();
ifs->open(path.string(), ifstream::in | ios::binary | ios::ate);
if(*ifs) {
auto length = ifs->tellg();
ifs->seekg(0, ios::beg);
header.emplace("Content-Length", to_string(length));
response->write(header);
default_resource_send(server, response, ifs);
}
else
throw invalid_argument("could not read file");
}
catch(const exception &e) {
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*/) {
// handle errors here
};
thread server_thread([&server]() {
//Start server
server.start();
});
//Wait for server to start so that the client can connect
this_thread::sleep_for(chrono::seconds(1));
//Client examples
//Second Client() parameter set to false: no certificate verification
HttpsClient client("localhost:8080", false);
// synchronous request examples
auto r1 = client.request("GET", "/match/123");
cout << r1->content.rdbuf() << endl; // Alternatively, use the convenience function r1->content.string()
string json_string = "{\"firstName\": \"John\",\"lastName\": \"Smith\",\"age\": 25}";
auto r2 = client.request("POST", "/string", json_string);
cout << r2->content.rdbuf() << endl;
// asynchronous request example
client.request("POST", "/json", json_string, [](std::shared_ptr<HttpsClient::Response> response, const SimpleWeb::error_code &ec) {
if(!ec)
cout << response->content.rdbuf() << endl;
});
client.io_service->reset(); // needed because the io_service has been run already in the synchronous examples
client.io_service->run();
server_thread.join();
}
void default_resource_send(const HttpsServer &server, const shared_ptr<HttpsServer::Response> &response, const shared_ptr<ifstream> &ifs) {
//read and send 128 KB at a time
static vector<char> buffer(131072); // Safe when server is running on one thread
streamsize read_length;
if((read_length = ifs->read(&buffer[0], buffer.size()).gcount()) > 0) {
response->write(&buffer[0], read_length);
if(read_length == static_cast<streamsize>(buffer.size())) {
server.send(response, [&server, response, ifs](const SimpleWeb::error_code &ec) {
if(!ec)
default_resource_send(server, response, ifs);
else
cerr << "Connection interrupted" << endl;
});
}
}
}
================================================
FILE: server_http.hpp
================================================
#ifndef SERVER_HTTP_HPP
#define SERVER_HTTP_HPP
#include "utility.hpp"
#include <functional>
#include <iostream>
#include <map>
#include <sstream>
#include <thread>
#ifdef USE_STANDALONE_ASIO
#include <asio.hpp>
namespace SimpleWeb {
using error_code = std::error_code;
using errc = std::errc;
namespace make_error_code = std;
} // namespace SimpleWeb
#else
#include <boost/asio.hpp>
namespace SimpleWeb {
namespace asio = boost::asio;
using error_code = boost::system::error_code;
namespace errc = boost::system::errc;
namespace make_error_code = boost::system::errc;
} // namespace SimpleWeb
#endif
// Late 2017 TODO: remove the following checks and always use std::regex
#ifdef USE_BOOST_REGEX
#include <boost/regex.hpp>
namespace SimpleWeb {
namespace regex = boost;
}
#else
#include <regex>
namespace SimpleWeb {
namespace regex = std;
}
#endif
namespace SimpleWeb {
template <class socket_type>
class Server;
template <class socket_type>
class ServerBase {
public:
virtual ~ServerBase() {}
class Response : public std::ostream {
friend class ServerBase<socket_type>;
asio::streambuf streambuf;
std::shared_ptr<socket_type> socket;
Response(const std::shared_ptr<socket_type> &socket) : std::ostream(&streambuf), socket(socket) {}
template <class size_type>
void write_header(const CaseInsensitiveMultimap &header, size_type size) {
bool content_length_written = false;
bool chunked_transfer_encoding = false;
for(auto &field : header) {
if(!content_length_written && case_insensitive_equal(field.first, "content-length"))
content_length_written = true;
else if(!chunked_transfer_encoding && case_insensitive_equal(field.first, "transfer-encoding") && case_insensitive_equal(field.second, "chunked"))
chunked_transfer_encoding = true;
*this << field.first << ": " << field.second << "\r\n";
}
if(!content_length_written && !chunked_transfer_encoding && !close_connection_after_response)
*this << "Content-Length: " << size << "\r\n\r\n";
else
*this << "\r\n";
}
public:
size_t size() {
return streambuf.size();
}
/// Write directly to stream buffer using std::ostream::write
void write(const char_type *ptr, std::streamsize n) {
std::ostream::write(ptr, n);
}
/// Convenience function for writing status line, potential header fields, and empty content
void write(StatusCode status_code = StatusCode::success_ok, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
write_header(header, 0);
}
/// Convenience function for writing status line, header fields, and content
void write(StatusCode status_code, const std::string &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
write_header(header, content.size());
if(!content.empty())
*this << content;
}
/// Convenience function for writing status line, header fields, and content
void write(StatusCode status_code, std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
content.seekg(0, std::ios::end);
auto size = content.tellg();
content.seekg(0, std::ios::beg);
write_header(header, size);
if(size)
*this << content.rdbuf();
}
/// Convenience function for writing success status line, header fields, and content
void write(const std::string &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
write(StatusCode::success_ok, content, header);
}
/// Convenience function for writing success status line, header fields, and content
void write(std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
write(StatusCode::success_ok, content, header);
}
/// Convenience function for writing success status line, and header fields
void write(const CaseInsensitiveMultimap &header) {
write(StatusCode::success_ok, std::string(), header);
}
/// If true, force server to close the connection after the response have been sent.
///
/// This is useful when implementing a HTTP/1.0-server sending content
/// without specifying the content length.
bool close_connection_after_response = false;
};
class Content : public std::istream {
friend class ServerBase<socket_type>;
public:
size_t size() {
return streambuf.size();
}
/// Convenience function to return std::string. Note that the stream buffer is emptied when this functions is used.
std::string string() {
std::stringstream ss;
ss << rdbuf();
return ss.str();
}
private:
asio::streambuf &streambuf;
Content(asio::streambuf &streambuf) : std::istream(&streambuf), streambuf(streambuf) {}
};
class Request {
friend class ServerBase<socket_type>;
friend class Server<socket_type>;
public:
std::string method, path, http_version;
Content content;
CaseInsensitiveMultimap header;
regex::smatch path_match;
std::string remote_endpoint_address;
unsigned short remote_endpoint_port;
/// Returns query keys with percent-decoded values.
CaseInsensitiveMultimap parse_query_string() {
auto pos = path.find('?');
if(pos != std::string::npos && pos + 1 < path.size())
return SimpleWeb::QueryString::parse(path.substr(pos + 1));
else
return CaseInsensitiveMultimap();
}
private:
Request(const socket_type &socket) : content(streambuf) {
try {
remote_endpoint_address = socket.lowest_layer().remote_endpoint().address().to_string();
remote_endpoint_port = socket.lowest_layer().remote_endpoint().port();
}
catch(...) {
}
}
asio::streambuf streambuf;
};
class Config {
friend class ServerBase<socket_type>;
Config(unsigned short port) : port(port) {}
public:
/// Port number to use. Defaults to 80 for HTTP and 443 for HTTPS.
unsigned short port;
/// Number of threads that the server will use when start() is called. Defaults to 1 thread.
size_t thread_pool_size = 1;
/// Timeout on request handling. Defaults to 5 seconds.
size_t timeout_request = 5;
/// Timeout on content handling. Defaults to 300 seconds.
size_t timeout_content = 300;
/// IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation.
/// If empty, the address will be any address.
std::string address;
/// Set to false to avoid binding the socket to an address that is already in use. Defaults to true.
bool reuse_address = true;
};
///Set before calling start().
Config config;
private:
class regex_orderable : public regex::regex {
std::string str;
public:
regex_orderable(const char *regex_cstr) : regex::regex(regex_cstr), str(regex_cstr) {}
regex_orderable(const std::string ®ex_str) : regex::regex(regex_str), str(regex_str) {}
bool operator<(const regex_orderable &rhs) const {
return str < rhs.str;
}
};
public:
/// Warning: do not add or remove resources after start() is called
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::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>::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;
virtual void start() {
if(!io_service)
io_service = std::make_shared<asio::io_service>();
if(io_service->stopped())
io_service->reset();
asio::ip::tcp::endpoint endpoint;
if(config.address.size() > 0)
endpoint = asio::ip::tcp::endpoint(asio::ip::address::from_string(config.address), config.port);
else
endpoint = asio::ip::tcp::endpoint(asio::ip::tcp::v4(), config.port);
if(!acceptor)
acceptor = std::unique_ptr<asio::ip::tcp::acceptor>(new asio::ip::tcp::acceptor(*io_service));
acceptor->open(endpoint.protocol());
acceptor->set_option(asio::socket_base::reuse_address(config.reuse_address));
acceptor->bind(endpoint);
acceptor->listen();
accept();
//If thread_pool_size>1, start m_io_service.run() in (thread_pool_size-1) threads for thread-pooling
threads.clear();
for(size_t c = 1; c < config.thread_pool_size; c++) {
threads.emplace_back([this]() {
io_service->run();
});
}
//Main thread
if(config.thread_pool_size > 0)
io_service->run();
//Wait for the rest of the threads, if any, to finish as well
for(auto &t : threads) {
t.join();
}
}
void stop() {
acceptor->close();
if(config.thread_pool_size > 0)
io_service->stop();
}
///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 {
asio::async_write(*response->socket, response->streambuf, [this, response, callback](const error_code &ec, size_t /*bytes_transferred*/) {
if(callback)
callback(ec);
});
}
/// 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.
std::shared_ptr<asio::io_service> io_service;
protected:
std::unique_ptr<asio::ip::tcp::acceptor> acceptor;
std::vector<std::thread> threads;
ServerBase(unsigned short port) : config(port) {}
virtual void accept() = 0;
std::shared_ptr<asio::deadline_timer> get_timeout_timer(const std::shared_ptr<socket_type> &socket, long seconds) {
if(seconds == 0)
return nullptr;
auto timer = std::make_shared<asio::deadline_timer>(*io_service);
timer->expires_from_now(boost::posix_time::seconds(seconds));
timer->async_wait([socket](const error_code &ec) {
if(!ec) {
error_code ec;
socket->lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ec);
socket->lowest_layer().close();
}
});
return timer;
}
void read_request_and_content(const std::shared_ptr<socket_type> &socket) {
//Create new streambuf (Request::streambuf) for async_read_until()
//shared_ptr is used to pass temporary objects to the asynchronous functions
std::shared_ptr<Request> request(new Request(*socket));
//Set timeout on the following asio::async-read or write function
auto timer = this->get_timeout_timer(socket, config.timeout_request);
asio::async_read_until(*socket, request->streambuf, "\r\n\r\n", [this, socket, request, timer](const error_code &ec, size_t bytes_transferred) {
if(timer)
timer->cancel();
if(!ec) {
//request->streambuf.size() is not necessarily the same as bytes_transferred, from Boost-docs:
//"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
//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;
if(!this->parse_request(request))
return;
//If content, read that as well
auto it = request->header.find("Content-Length");
if(it != request->header.end()) {
unsigned long long content_length;
try {
content_length = stoull(it->second);
}
catch(const std::exception &e) {
if(on_error)
on_error(request, make_error_code::make_error_code(errc::protocol_error));
return;
}
if(content_length > num_additional_bytes) {
//Set timeout on the following asio::async-read or write function
auto timer = this->get_timeout_timer(socket, config.timeout_content);
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*/) {
if(timer)
timer->cancel();
if(!ec)
this->find_resource(socket, request);
else if(on_error)
on_error(request, ec);
});
}
else
this->find_resource(socket, request);
}
else
this->find_resource(socket, request);
}
else if(on_error)
on_error(request, ec);
});
}
bool parse_request(const std::shared_ptr<Request> &request) const {
std::string line;
getline(request->content, line);
size_t method_end;
if((method_end = line.find(' ')) != std::string::npos) {
size_t path_end;
if((path_end = line.find(' ', method_end + 1)) != std::string::npos) {
request->method = line.substr(0, method_end);
request->path = line.substr(method_end + 1, path_end - method_end - 1);
size_t protocol_end;
if((protocol_end = line.find('/', path_end + 1)) != std::string::npos) {
if(line.compare(path_end + 1, protocol_end - path_end - 1, "HTTP") != 0)
return false;
request->http_version = line.substr(protocol_end + 1, line.size() - protocol_end - 2);
}
else
return false;
getline(request->content, line);
size_t param_end;
while((param_end = line.find(':')) != std::string::npos) {
size_t value_start = param_end + 1;
if((value_start) < line.size()) {
if(line[value_start] == ' ')
value_start++;
if(value_start < line.size())
request->header.emplace(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - 1));
}
getline(request->content, line);
}
}
else
return false;
}
else
return false;
return true;
}
void find_resource(const std::shared_ptr<socket_type> &socket, const std::shared_ptr<Request> &request) {
//Upgrade connection
if(on_upgrade) {
auto it = request->header.find("Upgrade");
if(it != request->header.end()) {
on_upgrade(socket, request);
return;
}
}
//Find path- and method-match, and call write_response
for(auto ®ex_method : resource) {
auto it = regex_method.second.find(request->method);
if(it != regex_method.second.end()) {
regex::smatch sm_res;
if(regex::regex_match(request->path, sm_res, regex_method.first)) {
request->path_match = std::move(sm_res);
write_response(socket, request, it->second);
return;
}
}
}
auto it = default_resource.find(request->method);
if(it != default_resource.end()) {
write_response(socket, request, it->second);
}
}
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::shared_ptr<typename ServerBase<socket_type>::Request>)> &resource_function) {
//Set timeout on the following asio::async-read or write function
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>(response_ptr);
this->send(response, [this, response, request, timer](const error_code &ec) {
if(timer)
timer->cancel();
if(!ec) {
if(response->close_connection_after_response)
return;
auto range = request->header.equal_range("Connection");
for(auto it = range.first; it != range.second; it++) {
if(case_insensitive_equal(it->second, "close")) {
return;
}
else if(case_insensitive_equal(it->second, "keep-alive")) {
this->read_request_and_content(response->socket);
return;
}
}
if(request->http_version >= "1.1")
this->read_request_and_content(response->socket);
}
else if(on_error)
on_error(request, ec);
});
});
try {
resource_function(response, request);
}
catch(const std::exception &e) {
if(on_error)
on_error(request, make_error_code::make_error_code(errc::operation_canceled));
return;
}
}
};
template <class socket_type>
class Server : public ServerBase<socket_type> {};
typedef asio::ip::tcp::socket HTTP;
template <>
class Server<HTTP> : public ServerBase<HTTP> {
public:
DEPRECATED Server(unsigned short port, size_t thread_pool_size = 1, long timeout_request = 5, long timeout_content = 300) : Server() {
config.port = port;
config.thread_pool_size = thread_pool_size;
config.timeout_request = timeout_request;
config.timeout_content = timeout_content;
}
Server() : ServerBase<HTTP>::ServerBase(80) {}
protected:
void accept() {
//Create new socket for this connection
//Shared_ptr is used to pass temporary objects to the asynchronous functions
auto socket = std::make_shared<HTTP>(*io_service);
acceptor->async_accept(*socket, [this, socket](const error_code &ec) {
//Immediately start accepting a new connection (if io_service hasn't been stopped)
if(ec != asio::error::operation_aborted)
accept();
if(!ec) {
asio::ip::tcp::no_delay option(true);
socket->set_option(option);
this->read_request_and_content(socket);
}
else if(on_error)
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
});
}
};
} // namespace SimpleWeb
#endif /* SERVER_HTTP_HPP */
================================================
FILE: server_https.hpp
================================================
#ifndef SERVER_HTTPS_HPP
#define SERVER_HTTPS_HPP
#include "server_http.hpp"
#ifdef USE_STANDALONE_ASIO
#include <asio/ssl.hpp>
#else
#include <boost/asio/ssl.hpp>
#endif
#include <algorithm>
#include <openssl/ssl.h>
namespace SimpleWeb {
typedef asio::ssl::stream<asio::ip::tcp::socket> HTTPS;
template <>
class Server<HTTPS> : public ServerBase<HTTPS> {
std::string session_id_context;
bool set_session_id_context = false;
public:
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, const std::string &verify_file = std::string())
: Server(cert_file, private_key_file, verify_file) {
config.port = port;
config.thread_pool_size = thread_pool_size;
config.timeout_request = timeout_request;
config.timeout_content = timeout_content;
}
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) {
context.use_certificate_chain_file(cert_file);
context.use_private_key_file(private_key_file, asio::ssl::context::pem);
if(verify_file.size() > 0) {
context.load_verify_file(verify_file);
context.set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert | asio::ssl::verify_client_once);
set_session_id_context = true;
}
}
void start() {
if(set_session_id_context) {
// 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.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()),
std::min<size_t>(session_id_context.size(), SSL_MAX_SSL_SESSION_ID_LENGTH));
}
ServerBase::start();
}
protected:
asio::ssl::context context;
void accept() {
//Create new socket for this connection
//Shared_ptr is used to pass temporary objects to the asynchronous functions
auto socket = std::make_shared<HTTPS>(*io_service, context);
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)
if(ec != asio::error::operation_aborted)
accept();
if(!ec) {
asio::ip::tcp::no_delay option(true);
socket->lowest_layer().set_option(option);
//Set timeout on the following asio::ssl::stream::async_handshake
auto timer = get_timeout_timer(socket, config.timeout_request);
socket->async_handshake(asio::ssl::stream_base::server, [this, socket, timer](const error_code &ec) {
if(timer)
timer->cancel();
if(!ec)
read_request_and_content(socket);
else if(on_error)
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
});
}
else if(on_error)
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
});
}
};
} // namespace SimpleWeb
#endif /* SERVER_HTTPS_HPP */
================================================
FILE: status_code.hpp
================================================
#ifndef SIMPLE_WEB_SERVER_STATUS_CODE_HPP
#define SIMPLE_WEB_SERVER_STATUS_CODE_HPP
#include <string>
#include <vector>
namespace SimpleWeb {
enum class StatusCode {
unknown = 0,
information_continue = 100,
information_switching_protocols,
information_processing,
success_ok = 200,
success_created,
success_accepted,
success_non_authoritative_information,
success_no_content,
success_reset_content,
success_partial_content,
success_multi_status,
success_already_reported,
success_im_used = 226,
redirection_multiple_choices = 300,
redirection_moved_permanently,
redirection_found,
redirection_see_other,
redirection_not_modified,
redirection_use_proxy,
redirection_switch_proxy,
redirection_temporary_redirect,
redirection_permanent_redirect,
client_error_bad_request = 400,
client_error_unauthorized,
client_error_payment_required,
client_error_forbidden,
client_error_not_found,
client_error_method_not_allowed,
client_error_not_acceptable,
client_error_proxy_authentication_required,
client_error_request_timeout,
client_error_conflict,
client_error_gone,
client_error_length_required,
client_error_precondition_failed,
client_error_payload_too_large,
client_error_uri_too_long,
client_error_unsupported_media_type,
client_error_range_not_satisfiable,
client_error_expectation_failed,
client_error_im_a_teapot,
client_error_misdirection_required = 421,
client_error_unprocessable_entity,
client_error_locked,
client_error_failed_dependency,
client_error_upgrade_required = 426,
client_error_precondition_required = 428,
client_error_too_many_requests,
client_error_request_header_fields_too_large = 431,
client_error_unavailable_for_legal_reasons = 451,
server_error_internal_server_error = 500,
server_error_not_implemented,
server_error_bad_gateway,
server_error_service_unavailable,
server_error_gateway_timeout,
server_error_http_version_not_supported,
server_error_variant_also_negotiates,
server_error_insufficient_storage,
server_error_loop_detected,
server_error_not_extended = 510,
server_error_network_authentication_required
};
inline const static std::vector<std::pair<StatusCode, std::string>> &status_codes() {
static std::vector<std::pair<StatusCode, std::string>> status_codes = {
{StatusCode::unknown, ""},
{StatusCode::information_continue, "100 Continue"},
{StatusCode::information_switching_protocols, "101 Switching Protocols"},
{StatusCode::information_processing, "102 Processing"},
{StatusCode::success_ok, "200 OK"},
{StatusCode::success_created, "201 Created"},
{StatusCode::success_accepted, "202 Accepted"},
{StatusCode::success_non_authoritative_information, "203 Non-Authoritative Information"},
{StatusCode::success_no_content, "204 No Content"},
{StatusCode::success_reset_content, "205 Reset Content"},
{StatusCode::success_partial_content, "206 Partial Content"},
{StatusCode::success_multi_status, "207 Multi-Status"},
{StatusCode::success_already_reported, "208 Already Reported"},
{StatusCode::success_im_used, "226 IM Used"},
{StatusCode::redirection_multiple_choices, "300 Multiple Choices"},
{StatusCode::redirection_moved_permanently, "301 Moved Permanently"},
{StatusCode::redirection_found, "302 Found"},
{StatusCode::redirection_see_other, "303 See Other"},
{StatusCode::redirection_not_modified, "304 Not Modified"},
{StatusCode::redirection_use_proxy, "305 Use Proxy"},
{StatusCode::redirection_switch_proxy, "306 Switch Proxy"},
{StatusCode::redirection_temporary_redirect, "307 Temporary Redirect"},
{StatusCode::redirection_permanent_redirect, "308 Permanent Redirect"},
{StatusCode::client_error_bad_request, "400 Bad Request"},
{StatusCode::client_error_unauthorized, "401 Unauthorized"},
{StatusCode::client_error_payment_required, "402 Payment Required"},
{StatusCode::client_error_forbidden, "403 Forbidden"},
{StatusCode::client_error_not_found, "404 Not Found"},
{StatusCode::client_error_method_not_allowed, "405 Method Not Allowed"},
{StatusCode::client_error_not_acceptable, "406 Not Acceptable"},
{StatusCode::client_error_proxy_authentication_required, "407 Proxy Authentication Required"},
{StatusCode::client_error_request_timeout, "408 Request Timeout"},
{StatusCode::client_error_conflict, "409 Conflict"},
{StatusCode::client_error_gone, "410 Gone"},
{StatusCode::client_error_length_required, "411 Length Required"},
{StatusCode::client_error_precondition_failed, "412 Precondition Failed"},
{StatusCode::client_error_payload_too_large, "413 Payload Too Large"},
{StatusCode::client_error_uri_too_long, "414 URI Too Long"},
{StatusCode::client_error_unsupported_media_type, "415 Unsupported Media Type"},
{StatusCode::client_error_range_not_satisfiable, "416 Range Not Satisfiable"},
{StatusCode::client_error_expectation_failed, "417 Expectation Failed"},
{StatusCode::client_error_im_a_teapot, "418 I'm a teapot"},
{StatusCode::client_error_misdirection_required, "421 Misdirected Request"},
{StatusCode::client_error_unprocessable_entity, "422 Unprocessable Entity"},
{StatusCode::client_error_locked, "423 Locked"},
{StatusCode::client_error_failed_dependency, "424 Failed Dependency"},
{StatusCode::client_error_upgrade_required, "426 Upgrade Required"},
{StatusCode::client_error_precondition_required, "428 Precondition Required"},
{StatusCode::client_error_too_many_requests, "429 Too Many Requests"},
{StatusCode::client_error_request_header_fields_too_large, "431 Request Header Fields Too Large"},
{StatusCode::client_error_unavailable_for_legal_reasons, "451 Unavailable For Legal Reasons"},
{StatusCode::server_error_internal_server_error, "500 Internal Server Error"},
{StatusCode::server_error_not_implemented, "501 Not Implemented"},
{StatusCode::server_error_bad_gateway, "502 Bad Gateway"},
{StatusCode::server_error_service_unavailable, "503 Service Unavailable"},
{StatusCode::server_error_gateway_timeout, "504 Gateway Timeout"},
{StatusCode::server_error_http_version_not_supported, "505 HTTP Version Not Supported"},
{StatusCode::server_error_variant_also_negotiates, "506 Variant Also Negotiates"},
{StatusCode::server_error_insufficient_storage, "507 Insufficient Storage"},
{StatusCode::server_error_loop_detected, "508 Loop Detected"},
{StatusCode::server_error_not_extended, "510 Not Extended"},
{StatusCode::server_error_network_authentication_required, "511 Network Authentication Required"}};
return status_codes;
}
inline StatusCode status_code(const std::string &status_code_str) {
for(auto &status_code : status_codes()) {
if(status_code.second == status_code_str)
return status_code.first;
}
return StatusCode::unknown;
}
inline const std::string &status_code(StatusCode status_code_enum) {
for(auto &status_code : status_codes()) {
if(status_code.first == status_code_enum)
return status_code.second;
}
return status_codes()[0].second;
}
} // namespace SimpleWeb
#endif // SIMPLE_WEB_SERVER_STATUS_CODE_HPP
================================================
FILE: tests/CMakeLists.txt
================================================
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-access-control")
add_executable(io_test io_test.cpp)
target_link_libraries(io_test ${Boost_LIBRARIES})
target_link_libraries(io_test ${CMAKE_THREAD_LIBS_INIT})
add_executable(parse_test parse_test.cpp)
target_link_libraries(parse_test ${Boost_LIBRARIES})
target_link_libraries(parse_test ${CMAKE_THREAD_LIBS_INIT})
if(MSYS) #TODO: Is MSYS true when MSVC is true?
target_link_libraries(io_test ws2_32 wsock32)
target_link_libraries(parse_test ws2_32 wsock32)
endif()
add_test(io_test io_test)
add_test(parse_test parse_test)
if(OPENSSL_FOUND)
add_executable(crypto_test crypto_test.cpp)
target_link_libraries(crypto_test ${OPENSSL_CRYPTO_LIBRARY})
add_test(crypto_test crypto_test)
endif()
================================================
FILE: tests/crypto_test.cpp
================================================
#include <cassert>
#include <vector>
#include "crypto.hpp"
using namespace std;
using namespace SimpleWeb;
const vector<pair<string, string>> base64_string_tests = {
{"", ""},
{"f", "Zg=="},
{"fo", "Zm8="},
{"foo", "Zm9v"},
{"foob", "Zm9vYg=="},
{"fooba", "Zm9vYmE="},
{"foobar", "Zm9vYmFy"}};
const vector<pair<string, string>> md5_string_tests = {
{"", "d41d8cd98f00b204e9800998ecf8427e"},
{"The quick brown fox jumps over the lazy dog", "9e107d9d372bb6826bd81d3542a419d6"}};
const vector<pair<string, string>> sha1_string_tests = {
{"", "da39a3ee5e6b4b0d3255bfef95601890afd80709"},
{"The quick brown fox jumps over the lazy dog", "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12"}};
const vector<pair<string, string>> sha256_string_tests = {
{"", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},
{"The quick brown fox jumps over the lazy dog", "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592"}};
const vector<pair<string, string>> sha512_string_tests = {
{"", "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"},
{"The quick brown fox jumps over the lazy dog", "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6"}};
int main() {
for(auto &string_test : base64_string_tests) {
assert(Crypto::Base64::encode(string_test.first) == string_test.second);
assert(Crypto::Base64::decode(string_test.second) == string_test.first);
}
for(auto &string_test : md5_string_tests) {
assert(Crypto::to_hex_string(Crypto::md5(string_test.first)) == string_test.second);
stringstream ss(string_test.first);
assert(Crypto::to_hex_string(Crypto::md5(ss)) == string_test.second);
}
for(auto &string_test : sha1_string_tests) {
assert(Crypto::to_hex_string(Crypto::sha1(string_test.first)) == string_test.second);
stringstream ss(string_test.first);
assert(Crypto::to_hex_string(Crypto::sha1(ss)) == string_test.second);
}
for(auto &string_test : sha256_string_tests) {
assert(Crypto::to_hex_string(Crypto::sha256(string_test.first)) == string_test.second);
stringstream ss(string_test.first);
assert(Crypto::to_hex_string(Crypto::sha256(ss)) == string_test.second);
}
for(auto &string_test : sha512_string_tests) {
assert(Crypto::to_hex_string(Crypto::sha512(string_test.first)) == string_test.second);
stringstream ss(string_test.first);
assert(Crypto::to_hex_string(Crypto::sha512(ss)) == string_test.second);
}
//Testing iterations
assert(Crypto::to_hex_string(Crypto::sha1("Test", 1)) == "640ab2bae07bedc4c163f679a746f7ab7fb5d1fa");
assert(Crypto::to_hex_string(Crypto::sha1("Test", 2)) == "af31c6cbdecd88726d0a9b3798c71ef41f1624d5");
stringstream ss("Test");
assert(Crypto::to_hex_string(Crypto::sha1(ss, 2)) == "af31c6cbdecd88726d0a9b3798c71ef41f1624d5");
assert(Crypto::to_hex_string(Crypto::pbkdf2("Password", "Salt", 4096, 128 / 8)) == "f66df50f8aaa11e4d9721e1312ff2e66");
assert(Crypto::to_hex_string(Crypto::pbkdf2("Password", "Salt", 8192, 512 / 8)) == "a941ccbc34d1ee8ebbd1d34824a419c3dc4eac9cbc7c36ae6c7ca8725e2b618a6ad22241e787af937b0960cf85aa8ea3a258f243e05d3cc9b08af5dd93be046c");
}
================================================
FILE: tests/io_test.cpp
================================================
#include "client_http.hpp"
#include "server_http.hpp"
#include <cassert>
using namespace std;
typedef SimpleWeb::Server<SimpleWeb::HTTP> HttpServer;
typedef SimpleWeb::Client<SimpleWeb::HTTP> HttpClient;
int main() {
HttpServer server;
server.config.port = 8080;
server.resource["^/string$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
auto content = request->content.string();
*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) {
response->write(request->content.string());
};
server.resource["^/string3$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
std::stringstream stream;
stream << request->content.rdbuf();
response->write(stream);
};
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"}});
};
server.resource["^/info$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
stringstream content_stream;
content_stream << request->method << " " << request->path << " " << request->http_version << " ";
content_stream << request->header.find("test parameter")->second;
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();
};
server.resource["^/match/([0-9]+)$"]["GET"] = [&server](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
string number = request->path_match[1];
*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) {
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;
};
thread server_thread([&server]() {
//Start server
server.start();
});
this_thread::sleep_for(chrono::seconds(1));
{
HttpClient client("localhost:8080");
{
stringstream output;
auto r = client.request("POST", "/string", "A string");
assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
output << r->content.rdbuf();
assert(output.str() == "A string");
}
{
stringstream output;
auto r = client.request("POST", "/string", "A string");
assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
assert(r->content.string() == "A string");
}
{
stringstream output;
auto r = client.request("POST", "/string2", "A string");
assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
output << r->content.rdbuf();
assert(output.str() == "A string");
}
{
stringstream output;
auto r = client.request("POST", "/string3", "A string");
assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
output << r->content.rdbuf();
assert(output.str() == "A string");
}
{
stringstream output;
auto r = client.request("POST", "/string4", "A string");
assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::client_error_forbidden);
assert(r->header.size() == 3);
assert(r->header.find("test1")->second == "test2");
assert(r->header.find("tEst3")->second == "test4");
assert(r->header.find("content-length")->second == "0");
output << r->content.rdbuf();
assert(output.str() == "");
}
{
stringstream output;
stringstream content("A string");
auto r = client.request("POST", "/string", content);
output << r->content.rdbuf();
assert(output.str() == "A string");
}
{
stringstream output;
auto r = client.request("GET", "/info", "", {{"Test Parameter", "test value"}});
output << r->content.rdbuf();
assert(output.str() == "GET /info 1.1 test value");
}
{
stringstream output;
auto r = client.request("GET", "/match/123");
output << r->content.rdbuf();
assert(output.str() == "123");
}
}
{
HttpClient client("localhost:8080");
HttpClient::Connection *connection;
{
// test performing the stream version of the request methods first
stringstream output;
stringstream content("A string");
auto r = client.request("POST", "/string", content);
output << r->content.rdbuf();
assert(output.str() == "A string");
assert(client.connections->size() == 1);
connection = client.connections->front().get();
}
{
stringstream output;
auto r = client.request("POST", "/string", "A string");
output << r->content.rdbuf();
assert(output.str() == "A string");
assert(client.connections->size() == 1);
assert(connection == client.connections->front().get());
}
{
stringstream output;
auto r = client.request("GET", "/header", "", {{"test1", "test"}, {"test2", "ing"}});
output << r->content.rdbuf();
assert(output.str() == "testing");
assert(client.connections->size() == 1);
assert(connection == client.connections->front().get());
}
}
{
HttpClient client("localhost:8080");
bool call = false;
client.request("GET", "/match/123", [&call](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
assert(!ec);
stringstream output;
output << response->content.rdbuf();
assert(output.str() == "123");
call = true;
});
client.io_service->run();
assert(call);
{
vector<int> calls(100);
vector<thread> threads;
for(size_t c = 0; c < 100; ++c) {
calls[c] = 0;
threads.emplace_back([c, &client, &calls] {
client.request("GET", "/match/123", [c, &calls](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
assert(!ec);
stringstream output;
output << response->content.rdbuf();
assert(output.str() == "123");
calls[c] = 1;
});
});
}
for(auto &thread : threads)
thread.join();
assert(client.connections->size() == 100);
client.io_service->reset();
client.io_service->run();
assert(client.connections->size() == 1);
for(auto call : calls)
assert(call);
}
}
{
HttpClient client("localhost:8080");
assert(client.connections->size() == 0);
for(size_t c = 0; c < 5000; ++c) {
auto r1 = client.request("POST", "/string", "A string");
assert(SimpleWeb::status_code(r1->status_code) == SimpleWeb::StatusCode::success_ok);
assert(r1->content.string() == "A string");
assert(client.connections->size() == 1);
stringstream content("A string");
auto r2 = client.request("POST", "/string", content);
assert(SimpleWeb::status_code(r2->status_code) == SimpleWeb::StatusCode::success_ok);
assert(r2->content.string() == "A string");
assert(client.connections->size() == 1);
}
}
for(size_t c = 0; c < 500; ++c) {
{
HttpClient client("localhost:8080");
auto r = client.request("POST", "/string", "A string");
assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
assert(r->content.string() == "A string");
assert(client.connections->size() == 1);
}
{
HttpClient client("localhost:8080");
stringstream content("A string");
auto r = client.request("POST", "/string", content);
assert(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
assert(r->content.string() == "A string");
assert(client.connections->size() == 1);
}
}
server.stop();
server_thread.join();
return 0;
}
================================================
FILE: tests/parse_test.cpp
================================================
#include "client_http.hpp"
#include "server_http.hpp"
#include <cassert>
#include <iostream>
using namespace std;
using namespace SimpleWeb;
class ServerTest : public ServerBase<HTTP> {
public:
ServerTest() : ServerBase<HTTP>::ServerBase(8080) {}
void accept() {}
void parse_request_test() {
HTTP socket(*io_service);
std::shared_ptr<Request> request(new Request(socket));
std::ostream stream(&request->content.streambuf);
stream << "GET /test/ HTTP/1.1\r\n";
stream << "TestHeader: test\r\n";
stream << "TestHeader2:test2\r\n";
stream << "TestHeader3:test3a\r\n";
stream << "TestHeader3:test3b\r\n";
stream << "\r\n";
assert(parse_request(request));
assert(request->method == "GET");
assert(request->path == "/test/");
assert(request->http_version == "1.1");
assert(request->header.size() == 4);
auto header_it = request->header.find("TestHeader");
assert(header_it != request->header.end() && header_it->second == "test");
header_it = request->header.find("TestHeader2");
assert(header_it != request->header.end() && header_it->second == "test2");
header_it = request->header.find("testheader");
assert(header_it != request->header.end() && header_it->second == "test");
header_it = request->header.find("testheader2");
assert(header_it != request->header.end() && header_it->second == "test2");
auto range = request->header.equal_range("testheader3");
auto first = range.first;
auto second = first;
++second;
assert(range.first != request->header.end() && range.second != request->header.end() &&
((first->second == "test3a" && second->second == "test3b") ||
(first->second == "test3b" && second->second == "test3a")));
}
};
class ClientTest : public ClientBase<HTTP> {
public:
ClientTest(const std::string &server_port_path) : ClientBase<HTTP>::ClientBase(server_port_path, 80) {}
std::shared_ptr<Connection> create_connection() override {
return nullptr;
}
void constructor_parse_test1() {
assert(host == "test.org");
assert(port == 8080);
}
void constructor_parse_test2() {
assert(host == "test.org");
assert(port == 80);
}
void parse_response_header_test() {
std::shared_ptr<Response> response(new Response());
ostream stream(&response->content_buffer);
stream << "HTTP/1.1 200 OK\r\n";
stream << "TestHeader: test\r\n";
stream << "TestHeader2:test2\r\n";
stream << "TestHeader3:test3a\r\n";
stream << "TestHeader3:test3b\r\n";
stream << "\r\n";
parse_response_header(response);
assert(response->http_version == "1.1");
assert(response->status_code == "200 OK");
assert(response->header.size() == 4);
auto header_it = response->header.find("TestHeader");
assert(header_it != response->header.end() && header_it->second == "test");
header_it = response->header.find("TestHeader2");
assert(header_it != response->header.end() && header_it->second == "test2");
header_it = response->header.find("testheader");
assert(header_it != response->header.end() && header_it->second == "test");
header_it = response->header.find("testheader2");
assert(header_it != response->header.end() && header_it->second == "test2");
auto range = response->header.equal_range("testheader3");
auto first = range.first;
auto second = first;
++second;
assert(range.first != response->header.end() && range.second != response->header.end() &&
((first->second == "test3a" && second->second == "test3b") ||
(first->second == "test3b" && second->second == "test3a")));
}
};
int main() {
assert(case_insensitive_equal("Test", "tesT"));
assert(case_insensitive_equal("tesT", "test"));
assert(!case_insensitive_equal("test", "tseT"));
CaseInsensitiveEqual equal;
assert(equal("Test", "tesT"));
assert(equal("tesT", "test"));
assert(!equal("test", "tset"));
CaseInsensitiveHash hash;
assert(hash("Test") == hash("tesT"));
assert(hash("tesT") == hash("test"));
assert(hash("test") != hash("tset"));
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";
assert(Percent::encode(percent_decoded) == percent_encoded);
assert(Percent::decode(percent_encoded) == percent_decoded);
assert(Percent::decode(Percent::encode(percent_decoded)) == percent_decoded);
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_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);
assert(query_string_result == query_string1 || query_string_result == query_string2);
auto fields_result1 = QueryString::parse(query_string1);
auto fields_result2 = QueryString::parse(query_string2);
assert(fields_result1 == fields_result2 && fields_result1 == fields);
ServerTest serverTest;
serverTest.io_service = std::make_shared<asio::io_service>();
serverTest.parse_request_test();
ClientTest clientTest("test.org:8080");
clientTest.constructor_parse_test1();
ClientTest clientTest2("test.org");
clientTest2.constructor_parse_test2();
clientTest2.parse_response_header_test();
asio::io_service io_service;
asio::ip::tcp::socket socket(io_service);
SimpleWeb::Server<HTTP>::Request request(socket);
{
request.path = "/?";
auto queries = request.parse_query_string();
assert(queries.empty());
}
{
request.path = "/";
auto queries = request.parse_query_string();
assert(queries.empty());
}
{
request.path = "/?=";
auto queries = request.parse_query_string();
assert(queries.empty());
}
{
request.path = "/?=test";
auto queries = request.parse_query_string();
assert(queries.empty());
}
{
request.path = "/?a=1%202%20%203&b=3+4&c&d=æ%25ø%26å%3F";
auto queries = request.parse_query_string();
{
auto range = queries.equal_range("a");
assert(range.first != range.second);
assert(range.first->second == "1 2 3");
}
{
auto range = queries.equal_range("b");
assert(range.first != range.second);
assert(range.first->second == "3 4");
}
{
auto range = queries.equal_range("c");
assert(range.first != range.second);
assert(range.first->second == "");
}
{
auto range = queries.equal_range("d");
assert(range.first != range.second);
assert(range.first->second == "æ%ø&å?");
}
}
}
================================================
FILE: utility.hpp
================================================
#ifndef SIMPLE_WEB_SERVER_UTILITY_HPP
#define SIMPLE_WEB_SERVER_UTILITY_HPP
#include "status_code.hpp"
#include <iostream>
#include <string>
#include <unordered_map>
// TODO when switching to c++14, use [[deprecated]] instead
#ifndef DEPRECATED
#ifdef __GNUC__
#define DEPRECATED __attribute__((deprecated))
#elif defined(_MSC_VER)
#define DEPRECATED __declspec(deprecated)
#else
#define DEPRECATED
#endif
#endif
namespace SimpleWeb {
#ifndef CASE_INSENSITIVE_EQUAL_AND_HASH
#define CASE_INSENSITIVE_EQUAL_AND_HASH
inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) {
return str1.size() == str2.size() &&
std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) {
return tolower(a) == tolower(b);
});
}
class CaseInsensitiveEqual {
public:
bool operator()(const std::string &str1, const std::string &str2) const {
return case_insensitive_equal(str1, str2);
}
};
// Based on https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226
class CaseInsensitiveHash {
public:
size_t operator()(const std::string &str) const {
size_t h = 0;
std::hash<int> hash;
for(auto c : str)
h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2);
return h;
}
};
#endif
typedef std::unordered_multimap<std::string, std::string, CaseInsensitiveHash, CaseInsensitiveEqual> CaseInsensitiveMultimap;
/// Percent encoding and decoding
class Percent {
public:
/// Returns percent-encoded string
static std::string encode(const std::string &value) {
static auto hex_chars = "0123456789ABCDEF";
std::string result;
result.reserve(value.size()); // minimum size of result
for(auto &chr : value) {
if(chr == ' ')
result += '+';
else if(chr == '!' || chr == '#' || chr == '$' || (chr >= '&' && chr <= ',') || (chr >= '/' && chr <= ';') || chr == '=' || chr == '?' || chr == '@' || chr == '[' || chr == ']')
result += std::string("%") + hex_chars[chr >> 4] + hex_chars[chr & 15];
else
result += chr;
}
return result;
}
/// Returns percent-decoded string
static std::string decode(const std::string &value) {
std::string result;
result.reserve(value.size() / 3 + (value.size() % 3)); // minimum size of result
for(size_t i = 0; i < value.size(); ++i) {
auto &chr = value[i];
if(chr == '%' && i + 2 < value.size()) {
auto hex = value.substr(i + 1, 2);
auto decoded_chr = static_cast<char>(std::strtol(hex.c_str(), nullptr, 16));
result += decoded_chr;
i += 2;
}
else if(chr == '+')
result += ' ';
else
result += chr;
}
return result;
}
};
/// Query string creation and parsing
class QueryString {
public:
/// Returns query string created from given field names and values
static std::string create(const CaseInsensitiveMultimap &fields) {
std::string result;
bool first = true;
for(auto &field : fields) {
result += (!first ? "&" : "") + field.first + '=' + Percent::encode(field.second);
first = false;
}
return result;
}
/// Returns query keys with percent-decoded values.
static CaseInsensitiveMultimap parse(const std::string &query_string) {
CaseInsensitiveMultimap result;
if(query_string.empty())
return result;
size_t name_pos = 0;
size_t name_end_pos = -1;
size_t value_pos = -1;
for(size_t c = 0; c < query_string.size(); ++c) {
if(query_string[c] == '&') {
auto name = query_string.substr(name_pos, (name_end_pos == std::string::npos ? c : name_end_pos) - name_pos);
if(!name.empty()) {
auto value = value_pos == std::string::npos ? std::string() : query_string.substr(value_pos, c - value_pos);
result.emplace(std::move(name), Percent::decode(value));
}
name_pos = c + 1;
name_end_pos = -1;
value_pos = -1;
}
else if(query_string[c] == '=') {
name_end_pos = c;
value_pos = c + 1;
}
}
if(name_pos < query_string.size()) {
auto name = query_string.substr(name_pos, name_end_pos - name_pos);
if(!name.empty()) {
auto value = value_pos >= query_string.size() ? std::string() : query_string.substr(value_pos);
result.emplace(std::move(name), Percent::decode(value));
}
}
return result;
}
};
/// Returns query keys with percent-decoded values.
DEPRECATED inline CaseInsensitiveMultimap parse_query_string(const std::string &query_string) {
return QueryString::parse(query_string);
}
} // namespace SimpleWeb
#endif // SIMPLE_WEB_SERVER_UTILITY_HPP
================================================
FILE: web/index.html
================================================
<html>
<head>
<title>Simple-Web-Server html-file</title>
</head>
<body>
This is the content of index.html
</body>
</html>
================================================
FILE: web/test.html
================================================
<html>
<head>
<title>Simple-Web-Server html-file</title>
</head>
<body>
This is the content of test.html
</body>
</html>
gitextract_5e6ws4b2/
├── .clang-format
├── .gitignore
├── .travis.yml
├── CMakeLists.txt
├── LICENSE
├── README.md
├── client_http.hpp
├── client_https.hpp
├── crypto.hpp
├── http_examples.cpp
├── https_examples.cpp
├── server_http.hpp
├── server_https.hpp
├── status_code.hpp
├── tests/
│ ├── CMakeLists.txt
│ ├── crypto_test.cpp
│ ├── io_test.cpp
│ └── parse_test.cpp
├── utility.hpp
└── web/
├── index.html
└── test.html
SYMBOL INDEX (143 symbols across 12 files)
FILE: client_http.hpp
type SimpleWeb (line 11) | namespace SimpleWeb {
class Client (line 33) | class Client
class ClientBase (line 36) | class ClientBase {
class Content (line 38) | class Content : public std::istream {
method size (line 42) | size_t size() {
method string (line 46) | std::string string() {
method Content (line 54) | Content(asio::streambuf &streambuf) : std::istream(&streambuf), st...
class Response (line 57) | class Response {
method Response (line 71) | Response() : content(content_buffer) {}
class Config (line 74) | class Config {
method Config (line 78) | Config() {}
class Connection (line 90) | class Connection {
method Connection (line 92) | Connection(const std::string &host, unsigned short port, const Con...
class Session (line 113) | class Session {
method Session (line 115) | Session(const std::shared_ptr<asio::io_service> &io_service, const...
method request (line 136) | std::shared_ptr<Response> request(const std::string &method, const s...
method request (line 153) | std::shared_ptr<Response> request(const std::string &method, const s...
method request (line 169) | void request(const std::string &method, const std::string &path, str...
method request (line 211) | void request(const std::string &method, const std::string &path, str...
method request (line 217) | void request(const std::string &method, const std::string &path,
method request (line 223) | void request(const std::string &method, std::function<void(std::shar...
method request (line 228) | void request(const std::string &method, const std::string &path, std...
method request (line 274) | void request(const std::string &method, const std::string &path, std...
method ClientBase (line 286) | ClientBase(const std::string &host_port, unsigned short default_port)
method get_connection (line 293) | std::shared_ptr<Connection> get_connection() {
method create_request_header (line 313) | std::unique_ptr<asio::streambuf> create_request_header(const std::st...
method parse_host_port (line 329) | static std::pair<std::string, unsigned short> parse_host_port(const ...
method get_timeout_timer (line 343) | static std::shared_ptr<asio::deadline_timer> get_timeout_timer(const...
method parse_response_header (line 358) | static void parse_response_header(const std::shared_ptr<Response> &r...
method write (line 384) | static void write(const std::shared_ptr<Session> &session) {
method read (line 398) | static void read(const std::shared_ptr<Session> &session) {
method read_chunked (line 468) | static void read_chunked(const std::shared_ptr<Session> &session, co...
method close (line 527) | static void close(const std::shared_ptr<Session> &session) {
class Client (line 535) | class Client : public ClientBase<socket_type> {}
class Client<HTTP> (line 540) | class Client<HTTP> : public ClientBase<HTTP> {
method Client (line 544) | Client(const std::string &server_port_path) : ClientBase<HTTP>::Clie...
method create_connection (line 547) | std::shared_ptr<Connection> create_connection() override {
method connect (line 551) | static void connect(const std::shared_ptr<Session> &session) {
type SimpleWeb (line 21) | namespace SimpleWeb {
class Client (line 33) | class Client
class ClientBase (line 36) | class ClientBase {
class Content (line 38) | class Content : public std::istream {
method size (line 42) | size_t size() {
method string (line 46) | std::string string() {
method Content (line 54) | Content(asio::streambuf &streambuf) : std::istream(&streambuf), st...
class Response (line 57) | class Response {
method Response (line 71) | Response() : content(content_buffer) {}
class Config (line 74) | class Config {
method Config (line 78) | Config() {}
class Connection (line 90) | class Connection {
method Connection (line 92) | Connection(const std::string &host, unsigned short port, const Con...
class Session (line 113) | class Session {
method Session (line 115) | Session(const std::shared_ptr<asio::io_service> &io_service, const...
method request (line 136) | std::shared_ptr<Response> request(const std::string &method, const s...
method request (line 153) | std::shared_ptr<Response> request(const std::string &method, const s...
method request (line 169) | void request(const std::string &method, const std::string &path, str...
method request (line 211) | void request(const std::string &method, const std::string &path, str...
method request (line 217) | void request(const std::string &method, const std::string &path,
method request (line 223) | void request(const std::string &method, std::function<void(std::shar...
method request (line 228) | void request(const std::string &method, const std::string &path, std...
method request (line 274) | void request(const std::string &method, const std::string &path, std...
method ClientBase (line 286) | ClientBase(const std::string &host_port, unsigned short default_port)
method get_connection (line 293) | std::shared_ptr<Connection> get_connection() {
method create_request_header (line 313) | std::unique_ptr<asio::streambuf> create_request_header(const std::st...
method parse_host_port (line 329) | static std::pair<std::string, unsigned short> parse_host_port(const ...
method get_timeout_timer (line 343) | static std::shared_ptr<asio::deadline_timer> get_timeout_timer(const...
method parse_response_header (line 358) | static void parse_response_header(const std::shared_ptr<Response> &r...
method write (line 384) | static void write(const std::shared_ptr<Session> &session) {
method read (line 398) | static void read(const std::shared_ptr<Session> &session) {
method read_chunked (line 468) | static void read_chunked(const std::shared_ptr<Session> &session, co...
method close (line 527) | static void close(const std::shared_ptr<Session> &session) {
class Client (line 535) | class Client : public ClientBase<socket_type> {}
class Client<HTTP> (line 540) | class Client<HTTP> : public ClientBase<HTTP> {
method Client (line 544) | Client(const std::string &server_port_path) : ClientBase<HTTP>::Clie...
method create_connection (line 547) | std::shared_ptr<Connection> create_connection() override {
method connect (line 551) | static void connect(const std::shared_ptr<Session> &session) {
type SimpleWeb (line 31) | namespace SimpleWeb {
class Client (line 33) | class Client
class ClientBase (line 36) | class ClientBase {
class Content (line 38) | class Content : public std::istream {
method size (line 42) | size_t size() {
method string (line 46) | std::string string() {
method Content (line 54) | Content(asio::streambuf &streambuf) : std::istream(&streambuf), st...
class Response (line 57) | class Response {
method Response (line 71) | Response() : content(content_buffer) {}
class Config (line 74) | class Config {
method Config (line 78) | Config() {}
class Connection (line 90) | class Connection {
method Connection (line 92) | Connection(const std::string &host, unsigned short port, const Con...
class Session (line 113) | class Session {
method Session (line 115) | Session(const std::shared_ptr<asio::io_service> &io_service, const...
method request (line 136) | std::shared_ptr<Response> request(const std::string &method, const s...
method request (line 153) | std::shared_ptr<Response> request(const std::string &method, const s...
method request (line 169) | void request(const std::string &method, const std::string &path, str...
method request (line 211) | void request(const std::string &method, const std::string &path, str...
method request (line 217) | void request(const std::string &method, const std::string &path,
method request (line 223) | void request(const std::string &method, std::function<void(std::shar...
method request (line 228) | void request(const std::string &method, const std::string &path, std...
method request (line 274) | void request(const std::string &method, const std::string &path, std...
method ClientBase (line 286) | ClientBase(const std::string &host_port, unsigned short default_port)
method get_connection (line 293) | std::shared_ptr<Connection> get_connection() {
method create_request_header (line 313) | std::unique_ptr<asio::streambuf> create_request_header(const std::st...
method parse_host_port (line 329) | static std::pair<std::string, unsigned short> parse_host_port(const ...
method get_timeout_timer (line 343) | static std::shared_ptr<asio::deadline_timer> get_timeout_timer(const...
method parse_response_header (line 358) | static void parse_response_header(const std::shared_ptr<Response> &r...
method write (line 384) | static void write(const std::shared_ptr<Session> &session) {
method read (line 398) | static void read(const std::shared_ptr<Session> &session) {
method read_chunked (line 468) | static void read_chunked(const std::shared_ptr<Session> &session, co...
method close (line 527) | static void close(const std::shared_ptr<Session> &session) {
class Client (line 535) | class Client : public ClientBase<socket_type> {}
class Client<HTTP> (line 540) | class Client<HTTP> : public ClientBase<HTTP> {
method Client (line 544) | Client(const std::string &server_port_path) : ClientBase<HTTP>::Clie...
method create_connection (line 547) | std::shared_ptr<Connection> create_connection() override {
method connect (line 551) | static void connect(const std::shared_ptr<Session> &session) {
FILE: client_https.hpp
type SimpleWeb (line 12) | namespace SimpleWeb {
class Client<HTTPS> (line 16) | class Client<HTTPS> : public ClientBase<HTTPS> {
method Client (line 20) | Client(const std::string &server_port_path, bool verify_certificate ...
method create_connection (line 45) | std::shared_ptr<Connection> create_connection() override {
method connect (line 49) | static void connect(const std::shared_ptr<Session> &session) {
method handshake (line 118) | static void handshake(const std::shared_ptr<Session> &session) {
FILE: crypto.hpp
type SimpleWeb (line 17) | namespace SimpleWeb {
function round (line 20) | inline double round(double x) { //custom definition of round() for pos...
class Crypto (line 25) | class Crypto {
class Base64 (line 29) | class Base64 {
method encode (line 31) | static std::string encode(const std::string &ascii) {
method decode (line 63) | static std::string decode(const std::string &base64) {
method to_hex_string (line 85) | static std::string to_hex_string(const std::string &input) {
method md5 (line 93) | static std::string md5(const std::string &input, size_t iterations =...
method md5 (line 105) | static std::string md5(std::istream &stream, size_t iterations = 1) {
method sha1 (line 122) | static std::string sha1(const std::string &input, size_t iterations ...
method sha1 (line 134) | static std::string sha1(std::istream &stream, size_t iterations = 1) {
method sha256 (line 151) | static std::string sha256(const std::string &input, size_t iteration...
method sha256 (line 163) | static std::string sha256(std::istream &stream, size_t iterations = ...
method sha512 (line 180) | static std::string sha512(const std::string &input, size_t iteration...
method sha512 (line 192) | static std::string sha512(std::istream &stream, size_t iterations = ...
method pbkdf2 (line 210) | static std::string pbkdf2(const std::string &password, const std::st...
FILE: http_examples.cpp
function main (line 28) | int main() {
function default_resource_send (line 231) | void default_resource_send(const HttpServer &server, const shared_ptr<Ht...
FILE: https_examples.cpp
function main (line 26) | int main() {
function default_resource_send (line 230) | void default_resource_send(const HttpsServer &server, const shared_ptr<H...
FILE: server_http.hpp
type SimpleWeb (line 13) | namespace SimpleWeb {
class Server (line 43) | class Server
class ServerBase (line 46) | class ServerBase {
class Response (line 50) | class Response : public std::ostream {
method Response (line 57) | Response(const std::shared_ptr<socket_type> &socket) : std::ostrea...
method write_header (line 60) | void write_header(const CaseInsensitiveMultimap &header, size_type...
method size (line 78) | size_t size() {
method write (line 83) | void write(const char_type *ptr, std::streamsize n) {
method write (line 88) | void write(StatusCode status_code = StatusCode::success_ok, const ...
method write (line 94) | void write(StatusCode status_code, const std::string &content, con...
method write (line 102) | void write(StatusCode status_code, std::istream &content, const Ca...
method write (line 113) | void write(const std::string &content, const CaseInsensitiveMultim...
method write (line 118) | void write(std::istream &content, const CaseInsensitiveMultimap &h...
method write (line 123) | void write(const CaseInsensitiveMultimap &header) {
class Content (line 134) | class Content : public std::istream {
method size (line 138) | size_t size() {
method string (line 142) | std::string string() {
method Content (line 150) | Content(asio::streambuf &streambuf) : std::istream(&streambuf), st...
class Request (line 153) | class Request {
method CaseInsensitiveMultimap (line 170) | CaseInsensitiveMultimap parse_query_string() {
method Request (line 179) | Request(const socket_type &socket) : content(streambuf) {
class Config (line 191) | class Config {
method Config (line 194) | Config(unsigned short port) : port(port) {}
class regex_orderable (line 215) | class regex_orderable : public regex::regex {
method regex_orderable (line 219) | regex_orderable(const char *regex_cstr) : regex::regex(regex_cstr)...
method regex_orderable (line 220) | regex_orderable(const std::string ®ex_str) : regex::regex(regex...
method start (line 236) | virtual void start() {
method stop (line 276) | void stop() {
method send (line 283) | void send(const std::shared_ptr<Response> &response, const std::func...
method ServerBase (line 298) | ServerBase(unsigned short port) : config(port) {}
method get_timeout_timer (line 302) | std::shared_ptr<asio::deadline_timer> get_timeout_timer(const std::s...
method read_request_and_content (line 318) | void read_request_and_content(const std::shared_ptr<socket_type> &so...
method parse_request (line 374) | bool parse_request(const std::shared_ptr<Request> &request) const {
method find_resource (line 415) | void find_resource(const std::shared_ptr<socket_type> &socket, const...
method write_response (line 442) | void write_response(const std::shared_ptr<socket_type> &socket, cons...
class Server (line 486) | class Server : public ServerBase<socket_type> {}
class Server<HTTP> (line 491) | class Server<HTTP> : public ServerBase<HTTP> {
method DEPRECATED (line 493) | DEPRECATED Server(unsigned short port, size_t thread_pool_size = 1, ...
method Server (line 500) | Server() : ServerBase<HTTP>::ServerBase(80) {}
method accept (line 503) | void accept() {
type SimpleWeb (line 20) | namespace SimpleWeb {
class Server (line 43) | class Server
class ServerBase (line 46) | class ServerBase {
class Response (line 50) | class Response : public std::ostream {
method Response (line 57) | Response(const std::shared_ptr<socket_type> &socket) : std::ostrea...
method write_header (line 60) | void write_header(const CaseInsensitiveMultimap &header, size_type...
method size (line 78) | size_t size() {
method write (line 83) | void write(const char_type *ptr, std::streamsize n) {
method write (line 88) | void write(StatusCode status_code = StatusCode::success_ok, const ...
method write (line 94) | void write(StatusCode status_code, const std::string &content, con...
method write (line 102) | void write(StatusCode status_code, std::istream &content, const Ca...
method write (line 113) | void write(const std::string &content, const CaseInsensitiveMultim...
method write (line 118) | void write(std::istream &content, const CaseInsensitiveMultimap &h...
method write (line 123) | void write(const CaseInsensitiveMultimap &header) {
class Content (line 134) | class Content : public std::istream {
method size (line 138) | size_t size() {
method string (line 142) | std::string string() {
method Content (line 150) | Content(asio::streambuf &streambuf) : std::istream(&streambuf), st...
class Request (line 153) | class Request {
method CaseInsensitiveMultimap (line 170) | CaseInsensitiveMultimap parse_query_string() {
method Request (line 179) | Request(const socket_type &socket) : content(streambuf) {
class Config (line 191) | class Config {
method Config (line 194) | Config(unsigned short port) : port(port) {}
class regex_orderable (line 215) | class regex_orderable : public regex::regex {
method regex_orderable (line 219) | regex_orderable(const char *regex_cstr) : regex::regex(regex_cstr)...
method regex_orderable (line 220) | regex_orderable(const std::string ®ex_str) : regex::regex(regex...
method start (line 236) | virtual void start() {
method stop (line 276) | void stop() {
method send (line 283) | void send(const std::shared_ptr<Response> &response, const std::func...
method ServerBase (line 298) | ServerBase(unsigned short port) : config(port) {}
method get_timeout_timer (line 302) | std::shared_ptr<asio::deadline_timer> get_timeout_timer(const std::s...
method read_request_and_content (line 318) | void read_request_and_content(const std::shared_ptr<socket_type> &so...
method parse_request (line 374) | bool parse_request(const std::shared_ptr<Request> &request) const {
method find_resource (line 415) | void find_resource(const std::shared_ptr<socket_type> &socket, const...
method write_response (line 442) | void write_response(const std::shared_ptr<socket_type> &socket, cons...
class Server (line 486) | class Server : public ServerBase<socket_type> {}
class Server<HTTP> (line 491) | class Server<HTTP> : public ServerBase<HTTP> {
method DEPRECATED (line 493) | DEPRECATED Server(unsigned short port, size_t thread_pool_size = 1, ...
method Server (line 500) | Server() : ServerBase<HTTP>::ServerBase(80) {}
method accept (line 503) | void accept() {
type SimpleWeb (line 31) | namespace SimpleWeb {
class Server (line 43) | class Server
class ServerBase (line 46) | class ServerBase {
class Response (line 50) | class Response : public std::ostream {
method Response (line 57) | Response(const std::shared_ptr<socket_type> &socket) : std::ostrea...
method write_header (line 60) | void write_header(const CaseInsensitiveMultimap &header, size_type...
method size (line 78) | size_t size() {
method write (line 83) | void write(const char_type *ptr, std::streamsize n) {
method write (line 88) | void write(StatusCode status_code = StatusCode::success_ok, const ...
method write (line 94) | void write(StatusCode status_code, const std::string &content, con...
method write (line 102) | void write(StatusCode status_code, std::istream &content, const Ca...
method write (line 113) | void write(const std::string &content, const CaseInsensitiveMultim...
method write (line 118) | void write(std::istream &content, const CaseInsensitiveMultimap &h...
method write (line 123) | void write(const CaseInsensitiveMultimap &header) {
class Content (line 134) | class Content : public std::istream {
method size (line 138) | size_t size() {
method string (line 142) | std::string string() {
method Content (line 150) | Content(asio::streambuf &streambuf) : std::istream(&streambuf), st...
class Request (line 153) | class Request {
method CaseInsensitiveMultimap (line 170) | CaseInsensitiveMultimap parse_query_string() {
method Request (line 179) | Request(const socket_type &socket) : content(streambuf) {
class Config (line 191) | class Config {
method Config (line 194) | Config(unsigned short port) : port(port) {}
class regex_orderable (line 215) | class regex_orderable : public regex::regex {
method regex_orderable (line 219) | regex_orderable(const char *regex_cstr) : regex::regex(regex_cstr)...
method regex_orderable (line 220) | regex_orderable(const std::string ®ex_str) : regex::regex(regex...
method start (line 236) | virtual void start() {
method stop (line 276) | void stop() {
method send (line 283) | void send(const std::shared_ptr<Response> &response, const std::func...
method ServerBase (line 298) | ServerBase(unsigned short port) : config(port) {}
method get_timeout_timer (line 302) | std::shared_ptr<asio::deadline_timer> get_timeout_timer(const std::s...
method read_request_and_content (line 318) | void read_request_and_content(const std::shared_ptr<socket_type> &so...
method parse_request (line 374) | bool parse_request(const std::shared_ptr<Request> &request) const {
method find_resource (line 415) | void find_resource(const std::shared_ptr<socket_type> &socket, const...
method write_response (line 442) | void write_response(const std::shared_ptr<socket_type> &socket, cons...
class Server (line 486) | class Server : public ServerBase<socket_type> {}
class Server<HTTP> (line 491) | class Server<HTTP> : public ServerBase<HTTP> {
method DEPRECATED (line 493) | DEPRECATED Server(unsigned short port, size_t thread_pool_size = 1, ...
method Server (line 500) | Server() : ServerBase<HTTP>::ServerBase(80) {}
method accept (line 503) | void accept() {
type SimpleWeb (line 36) | namespace SimpleWeb {
class Server (line 43) | class Server
class ServerBase (line 46) | class ServerBase {
class Response (line 50) | class Response : public std::ostream {
method Response (line 57) | Response(const std::shared_ptr<socket_type> &socket) : std::ostrea...
method write_header (line 60) | void write_header(const CaseInsensitiveMultimap &header, size_type...
method size (line 78) | size_t size() {
method write (line 83) | void write(const char_type *ptr, std::streamsize n) {
method write (line 88) | void write(StatusCode status_code = StatusCode::success_ok, const ...
method write (line 94) | void write(StatusCode status_code, const std::string &content, con...
method write (line 102) | void write(StatusCode status_code, std::istream &content, const Ca...
method write (line 113) | void write(const std::string &content, const CaseInsensitiveMultim...
method write (line 118) | void write(std::istream &content, const CaseInsensitiveMultimap &h...
method write (line 123) | void write(const CaseInsensitiveMultimap &header) {
class Content (line 134) | class Content : public std::istream {
method size (line 138) | size_t size() {
method string (line 142) | std::string string() {
method Content (line 150) | Content(asio::streambuf &streambuf) : std::istream(&streambuf), st...
class Request (line 153) | class Request {
method CaseInsensitiveMultimap (line 170) | CaseInsensitiveMultimap parse_query_string() {
method Request (line 179) | Request(const socket_type &socket) : content(streambuf) {
class Config (line 191) | class Config {
method Config (line 194) | Config(unsigned short port) : port(port) {}
class regex_orderable (line 215) | class regex_orderable : public regex::regex {
method regex_orderable (line 219) | regex_orderable(const char *regex_cstr) : regex::regex(regex_cstr)...
method regex_orderable (line 220) | regex_orderable(const std::string ®ex_str) : regex::regex(regex...
method start (line 236) | virtual void start() {
method stop (line 276) | void stop() {
method send (line 283) | void send(const std::shared_ptr<Response> &response, const std::func...
method ServerBase (line 298) | ServerBase(unsigned short port) : config(port) {}
method get_timeout_timer (line 302) | std::shared_ptr<asio::deadline_timer> get_timeout_timer(const std::s...
method read_request_and_content (line 318) | void read_request_and_content(const std::shared_ptr<socket_type> &so...
method parse_request (line 374) | bool parse_request(const std::shared_ptr<Request> &request) const {
method find_resource (line 415) | void find_resource(const std::shared_ptr<socket_type> &socket, const...
method write_response (line 442) | void write_response(const std::shared_ptr<socket_type> &socket, cons...
class Server (line 486) | class Server : public ServerBase<socket_type> {}
class Server<HTTP> (line 491) | class Server<HTTP> : public ServerBase<HTTP> {
method DEPRECATED (line 493) | DEPRECATED Server(unsigned short port, size_t thread_pool_size = 1, ...
method Server (line 500) | Server() : ServerBase<HTTP>::ServerBase(80) {}
method accept (line 503) | void accept() {
type SimpleWeb (line 41) | namespace SimpleWeb {
class Server (line 43) | class Server
class ServerBase (line 46) | class ServerBase {
class Response (line 50) | class Response : public std::ostream {
method Response (line 57) | Response(const std::shared_ptr<socket_type> &socket) : std::ostrea...
method write_header (line 60) | void write_header(const CaseInsensitiveMultimap &header, size_type...
method size (line 78) | size_t size() {
method write (line 83) | void write(const char_type *ptr, std::streamsize n) {
method write (line 88) | void write(StatusCode status_code = StatusCode::success_ok, const ...
method write (line 94) | void write(StatusCode status_code, const std::string &content, con...
method write (line 102) | void write(StatusCode status_code, std::istream &content, const Ca...
method write (line 113) | void write(const std::string &content, const CaseInsensitiveMultim...
method write (line 118) | void write(std::istream &content, const CaseInsensitiveMultimap &h...
method write (line 123) | void write(const CaseInsensitiveMultimap &header) {
class Content (line 134) | class Content : public std::istream {
method size (line 138) | size_t size() {
method string (line 142) | std::string string() {
method Content (line 150) | Content(asio::streambuf &streambuf) : std::istream(&streambuf), st...
class Request (line 153) | class Request {
method CaseInsensitiveMultimap (line 170) | CaseInsensitiveMultimap parse_query_string() {
method Request (line 179) | Request(const socket_type &socket) : content(streambuf) {
class Config (line 191) | class Config {
method Config (line 194) | Config(unsigned short port) : port(port) {}
class regex_orderable (line 215) | class regex_orderable : public regex::regex {
method regex_orderable (line 219) | regex_orderable(const char *regex_cstr) : regex::regex(regex_cstr)...
method regex_orderable (line 220) | regex_orderable(const std::string ®ex_str) : regex::regex(regex...
method start (line 236) | virtual void start() {
method stop (line 276) | void stop() {
method send (line 283) | void send(const std::shared_ptr<Response> &response, const std::func...
method ServerBase (line 298) | ServerBase(unsigned short port) : config(port) {}
method get_timeout_timer (line 302) | std::shared_ptr<asio::deadline_timer> get_timeout_timer(const std::s...
method read_request_and_content (line 318) | void read_request_and_content(const std::shared_ptr<socket_type> &so...
method parse_request (line 374) | bool parse_request(const std::shared_ptr<Request> &request) const {
method find_resource (line 415) | void find_resource(const std::shared_ptr<socket_type> &socket, const...
method write_response (line 442) | void write_response(const std::shared_ptr<socket_type> &socket, cons...
class Server (line 486) | class Server : public ServerBase<socket_type> {}
class Server<HTTP> (line 491) | class Server<HTTP> : public ServerBase<HTTP> {
method DEPRECATED (line 493) | DEPRECATED Server(unsigned short port, size_t thread_pool_size = 1, ...
method Server (line 500) | Server() : ServerBase<HTTP>::ServerBase(80) {}
method accept (line 503) | void accept() {
FILE: server_https.hpp
type SimpleWeb (line 15) | namespace SimpleWeb {
class Server<HTTPS> (line 19) | class Server<HTTPS> : public ServerBase<HTTPS> {
method DEPRECATED (line 24) | DEPRECATED Server(unsigned short port, size_t thread_pool_size, cons...
method Server (line 33) | Server(const std::string &cert_file, const std::string &private_key_...
method start (line 45) | void start() {
method accept (line 59) | void accept() {
FILE: status_code.hpp
type SimpleWeb (line 7) | namespace SimpleWeb {
type StatusCode (line 8) | enum class StatusCode {
function StatusCode (line 140) | inline StatusCode status_code(const std::string &status_code_str) {
FILE: tests/crypto_test.cpp
function main (line 34) | int main() {
FILE: tests/io_test.cpp
function main (line 11) | int main() {
FILE: tests/parse_test.cpp
class ServerTest (line 9) | class ServerTest : public ServerBase<HTTP> {
method ServerTest (line 11) | ServerTest() : ServerBase<HTTP>::ServerBase(8080) {}
method accept (line 13) | void accept() {}
method parse_request_test (line 15) | void parse_request_test() {
class ClientTest (line 54) | class ClientTest : public ClientBase<HTTP> {
method ClientTest (line 56) | ClientTest(const std::string &server_port_path) : ClientBase<HTTP>::Cl...
method create_connection (line 58) | std::shared_ptr<Connection> create_connection() override {
method constructor_parse_test1 (line 62) | void constructor_parse_test1() {
method constructor_parse_test2 (line 67) | void constructor_parse_test2() {
method parse_response_header_test (line 72) | void parse_response_header_test() {
function main (line 109) | int main() {
FILE: utility.hpp
type SimpleWeb (line 20) | namespace SimpleWeb {
function case_insensitive_equal (line 23) | inline bool case_insensitive_equal(const std::string &str1, const std:...
class CaseInsensitiveEqual (line 29) | class CaseInsensitiveEqual {
class CaseInsensitiveHash (line 36) | class CaseInsensitiveHash {
class Percent (line 51) | class Percent {
method encode (line 54) | static std::string encode(const std::string &value) {
method decode (line 73) | static std::string decode(const std::string &value) {
class QueryString (line 96) | class QueryString {
method create (line 99) | static std::string create(const CaseInsensitiveMultimap &fields) {
method CaseInsensitiveMultimap (line 112) | static CaseInsensitiveMultimap parse(const std::string &query_string) {
function DEPRECATED (line 150) | DEPRECATED inline CaseInsensitiveMultimap parse_query_string(const std...
Condensed preview — 21 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (123K chars).
[
{
"path": ".clang-format",
"chars": 222,
"preview": "IndentWidth: 2\nAccessModifierOffset: -2\nUseTab: Never\nColumnLimit: 0\nMaxEmptyLinesToKeep: 2\nSpaceBeforeParens: Never\nBre"
},
{
"path": ".gitignore",
"chars": 346,
"preview": "# https://github.com/github/gitignore/blob/master/CMake.gitignore\nCMakeCache.txt\nCMakeFiles\nCMakeScripts\nMakefile\ncmake_"
},
{
"path": ".travis.yml",
"chars": 830,
"preview": "sudo: required\n\nservices:\n - docker\n\nscript:\n - sudo docker run -it -v \"$PWD:/repository\" debian:testing sh -c \"\n "
},
{
"path": "CMakeLists.txt",
"chars": 1873,
"preview": "cmake_minimum_required (VERSION 2.8.8)\nproject (Simple-Web-Server)\nset(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -std=c++11 -W"
},
{
"path": "LICENSE",
"chars": 1093,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2014-2016 Ole Christian Eidheim\n\nPermission is hereby granted, free of charge, to a"
},
{
"path": "README.md",
"chars": 1990,
"preview": "Simple-Web-Server [](https://travis-ci"
},
{
"path": "client_http.hpp",
"chars": 23128,
"preview": "#ifndef CLIENT_HTTP_HPP\n#define CLIENT_HTTP_HPP\n\n#include \"utility.hpp\"\n#include <mutex>\n#include <random>\n#include <vec"
},
{
"path": "client_https.hpp",
"chars": 5522,
"preview": "#ifndef CLIENT_HTTPS_HPP\n#define CLIENT_HTTPS_HPP\n\n#include \"client_http.hpp\"\n\n#ifdef USE_STANDALONE_ASIO\n#include <asio"
},
{
"path": "crypto.hpp",
"chars": 7745,
"preview": "#ifndef SIMPLE_WEB_CRYPTO_HPP\n#define SIMPLE_WEB_CRYPTO_HPP\n\n#include <cmath>\n#include <iomanip>\n#include <istream>\n#inc"
},
{
"path": "http_examples.cpp",
"chars": 9605,
"preview": "#include \"client_http.hpp\"\n#include \"server_http.hpp\"\n\n//Added for the json-example\n#define BOOST_SPIRIT_THREADSAFE\n#inc"
},
{
"path": "https_examples.cpp",
"chars": 9712,
"preview": "#include \"client_https.hpp\"\n#include \"server_https.hpp\"\n\n//Added for the json-example\n#define BOOST_SPIRIT_THREADSAFE\n#i"
},
{
"path": "server_http.hpp",
"chars": 19456,
"preview": "#ifndef SERVER_HTTP_HPP\n#define SERVER_HTTP_HPP\n\n#include \"utility.hpp\"\n#include <functional>\n#include <iostream>\n#inclu"
},
{
"path": "server_https.hpp",
"chars": 3494,
"preview": "#ifndef SERVER_HTTPS_HPP\n#define SERVER_HTTPS_HPP\n\n#include \"server_http.hpp\"\n\n#ifdef USE_STANDALONE_ASIO\n#include <asio"
},
{
"path": "status_code.hpp",
"chars": 7639,
"preview": "#ifndef SIMPLE_WEB_SERVER_STATUS_CODE_HPP\n#define SIMPLE_WEB_SERVER_STATUS_CODE_HPP\n\n#include <string>\n#include <vector>"
},
{
"path": "tests/CMakeLists.txt",
"chars": 759,
"preview": "set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fno-access-control\")\n\nadd_executable(io_test io_test.cpp)\ntarget_link_libraries"
},
{
"path": "tests/crypto_test.cpp",
"chars": 3336,
"preview": "#include <cassert>\n#include <vector>\n\n#include \"crypto.hpp\"\n\nusing namespace std;\nusing namespace SimpleWeb;\n\nconst vect"
},
{
"path": "tests/io_test.cpp",
"chars": 8461,
"preview": "#include \"client_http.hpp\"\n#include \"server_http.hpp\"\n\n#include <cassert>\n\nusing namespace std;\n\ntypedef SimpleWeb::Serv"
},
{
"path": "tests/parse_test.cpp",
"chars": 6717,
"preview": "#include \"client_http.hpp\"\n#include \"server_http.hpp\"\n#include <cassert>\n#include <iostream>\n\nusing namespace std;\nusing"
},
{
"path": "utility.hpp",
"chars": 4927,
"preview": "#ifndef SIMPLE_WEB_SERVER_UTILITY_HPP\n#define SIMPLE_WEB_SERVER_UTILITY_HPP\n\n#include \"status_code.hpp\"\n#include <iostre"
},
{
"path": "web/index.html",
"chars": 154,
"preview": "<html>\n <head>\n <title>Simple-Web-Server html-file</title>\n </head>\n <body>\n This is the content "
},
{
"path": "web/test.html",
"chars": 153,
"preview": "<html>\n <head>\n <title>Simple-Web-Server html-file</title>\n </head>\n <body>\n This is the content "
}
]
About this extraction
This page contains the full source code of the teslamotors/Simple-Web-Server GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 21 files (114.4 KB), approximately 28.1k tokens, and a symbol index with 143 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.