Update Simple-WebSocket-Server and move it to submodules (#639)

* Fix server build with current boost, move simple-websocket-server to submodule
* Change submodule to marian-nmt/Simple-WebSocket-Server
* Update submodule simple-websocket-server

Co-authored-by: Gleb Tv <glebtv@gmail.com>
This commit is contained in:
Roman Grundkiewicz 2020-04-27 10:34:10 +01:00 committed by GitHub
parent 58e316db61
commit f2347a827f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 10 additions and 1649 deletions

3
.gitmodules vendored
View File

@ -14,3 +14,6 @@
path = src/3rd_party/fbgemm path = src/3rd_party/fbgemm
url = https://github.com/marian-nmt/FBGEMM url = https://github.com/marian-nmt/FBGEMM
branch = master branch = master
[submodule "src/3rd_party/simple-websocket-server"]
path = src/3rd_party/simple-websocket-server
url = https://github.com/marian-nmt/Simple-WebSocket-Server

View File

@ -15,12 +15,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
and translation with options --tsv and --tsv-fields n. and translation with options --tsv and --tsv-fields n.
### Fixed ### Fixed
- Fix building server with Boost 1.72
- Make mini-batch scaling depend on mini-batch-words and not on mini-batch-words-ref - Make mini-batch scaling depend on mini-batch-words and not on mini-batch-words-ref
- In concatenation make sure that we do not multiply 0 with nan (which results in nan) - In concatenation make sure that we do not multiply 0 with nan (which results in nan)
- Change Approx.epsilon(0.01) to Approx.margin(0.001) in unit tests. Tolerance is now - Change Approx.epsilon(0.01) to Approx.margin(0.001) in unit tests. Tolerance is now
absolute and not relative. We assumed incorrectly that epsilon is absolute tolerance. absolute and not relative. We assumed incorrectly that epsilon is absolute tolerance.
### Changed ### Changed
- Move Simple-WebSocket-Server to submodule
- Python scripts start with #!/usr/bin/env python3 instead of python - Python scripts start with #!/usr/bin/env python3 instead of python
- Changed compile flags -Ofast to -O3 and remove --ffinite-math - Changed compile flags -Ofast to -O3 and remove --ffinite-math
- Moved old graph groups to depracated folder - Moved old graph groups to depracated folder

View File

@ -1 +1 @@
v1.9.8 v1.9.9

View File

@ -6,6 +6,7 @@ import sys
import time import time
import argparse import argparse
# pip install websocket_client
from websocket import create_connection from websocket import create_connection

@ -0,0 +1 @@
Subproject commit 417a2a9e9dbd720b8d2dfa1dafe57cf1b37ca0d7

View File

@ -1,251 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2017 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.
*/
#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:
static std::string encode(const std::string &ascii) 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>(ascii.size()) / 3.0)));
base64.resize(base64_length);
bptr->length = 0;
bptr->max = base64_length + 1;
bptr->data = &base64[0];
if(BIO_write(b64, &ascii[0], static_cast<int>(ascii.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;
}
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 OPENSSL_VERSION_NUMBER <= 0x1000115fL
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;
}
};
/// Return 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();
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
/// 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) noexcept {
std::string key;
key.resize(static_cast<std::size_t>(key_size));
PKCS5_PBKDF2_HMAC_SHA1(password.c_str(), (int)password.size(),
reinterpret_cast<const unsigned char *>(salt.c_str()), (int)salt.size(), iterations,
key_size, reinterpret_cast<unsigned char *>(&key[0]));
return key;
}
};
}
#endif /* SIMPLE_WEB_CRYPTO_HPP */

View File

@ -1,823 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2017 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.
*/
#ifndef SERVER_WS_HPP
#define SERVER_WS_HPP
#include "crypto.hpp"
#include "utility.hpp"
#include <array>
#include <atomic>
#include <iostream>
#include <limits>
#include <list>
#include <memory>
#include <mutex>
#include <thread>
#include <unordered_set>
#ifdef USE_STANDALONE_ASIO
#include <asio.hpp>
#include <asio/steady_timer.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>
#include <boost/asio/steady_timer.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 SocketServer;
template <class socket_type>
class SocketServerBase {
public:
class Message : public std::istream {
friend class SocketServerBase<socket_type>;
public:
unsigned char fin_rsv_opcode;
std::size_t size() noexcept {
return length;
}
/// Convenience function to return std::string. The stream buffer is consumed.
std::string string() noexcept {
try {
std::stringstream ss;
ss << rdbuf();
return ss.str();
}
catch(...) {
return std::string();
}
}
private:
Message() noexcept : std::istream(&streambuf), length(0) {}
Message(unsigned char fin_rsv_opcode, std::size_t length) noexcept : std::istream(&streambuf), fin_rsv_opcode(fin_rsv_opcode), length(length) {}
std::size_t length;
asio::streambuf streambuf;
};
/// The buffer is not consumed during send operations.
/// Do not alter while sending.
class SendStream : public std::ostream {
friend class SocketServerBase<socket_type>;
asio::streambuf streambuf;
public:
SendStream() noexcept : std::ostream(&streambuf) {}
/// Returns the size of the buffer
std::size_t size() const noexcept {
return streambuf.size();
}
};
class Connection : public std::enable_shared_from_this<Connection> {
friend class SocketServerBase<socket_type>;
friend class SocketServer<socket_type>;
public:
Connection(std::unique_ptr<socket_type> &&socket) noexcept : socket(std::move(socket)), timeout_idle(0), strand(this->socket->get_io_service()), closed(false) {}
std::string method, path, query_string, http_version;
CaseInsensitiveMultimap header;
regex::smatch path_match;
asio::ip::tcp::endpoint remote_endpoint;
std::string remote_endpoint_address() noexcept {
try {
return remote_endpoint.address().to_string();
}
catch(...) {
return std::string();
}
}
unsigned short remote_endpoint_port() noexcept {
return remote_endpoint.port();
}
private:
template <typename... Args>
Connection(std::shared_ptr<ScopeRunner> handler_runner, long timeout_idle, Args &&... args) noexcept
: handler_runner(std::move(handler_runner)), socket(new socket_type(std::forward<Args>(args)...)), timeout_idle(timeout_idle), strand(socket->get_io_service()), closed(false) {}
std::shared_ptr<ScopeRunner> handler_runner;
std::unique_ptr<socket_type> socket; // Socket must be unique_ptr since asio::ssl::stream<asio::ip::tcp::socket> is not movable
std::mutex socket_close_mutex;
asio::streambuf read_buffer;
std::shared_ptr<Message> fragmented_message;
long timeout_idle;
std::unique_ptr<asio::steady_timer> timer;
std::mutex timer_mutex;
void close() noexcept {
error_code ec;
std::unique_lock<std::mutex> lock(socket_close_mutex); // The following operations seems to be needed to run sequentially
socket->lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ec);
socket->lowest_layer().close(ec);
}
void set_timeout(long seconds = -1) noexcept {
bool use_timeout_idle = false;
if(seconds == -1) {
use_timeout_idle = true;
seconds = timeout_idle;
}
std::unique_lock<std::mutex> lock(timer_mutex);
if(seconds == 0) {
timer = nullptr;
return;
}
timer = std::unique_ptr<asio::steady_timer>(new asio::steady_timer(socket->get_io_service()));
timer->expires_from_now(std::chrono::seconds(seconds));
std::weak_ptr<Connection> connection_weak(this->shared_from_this()); // To avoid keeping Connection instance alive longer than needed
timer->async_wait([connection_weak, use_timeout_idle](const error_code &ec) {
if(!ec) {
if(auto connection = connection_weak.lock()) {
if(use_timeout_idle)
connection->send_close(1000, "idle timeout"); // 1000=normal closure
else
connection->close();
}
}
});
}
void cancel_timeout() noexcept {
std::unique_lock<std::mutex> lock(timer_mutex);
if(timer) {
error_code ec;
timer->cancel(ec);
}
}
bool generate_handshake(const std::shared_ptr<asio::streambuf> &write_buffer) {
std::ostream handshake(write_buffer.get());
auto header_it = header.find("Sec-WebSocket-Key");
if(header_it == header.end())
return false;
static auto ws_magic_string = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
auto sha1 = Crypto::sha1(header_it->second + ws_magic_string);
handshake << "HTTP/1.1 101 Web Socket Protocol Handshake\r\n";
handshake << "Upgrade: websocket\r\n";
handshake << "Connection: Upgrade\r\n";
handshake << "Sec-WebSocket-Accept: " << Crypto::Base64::encode(sha1) << "\r\n";
handshake << "\r\n";
return true;
}
asio::io_service::strand strand;
class SendData {
public:
SendData(std::shared_ptr<SendStream> header_stream, std::shared_ptr<SendStream> message_stream,
std::function<void(const error_code)> &&callback) noexcept
: header_stream(std::move(header_stream)), message_stream(std::move(message_stream)), callback(std::move(callback)) {}
std::shared_ptr<SendStream> header_stream;
std::shared_ptr<SendStream> message_stream;
std::function<void(const error_code)> callback;
};
std::list<SendData> send_queue;
void send_from_queue() {
auto self = this->shared_from_this();
strand.post([self]() {
asio::async_write(*self->socket, self->send_queue.begin()->header_stream->streambuf, self->strand.wrap([self](const error_code &ec, std::size_t /*bytes_transferred*/) {
auto lock = self->handler_runner->continue_lock();
if(!lock)
return;
if(!ec) {
asio::async_write(*self->socket, self->send_queue.begin()->message_stream->streambuf.data(), self->strand.wrap([self](const error_code &ec, std::size_t /*bytes_transferred*/) {
auto lock = self->handler_runner->continue_lock();
if(!lock)
return;
auto send_queued = self->send_queue.begin();
if(send_queued->callback)
send_queued->callback(ec);
if(!ec) {
self->send_queue.erase(send_queued);
if(self->send_queue.size() > 0)
self->send_from_queue();
}
else
self->send_queue.clear();
}));
}
else {
auto send_queued = self->send_queue.begin();
if(send_queued->callback)
send_queued->callback(ec);
self->send_queue.clear();
}
}));
});
}
std::atomic<bool> closed;
void read_remote_endpoint() noexcept {
try {
remote_endpoint = socket->lowest_layer().remote_endpoint();
}
catch(...) {
}
}
public:
/// fin_rsv_opcode: 129=one fragment, text, 130=one fragment, binary, 136=close connection.
/// See http://tools.ietf.org/html/rfc6455#section-5.2 for more information
void send(const std::shared_ptr<SendStream> &send_stream, const std::function<void(const error_code &)> &callback = nullptr,
unsigned char fin_rsv_opcode = 129) {
cancel_timeout();
set_timeout();
auto header_stream = std::make_shared<SendStream>();
std::size_t length = send_stream->size();
header_stream->put(static_cast<char>(fin_rsv_opcode));
// Unmasked (first length byte<128)
if(length >= 126) {
std::size_t num_bytes;
if(length > 0xffff) {
num_bytes = 8;
header_stream->put(127);
}
else {
num_bytes = 2;
header_stream->put(126);
}
for(std::size_t c = num_bytes - 1; c != static_cast<std::size_t>(-1); c--)
header_stream->put((static_cast<unsigned long long>(length) >> (8 * c)) % 256);
}
else
header_stream->put(static_cast<char>(length));
auto self = this->shared_from_this();
strand.post([self, header_stream, send_stream, callback]() {
self->send_queue.emplace_back(header_stream, send_stream, callback);
if(self->send_queue.size() == 1)
self->send_from_queue();
});
}
void send_close(int status, const std::string &reason = "", const std::function<void(const error_code &)> &callback = nullptr) {
// Send close only once (in case close is initiated by server)
if(closed)
return;
closed = true;
auto send_stream = std::make_shared<SendStream>();
send_stream->put((unsigned char)(status >> 8));
send_stream->put((unsigned char)(status % 256));
*send_stream << reason;
// fin_rsv_opcode=136: message close
send(send_stream, callback, 136);
}
};
class Endpoint {
friend class SocketServerBase<socket_type>;
private:
std::unordered_set<std::shared_ptr<Connection>> connections;
std::mutex connections_mutex;
public:
std::function<void(std::shared_ptr<Connection>)> on_open;
std::function<void(std::shared_ptr<Connection>, std::shared_ptr<Message>)> on_message;
std::function<void(std::shared_ptr<Connection>, int, const std::string &)> on_close;
std::function<void(std::shared_ptr<Connection>, const error_code &)> on_error;
std::function<void(std::shared_ptr<Connection>)> on_ping;
std::function<void(std::shared_ptr<Connection>)> on_pong;
std::unordered_set<std::shared_ptr<Connection>> get_connections() noexcept {
std::unique_lock<std::mutex> lock(connections_mutex);
auto copy = connections;
return copy;
}
};
class Config {
friend class SocketServerBase<socket_type>;
private:
Config(unsigned short port) noexcept : port(port) {}
public:
/// Port number to use. Defaults to 80 for HTTP and 443 for HTTPS.
unsigned short port;
/// If io_service is not set, number of threads that the server will use when start() is called.
/// Defaults to 1 thread.
std::size_t thread_pool_size = 1;
/// Timeout on request handling. Defaults to 5 seconds.
long timeout_request = 5;
/// Idle timeout. Defaults to no timeout.
long timeout_idle = 0;
/// Maximum size of incoming messages. Defaults to architecture maximum.
/// Exceeding this limit will result in a message_size error code and the connection will be closed.
std::size_t max_message_size = std::numeric_limits<std::size_t>::max();
/// 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 &regex_str) : regex::regex(regex_str), str(regex_str) {}
bool operator<(const regex_orderable &rhs) const noexcept {
return str < rhs.str;
}
};
public:
/// Warning: do not add or remove endpoints after start() is called
std::map<regex_orderable, Endpoint> endpoint;
virtual void start() {
if(!io_service) {
io_service = std::make_shared<asio::io_service>();
internal_io_service = true;
}
if(io_service->stopped())
io_service->reset();
asio::ip::tcp::endpoint ep;
if(config.address.size() > 0)
ep = asio::ip::tcp::endpoint(asio::ip::address::from_string(config.address), config.port);
else
ep = 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(ep.protocol());
acceptor->set_option(asio::socket_base::reuse_address(config.reuse_address));
acceptor->bind(ep);
acceptor->listen();
accept();
if(internal_io_service) {
// If thread_pool_size>1, start m_io_service.run() in (thread_pool_size-1) threads for thread-pooling
threads.clear();
for(std::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() noexcept {
if(acceptor) {
error_code ec;
acceptor->close(ec);
for(auto &pair : endpoint) {
std::unique_lock<std::mutex> lock(pair.second.connections_mutex);
for(auto &connection : pair.second.connections)
connection->close();
pair.second.connections.clear();
}
if(internal_io_service)
io_service->stop();
}
}
virtual ~SocketServerBase() noexcept {}
std::unordered_set<std::shared_ptr<Connection>> get_connections() noexcept {
std::unordered_set<std::shared_ptr<Connection>> all_connections;
for(auto &e : endpoint) {
std::unique_lock<std::mutex> lock(e.second.connections_mutex);
all_connections.insert(e.second.connections.begin(), e.second.connections.end());
}
return all_connections;
}
/**
* Upgrades a request, from for instance Simple-Web-Server, to a WebSocket connection.
* The parameters are moved to the Connection object.
* See also Server::on_upgrade in the Simple-Web-Server project.
* The socket's io_service is used, thus running start() is not needed.
*
* Example use:
* server.on_upgrade=[&socket_server] (auto socket, auto request) {
* auto connection=std::make_shared<SimpleWeb::SocketServer<SimpleWeb::WS>::Connection>(std::move(socket));
* connection->method=std::move(request->method);
* connection->path=std::move(request->path);
* connection->query_string=std::move(request->query_string);
* connection->http_version=std::move(request->http_version);
* connection->header=std::move(request->header);
* connection->remote_endpoint=std::move(*request->remote_endpoint);
* socket_server.upgrade(connection);
* }
*/
void upgrade(const std::shared_ptr<Connection> &connection) {
connection->handler_runner = handler_runner;
connection->timeout_idle = config.timeout_idle;
write_handshake(connection);
}
/// If you have your own asio::io_service, store its pointer here before running start().
std::shared_ptr<asio::io_service> io_service;
protected:
bool internal_io_service = false;
std::unique_ptr<asio::ip::tcp::acceptor> acceptor;
std::vector<std::thread> threads;
std::shared_ptr<ScopeRunner> handler_runner;
SocketServerBase(unsigned short port) noexcept : config(port), handler_runner(new ScopeRunner()) {}
virtual void accept() = 0;
void read_handshake(const std::shared_ptr<Connection> &connection) {
connection->read_remote_endpoint();
connection->set_timeout(config.timeout_request);
asio::async_read_until(*connection->socket, connection->read_buffer, "\r\n\r\n", [this, connection](const error_code &ec, std::size_t /*bytes_transferred*/) {
connection->cancel_timeout();
auto lock = connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec) {
std::istream stream(&connection->read_buffer);
if(RequestMessage::parse(stream, connection->method, connection->path, connection->query_string, connection->http_version, connection->header))
write_handshake(connection);
}
});
}
void write_handshake(const std::shared_ptr<Connection> &connection) {
for(auto &regex_endpoint : endpoint) {
regex::smatch path_match;
if(regex::regex_match(connection->path, path_match, regex_endpoint.first)) {
auto write_buffer = std::make_shared<asio::streambuf>();
if(connection->generate_handshake(write_buffer)) {
connection->path_match = std::move(path_match);
connection->set_timeout(config.timeout_request);
asio::async_write(*connection->socket, *write_buffer, [this, connection, write_buffer, &regex_endpoint](const error_code &ec, std::size_t /*bytes_transferred*/) {
connection->cancel_timeout();
auto lock = connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec) {
connection_open(connection, regex_endpoint.second);
read_message(connection, regex_endpoint.second);
}
else
connection_error(connection, regex_endpoint.second, ec);
});
}
return;
}
}
}
void read_message(const std::shared_ptr<Connection> &connection, Endpoint &ep) const {
asio::async_read(*connection->socket, connection->read_buffer, asio::transfer_exactly(2), [this, connection, &ep](const error_code &ec, std::size_t bytes_transferred) {
auto lock = connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec) {
if(bytes_transferred == 0) { // TODO: why does this happen sometimes?
read_message(connection, ep);
return;
}
std::istream stream(&connection->read_buffer);
std::array<unsigned char, 2> first_bytes;
stream.read((char *)&first_bytes[0], 2);
unsigned char fin_rsv_opcode = first_bytes[0];
// Close connection if unmasked message from client (protocol error)
if(first_bytes[1] < 128) {
const std::string reason("message from client not masked");
connection->send_close(1002, reason);
connection_close(connection, ep, 1002, reason);
return;
}
std::size_t length = (first_bytes[1] & 127);
if(length == 126) {
// 2 next bytes is the size of content
asio::async_read(*connection->socket, connection->read_buffer, asio::transfer_exactly(2), [this, connection, &ep, fin_rsv_opcode](const error_code &ec, std::size_t /*bytes_transferred*/) {
auto lock = connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec) {
std::istream stream(&connection->read_buffer);
std::array<unsigned char, 2> length_bytes;
stream.read((char *)&length_bytes[0], 2);
std::size_t length = 0;
std::size_t num_bytes = 2;
for(std::size_t c = 0; c < num_bytes; c++)
length += static_cast<std::size_t>(length_bytes[c]) << (8 * (num_bytes - 1 - c));
read_message_content(connection, length, ep, fin_rsv_opcode);
}
else
connection_error(connection, ep, ec);
});
}
else if(length == 127) {
// 8 next bytes is the size of content
asio::async_read(*connection->socket, connection->read_buffer, asio::transfer_exactly(8), [this, connection, &ep, fin_rsv_opcode](const error_code &ec, std::size_t /*bytes_transferred*/) {
auto lock = connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec) {
std::istream stream(&connection->read_buffer);
std::array<unsigned char, 8> length_bytes;
stream.read((char *)&length_bytes[0], 8);
std::size_t length = 0;
std::size_t num_bytes = 8;
for(std::size_t c = 0; c < num_bytes; c++)
length += static_cast<std::size_t>(length_bytes[c]) << (8 * (num_bytes - 1 - c));
read_message_content(connection, length, ep, fin_rsv_opcode);
}
else
connection_error(connection, ep, ec);
});
}
else
read_message_content(connection, length, ep, fin_rsv_opcode);
}
else
connection_error(connection, ep, ec);
});
}
void read_message_content(const std::shared_ptr<Connection> &connection, std::size_t length, Endpoint &ep, unsigned char fin_rsv_opcode) const {
if(length + (connection->fragmented_message ? connection->fragmented_message->length : 0) > config.max_message_size) {
connection_error(connection, ep, make_error_code::make_error_code(errc::message_size));
const int status = 1009;
const std::string reason = "message too big";
connection->send_close(status, reason);
connection_close(connection, ep, status, reason);
return;
}
asio::async_read(*connection->socket, connection->read_buffer, asio::transfer_exactly(4 + length), [this, connection, length, &ep, fin_rsv_opcode](const error_code &ec, std::size_t /*bytes_transferred*/) {
auto lock = connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec) {
std::istream istream(&connection->read_buffer);
// Read mask
std::array<unsigned char, 4> mask;
istream.read((char *)&mask[0], 4);
std::shared_ptr<Message> message;
// If fragmented message
if((fin_rsv_opcode & 0x80) == 0 || (fin_rsv_opcode & 0x0f) == 0) {
if(!connection->fragmented_message) {
connection->fragmented_message = std::shared_ptr<Message>(new Message(fin_rsv_opcode, length));
connection->fragmented_message->fin_rsv_opcode |= 0x80;
}
else
connection->fragmented_message->length += length;
message = connection->fragmented_message;
}
else
message = std::shared_ptr<Message>(new Message(fin_rsv_opcode, length));
std::ostream ostream(&message->streambuf);
for(std::size_t c = 0; c < length; c++)
ostream.put((unsigned char)(istream.get() ^ mask[c % 4]));
// If connection close
if((fin_rsv_opcode & 0x0f) == 8) {
connection->cancel_timeout();
connection->set_timeout();
int status = 0;
if(length >= 2) {
unsigned char byte1 = (unsigned char)(message->get());
unsigned char byte2 = (unsigned char)(message->get());
status = (static_cast<int>(byte1) << 8) + byte2;
}
auto reason = message->string();
connection->send_close(status, reason);
this->connection_close(connection, ep, status, reason);
}
// If ping
else if((fin_rsv_opcode & 0x0f) == 9) {
connection->cancel_timeout();
connection->set_timeout();
// Send pong
auto empty_send_stream = std::make_shared<SendStream>();
connection->send(empty_send_stream, nullptr, fin_rsv_opcode + 1);
if(ep.on_ping)
ep.on_ping(connection);
// Next message
this->read_message(connection, ep);
}
// If pong
else if((fin_rsv_opcode & 0x0f) == 10) {
connection->cancel_timeout();
connection->set_timeout();
if(ep.on_pong)
ep.on_pong(connection);
// Next message
this->read_message(connection, ep);
}
// If fragmented message and not final fragment
else if((fin_rsv_opcode & 0x80) == 0) {
// Next message
this->read_message(connection, ep);
}
else {
connection->cancel_timeout();
connection->set_timeout();
if(ep.on_message)
ep.on_message(connection, message);
// Next message
// Only reset fragmented_message for non-control frames (control frames can be in between a fragmented message)
connection->fragmented_message = nullptr;
this->read_message(connection, ep);
}
}
else
this->connection_error(connection, ep, ec);
});
}
void connection_open(const std::shared_ptr<Connection> &connection, Endpoint &ep) const {
connection->cancel_timeout();
connection->set_timeout();
{
std::unique_lock<std::mutex> lock(ep.connections_mutex);
ep.connections.insert(connection);
}
if(ep.on_open)
ep.on_open(connection);
}
void connection_close(const std::shared_ptr<Connection> &connection, Endpoint &ep, int status, const std::string &reason) const {
connection->cancel_timeout();
connection->set_timeout();
{
std::unique_lock<std::mutex> lock(ep.connections_mutex);
ep.connections.erase(connection);
}
if(ep.on_close)
ep.on_close(connection, status, reason);
}
void connection_error(const std::shared_ptr<Connection> &connection, Endpoint &ep, const error_code &ec) const {
connection->cancel_timeout();
connection->set_timeout();
{
std::unique_lock<std::mutex> lock(ep.connections_mutex);
ep.connections.erase(connection);
}
if(ep.on_error)
ep.on_error(connection, ec);
}
};
template <class socket_type>
class SocketServer : public SocketServerBase<socket_type> {};
using WS = asio::ip::tcp::socket;
template <>
class SocketServer<WS> : public SocketServerBase<WS> {
public:
SocketServer() noexcept : SocketServerBase<WS>(80) {}
protected:
void accept() override {
std::shared_ptr<Connection> connection(new Connection(handler_runner, config.timeout_idle, *io_service));
acceptor->async_accept(*connection->socket, [this, connection](const error_code &ec) {
auto lock = connection->handler_runner->continue_lock();
if(!lock)
return;
// 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);
connection->socket->set_option(option);
read_handshake(connection);
}
});
}
};
} // namespace SimpleWeb
#endif /* SERVER_WS_HPP */

View File

@ -1,191 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2017 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.
*/
#ifndef SIMPLE_WEB_STATUS_CODE_HPP
#define SIMPLE_WEB_STATUS_CODE_HPP
#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 {
class StringToStatusCode : public std::unordered_map<std::string, SimpleWeb::StatusCode> {
public:
StringToStatusCode() {
for(auto &status_code : status_code_strings())
emplace(status_code.second, status_code.first);
}
};
static StringToStatusCode string_to_status_code;
auto pos = string_to_status_code.find(status_code_string);
if(pos == string_to_status_code.end())
return StatusCode::unknown;
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

View File

@ -1,381 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2017 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.
*/
#ifndef SIMPLE_WEB_UTILITY_HPP
#define SIMPLE_WEB_UTILITY_HPP
#include "status_code.hpp"
#include <atomic>
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
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 == ' ')
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) 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
static CaseInsensitiveMultimap parse(std::istream &stream) noexcept {
CaseInsensitiveMultimap result;
std::string line;
getline(stream, line);
std::size_t param_end;
while((param_end = line.find(':')) != std::string::npos) {
std::size_t value_start = param_end + 1;
if(value_start < line.size()) {
if(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 - 1));
}
getline(stream, line);
}
return result;
}
class FieldValue {
public:
class SemicolonSeparatedAttributes {
public:
/// Parse Set-Cookie or Content-Disposition header field value. Attribute values are percent-decoded.
static CaseInsensitiveMultimap parse(const std::string &str) {
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 < str.size(); ++c) {
if(name_start_pos == std::string::npos) {
if(str[c] != ' ' && str[c] != ';')
name_start_pos = c;
}
else {
if(name_end_pos == std::string::npos) {
if(str[c] == ';') {
result.emplace(str.substr(name_start_pos, c - name_start_pos), std::string());
name_start_pos = std::string::npos;
}
else if(str[c] == '=')
name_end_pos = c;
}
else {
if(value_start_pos == std::string::npos) {
if(str[c] == '"' && c + 1 < str.size())
value_start_pos = c + 1;
else
value_start_pos = c;
}
else if(str[c] == '"' || str[c] == ';') {
result.emplace(str.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(str.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(str.substr(name_start_pos), std::string());
else if(value_start_pos != std::string::npos) {
if(str.back() == '"')
result.emplace(str.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(str.substr(value_start_pos, str.size() - 1)));
else
result.emplace(str.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(str.substr(value_start_pos)));
}
}
return result;
}
};
};
}; // namespace SimpleWeb
class RequestMessage {
public:
/// Parse request line and header fields
static bool parse(std::istream &stream, std::string &method, std::string &path, std::string &query_string, std::string &version, CaseInsensitiveMultimap &header) noexcept {
header.clear();
std::string line;
getline(stream, line);
std::size_t method_end;
if((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
static bool parse(std::istream &stream, std::string &version, std::string &status_code, CaseInsensitiveMultimap &header) noexcept {
header.clear();
std::string line;
getline(stream, line);
std::size_t version_end = line.find(' ');
if(version_end != 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) - 1);
else
return false;
header = HttpHeader::parse(stream);
}
else
return false;
return true;
}
};
} // 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
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

View File

@ -22,10 +22,10 @@ int main(int argc, char **argv) {
auto &translate = server.endpoint["^/translate/?$"]; auto &translate = server.endpoint["^/translate/?$"];
translate.on_message = [&task](Ptr<WSServer::Connection> connection, translate.on_message = [&task](Ptr<WSServer::Connection> connection,
Ptr<WSServer::Message> message) { Ptr<WSServer::InMessage> message) {
// Get input text // Get input text
auto inputText = message->string(); auto inputText = message->string();
auto sendStream = std::make_shared<WSServer::SendStream>(); auto sendStream = std::make_shared<WSServer::OutMessage>();
// Translate // Translate
timer::Timer timer; timer::Timer timer;