Compare commits

...
1 Commits
Author SHA1 Message Date
Vladislav Khorev 28fb81ad45 Working with HTTPS 2026-11-09 23:28:18 +06:00
7 changed files with 409 additions and 58 deletions

No files matched your search

+12 -2
View File
@@ -41,7 +41,7 @@ configured player slot connects or disconnects.
`WebsocketGameClient` implements `IGameServer`. `WebsocketGameClient` implements `IGameServer`.
- It connects asynchronously to `ws://127.0.0.1:8080/`. - It connects asynchronously to a `ws://` or `wss://` endpoint (`WebsocketEndpoint::parse`).
- Received messages are queued by the network thread and applied on the game - Received messages are queued by the network thread and applied on the game
thread from `update()`. thread from `update()`.
- It maintains an internal `InternalGameServer` replica so existing snapshot - It maintains an internal `InternalGameServer` replica so existing snapshot
@@ -145,6 +145,13 @@ Networking is enabled for Windows and the Emscripten web client:
replica, and remote-host code, and links Emscripten's `websocket.js` library. replica, and remote-host code, and links Emscripten's `websocket.js` library.
`WebsocketGameClient.cpp` selects the browser WebSocket API on Emscripten; it `WebsocketGameClient.cpp` selects the browser WebSocket API on Emscripten; it
does not use Boost.Asio or Boost.Beast there. does not use Boost.Asio or Boost.Beast there.
- `wss://` (TLS) is supported by the Windows client only. `proj-windows` also defines
`NETWORK_TLS` and links static OpenSSL 3.4.1, which CMake downloads and builds
automatically at configure time (`cmake/ThirdParty.cmake`, section 12) using a
downloaded portable Strawberry Perl and the nmake from the installed Visual Studio
(located through `vswhere`). Builds go to `thirdparty/openssl-3.4.1/install-Debug`
and `install-Release`; later configures skip them. `NETWORK_TLS` unset (server tests)
means `wss://` endpoints fail with a logged error.
- Linux and Android builds do not define `NETWORK` and do not compile the - Linux and Android builds do not define `NETWORK` and do not compile the
network transport. network transport.
@@ -196,7 +203,10 @@ submission, and contiguous tick consumption.
not implemented. not implemented.
- There is no authentication or reconnect/replay history. - There is no authentication or reconnect/replay history.
- A hash mismatch is logged but does not yet trigger checkpoint resynchronization. - A hash mismatch is logged but does not yet trigger checkpoint resynchronization.
- The endpoint is hardcoded in the graphical client. - The endpoint is hardcoded in the graphical client (`kMultiplayerServerUrl` in
`Game.cpp`, currently `wss://api.shadowoverbishkek.com/server001/`).
- TLS certificate or hostname problems are logged but deliberately do not abort the
connection. `GameServer` itself has no TLS; it must sit behind a TLS-terminating proxy.
- Multiplayer Lua and dialogue behavior remains unsupported. - Multiplayer Lua and dialogue behavior remains unsupported.
- Determinism is supported for identical executable/content builds. The - Determinism is supported for identical executable/content builds. The
floating-point simulation is not guaranteed bit-identical across platforms floating-point simulation is not guaranteed bit-identical across platforms
+27
View File
@@ -56,3 +56,30 @@ check_and_download("https://github.com/ThePhD/sol2/archive/refs/tags/v3.3.0.zip"
# 11) SDL2_mixer # 11) SDL2_mixer
check_and_download("https://github.com/libsdl-org/SDL_mixer/archive/refs/tags/release-2.8.0.zip" "SDL_mixer-release-2.8.0.zip" "SDL_mixer-release-2.8.0" "CMakeLists.txt") check_and_download("https://github.com/libsdl-org/SDL_mixer/archive/refs/tags/release-2.8.0.zip" "SDL_mixer-release-2.8.0.zip" "SDL_mixer-release-2.8.0" "CMakeLists.txt")
# 12) OpenSSL (TLS for wss://) and a native Windows Perl needed to build it
if(WIN32)
check_and_download("https://github.com/openssl/openssl/releases/download/openssl-3.4.1/openssl-3.4.1.tar.gz" "openssl-3.4.1.tar.gz" "openssl-3.4.1" "Configure")
# Strawberry Perl portable. The zip has no top-level folder, so it is
# extracted into its own directory. (Git's MSYS perl cannot build OpenSSL.)
set(STRAWBERRY_PERL_DIR "${THIRDPARTY_DIR}/strawberry-perl")
set(STRAWBERRY_PERL_ARCHIVE "${THIRDPARTY_DIR}/strawberry-perl-5.42.3.1-64bit-portable.zip")
if(NOT EXISTS "${STRAWBERRY_PERL_DIR}/perl/bin/perl.exe")
if(NOT EXISTS "${STRAWBERRY_PERL_ARCHIVE}")
message(STATUS "Downloading Strawberry Perl...")
file(DOWNLOAD
"https://github.com/StrawberryPerl/Perl-Dist-Strawberry/releases/download/SP_54231_64bit/strawberry-perl-5.42.3.1-64bit-portable.zip"
"${STRAWBERRY_PERL_ARCHIVE}" SHOW_PROGRESS)
endif()
message(STATUS "Extracting Strawberry Perl...")
file(MAKE_DIRECTORY "${STRAWBERRY_PERL_DIR}")
execute_process(
COMMAND ${CMAKE_COMMAND} -E tar xf "${STRAWBERRY_PERL_ARCHIVE}"
WORKING_DIRECTORY "${STRAWBERRY_PERL_DIR}"
)
if(NOT EXISTS "${STRAWBERRY_PERL_DIR}/perl/bin/perl.exe")
message(FATAL_ERROR "Strawberry Perl extraction failed")
endif()
endif()
endif()
+103 -1
View File
@@ -725,4 +725,106 @@ set_target_properties(SDL2_mixer_external_lib PROPERTIES
"$<IF:$<CONFIG:Debug>,${SDL2MIXER_BASE_DIR}-Debug/include,${SDL2MIXER_BASE_DIR}-Release/include>" "$<IF:$<CONFIG:Debug>,${SDL2MIXER_BASE_DIR}-Debug/include,${SDL2MIXER_BASE_DIR}-Release/include>"
INTERFACE_LINK_LIBRARIES INTERFACE_LINK_LIBRARIES
"SDL2_external_lib" "SDL2_external_lib"
) )
# ===========================================
# 12) OpenSSL (3.4.1) - TLS for wss:// (Windows only, static libs)
# ===========================================
if(WIN32)
set(OPENSSL_SRC_DIR "${THIRDPARTY_DIR}/openssl-3.4.1")
set(OPENSSL_BASE_DIR "${OPENSSL_SRC_DIR}/install")
set(_have_openssl TRUE)
foreach(cfg IN LISTS BUILD_CONFIGS)
if(NOT EXISTS "${OPENSSL_BASE_DIR}-${cfg}/lib/libssl.lib" OR
NOT EXISTS "${OPENSSL_BASE_DIR}-${cfg}/lib/libcrypto.lib")
set(_have_openssl FALSE)
endif()
endforeach()
if(NOT _have_openssl)
# Locate Visual Studio's vcvarsall.bat (provides cl, link, nmake).
set(_vswhere "$ENV{ProgramFiles\(x86\)}/Microsoft Visual Studio/Installer/vswhere.exe")
set(_vcvarsall "")
if(EXISTS "${_vswhere}")
execute_process(
COMMAND "${_vswhere}" -latest -products * -property installationPath
OUTPUT_VARIABLE _vs_path
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(_vs_path AND EXISTS "${_vs_path}/VC/Auxiliary/Build/vcvarsall.bat")
set(_vcvarsall "${_vs_path}/VC/Auxiliary/Build/vcvarsall.bat")
endif()
endif()
if(_vcvarsall STREQUAL "")
message(FATAL_ERROR "Visual Studio (vcvarsall.bat) not found; it is required to build OpenSSL")
endif()
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_ossl_vc_arch x64)
set(_ossl_target VC-WIN64A)
else()
set(_ossl_vc_arch x86)
set(_ossl_target VC-WIN32)
endif()
file(TO_NATIVE_PATH "${_vcvarsall}" _vcvarsall_native)
file(TO_NATIVE_PATH "${STRAWBERRY_PERL_DIR}/perl/bin" _perl_bin_native)
file(TO_NATIVE_PATH "${STRAWBERRY_PERL_DIR}/perl/site/bin" _perl_site_native)
foreach(cfg IN LISTS BUILD_CONFIGS)
set(_ossl_build "${OPENSSL_SRC_DIR}/build-${cfg}")
file(REMOVE_RECURSE "${_ossl_build}")
file(MAKE_DIRECTORY "${_ossl_build}")
file(TO_NATIVE_PATH "${_ossl_build}" _ossl_build_native)
file(TO_NATIVE_PATH "${OPENSSL_SRC_DIR}/Configure" _ossl_configure_native)
file(TO_NATIVE_PATH "${OPENSSL_BASE_DIR}-${cfg}" _ossl_prefix_native)
set(_ossl_cfg_flags "")
if(cfg STREQUAL "Debug")
# /MDd: link the dynamic debug CRT like the game (static libs default to /MTd)
set(_ossl_cfg_flags "--debug /MDd")
else()
set(_ossl_cfg_flags "--release /MD")
endif()
# A batch file avoids cmd.exe quoting problems.
set(_ossl_bat "${_ossl_build}/build_openssl.bat")
file(WRITE "${_ossl_bat}"
"@echo off
call \"${_vcvarsall_native}\" ${_ossl_vc_arch} || exit /b 1
set \"PATH=${_perl_bin_native};${_perl_site_native};%PATH%\"
set CL=/MP
cd /d \"${_ossl_build_native}\" || exit /b 1
perl \"${_ossl_configure_native}\" ${_ossl_target} no-shared no-asm no-tests no-docs ${_ossl_cfg_flags} --prefix=\"${_ossl_prefix_native}\" --openssldir=\"${_ossl_prefix_native}/ssl\" || exit /b 1
nmake || exit /b 1
nmake install_sw || exit /b 1
")
log("Building OpenSSL (${cfg}) - this takes several minutes ...")
execute_process(
COMMAND cmd /c "${_ossl_bat}"
RESULT_VARIABLE _ossl_res
)
if(NOT _ossl_res EQUAL 0)
message(FATAL_ERROR "OpenSSL build failed for ${cfg} (see output above)")
endif()
endforeach()
endif()
add_library(openssl_crypto_external_lib UNKNOWN IMPORTED GLOBAL)
set_target_properties(openssl_crypto_external_lib PROPERTIES
IMPORTED_LOCATION_DEBUG "${OPENSSL_BASE_DIR}-Debug/lib/libcrypto.lib"
IMPORTED_LOCATION_RELEASE "${OPENSSL_BASE_DIR}-Release/lib/libcrypto.lib"
INTERFACE_INCLUDE_DIRECTORIES
"$<IF:$<CONFIG:Debug>,${OPENSSL_BASE_DIR}-Debug/include,${OPENSSL_BASE_DIR}-Release/include>"
INTERFACE_LINK_LIBRARIES "ws2_32;crypt32;advapi32;user32;gdi32"
)
add_library(openssl_ssl_external_lib UNKNOWN IMPORTED GLOBAL)
set_target_properties(openssl_ssl_external_lib PROPERTIES
IMPORTED_LOCATION_DEBUG "${OPENSSL_BASE_DIR}-Debug/lib/libssl.lib"
IMPORTED_LOCATION_RELEASE "${OPENSSL_BASE_DIR}-Release/lib/libssl.lib"
INTERFACE_INCLUDE_DIRECTORIES
"$<IF:$<CONFIG:Debug>,${OPENSSL_BASE_DIR}-Debug/include,${OPENSSL_BASE_DIR}-Release/include>"
INTERFACE_LINK_LIBRARIES "openssl_crypto_external_lib"
)
endif()
+3 -1
View File
@@ -143,6 +143,7 @@ target_compile_definitions(ShadowOverBishkekDemo PRIVATE
_WIN32_WINNT=0x0A00 _WIN32_WINNT=0x0A00
PNG_ENABLED PNG_ENABLED
NETWORK NETWORK
NETWORK_TLS
) )
# Линкуем с SDL2main, если он вообще установлен # Линкуем с SDL2main, если он вообще установлен
@@ -159,7 +160,8 @@ target_link_libraries(ShadowOverBishkekDemo PRIVATE
eigen_external_lib eigen_external_lib
boost_external_lib boost_external_lib
sol2_external_lib sol2_external_lib
SDL2_mixer_external_lib SDL2_mixer_external_lib
openssl_ssl_external_lib
) )
# Линкуем OpenGL (Windows) # Линкуем OpenGL (Windows)
+9 -1
View File
@@ -557,7 +557,15 @@ namespace FRG
if (remoteCampaignActive) { if (remoteCampaignActive) {
logger() << "[network] Campaign '" << campaignId logger() << "[network] Campaign '" << campaignId
<< "' selected; creating WebSocket client" << std::endl; << "' selected; creating WebSocket client" << std::endl;
websocketGameClient = std::make_unique<WebsocketGameClient>(); // Use "ws://127.0.0.1:8080/" for a local development server.
static const char* const kMultiplayerServerUrl =
"wss://api.shadowoverbishkek.com/server001/";
WebsocketEndpoint endpoint;
if (!endpoint.parse(kMultiplayerServerUrl)) {
logger() << "[network] Invalid multiplayer server URL: "
<< kMultiplayerServerUrl << std::endl;
}
websocketGameClient = std::make_unique<WebsocketGameClient>(endpoint);
remoteGameServerHost = remoteGameServerHost =
std::make_unique<RemoteGameServerHost>(*websocketGameClient); std::make_unique<RemoteGameServerHost>(*websocketGameClient);
gameServer.use(*websocketGameClient); gameServer.use(*websocketGameClient);
+240 -52
View File
@@ -5,6 +5,21 @@
#else #else
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/beast.hpp> #include <boost/beast.hpp>
#ifdef NETWORK_TLS
#include <wincrypt.h>
// wincrypt.h defines macros that collide with OpenSSL identifiers.
#undef X509_NAME
#undef X509_EXTENSIONS
#undef X509_CERT_PAIR
#undef PKCS7_ISSUER_AND_SERIAL
#undef OCSP_REQUEST
#undef OCSP_RESPONSE
#include <boost/asio/ssl.hpp>
#include <boost/beast/ssl.hpp>
#include <openssl/err.h>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>
#endif
#endif #endif
#include <atomic> #include <atomic>
#include <deque> #include <deque>
@@ -23,19 +38,104 @@ namespace asio = boost::asio;
namespace beast = boost::beast; namespace beast = boost::beast;
namespace websocket = beast::websocket; namespace websocket = beast::websocket;
using tcp = asio::ip::tcp; using tcp = asio::ip::tcp;
#ifdef NETWORK_TLS
// Loads the Windows "ROOT" certificate store into the OpenSSL context.
void loadWindowsRootCertificates(asio::ssl::context& context)
{
HCERTSTORE store = CertOpenSystemStoreA(0, "ROOT");
if (!store) {
std::cerr << "[network] Could not open the Windows root certificate store\n";
return;
}
X509_STORE* x509Store = SSL_CTX_get_cert_store(context.native_handle());
PCCERT_CONTEXT cert = nullptr;
while ((cert = CertEnumCertificatesInStore(store, cert)) != nullptr) {
const unsigned char* data = cert->pbCertEncoded;
X509* x509 = d2i_X509(nullptr, &data, static_cast<long>(cert->cbCertEncoded));
if (x509) {
X509_STORE_add_cert(x509Store, x509);
X509_free(x509);
}
}
CertCloseStore(store, 0);
}
// Certificate problems are logged but deliberately do not abort the connection.
bool logAndAcceptCertificate(bool preverified, asio::ssl::verify_context& context)
{
if (!preverified) {
X509_STORE_CTX* storeContext = context.native_handle();
const int error = X509_STORE_CTX_get_error(storeContext);
const int depth = X509_STORE_CTX_get_error_depth(storeContext);
char subject[256] = "?";
if (X509* cert = X509_STORE_CTX_get_current_cert(storeContext))
X509_NAME_oneline(X509_get_subject_name(cert), subject, sizeof(subject));
std::cerr << "[network] TLS certificate warning (depth " << depth
<< ", subject " << subject << "): "
<< X509_verify_cert_error_string(error)
<< " - continuing anyway\n";
}
return true;
}
#endif
#endif #endif
} // namespace } // namespace
bool WebsocketEndpoint::parse(const std::string& url)
{
bool tls = false;
std::string rest;
if (url.rfind("wss://", 0) == 0) {
tls = true;
rest = url.substr(6);
} else if (url.rfind("ws://", 0) == 0) {
rest = url.substr(5);
} else {
return false;
}
std::string authority = rest;
std::string newPath = "/";
const std::size_t slash = rest.find('/');
if (slash != std::string::npos) {
authority = rest.substr(0, slash);
newPath = rest.substr(slash);
}
std::string newHost = authority;
std::string newPort = tls ? "443" : "80";
if (!authority.empty() && authority.front() == '[') { // [ipv6]:port
const std::size_t close = authority.find(']');
if (close == std::string::npos) return false;
newHost = authority.substr(1, close - 1);
if (close + 1 < authority.size()) {
if (authority[close + 1] != ':') return false;
newPort = authority.substr(close + 2);
}
} else {
const std::size_t colon = authority.rfind(':');
if (colon != std::string::npos) {
newHost = authority.substr(0, colon);
newPort = authority.substr(colon + 1);
}
}
if (newHost.empty() || newPort.empty()) return false;
host = std::move(newHost);
port = std::move(newPort);
path = std::move(newPath);
useTls = tls;
return true;
}
class WebsocketGameClient::Impl { class WebsocketGameClient::Impl {
public: public:
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
Impl(std::string host, std::string port, std::string path) Impl(std::string host, std::string port, std::string path, bool useTls)
: host_(std::move(host)) : host_(std::move(host))
, port_(std::move(port)) , port_(std::move(port))
, path_(std::move(path)) , path_(std::move(path))
{ {
url_ = "ws://" + host_ + ':' + port_ + url_ = std::string(useTls ? "wss://" : "ws://") + host_ + ':' + port_ +
(path_.empty() || path_.front() == '/' ? path_ : "/" + path_); (path_.empty() || path_.front() == '/' ? path_ : "/" + path_);
std::cout << "[network] Opening " << url_ << std::endl; std::cout << "[network] Opening " << url_ << std::endl;
@@ -143,14 +243,38 @@ private:
bool closing_ = false; bool closing_ = false;
std::vector<std::string> messages_; std::vector<std::string> messages_;
#else #else
Impl(std::string host, std::string port, std::string path) Impl(std::string host, std::string port, std::string path, bool useTls)
: resolver_(io_) :
#ifdef NETWORK_TLS
sslContext_(asio::ssl::context::tls_client)
, resolver_(io_)
, secureSocket_(io_, sslContext_)
#else
resolver_(io_)
#endif
, socket_(io_) , socket_(io_)
, host_(std::move(host)) , host_(std::move(host))
, port_(std::move(port)) , port_(std::move(port))
, path_(std::move(path)) , path_(std::move(path))
, useTls_(useTls)
{ {
std::cout << "[network] Resolving ws://" << host_ << ':' << port_ #ifdef NETWORK_TLS
sslContext_.set_options(
asio::ssl::context::default_workarounds |
asio::ssl::context::no_sslv2 | asio::ssl::context::no_sslv3 |
asio::ssl::context::no_tlsv1 | asio::ssl::context::no_tlsv1_1);
loadWindowsRootCertificates(sslContext_);
sslContext_.set_verify_mode(asio::ssl::verify_peer);
sslContext_.set_verify_callback(&logAndAcceptCertificate);
#else
if (useTls_) {
std::cerr << "[network] wss:// requested but TLS support was not "
"compiled in (NETWORK_TLS)" << std::endl;
connectionFailed_.store(true);
return;
}
#endif
std::cout << "[network] Resolving " << scheme() << host_ << ':' << port_
<< path_ << std::endl; << path_ << std::endl;
resolve(); resolve();
thread_ = std::thread([this]() { io_.run(); }); thread_ = std::thread([this]() { io_.run(); });
@@ -184,6 +308,20 @@ private:
} }
private: private:
const char* scheme() const { return useTls_ ? "wss://" : "ws://"; }
template <class F>
void withStream(F&& f)
{
#ifdef NETWORK_TLS
if (useTls_) {
f(secureSocket_);
return;
}
#endif
f(socket_);
}
void resolve() void resolve()
{ {
resolver_.async_resolve( resolver_.async_resolve(
@@ -192,60 +330,97 @@ private:
if (error) return fail("resolve", error); if (error) return fail("resolve", error);
std::cout << "[network] WebSocket host resolved; connecting" std::cout << "[network] WebSocket host resolved; connecting"
<< std::endl; << std::endl;
asio::async_connect( withStream([&](auto& stream) {
socket_.next_layer(), results, asio::async_connect(
[this](beast::error_code connectError, beast::get_lowest_layer(stream), results,
const tcp::endpoint&) { [this](beast::error_code connectError,
if (connectError) return fail("connect", connectError); const tcp::endpoint&) {
std::cout << "[network] TCP connected; starting WebSocket handshake" if (connectError) return fail("connect", connectError);
<< std::endl; std::cout << "[network] TCP connected" << std::endl;
socket_.set_option( startTlsOrWebsocketHandshake();
websocket::stream_base::timeout::suggested( });
beast::role_type::client)); });
socket_.async_handshake(
host_, path_,
[this](beast::error_code handshakeError) {
if (handshakeError)
return fail("handshake", handshakeError);
connected_.store(true);
std::cout
<< "[network] Connected to ws://"
<< host_ << ':' << port_ << path_ << std::endl;
read();
});
});
}); });
} }
void startTlsOrWebsocketHandshake()
{
#ifdef NETWORK_TLS
if (useTls_) {
SSL* ssl = secureSocket_.next_layer().native_handle();
// SNI, and hostname verification (failures are only logged).
SSL_set_tlsext_host_name(ssl, const_cast<char*>(host_.c_str()));
SSL_set1_host(ssl, host_.c_str());
std::cout << "[network] Starting TLS handshake" << std::endl;
secureSocket_.next_layer().async_handshake(
asio::ssl::stream_base::client,
[this](beast::error_code tlsError) {
if (tlsError) return fail("TLS handshake", tlsError);
std::cout << "[network] TLS established" << std::endl;
startWebsocketHandshake();
});
return;
}
#endif
startWebsocketHandshake();
}
void startWebsocketHandshake()
{
const bool defaultPort = port_ == (useTls_ ? "443" : "80");
const std::string hostHeader = defaultPort ? host_ : host_ + ':' + port_;
std::cout << "[network] Starting WebSocket handshake" << std::endl;
withStream([&](auto& stream) {
stream.set_option(
websocket::stream_base::timeout::suggested(
beast::role_type::client));
stream.async_handshake(
hostHeader, path_,
[this](beast::error_code handshakeError) {
if (handshakeError)
return fail("handshake", handshakeError);
connected_.store(true);
std::cout
<< "[network] Connected to " << scheme()
<< host_ << ':' << port_ << path_ << std::endl;
read();
});
});
}
void read() void read()
{ {
socket_.async_read( withStream([this](auto& stream) {
buffer_, stream.async_read(
[this](beast::error_code error, std::size_t) { buffer_,
if (error) { [this](beast::error_code error, std::size_t) {
connected_.store(false); if (error) {
if (error != websocket::error::closed) fail("read", error); connected_.store(false);
return; if (error != websocket::error::closed) fail("read", error);
} return;
std::string message = beast::buffers_to_string(buffer_.data()); }
buffer_.consume(buffer_.size()); std::string message = beast::buffers_to_string(buffer_.data());
{ buffer_.consume(buffer_.size());
std::lock_guard<std::mutex> lock(messagesMutex_); {
messages_.push_back(std::move(message)); std::lock_guard<std::mutex> lock(messagesMutex_);
} messages_.push_back(std::move(message));
read(); }
}); read();
});
});
} }
void write() void write()
{ {
socket_.async_write( withStream([this](auto& stream) {
asio::buffer(outbound_.front()), stream.async_write(
[this](beast::error_code error, std::size_t) { asio::buffer(outbound_.front()),
if (error) return fail("write", error); [this](beast::error_code error, std::size_t) {
outbound_.pop_front(); if (error) return fail("write", error);
if (!outbound_.empty()) write(); outbound_.pop_front();
}); if (!outbound_.empty()) write();
});
});
} }
void fail(const char* operation, beast::error_code error) void fail(const char* operation, beast::error_code error)
@@ -257,13 +432,20 @@ private:
} }
asio::io_context io_; asio::io_context io_;
#ifdef NETWORK_TLS
asio::ssl::context sslContext_;
#endif
tcp::resolver resolver_; tcp::resolver resolver_;
#ifdef NETWORK_TLS
websocket::stream<beast::ssl_stream<tcp::socket>> secureSocket_;
#endif
websocket::stream<tcp::socket> socket_; websocket::stream<tcp::socket> socket_;
beast::flat_buffer buffer_; beast::flat_buffer buffer_;
std::thread thread_; std::thread thread_;
std::string host_; std::string host_;
std::string port_; std::string port_;
std::string path_; std::string path_;
bool useTls_ = false;
std::atomic_bool connected_{false}; std::atomic_bool connected_{false};
std::atomic_bool connectionFailed_{false}; std::atomic_bool connectionFailed_{false};
std::mutex messagesMutex_; std::mutex messagesMutex_;
@@ -273,9 +455,15 @@ private:
}; };
WebsocketGameClient::WebsocketGameClient( WebsocketGameClient::WebsocketGameClient(
std::string host, std::string port, std::string path) std::string host, std::string port, std::string path, bool useTls)
: impl_(std::make_unique<Impl>( : impl_(std::make_unique<Impl>(
std::move(host), std::move(port), std::move(path))) std::move(host), std::move(port), std::move(path), useTls))
{
}
WebsocketGameClient::WebsocketGameClient(const WebsocketEndpoint& endpoint)
: WebsocketGameClient(
endpoint.host, endpoint.port, endpoint.path, endpoint.useTls)
{ {
} }
+15 -1
View File
@@ -11,12 +11,26 @@ namespace FRG {
class RemoteGameServerHost; class RemoteGameServerHost;
// Parsed "ws://host[:port][/path]" or "wss://host[:port][/path]".
struct WebsocketEndpoint {
std::string host = "127.0.0.1";
std::string port = "8080";
std::string path = "/";
bool useTls = false;
// Returns false (and leaves *this untouched) for an unsupported/malformed URL.
// Default ports: 80 for ws://, 443 for wss://.
bool parse(const std::string& url);
};
class WebsocketGameClient final : public IGameServer { class WebsocketGameClient final : public IGameServer {
public: public:
WebsocketGameClient( WebsocketGameClient(
std::string host = "127.0.0.1", std::string host = "127.0.0.1",
std::string port = "8080", std::string port = "8080",
std::string path = "/"); std::string path = "/",
bool useTls = false);
explicit WebsocketGameClient(const WebsocketEndpoint& endpoint);
~WebsocketGameClient(); ~WebsocketGameClient();
WebsocketGameClient(const WebsocketGameClient&) = delete; WebsocketGameClient(const WebsocketGameClient&) = delete;