mirror of
https://github.com/marian-nmt/marian.git
synced 2024-09-11 06:15:56 +03:00
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:
parent
58e316db61
commit
f2347a827f
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -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
|
||||
|
@ -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
|
||||
|
@ -6,6 +6,7 @@ import sys
|
||||
import time
|
||||
import argparse
|
||||
|
||||
# pip install websocket_client
|
||||
from websocket import create_connection
|
||||
|
||||
|
||||
|
1
src/3rd_party/simple-websocket-server
vendored
Submodule
1
src/3rd_party/simple-websocket-server
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 417a2a9e9dbd720b8d2dfa1dafe57cf1b37ca0d7
|
251
src/3rd_party/simple-websocket-server/crypto.hpp
vendored
251
src/3rd_party/simple-websocket-server/crypto.hpp
vendored
@ -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 */
|
823
src/3rd_party/simple-websocket-server/server_ws.hpp
vendored
823
src/3rd_party/simple-websocket-server/server_ws.hpp
vendored
@ -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 ®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<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 ®ex_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, ®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> &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 */
|
@ -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
|
381
src/3rd_party/simple-websocket-server/utility.hpp
vendored
381
src/3rd_party/simple-websocket-server/utility.hpp
vendored
@ -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
|
@ -22,10 +22,10 @@ int main(int argc, char **argv) {
|
||||
auto &translate = server.endpoint["^/translate/?$"];
|
||||
|
||||
translate.on_message = [&task](Ptr<WSServer::Connection> connection,
|
||||
Ptr<WSServer::Message> message) {
|
||||
Ptr<WSServer::InMessage> message) {
|
||||
// Get input text
|
||||
auto inputText = message->string();
|
||||
auto sendStream = std::make_shared<WSServer::SendStream>();
|
||||
auto sendStream = std::make_shared<WSServer::OutMessage>();
|
||||
|
||||
// Translate
|
||||
timer::Timer timer;
|
||||
|
Loading…
Reference in New Issue
Block a user