Skip to content
Snippets Groups Projects
Commit c0bfd99e authored by Ole Christian Eidheim's avatar Ole Christian Eidheim Committed by eidheim
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
IndentWidth: 2
AccessModifierOffset: -2
UseTab: Never
ColumnLimit: 0
MaxEmptyLinesToKeep: 2
SpaceBeforeParens: Never
BreakBeforeBraces: Custom
BraceWrapping: {BeforeElse: true, BeforeCatch: true}
NamespaceIndentation: All
AllowShortCaseLabelsOnASingleLine: true
build
cmake_minimum_required (VERSION 3.0)
project (buggy-web-server)
add_compile_options(-std=c++11 -Wall -Wextra)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wthread-safety)
endif()
add_library(simple-web-server INTERFACE)
target_include_directories(simple-web-server INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
find_package(Threads REQUIRED)
target_link_libraries(simple-web-server INTERFACE ${CMAKE_THREAD_LIBS_INIT})
find_package(Boost 1.53.0 COMPONENTS system thread REQUIRED)
target_link_libraries(simple-web-server INTERFACE ${Boost_LIBRARIES})
target_include_directories(simple-web-server INTERFACE ${Boost_INCLUDE_DIR})
add_executable(web_server main.cpp)
target_link_libraries(web_server simple-web-server)
LICENSE 0 → 100644
The MIT License (MIT)
Copyright (c) 2014-2019 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.
# An example, not entirely stable, web server
## Prerequisites
* Linux or MacOS
* Optionally: the C++ IDE [juCi++](https://gitlab.com/cppit/jucipp)
* If you do not install juCi++, you will need to install additional dependencies such as g++, cmake and make
## Installing dependencies
### Debian based distributions
`sudo apt-get install libboost-thread-dev libboost-regex-dev`
### Arch Linux based distributions
`sudo pacman -S boost`
### MacOS
`brew install boost`
## Download
```sh
git clone https://gitlab.stud.iie.ntnu.no/eidheim/buggy-web-server
```
## Compile and run
### Alternative 1
In a terminal:
```sh
juci buggy-web-server
```
In juCi++, choose Compile and Run in the Project menu.
### Alternative 2
In a terminal:
```sh
cd buggy-web-server
mkdir build
cd build
cmake ..
make
./web_server # Press control-c to stop the server
```
## See web page
### Alternative 1
Open `http://localhost:8080` in a web browser
### Alternative 2
In a terminal:
```sh
telnet localhost 8080
GET / HTTP/1.1 # Press enter twice here
```
## Debugging
First, stop the web server (if it is running). In juCi++, choose Kill Last Process in the Project menu.
In a terminal:
```sh
cd build
mkdir debug
cd debug
cmake -DCMAKE_BUILD_TYPE=Debug ../..
make
gdb ./web_server # In MacOS, instead use: lldb ./web_server
run
```
### Backtrace of a stopped process
```sh
bt
```
#ifndef SIMPLE_WEB_ASIO_COMPATIBILITY_HPP
#define SIMPLE_WEB_ASIO_COMPATIBILITY_HPP
#include <memory>
#ifdef USE_STANDALONE_ASIO
#include <asio.hpp>
#include <asio/steady_timer.hpp>
namespace SimpleWeb {
namespace error = asio::error;
using error_code = std::error_code;
using errc = std::errc;
using system_error = std::system_error;
namespace make_error_code = std;
} // namespace SimpleWeb
#else
#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>
namespace SimpleWeb {
namespace asio = boost::asio;
namespace error = asio::error;
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;
} // namespace SimpleWeb
#endif
namespace SimpleWeb {
#if(USE_STANDALONE_ASIO && ASIO_VERSION >= 101300) || BOOST_ASIO_VERSION >= 101300
using io_context = asio::io_context;
using resolver_results = asio::ip::tcp::resolver::results_type;
using async_connect_endpoint = asio::ip::tcp::endpoint;
inline void restart(io_context &context) noexcept {
context.restart();
}
inline asio::ip::address make_address(const std::string &str) noexcept {
return asio::ip::make_address(str);
}
template <typename socket_type>
asio::executor get_socket_executor(socket_type &socket) {
return socket.get_executor();
}
template <typename handler_type>
void async_resolve(asio::ip::tcp::resolver &resolver, const std::pair<std::string, std::string> &host_port, handler_type &&handler) {
resolver.async_resolve(host_port.first, host_port.second, std::forward<handler_type>(handler));
}
#else
using io_context = asio::io_service;
using resolver_results = asio::ip::tcp::resolver::iterator;
using async_connect_endpoint = asio::ip::tcp::resolver::iterator;
inline void restart(io_context &context) noexcept {
context.reset();
}
inline asio::ip::address make_address(const std::string &str) noexcept {
return asio::ip::address::from_string(str);
}
template <typename socket_type>
io_context &get_socket_executor(socket_type &socket) {
return socket.get_io_service();
}
template <typename handler_type>
void async_resolve(asio::ip::tcp::resolver &resolver, const std::pair<std::string, std::string> &host_port, handler_type &&handler) {
resolver.async_resolve(asio::ip::tcp::resolver::query(host_port.first, host_port.second), std::forward<handler_type>(handler));
}
#endif
} // namespace SimpleWeb
#endif /* SIMPLE_WEB_ASIO_COMPATIBILITY_HPP */
This diff is collapsed.
#ifndef SIMPLE_WEB_CLIENT_HTTPS_HPP
#define SIMPLE_WEB_CLIENT_HTTPS_HPP
#include "client_http.hpp"
#ifdef USE_STANDALONE_ASIO
#include <asio/ssl.hpp>
#else
#include <boost/asio/ssl.hpp>
#endif
namespace SimpleWeb {
using HTTPS = asio::ssl::stream<asio::ip::tcp::socket>;
template <>
class Client<HTTPS> : public ClientBase<HTTPS> {
public:
/**
* Constructs a client object.
*
* @param server_port_path Server resource given by host[:port][/path]
* @param verify_certificate Set to true (default) to verify the server's certificate and hostname according to RFC 2818.
* @param certification_file If non-empty, sends the given certification file to server. Requires private_key_file.
* @param private_key_file If non-empty, specifies the file containing the private key for certification_file. Requires certification_file.
* @param verify_file If non-empty, use this certificate authority file to perform verification.
*/
Client(const std::string &server_port_path, bool verify_certificate = true, const std::string &certification_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(certification_file.size() > 0 && private_key_file.size() > 0) {
context.use_certificate_chain_file(certification_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() noexcept override {
return std::make_shared<Connection>(handler_runner, config.timeout, *io_service, context);
}
void connect(const std::shared_ptr<Session> &session) override {
if(!session->connection->socket->lowest_layer().is_open()) {
auto resolver = std::make_shared<asio::ip::tcp::resolver>(*io_service);
async_resolve(*resolver, *host_port, [this, session, resolver](const error_code &ec, resolver_results results) {
auto lock = session->connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec) {
session->connection->set_timeout(this->config.timeout_connect);
asio::async_connect(session->connection->socket->lowest_layer(), results, [this, session, resolver](const error_code &ec, async_connect_endpoint /*endpoint*/) {
session->connection->cancel_timeout();
auto lock = session->connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec) {
asio::ip::tcp::no_delay option(true);
error_code ec;
session->connection->socket->lowest_layer().set_option(option, ec);
if(!this->config.proxy_server.empty()) {
auto write_buffer = std::make_shared<asio::streambuf>();
std::ostream write_stream(write_buffer.get());
auto host_port = this->host + ':' + std::to_string(this->port);
write_stream << "CONNECT " + host_port + " HTTP/1.1\r\n"
<< "Host: " << host_port << "\r\n\r\n";
session->connection->set_timeout(this->config.timeout_connect);
asio::async_write(session->connection->socket->next_layer(), *write_buffer, [this, session, write_buffer](const error_code &ec, std::size_t /*bytes_transferred*/) {
session->connection->cancel_timeout();
auto lock = session->connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec) {
std::shared_ptr<Response> response(new Response(this->config.max_response_streambuf_size));
session->connection->set_timeout(this->config.timeout_connect);
asio::async_read_until(session->connection->socket->next_layer(), response->streambuf, "\r\n\r\n", [this, session, response](const error_code &ec, std::size_t /*bytes_transferred*/) {
session->connection->cancel_timeout();
auto lock = session->connection->handler_runner->continue_lock();
if(!lock)
return;
if(response->streambuf.size() == response->streambuf.max_size()) {
session->callback(make_error_code::make_error_code(errc::message_size));
return;
}
if(!ec) {
if(!ResponseMessage::parse(response->content, response->http_version, response->status_code, response->header))
session->callback(make_error_code::make_error_code(errc::protocol_error));
else {
if(response->status_code.compare(0, 3, "200") != 0)
session->callback(make_error_code::make_error_code(errc::permission_denied));
else
this->handshake(session);
}
}
else
session->callback(ec);
});
}
else
session->callback(ec);
});
}
else
this->handshake(session);
}
else
session->callback(ec);
});
}
else
session->callback(ec);
});
}
else
write(session);
}
void handshake(const std::shared_ptr<Session> &session) {
SSL_set_tlsext_host_name(session->connection->socket->native_handle(), this->host.c_str());
session->connection->set_timeout(this->config.timeout_connect);
session->connection->socket->async_handshake(asio::ssl::stream_base::client, [this, session](const error_code &ec) {
session->connection->cancel_timeout();
auto lock = session->connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec)
this->write(session);
else
session->callback(ec);
});
}
};
} // namespace SimpleWeb
#endif /* SIMPLE_WEB_CLIENT_HTTPS_HPP */
#ifndef SIMPLE_WEB_CRYPTO_HPP
#define SIMPLE_WEB_CRYPTO_HPP
#include <cmath>
#include <iomanip>
#include <istream>
#include <sstream>
#include <string>
#include <vector>
#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) noexcept { // Custom definition of round() for positive numbers
return floor(x + 0.5);
}
#endif
class Crypto {
const static std::size_t buffer_size = 131072;
public:
class Base64 {
public:
/// Returns Base64 encoded string from input string.
static std::string encode(const std::string &input) noexcept {
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
auto base64_length = static_cast<std::size_t>(round(4 * ceil(static_cast<double>(input.size()) / 3.0)));
base64.resize(base64_length);
bptr->length = 0;
bptr->max = base64_length + 1;
bptr->data = &base64[0];
if(BIO_write(b64, &input[0], static_cast<int>(input.size())) <= 0 || BIO_flush(b64) <= 0)
base64.clear();
// To keep &base64[0] through BIO_free_all(b64)
bptr->length = 0;
bptr->max = 0;
bptr->data = nullptr;
BIO_free_all(b64);
return base64;
}
/// Returns Base64 decoded string from base64 input.
static std::string decode(const std::string &base64) noexcept {
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);
// TODO: Remove in 2020
#if(defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER <= 0x1000115fL) || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2080000fL)
bio = BIO_new_mem_buf((char *)&base64[0], static_cast<int>(base64.size()));
#else
bio = BIO_new_mem_buf(&base64[0], static_cast<int>(base64.size()));
#endif
bio = BIO_push(b64, bio);
auto decoded_length = BIO_read(bio, &ascii[0], static_cast<int>(ascii.size()));
if(decoded_length > 0)
ascii.resize(static_cast<std::size_t>(decoded_length));
else
ascii.clear();
BIO_free_all(b64);
return ascii;
}
};
/// Returns hex string from bytes in input string.
static std::string to_hex_string(const std::string &input) noexcept {
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();
}
/// Returns md5 hash value from input string.
static std::string md5(const std::string &input, std::size_t iterations = 1) noexcept {
std::string hash;
hash.resize(128 / 8);
MD5(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));
for(std::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;
}
/// Returns md5 hash value from input stream.
static std::string md5(std::istream &stream, std::size_t iterations = 1) noexcept {
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(), static_cast<std::size_t>(read_length));
std::string hash;
hash.resize(128 / 8);
MD5_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);
for(std::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;
}
/// Returns sha1 hash value from input string.
static std::string sha1(const std::string &input, std::size_t iterations = 1) noexcept {
std::string hash;
hash.resize(160 / 8);
SHA1(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));
for(std::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;
}
/// Returns sha1 hash value from input stream.
static std::string sha1(std::istream &stream, std::size_t iterations = 1) noexcept {
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(), static_cast<std::size_t>(read_length));
std::string hash;
hash.resize(160 / 8);
SHA1_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);
for(std::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;
}
/// Returns sha256 hash value from input string.
static std::string sha256(const std::string &input, std::size_t iterations = 1) noexcept {
std::string hash;
hash.resize(256 / 8);
SHA256(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));
for(std::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;
}
/// Returns sha256 hash value from input stream.
static std::string sha256(std::istream &stream, std::size_t iterations = 1) noexcept {
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(), static_cast<std::size_t>(read_length));
std::string hash;
hash.resize(256 / 8);
SHA256_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);
for(std::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;
}
/// Returns sha512 hash value from input string.
static std::string sha512(const std::string &input, std::size_t iterations = 1) noexcept {
std::string hash;
hash.resize(512 / 8);
SHA512(reinterpret_cast<const unsigned char *>(&input[0]), input.size(), reinterpret_cast<unsigned char *>(&hash[0]));
for(std::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;
}
/// Returns sha512 hash value from input stream.
static std::string sha512(std::istream &stream, std::size_t iterations = 1) noexcept {
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(), static_cast<std::size_t>(read_length));
std::string hash;
hash.resize(512 / 8);
SHA512_Final(reinterpret_cast<unsigned char *>(&hash[0]), &context);
for(std::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;
}
/// Returns PBKDF2 hash value from the given password
/// Input parameter key_size number of bytes of the returned key.
/**
* Returns PBKDF2 derived key from the given password.
*
* @param password The password to derive key from.
* @param salt The salt to be used in the algorithm.
* @param iterations Number of iterations to be used in the algorithm.
* @param key_size Number of bytes of the returned key.
*
* @return The PBKDF2 derived key.
*/
static std::string pbkdf2(const std::string &password, const std::string &salt, int iterations, int key_size) noexcept {
std::string key;
key.resize(static_cast<std::size_t>(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;
}
};
} // namespace SimpleWeb
#endif /* SIMPLE_WEB_CRYPTO_HPP */
main.cpp 0 → 100644
#include "server_http.hpp"
typedef SimpleWeb::Server<SimpleWeb::HTTP> HttpServer;
int main() {
// Construct HTTP-server at port 8080 using 1 thread, and without request timeout
HttpServer server;
server.config.port = 8080;
server.config.timeout_request = 0;
// Response to all GET requests
server.resource["^.*$"]["GET"] = [](std::shared_ptr<HttpServer::Response> response, std::shared_ptr<HttpServer::Request> /*request*/) {
response->write("<h1>The web server is working!</h1>");
};
server.start();
}
mutex.hpp 0 → 100644
// Based on https://clang.llvm.org/docs/ThreadSafetyAnalysis.html
#ifndef SIMPLE_WEB_MUTEX_HPP
#define SIMPLE_WEB_MUTEX_HPP
#include <mutex>
// Enable thread safety attributes only with clang.
#if defined(__clang__) && (!defined(SWIG))
#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
#else
#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op
#endif
#define CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(capability(x))
#define SCOPED_CAPABILITY \
THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
#define GUARDED_BY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
#define PT_GUARDED_BY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))
#define ACQUIRED_BEFORE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))
#define ACQUIRED_AFTER(...) \
THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))
#define REQUIRES(...) \
THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))
#define REQUIRES_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__))
#define ACQUIRE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__))
#define ACQUIRE_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__))
#define RELEASE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))
#define RELEASE_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__))
#define TRY_ACQUIRE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__))
#define TRY_ACQUIRE_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__))
#define EXCLUDES(...) \
THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))
#define ASSERT_CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x))
#define ASSERT_SHARED_CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x))
#define RETURN_CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
#define NO_THREAD_SAFETY_ANALYSIS \
THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
namespace SimpleWeb {
/// Mutex class that is annotated for Clang Thread Safety Analysis.
class CAPABILITY("mutex") Mutex {
std::mutex mutex;
public:
void lock() ACQUIRE() {
mutex.lock();
}
void unlock() RELEASE() {
mutex.unlock();
}
};
/// Scoped mutex guard class that is annotated for Clang Thread Safety Analysis.
class SCOPED_CAPABILITY LockGuard {
Mutex &mutex;
bool locked = true;
public:
LockGuard(Mutex &mutex_) ACQUIRE(mutex_) : mutex(mutex_) {
mutex.lock();
}
void unlock() RELEASE() {
mutex.unlock();
locked = false;
}
~LockGuard() RELEASE() {
if(locked)
mutex.unlock();
}
};
} // namespace SimpleWeb
#endif // SIMPLE_WEB_MUTEX_HPP
This diff is collapsed.
#ifndef SIMPLE_WEB_SERVER_HTTPS_HPP
#define SIMPLE_WEB_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 {
using HTTPS = asio::ssl::stream<asio::ip::tcp::socket>;
template <>
class Server<HTTPS> : public ServerBase<HTTPS> {
bool set_session_id_context = false;
public:
/**
* Constructs a server object.
*
* @param certification_file If non-empty, sends the given certification file to client.
* @param private_key_file Specifies the file containing the private key for certification_file.
* @param verify_file If non-empty, use this certificate authority file to perform verification of client's certificate and hostname according to RFC 2818.
*/
Server(const std::string &certification_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(certification_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;
}
}
protected:
asio::ssl::context context;
void after_bind() override {
if(set_session_id_context) {
// Creating session_id_context from address:port but reversed due to small SSL_MAX_SSL_SESSION_ID_LENGTH
auto session_id_context = std::to_string(acceptor->local_endpoint().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<std::size_t>(session_id_context.size(), SSL_MAX_SSL_SESSION_ID_LENGTH));
}
}
void accept() override {
auto connection = create_connection(*io_service, context);
acceptor->async_accept(connection->socket->lowest_layer(), [this, connection](const error_code &ec) {
auto lock = connection->handler_runner->continue_lock();
if(!lock)
return;
if(ec != error::operation_aborted)
this->accept();
auto session = std::make_shared<Session>(config.max_request_streambuf_size, connection);
if(!ec) {
asio::ip::tcp::no_delay option(true);
error_code ec;
session->connection->socket->lowest_layer().set_option(option, ec);
session->connection->set_timeout(config.timeout_request);
session->connection->socket->async_handshake(asio::ssl::stream_base::server, [this, session](const error_code &ec) {
session->connection->cancel_timeout();
auto lock = session->connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec)
this->read(session);
else if(this->on_error)
this->on_error(session->request, ec);
});
}
else if(this->on_error)
this->on_error(session->request, ec);
});
}
};
} // namespace SimpleWeb
#endif /* SIMPLE_WEB_SERVER_HTTPS_HPP */
#ifndef SIMPLE_WEB_STATUS_CODE_HPP
#define SIMPLE_WEB_STATUS_CODE_HPP
#include <cstdlib>
#include <map>
#include <string>
#include <unordered_map>
#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 std::map<StatusCode, std::string> &status_code_strings() {
static const std::map<StatusCode, std::string> status_code_strings = {
{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_code_strings;
}
inline StatusCode status_code(const std::string &status_code_string) noexcept {
if(status_code_string.size() < 3)
return StatusCode::unknown;
auto number = status_code_string.substr(0, 3);
if(number[0] < '0' || number[0] > '9' || number[1] < '0' || number[1] > '9' || number[2] < '0' || number[2] > '9')
return StatusCode::unknown;
class StringToStatusCode : public std::unordered_map<std::string, SimpleWeb::StatusCode> {
public:
StringToStatusCode() {
for(auto &status_code : status_code_strings())
emplace(status_code.second.substr(0, 3), status_code.first);
}
};
static StringToStatusCode string_to_status_code;
auto pos = string_to_status_code.find(number);
if(pos == string_to_status_code.end())
return static_cast<StatusCode>(atoi(number.c_str()));
return pos->second;
}
inline const std::string &status_code(StatusCode status_code_enum) noexcept {
auto pos = status_code_strings().find(status_code_enum);
if(pos == status_code_strings().end()) {
static std::string empty_string;
return empty_string;
}
return pos->second;
}
} // namespace SimpleWeb
#endif // SIMPLE_WEB_STATUS_CODE_HPP
#ifndef SIMPLE_WEB_UTILITY_HPP
#define SIMPLE_WEB_UTILITY_HPP
#include "status_code.hpp"
#include <atomic>
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#if __cplusplus > 201402L || _MSVC_LANG > 201402L
#include <string_view>
namespace SimpleWeb {
using string_view = std::string_view;
}
#elif !defined(USE_STANDALONE_ASIO)
#include <boost/utility/string_ref.hpp>
namespace SimpleWeb {
using string_view = boost::string_ref;
}
#else
namespace SimpleWeb {
using string_view = const std::string &;
}
#endif
namespace SimpleWeb {
inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) noexcept {
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 noexcept {
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:
std::size_t operator()(const std::string &str) const noexcept {
std::size_t h = 0;
std::hash<int> hash;
for(auto c : str)
h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2);
return h;
}
};
using CaseInsensitiveMultimap = std::unordered_multimap<std::string, std::string, CaseInsensitiveHash, CaseInsensitiveEqual>;
/// Percent encoding and decoding
class Percent {
public:
/// Returns percent-encoded string
static std::string encode(const std::string &value) noexcept {
static auto hex_chars = "0123456789ABCDEF";
std::string result;
result.reserve(value.size()); // Minimum size of result
for(auto &chr : value) {
if(!((chr >= '0' && chr <= '9') || (chr >= 'A' && chr <= 'Z') || (chr >= 'a' && chr <= 'z') || chr == '-' || chr == '.' || chr == '_' || chr == '~'))
result += std::string("%") + hex_chars[static_cast<unsigned char>(chr) >> 4] + hex_chars[static_cast<unsigned char>(chr) & 15];
else
result += chr;
}
return result;
}
/// Returns percent-decoded string
static std::string decode(const std::string &value) noexcept {
std::string result;
result.reserve(value.size() / 3 + (value.size() % 3)); // Minimum size of result
for(std::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) noexcept {
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) noexcept {
CaseInsensitiveMultimap result;
if(query_string.empty())
return result;
std::size_t name_pos = 0;
auto name_end_pos = std::string::npos;
auto value_pos = std::string::npos;
for(std::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 = std::string::npos;
value_pos = std::string::npos;
}
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;
}
};
class HttpHeader {
public:
/// Parse header fields from stream
static CaseInsensitiveMultimap parse(std::istream &stream) noexcept {
CaseInsensitiveMultimap result;
std::string line;
std::size_t param_end;
while(getline(stream, line) && (param_end = line.find(':')) != std::string::npos) {
std::size_t value_start = param_end + 1;
while(value_start + 1 < line.size() && line[value_start] == ' ')
++value_start;
if(value_start < line.size())
result.emplace(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - (line.back() == '\r' ? 1 : 0)));
}
return result;
}
class FieldValue {
public:
class SemicolonSeparatedAttributes {
public:
/// Parse Set-Cookie or Content-Disposition from given header field value.
/// Attribute values are percent-decoded.
static CaseInsensitiveMultimap parse(const std::string &value) {
CaseInsensitiveMultimap result;
std::size_t name_start_pos = std::string::npos;
std::size_t name_end_pos = std::string::npos;
std::size_t value_start_pos = std::string::npos;
for(std::size_t c = 0; c < value.size(); ++c) {
if(name_start_pos == std::string::npos) {
if(value[c] != ' ' && value[c] != ';')
name_start_pos = c;
}
else {
if(name_end_pos == std::string::npos) {
if(value[c] == ';') {
result.emplace(value.substr(name_start_pos, c - name_start_pos), std::string());
name_start_pos = std::string::npos;
}
else if(value[c] == '=')
name_end_pos = c;
}
else {
if(value_start_pos == std::string::npos) {
if(value[c] == '"' && c + 1 < value.size())
value_start_pos = c + 1;
else
value_start_pos = c;
}
else if(value[c] == '"' || value[c] == ';') {
result.emplace(value.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(value.substr(value_start_pos, c - value_start_pos)));
name_start_pos = std::string::npos;
name_end_pos = std::string::npos;
value_start_pos = std::string::npos;
}
}
}
}
if(name_start_pos != std::string::npos) {
if(name_end_pos == std::string::npos)
result.emplace(value.substr(name_start_pos), std::string());
else if(value_start_pos != std::string::npos) {
if(value.back() == '"')
result.emplace(value.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(value.substr(value_start_pos, value.size() - 1)));
else
result.emplace(value.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(value.substr(value_start_pos)));
}
}
return result;
}
};
};
};
class RequestMessage {
public:
/** Parse request line and header fields from a request stream.
*
* @param[in] stream Stream to parse.
* @param[out] method HTTP method.
* @param[out] path Path from request URI.
* @param[out] query_string Query string from request URI.
* @param[out] version HTTP version.
* @param[out] header Header fields.
*
* @return True if stream is parsed successfully, false if not.
*/
static bool parse(std::istream &stream, std::string &method, std::string &path, std::string &query_string, std::string &version, CaseInsensitiveMultimap &header) noexcept {
std::string line;
std::size_t method_end;
if(getline(stream, line) && (method_end = line.find(' ')) != std::string::npos) {
method = line.substr(0, method_end);
std::size_t query_start = std::string::npos;
std::size_t path_and_query_string_end = std::string::npos;
for(std::size_t i = method_end + 1; i < line.size(); ++i) {
if(line[i] == '?' && (i + 1) < line.size())
query_start = i + 1;
else if(line[i] == ' ') {
path_and_query_string_end = i;
break;
}
}
if(path_and_query_string_end != std::string::npos) {
if(query_start != std::string::npos) {
path = line.substr(method_end + 1, query_start - method_end - 2);
query_string = line.substr(query_start, path_and_query_string_end - query_start);
}
else
path = line.substr(method_end + 1, path_and_query_string_end - method_end - 1);
std::size_t protocol_end;
if((protocol_end = line.find('/', path_and_query_string_end + 1)) != std::string::npos) {
if(line.compare(path_and_query_string_end + 1, protocol_end - path_and_query_string_end - 1, "HTTP") != 0)
return false;
version = line.substr(protocol_end + 1, line.size() - protocol_end - 2);
}
else
return false;
header = HttpHeader::parse(stream);
}
else
return false;
}
else
return false;
return true;
}
};
class ResponseMessage {
public:
/** Parse status line and header fields from a response stream.
*
* @param[in] stream Stream to parse.
* @param[out] version HTTP version.
* @param[out] status_code HTTP status code.
* @param[out] header Header fields.
*
* @return True if stream is parsed successfully, false if not.
*/
static bool parse(std::istream &stream, std::string &version, std::string &status_code, CaseInsensitiveMultimap &header) noexcept {
std::string line;
std::size_t version_end;
if(getline(stream, line) && (version_end = line.find(' ')) != std::string::npos) {
if(5 < line.size())
version = line.substr(5, version_end - 5);
else
return false;
if((version_end + 1) < line.size())
status_code = line.substr(version_end + 1, line.size() - (version_end + 1) - (line.back() == '\r' ? 1 : 0));
else
return false;
header = HttpHeader::parse(stream);
}
else
return false;
return true;
}
};
/// Date class working with formats specified in RFC 7231 Date/Time Formats
class Date {
public:
/// Returns the given std::chrono::system_clock::time_point as a string with the following format: Wed, 31 Jul 2019 11:34:23 GMT.
/// Warning: while this function is thread safe with other Date::to_string() calls,
/// it is not thread safe with other functions that include calls to std::gmtime.
static std::string to_string(const std::chrono::system_clock::time_point time_point) noexcept {
static std::string result_cache;
static std::chrono::system_clock::time_point last_time_point;
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
if(std::chrono::duration_cast<std::chrono::seconds>(time_point - last_time_point).count() == 0 && !result_cache.empty())
return result_cache;
last_time_point = time_point;
std::string result;
result.reserve(29);
auto time = std::chrono::system_clock::to_time_t(time_point);
auto gmtime = std::gmtime(&time);
switch(gmtime->tm_wday) {
case 0: result += "Sun, "; break;
case 1: result += "Mon, "; break;
case 2: result += "Tue, "; break;
case 3: result += "Wed, "; break;
case 4: result += "Thu, "; break;
case 5: result += "Fri, "; break;
case 6: result += "Sat, "; break;
}
result += gmtime->tm_mday < 10 ? '0' : static_cast<char>(gmtime->tm_mday / 10 + 48);
result += static_cast<char>(gmtime->tm_mday % 10 + 48);
switch(gmtime->tm_mon) {
case 0: result += " Jan "; break;
case 1: result += " Feb "; break;
case 2: result += " Mar "; break;
case 3: result += " Apr "; break;
case 4: result += " May "; break;
case 5: result += " Jun "; break;
case 6: result += " Jul "; break;
case 7: result += " Aug "; break;
case 8: result += " Sep "; break;
case 9: result += " Oct "; break;
case 10: result += " Nov "; break;
case 11: result += " Dec "; break;
}
auto year = gmtime->tm_year + 1900;
result += static_cast<char>(year / 1000 + 48);
result += static_cast<char>((year / 100) % 10 + 48);
result += static_cast<char>((year / 10) % 10 + 48);
result += static_cast<char>(year % 10 + 48);
result += ' ';
result += gmtime->tm_hour < 10 ? '0' : static_cast<char>(gmtime->tm_hour / 10 + 48);
result += static_cast<char>(gmtime->tm_hour % 10 + 48);
result += ':';
result += gmtime->tm_min < 10 ? '0' : static_cast<char>(gmtime->tm_min / 10 + 48);
result += static_cast<char>(gmtime->tm_min % 10 + 48);
result += ':';
result += gmtime->tm_sec < 10 ? '0' : static_cast<char>(gmtime->tm_sec / 10 + 48);
result += static_cast<char>(gmtime->tm_sec % 10 + 48);
result += " GMT";
result_cache = result;
return result;
}
};
} // namespace SimpleWeb
#ifdef __SSE2__
#include <emmintrin.h>
namespace SimpleWeb {
inline void spin_loop_pause() noexcept { _mm_pause(); }
} // namespace SimpleWeb
// TODO: need verification that the following checks are correct:
#elif defined(_MSC_VER) && _MSC_VER >= 1800 && (defined(_M_X64) || defined(_M_IX86))
#include <intrin.h>
namespace SimpleWeb {
inline void spin_loop_pause() noexcept { _mm_pause(); }
} // namespace SimpleWeb
#else
namespace SimpleWeb {
inline void spin_loop_pause() noexcept {}
} // namespace SimpleWeb
#endif
namespace SimpleWeb {
/// Makes it possible to for instance cancel Asio handlers without stopping asio::io_service.
class ScopeRunner {
/// Scope count that is set to -1 if scopes are to be canceled.
std::atomic<long> count;
public:
class SharedLock {
friend class ScopeRunner;
std::atomic<long> &count;
SharedLock(std::atomic<long> &count) noexcept : count(count) {}
SharedLock &operator=(const SharedLock &) = delete;
SharedLock(const SharedLock &) = delete;
public:
~SharedLock() noexcept {
count.fetch_sub(1);
}
};
ScopeRunner() noexcept : count(0) {}
/// Returns nullptr if scope should be exited, or a shared lock otherwise.
/// The shared lock ensures that a potential destructor call is delayed until all locks are released.
std::unique_ptr<SharedLock> continue_lock() noexcept {
long expected = count;
while(expected >= 0 && !count.compare_exchange_weak(expected, expected + 1))
spin_loop_pause();
if(expected < 0)
return nullptr;
else
return std::unique_ptr<SharedLock>(new SharedLock(count));
}
/// Blocks until all shared locks are released, then prevents future shared locks.
void stop() noexcept {
long expected = 0;
while(!count.compare_exchange_weak(expected, -1)) {
if(expected < 0)
return;
expected = 0;
spin_loop_pause();
}
}
};
} // namespace SimpleWeb
#endif // SIMPLE_WEB_UTILITY_HPP
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment