From f2347a827fcfd7eebaaa552e2b3c2461f8ea45c2 Mon Sep 17 00:00:00 2001 From: Roman Grundkiewicz Date: Mon, 27 Apr 2020 10:34:10 +0100 Subject: [PATCH] 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 --- .gitmodules | 3 + CHANGELOG.md | 2 + VERSION | 2 +- scripts/server/client_example.py | 1 + src/3rd_party/simple-websocket-server | 1 + .../simple-websocket-server/crypto.hpp | 251 ------ .../simple-websocket-server/server_ws.hpp | 823 ------------------ .../simple-websocket-server/status_code.hpp | 191 ---- .../simple-websocket-server/utility.hpp | 381 -------- src/command/marian_server.cpp | 4 +- 10 files changed, 10 insertions(+), 1649 deletions(-) create mode 160000 src/3rd_party/simple-websocket-server delete mode 100644 src/3rd_party/simple-websocket-server/crypto.hpp delete mode 100644 src/3rd_party/simple-websocket-server/server_ws.hpp delete mode 100644 src/3rd_party/simple-websocket-server/status_code.hpp delete mode 100644 src/3rd_party/simple-websocket-server/utility.hpp diff --git a/.gitmodules b/.gitmodules index b7c67bef..6cb63fc0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,6 @@ path = src/3rd_party/fbgemm url = https://github.com/marian-nmt/FBGEMM 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 12c88afc..9473d372 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. ### Fixed +- Fix building server with Boost 1.72 - 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) - 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. ### Changed +- Move Simple-WebSocket-Server to submodule - Python scripts start with #!/usr/bin/env python3 instead of python - Changed compile flags -Ofast to -O3 and remove --ffinite-math - Moved old graph groups to depracated folder diff --git a/VERSION b/VERSION index b0376728..dd63e963 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.9.8 +v1.9.9 diff --git a/scripts/server/client_example.py b/scripts/server/client_example.py index 4c194d74..a68ffc2a 100755 --- a/scripts/server/client_example.py +++ b/scripts/server/client_example.py @@ -6,6 +6,7 @@ import sys import time import argparse +# pip install websocket_client from websocket import create_connection diff --git a/src/3rd_party/simple-websocket-server b/src/3rd_party/simple-websocket-server new file mode 160000 index 00000000..417a2a9e --- /dev/null +++ b/src/3rd_party/simple-websocket-server @@ -0,0 +1 @@ +Subproject commit 417a2a9e9dbd720b8d2dfa1dafe57cf1b37ca0d7 diff --git a/src/3rd_party/simple-websocket-server/crypto.hpp b/src/3rd_party/simple-websocket-server/crypto.hpp deleted file mode 100644 index 0e3dbf34..00000000 --- a/src/3rd_party/simple-websocket-server/crypto.hpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -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(round(4 * ceil(static_cast(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(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(base64.size())); -#else - bio = BIO_new_mem_buf(&base64[0], static_cast(base64.size())); -#endif - bio = BIO_push(b64, bio); - - auto decoded_length = BIO_read(bio, &ascii[0], static_cast(ascii.size())); - if(decoded_length > 0) - ascii.resize(static_cast(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(static_cast(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(&input[0]), input.size(), reinterpret_cast(&hash[0])); - - for(std::size_t c = 1; c < iterations; ++c) - MD5(reinterpret_cast(&hash[0]), hash.size(), reinterpret_cast(&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 buffer(buffer_size); - while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0) - MD5_Update(&context, buffer.data(), static_cast(read_length)); - std::string hash; - hash.resize(128 / 8); - MD5_Final(reinterpret_cast(&hash[0]), &context); - - for(std::size_t c = 1; c < iterations; ++c) - MD5(reinterpret_cast(&hash[0]), hash.size(), reinterpret_cast(&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(&input[0]), input.size(), reinterpret_cast(&hash[0])); - - for(std::size_t c = 1; c < iterations; ++c) - SHA1(reinterpret_cast(&hash[0]), hash.size(), reinterpret_cast(&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 buffer(buffer_size); - while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0) - SHA1_Update(&context, buffer.data(), static_cast(read_length)); - std::string hash; - hash.resize(160 / 8); - SHA1_Final(reinterpret_cast(&hash[0]), &context); - - for(std::size_t c = 1; c < iterations; ++c) - SHA1(reinterpret_cast(&hash[0]), hash.size(), reinterpret_cast(&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(&input[0]), input.size(), reinterpret_cast(&hash[0])); - - for(std::size_t c = 1; c < iterations; ++c) - SHA256(reinterpret_cast(&hash[0]), hash.size(), reinterpret_cast(&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 buffer(buffer_size); - while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0) - SHA256_Update(&context, buffer.data(), static_cast(read_length)); - std::string hash; - hash.resize(256 / 8); - SHA256_Final(reinterpret_cast(&hash[0]), &context); - - for(std::size_t c = 1; c < iterations; ++c) - SHA256(reinterpret_cast(&hash[0]), hash.size(), reinterpret_cast(&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(&input[0]), input.size(), reinterpret_cast(&hash[0])); - - for(std::size_t c = 1; c < iterations; ++c) - SHA512(reinterpret_cast(&hash[0]), hash.size(), reinterpret_cast(&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 buffer(buffer_size); - while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0) - SHA512_Update(&context, buffer.data(), static_cast(read_length)); - std::string hash; - hash.resize(512 / 8); - SHA512_Final(reinterpret_cast(&hash[0]), &context); - - for(std::size_t c = 1; c < iterations; ++c) - SHA512(reinterpret_cast(&hash[0]), hash.size(), reinterpret_cast(&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(key_size)); - PKCS5_PBKDF2_HMAC_SHA1(password.c_str(), (int)password.size(), - reinterpret_cast(salt.c_str()), (int)salt.size(), iterations, - key_size, reinterpret_cast(&key[0])); - return key; - } - }; -} -#endif /* SIMPLE_WEB_CRYPTO_HPP */ diff --git a/src/3rd_party/simple-websocket-server/server_ws.hpp b/src/3rd_party/simple-websocket-server/server_ws.hpp deleted file mode 100644 index 609a8618..00000000 --- a/src/3rd_party/simple-websocket-server/server_ws.hpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef USE_STANDALONE_ASIO -#include -#include -namespace SimpleWeb { - using error_code = std::error_code; - using errc = std::errc; - namespace make_error_code = std; -} // namespace SimpleWeb -#else -#include -#include -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 -namespace SimpleWeb { - namespace regex = boost; -} -#else -#include -namespace SimpleWeb { - namespace regex = std; -} -#endif - -namespace SimpleWeb { - template - class SocketServer; - - template - class SocketServerBase { - public: - class Message : public std::istream { - friend class SocketServerBase; - - 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; - - 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 { - friend class SocketServerBase; - friend class SocketServer; - - public: - Connection(std::unique_ptr &&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 - Connection(std::shared_ptr handler_runner, long timeout_idle, Args &&... args) noexcept - : handler_runner(std::move(handler_runner)), socket(new socket_type(std::forward(args)...)), timeout_idle(timeout_idle), strand(socket->get_io_service()), closed(false) {} - - std::shared_ptr handler_runner; - - std::unique_ptr socket; // Socket must be unique_ptr since asio::ssl::stream is not movable - std::mutex socket_close_mutex; - - asio::streambuf read_buffer; - std::shared_ptr fragmented_message; - - long timeout_idle; - std::unique_ptr timer; - std::mutex timer_mutex; - - void close() noexcept { - error_code ec; - std::unique_lock 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 lock(timer_mutex); - - if(seconds == 0) { - timer = nullptr; - return; - } - - timer = std::unique_ptr(new asio::steady_timer(socket->get_io_service())); - timer->expires_from_now(std::chrono::seconds(seconds)); - std::weak_ptr 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 lock(timer_mutex); - if(timer) { - error_code ec; - timer->cancel(ec); - } - } - - bool generate_handshake(const std::shared_ptr &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 header_stream, std::shared_ptr message_stream, - std::function &&callback) noexcept - : header_stream(std::move(header_stream)), message_stream(std::move(message_stream)), callback(std::move(callback)) {} - std::shared_ptr header_stream; - std::shared_ptr message_stream; - std::function callback; - }; - - std::list 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 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 &send_stream, const std::function &callback = nullptr, - unsigned char fin_rsv_opcode = 129) { - cancel_timeout(); - set_timeout(); - - auto header_stream = std::make_shared(); - - std::size_t length = send_stream->size(); - - header_stream->put(static_cast(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(-1); c--) - header_stream->put((static_cast(length) >> (8 * c)) % 256); - } - else - header_stream->put(static_cast(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 &callback = nullptr) { - // Send close only once (in case close is initiated by server) - if(closed) - return; - closed = true; - - auto send_stream = std::make_shared(); - - 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; - - private: - std::unordered_set> connections; - std::mutex connections_mutex; - - public: - std::function)> on_open; - std::function, std::shared_ptr)> on_message; - std::function, int, const std::string &)> on_close; - std::function, const error_code &)> on_error; - std::function)> on_ping; - std::function)> on_pong; - - std::unordered_set> get_connections() noexcept { - std::unique_lock lock(connections_mutex); - auto copy = connections; - return copy; - } - }; - - class Config { - friend class SocketServerBase; - - 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::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 ®ex_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 endpoint; - - virtual void start() { - if(!io_service) { - io_service = std::make_shared(); - 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(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 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> get_connections() noexcept { - std::unordered_set> all_connections; - for(auto &e : endpoint) { - std::unique_lock 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::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->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 io_service; - - protected: - bool internal_io_service = false; - - std::unique_ptr acceptor; - std::vector threads; - - std::shared_ptr 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->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) { - for(auto ®ex_endpoint : endpoint) { - regex::smatch path_match; - if(regex::regex_match(connection->path, path_match, regex_endpoint.first)) { - auto write_buffer = std::make_shared(); - - 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, ®ex_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, 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 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 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(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 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(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, 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 mask; - istream.read((char *)&mask[0], 4); - - std::shared_ptr 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(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(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(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(); - 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, Endpoint &ep) const { - connection->cancel_timeout(); - connection->set_timeout(); - - { - std::unique_lock lock(ep.connections_mutex); - ep.connections.insert(connection); - } - - if(ep.on_open) - ep.on_open(connection); - } - - void connection_close(const std::shared_ptr &connection, Endpoint &ep, int status, const std::string &reason) const { - connection->cancel_timeout(); - connection->set_timeout(); - - { - std::unique_lock 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, Endpoint &ep, const error_code &ec) const { - connection->cancel_timeout(); - connection->set_timeout(); - - { - std::unique_lock lock(ep.connections_mutex); - ep.connections.erase(connection); - } - - if(ep.on_error) - ep.on_error(connection, ec); - } - }; - - template - class SocketServer : public SocketServerBase {}; - - using WS = asio::ip::tcp::socket; - - template <> - class SocketServer : public SocketServerBase { - public: - SocketServer() noexcept : SocketServerBase(80) {} - - protected: - void accept() override { - std::shared_ptr 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 */ diff --git a/src/3rd_party/simple-websocket-server/status_code.hpp b/src/3rd_party/simple-websocket-server/status_code.hpp deleted file mode 100644 index 81bc5c8d..00000000 --- a/src/3rd_party/simple-websocket-server/status_code.hpp +++ /dev/null @@ -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 -#include -#include -#include - -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 &status_code_strings() { - static const std::map 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 { - 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 diff --git a/src/3rd_party/simple-websocket-server/utility.hpp b/src/3rd_party/simple-websocket-server/utility.hpp deleted file mode 100644 index d2abcf53..00000000 --- a/src/3rd_party/simple-websocket-server/utility.hpp +++ /dev/null @@ -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 -#include -#include -#include -#include - -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 hash; - for(auto c : str) - h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2); - return h; - } - }; - - using CaseInsensitiveMultimap = std::unordered_multimap; - - /// 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(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 -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 -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 count; - - public: - class SharedLock { - friend class ScopeRunner; - std::atomic &count; - SharedLock(std::atomic &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 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(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 diff --git a/src/command/marian_server.cpp b/src/command/marian_server.cpp index e4074bd1..2c364940 100644 --- a/src/command/marian_server.cpp +++ b/src/command/marian_server.cpp @@ -22,10 +22,10 @@ int main(int argc, char **argv) { auto &translate = server.endpoint["^/translate/?$"]; translate.on_message = [&task](Ptr connection, - Ptr message) { + Ptr message) { // Get input text auto inputText = message->string(); - auto sendStream = std::make_shared(); + auto sendStream = std::make_shared(); // Translate timer::Timer timer;