From 33f499c32d244953bffa71e87bc6994b48c65af4 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Fri, 7 Jun 2024 17:46:48 +0100 Subject: [PATCH 01/41] Begin adding flexible networking --- include/cpprealm/internal/bridge/status.hpp | 2 +- .../internal/generic_network_transport.hpp | 11 +- include/cpprealm/networking/networking.hpp | 163 ++++++++++++ src/CMakeLists.txt | 2 + src/cpprealm/analytics.cpp | 4 +- src/cpprealm/app.cpp | 246 ++++++++++++++++-- src/cpprealm/flex_sync.cpp | 4 - src/cpprealm/internal/bridge/status.cpp | 8 + .../internal/network/network_transport.cpp | 27 +- src/cpprealm/networking/networking.cpp | 45 ++++ tests/admin_utils.cpp | 6 +- tests/main.cpp | 14 +- 12 files changed, 473 insertions(+), 59 deletions(-) create mode 100644 include/cpprealm/networking/networking.hpp create mode 100644 src/cpprealm/networking/networking.cpp diff --git a/include/cpprealm/internal/bridge/status.hpp b/include/cpprealm/internal/bridge/status.hpp index ea5f7d777..eb0a8e00e 100644 --- a/include/cpprealm/internal/bridge/status.hpp +++ b/include/cpprealm/internal/bridge/status.hpp @@ -73,7 +73,7 @@ namespace realm::internal::bridge { bool is_ok() const noexcept; const std::string& reason() const noexcept; std::string_view code_string() const noexcept; - + operator ::realm::Status() const noexcept; private: #ifdef CPPREALM_HAVE_GENERATED_BRIDGE_TYPES storage::Status m_status[1]; diff --git a/include/cpprealm/internal/generic_network_transport.hpp b/include/cpprealm/internal/generic_network_transport.hpp index 65e51d7f1..f8e3b6c59 100644 --- a/include/cpprealm/internal/generic_network_transport.hpp +++ b/include/cpprealm/internal/generic_network_transport.hpp @@ -20,6 +20,7 @@ #define CPPREALM_GENERIC_NETWORK_TRANSPORT_CPP #include +#include #include #include @@ -30,18 +31,20 @@ namespace realm { struct Response; struct GenericNetworkTransport; } + namespace sync { + class SyncSocketProvider; + } } - namespace realm::internal { -class DefaultTransport { +class DefaultTransport : public networking::http_network_transport_client { public: DefaultTransport(const std::optional>& custom_http_headers = std::nullopt, const std::optional& proxy_config = std::nullopt); ~DefaultTransport() = default; - void send_request_to_server(const app::Request& request, - std::function&& completion); + void send_request_to_server(const ::realm::networking::request& request, + std::function&& completion); private: std::optional> m_custom_http_headers; std::optional m_proxy_config; diff --git a/include/cpprealm/networking/networking.hpp b/include/cpprealm/networking/networking.hpp new file mode 100644 index 000000000..b1d823acd --- /dev/null +++ b/include/cpprealm/networking/networking.hpp @@ -0,0 +1,163 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef CPPREALM_NETWORKING_HPP +#define CPPREALM_NETWORKING_HPP + +#include + +namespace realm { + namespace app { + struct Request; + struct Response; + } +} + +namespace realm::networking { + /** + * An HTTP method type. + */ + enum class http_method { get, post, patch, put, del }; + /** + * Request/Response headers type + */ + using http_headers = std::map; + + /** + * An HTTP request that can be made to an arbitrary server. + */ + struct request { + /** + * The HTTP method of this request. + */ + http_method method = http_method::get; + + /** + * The URL to which this request will be made. + */ + std::string url; + + /** + * The number of milliseconds that the underlying transport should spend on an HTTP round trip before failing with + * an error. + */ + uint64_t timeout_ms = 0; + + /** + * The HTTP headers of this request - keys are case insensitive. + */ + http_headers headers; + + /** + * The body of the request. + */ + std::string body; + }; + + /** + * The contents of an HTTP response. + */ + struct response { + /** + * The status code of the HTTP response. + */ + int http_status_code; + + /** + * A custom status code provided by the language binding (SDK). + */ + int custom_status_code; + + /** + * The headers of the HTTP response - keys are case insensitive. + */ + http_headers headers; + + /** + * The body of the HTTP response. + */ + std::string body; + + /** + * An error code used by the client to report http processing errors. + */ + std::optional client_error_code; + + }; + + struct ws_endpoint { + using port_type = std::uint_fast16_t; + + std::string address; // Host address + port_type port; // Host port number + std::string path; // Includes access token in query. + std::vector protocols; // Array of one or more websocket protocols + bool is_ssl; // true if SSL should be used + }; + + enum websocket_err_codes { + RLM_ERR_WEBSOCKET_OK = 1000, + RLM_ERR_WEBSOCKET_GOINGAWAY = 1001, + RLM_ERR_WEBSOCKET_PROTOCOLERROR = 1002, + RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA = 1003, + RLM_ERR_WEBSOCKET_RESERVED = 1004, + RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED = 1005, + RLM_ERR_WEBSOCKET_ABNORMALCLOSURE = 1006, + RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA = 1007, + RLM_ERR_WEBSOCKET_POLICYVIOLATION = 1008, + RLM_ERR_WEBSOCKET_MESSAGETOOBIG = 1009, + RLM_ERR_WEBSOCKET_INAVALIDEXTENSION = 1010, + RLM_ERR_WEBSOCKET_INTERNALSERVERERROR = 1011, + RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED = 1015, + + RLM_ERR_WEBSOCKET_UNAUTHORIZED = 4001, + RLM_ERR_WEBSOCKET_FORBIDDEN = 4002, + RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY = 4003, + RLM_ERR_WEBSOCKET_CLIENT_TOO_OLD = 4004, + RLM_ERR_WEBSOCKET_CLIENT_TOO_NEW = 4005, + RLM_ERR_WEBSOCKET_PROTOCOL_MISMATCH = 4006, + + RLM_ERR_WEBSOCKET_RESOLVE_FAILED = 4400, + RLM_ERR_WEBSOCKET_CONNECTION_FAILED = 4401, + RLM_ERR_WEBSOCKET_READ_ERROR = 4402, + RLM_ERR_WEBSOCKET_WRITE_ERROR = 4403, + RLM_ERR_WEBSOCKET_RETRY_ERROR = 4404, + RLM_ERR_WEBSOCKET_FATAL_ERROR = 4405, + }; + + // Interface for providing http transport + struct http_network_transport_client { + virtual ~http_network_transport_client() = default; + // HTTP + virtual void send_request_to_server(const request& request, + std::function&& completion) = 0; + }; + + struct websocket_event_handler { + std::function on_connect; + }; + + request to_request(const ::realm::app::Request&); + ::realm::app::Request to_core_request(const request&); + + response to_response(const ::realm::app::Response&); + ::realm::app::Response to_core_response(const response&); +} + + +#endif//CPPREALM_NETWORKING_HPP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2d4c55329..68b4292f0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -52,6 +52,7 @@ set(SOURCES cpprealm/internal/bridge/timestamp.cpp cpprealm/internal/bridge/uuid.cpp cpprealm/internal/scheduler/realm_core_scheduler.cpp + cpprealm/networking/networking.cpp cpprealm/schedulers/default_scheduler.cpp cpprealm/logger.cpp cpprealm/sdk.cpp) # REALM_SOURCES @@ -113,6 +114,7 @@ set(HEADERS ../include/cpprealm/internal/type_info.hpp ../include/cpprealm/internal/scheduler/realm_core_scheduler.hpp ../include/cpprealm/schedulers/default_scheduler.hpp + ../include/cpprealm/networking/networking.hpp ../include/cpprealm/logger.hpp ../include/cpprealm/notifications.hpp ../include/cpprealm/rbool.hpp diff --git a/src/cpprealm/analytics.cpp b/src/cpprealm/analytics.cpp index af01c3d37..39b8d93ec 100644 --- a/src/cpprealm/analytics.cpp +++ b/src/cpprealm/analytics.cpp @@ -286,8 +286,8 @@ namespace realm { buffer.resize(s); auto base64_str = std::string(buffer.begin(), buffer.end()); - app::Request request; - request.method = realm::app::HttpMethod::get; + networking::request request; + request.method = networking::http_method::get; request.url = util::format("https://data.mongodb-api.com/app/realmsdkmetrics-zmhtm/endpoint/metric_webhook/metric?data=%1", base64_str); transport->send_request_to_server(std::move(request), [](auto) { // noop diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index 8e74a61d6..b7565c567 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -1,4 +1,5 @@ #include +#include #include #ifndef REALMCXX_VERSION_MAJOR @@ -10,7 +11,9 @@ #include #include #include +#include #include +#include #include @@ -18,34 +21,220 @@ #include #endif -namespace realm { - static_assert((int)user::state::logged_in == (int)SyncUser::State::LoggedIn); - static_assert((int)user::state::logged_out == (int)SyncUser::State::LoggedOut); - static_assert((int)user::state::removed == (int)SyncUser::State::Removed); +namespace realm::networking { + + /// The WebSocket base class that is used by the SyncClient to send data over the + /// WebSocket connection with the server. This is the class that is returned by + /// SyncSocketProvider::connect() when a connection to an endpoint is requested. + /// If an error occurs while establishing the connection, the error is presented + /// to the WebSocketObserver provided when the WebSocket was created. + struct websocket_interface { + /// The destructor must close the websocket connection when the WebSocket object + /// is destroyed + virtual ~websocket_interface() = default; + + using status = internal::bridge::status; + using FunctionHandler = std::function; + + /// Write data asynchronously to the WebSocket connection. The handler function + /// will be called when the data has been sent successfully. The WebSocketOberver + /// provided when the WebSocket was created will be called if any errors occur + /// during the write operation. + /// @param data A util::Span containing the data to be sent to the server. + /// @param handler The handler function to be called when the data has been sent + /// successfully or the websocket has been closed (with + /// ErrorCodes::OperationAborted). If an error occurs during the + /// write operation, the websocket will be closed and the error + /// will be provided via the websocket_closed_handler() function. + virtual void async_write_binary(util::Span data, FunctionHandler&& handler) = 0; + }; - namespace internal { - struct CoreTransport : app::GenericNetworkTransport { - ~CoreTransport() = default; - CoreTransport(const std::optional>& custom_http_headers = std::nullopt, - const std::optional& proxy_config = std::nullopt) { - m_transport = DefaultTransport(custom_http_headers, proxy_config); - } + struct core_websocket_shim : public ::realm::sync::WebSocketInterface { + ~core_websocket_shim() = default; + explicit core_websocket_shim(const std::shared_ptr& ws) : m_interface(ws) {} - void send_request_to_server(const app::Request& request, - util::UniqueFunction&& completion) { - auto completion_ptr = completion.release(); - m_transport.send_request_to_server(request, - [f = std::move(completion_ptr)] - (const app::Response& response) { - auto uf = util::UniqueFunction(std::move(f)); - uf(response); - }); - } + void async_write_binary(util::Span data, sync::SyncSocketProvider::FunctionHandler&& handler) override { + auto handler_ptr = handler.release(); + m_interface->async_write_binary(data, [ptr = std::move(handler_ptr)](websocket_interface::status s) { + auto uf = util::UniqueFunction(std::move(ptr)); + return uf(s.operator ::realm::Status()); + }); + }; - private: - DefaultTransport m_transport; + std::shared_ptr m_interface; + }; + + struct websocket_shim : public websocket_interface { + ~websocket_shim() = default; + void async_write_binary(util::Span data, websocket_interface::FunctionHandler&& handler) override { + m_interface->async_write_binary(data, [fn = std::move(handler)](auto s) { + fn(s); + }); }; - } + + std::shared_ptr<::realm::sync::WebSocketInterface> m_interface; + }; + + + /// WebSocket observer interface in the SyncClient that receives the websocket + /// events during operation. + struct websocket_observer { + virtual ~websocket_observer() = default; + + /// Called when the websocket is connected, i.e. after the handshake is done. + /// The Sync Client is not allowed to send messages on the socket before the + /// handshake is complete and no message_received callbacks will be called + /// before the handshake is done. + /// + /// @param protocol The negotiated subprotocol value returned by the server + virtual void websocket_connected_handler(const std::string& protocol) = 0; + + /// Called when an error occurs while establishing the WebSocket connection + /// to the server or during normal operations. No additional binary messages + /// will be processed after this function is called. + virtual void websocket_error_handler() = 0; + + /// Called whenever a full message has arrived. The WebSocket implementation + /// is responsible for defragmenting fragmented messages internally and + /// delivering a full message to the Sync Client. + /// + /// @param data A util::Span containing the data received from the server. + /// The buffer is only valid until the function returns. + /// + /// @return bool designates whether the WebSocket object should continue + /// processing messages. The normal return value is true . False must + /// be returned if the websocket object has been destroyed during + /// execution of the function. + virtual bool websocket_binary_message_received(util::Span data) = 0; + + /// Called whenever the WebSocket connection has been closed, either as a result + /// of a WebSocket error or a normal close. + /// + /// @param was_clean Was the TCP connection closed after the WebSocket closing + /// handshake was completed. + /// @param error_code The error code received or synthesized when the websocket was closed. + /// @param message The message received in the close frame when the websocket was closed. + /// + /// @return bool designates whether the WebSocket object has been destroyed + /// during the execution of this function. The normal return value is + /// True to indicate the WebSocket object is no longer valid. If False + /// is returned, the WebSocket object will be destroyed at some point + /// in the future. + virtual bool websocket_closed_handler(bool was_clean, websocket_err_codes error_code, + std::string_view message) = 0; + }; + + struct core_websocket_observer_shim : public ::realm::sync::WebSocketObserver { + ~core_websocket_observer_shim() = default; + + void websocket_connected_handler(const std::string& protocol) override { + m_observer->websocket_connected_handler(protocol); + } + + void websocket_error_handler() override { + m_observer->websocket_error_handler(); + } + + bool websocket_binary_message_received(util::Span data) override { + return m_observer->websocket_binary_message_received(data); + } + + bool websocket_closed_handler(bool was_clean, ::realm::sync::websocket::WebSocketError error_code, + std::string_view message) override { + return m_observer->websocket_closed_handler(was_clean, static_cast(error_code), message); + } + + std::shared_ptr m_observer; + }; + + struct websocket_observer_shim : public websocket_observer { + ~websocket_observer_shim() = default; + + void websocket_connected_handler(const std::string& protocol) override { + m_observer->websocket_connected_handler(protocol); + } + + void websocket_error_handler() override { + m_observer->websocket_error_handler(); + } + + bool websocket_binary_message_received(util::Span data) override { + return m_observer->websocket_binary_message_received(data); + } + + bool websocket_closed_handler(bool was_clean, websocket_err_codes error_code, + std::string_view message) override { + return m_observer->websocket_closed_handler(was_clean, static_cast<::realm::sync::websocket::WebSocketError>(error_code), message); + } + + std::shared_ptr<::realm::sync::WebSocketObserver> m_observer; + }; + + + struct core_http_transport_shim : app::GenericNetworkTransport { + ~core_http_transport_shim() = default; + core_http_transport_shim(const std::optional>& custom_http_headers = std::nullopt, + const std::optional& proxy_config = std::nullopt) { + m_transport = internal::DefaultTransport(custom_http_headers, proxy_config); + } + + void send_request_to_server(const app::Request& request, + util::UniqueFunction&& completion) { + auto completion_ptr = completion.release(); + m_transport.send_request_to_server(to_request(request), + [f = std::move(completion_ptr)] + (const networking::response& response) { + auto uf = util::UniqueFunction(std::move(f)); + uf(to_core_response(response)); + }); + } + + private: + internal::DefaultTransport m_transport; + }; + + // Internal only wrapper for bridging C++ SDK networking to RealmCore + class core_transport_provider final : public ::realm::sync::SyncSocketProvider{ + public: + explicit core_transport_provider(const std::shared_ptr& logger, + const std::string& user_agent_binding_info, + const std::string& user_agent_application_info) { + auto user_agent = util::format("RealmSync/%1 (%2) %3 %4", REALM_VERSION_STRING, util::get_platform_info(), user_agent_binding_info, user_agent_application_info); + m_default_provider = std::make_unique<::realm::sync::websocket::DefaultSocketProvider>(logger, user_agent); + } + + ~core_transport_provider() = default; + + std::unique_ptr<::realm::sync::WebSocketInterface> connect(std::unique_ptr<::realm::sync::WebSocketObserver> observer, ::realm::sync::WebSocketEndpoint&& endpoint) override { + if (m_websocket_event_handler) { + m_websocket_event_handler->on_connect({});// TODO pass endpoint + } + + return m_default_provider->connect(std::move(observer), std::move(endpoint)); + } + + void post(FunctionHandler&& handler) override { + m_default_provider->post(std::move(handler)); + } + + ::realm::sync::SyncSocketProvider::SyncTimer create_timer(std::chrono::milliseconds delay, ::realm::sync::SyncSocketProvider::FunctionHandler&& handler) override { + return m_default_provider->create_timer(delay, std::move(handler)); + } + + void stop(bool b = false) override { + m_default_provider->stop(b); + } + private: + std::shared_ptr m_websocket_event_handler; +// std::unique_ptr<::realm::sync::websocket::DefaultSocketProvider> m_default_provider; + std::unique_ptr<::realm::sync::websocket::DefaultSocketProvider> m_default_provider; + }; +} + +namespace realm { + static_assert((int)user::state::logged_in == (int)SyncUser::State::LoggedIn); + static_assert((int)user::state::logged_out == (int)SyncUser::State::LoggedOut); + static_assert((int)user::state::removed == (int)SyncUser::State::Removed); app_error::app_error(const app_error& other) { #ifdef CPPREALM_HAVE_GENERATED_BRIDGE_TYPES @@ -514,8 +703,15 @@ namespace realm { client_config.user_agent_binding_info = std::string("RealmCpp/") + std::string(REALMCXX_VERSION_STRING); client_config.user_agent_application_info = config.app_id; + auto network_transport = std::make_shared<::realm::networking::core_transport_provider>( + client_config.logger_factory(util::Logger::Level::off), // TODO: allow log level to be set from app config + client_config.user_agent_binding_info, + client_config.user_agent_application_info); + + client_config.socket_provider = network_transport; + app_config.app_id = config.app_id; - app_config.transport = std::make_shared(config.custom_http_headers, config.proxy_configuration); + app_config.transport = std::make_shared(config.custom_http_headers, config.proxy_configuration); app_config.base_url = config.base_url; app_config.metadata_mode = should_encrypt ? app::AppConfig::MetadataMode::Encryption : app::AppConfig::MetadataMode::NoEncryption; diff --git a/src/cpprealm/flex_sync.cpp b/src/cpprealm/flex_sync.cpp index 31ca84e94..14d0489d3 100644 --- a/src/cpprealm/flex_sync.cpp +++ b/src/cpprealm/flex_sync.cpp @@ -3,10 +3,6 @@ #include #include -#ifdef CPPREALM_HAVE_GENERATED_BRIDGE_TYPES -#else -#endif - namespace realm { sync_subscription::sync_subscription(const sync::Subscription &v) { diff --git a/src/cpprealm/internal/bridge/status.cpp b/src/cpprealm/internal/bridge/status.cpp index 262ad877d..936cd54ea 100644 --- a/src/cpprealm/internal/bridge/status.cpp +++ b/src/cpprealm/internal/bridge/status.cpp @@ -98,4 +98,12 @@ namespace realm::internal::bridge { #endif } + status::operator ::realm::Status() const noexcept { +#ifdef CPPREALM_HAVE_GENERATED_BRIDGE_TYPES + return *reinterpret_cast(&m_status); +#else + return *m_status; +#endif + } + } \ No newline at end of file diff --git a/src/cpprealm/internal/network/network_transport.cpp b/src/cpprealm/internal/network/network_transport.cpp index f5d0cc613..fa8cb2e65 100644 --- a/src/cpprealm/internal/network/network_transport.cpp +++ b/src/cpprealm/internal/network/network_transport.cpp @@ -21,6 +21,7 @@ #endif #include #include +#include #include #include @@ -129,8 +130,8 @@ namespace realm::internal { } } - void DefaultTransport::send_request_to_server(const app::Request& request, - std::function&& completion_block) { + void DefaultTransport::send_request_to_server(const networking::request& request, + std::function&& completion_block) { const auto uri = realm::util::Uri(request.url); std::string userinfo, host, port; uri.get_auth(userinfo, host, port); @@ -154,7 +155,7 @@ namespace realm::internal { std::error_code e; auto address = realm::sync::network::make_address(m_proxy_config->address, e); if (e.value() > 0) { - app::Response response; + networking::response response; response.custom_status_code = e.value(); response.body = e.message(); completion_block(std::move(response)); @@ -177,7 +178,7 @@ namespace realm::internal { } socket.connect(ep); } catch (...) { - app::Response response; + networking::response response; response.custom_status_code = util::error::operation_aborted; completion_block(std::move(response)); return; @@ -199,14 +200,14 @@ namespace realm::internal { realm::sync::HTTPClient m_proxy_client = realm::sync::HTTPClient(socket, logger); auto handler = [&](realm::sync::HTTPResponse response, std::error_code ec) { if (ec && ec != util::error::operation_aborted) { - app::Response res; + networking::response res; res.custom_status_code = util::error::operation_aborted; completion_block(std::move(res)); return; } if (response.status != realm::sync::HTTPStatus::Ok) { - app::Response res; + networking::response res; res.http_status_code = static_cast(response.status); completion_block(std::move(res)); return; @@ -252,19 +253,19 @@ namespace realm::internal { realm::sync::HTTPClient m_http_client = realm::sync::HTTPClient(socket, logger); realm::sync::HTTPMethod method; switch (request.method) { - case app::HttpMethod::get: + case networking::http_method::get: method = realm::sync::HTTPMethod::Get; break; - case app::HttpMethod::put: + case networking::http_method::put: method = realm::sync::HTTPMethod::Put; break; - case app::HttpMethod::post: + case networking::http_method::post: method = realm::sync::HTTPMethod::Post; break; - case app::HttpMethod::patch: + case networking::http_method::patch: method = realm::sync::HTTPMethod::Patch; break; - case app::HttpMethod::del: + case networking::http_method::del: method = realm::sync::HTTPMethod::Delete; break; default: @@ -291,7 +292,7 @@ namespace realm::internal { req.body = request.body.empty() ? std::nullopt : std::optional(request.body); m_http_client.async_request(std::move(req), [cb = std::move(completion_block)](const realm::sync::HTTPResponse& r, const std::error_code&) { - app::Response res; + networking::response res; res.body = r.body ? *r.body : ""; for (auto& [k, v] : r.headers) { res.headers[k] = v; @@ -301,7 +302,7 @@ namespace realm::internal { cb(res); }); } else { - app::Response response; + networking::response response; response.custom_status_code = util::error::operation_aborted; completion_block(std::move(response)); return; diff --git a/src/cpprealm/networking/networking.cpp b/src/cpprealm/networking/networking.cpp new file mode 100644 index 000000000..d37b9f972 --- /dev/null +++ b/src/cpprealm/networking/networking.cpp @@ -0,0 +1,45 @@ +#include + +#include + +namespace realm::networking { + request to_request(const ::realm::app::Request& core_request) { + request req; + req.method = static_cast(core_request.method); + req.url = core_request.url; + req.timeout_ms = core_request.timeout_ms; + req.headers = core_request.headers; + req.body = core_request.body; + return req; + } + ::realm::app::Request to_core_request(const request& req) { + ::realm::app::Request core_request; + core_request.method = static_cast(req.method); + core_request.url = req.url; + core_request.timeout_ms = req.timeout_ms; + core_request.headers = req.headers; + core_request.body = req.body; + return core_request; + } + + response to_response(const ::realm::app::Response& core_response) { + response req; + req.http_status_code = core_response.http_status_code; + req.custom_status_code = core_response.custom_status_code; + req.headers = core_response.headers; + req.client_error_code = core_response.client_error_code; + req.body = core_response.body; + return req; + } + ::realm::app::Response to_core_response(const response& req) { + ::realm::app::Response core_response; + core_response.http_status_code = req.http_status_code; + core_response.custom_status_code = req.custom_status_code; + core_response.headers = req.headers; + core_response.body = req.body; + if (req.client_error_code) { + core_response.client_error_code = static_cast(*req.client_error_code); + } + return core_response; + } +} \ No newline at end of file diff --git a/tests/admin_utils.cpp b/tests/admin_utils.cpp index 8a483c516..28e7833fb 100644 --- a/tests/admin_utils.cpp +++ b/tests/admin_utils.cpp @@ -33,9 +33,9 @@ namespace Admin { internal::DefaultTransport transport; std::promise p; std::future f = p.get_future(); - transport.send_request_to_server(std::move(request), - [&p](auto &&response) { - p.set_value(std::move(response)); + transport.send_request_to_server(networking::to_request(std::move(request)), + [&p](auto&& response) { + p.set_value(networking::to_core_response(std::move(response))); }); return f.get(); } diff --git a/tests/main.cpp b/tests/main.cpp index 2210994ee..49f8a8cb3 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -42,14 +42,14 @@ int main(int argc, char *argv[]) { #ifdef CPPREALM_ENABLE_SYNC_TESTS std::optional baas_manager; - if (const char* api_key = getenv("APIKEY")) { - baas_manager.emplace(std::string(api_key)); - baas_manager->start(); - auto url = baas_manager->wait_for_container(); - Admin::Session::shared().prepare(url); - } else { +// if (const char* api_key = getenv("APIKEY")) { +// baas_manager.emplace(std::string(api_key)); +// baas_manager->start(); +// auto url = baas_manager->wait_for_container(); +// Admin::Session::shared().prepare(url); +// } else { Admin::Session::shared().prepare(); - } +// } auto app_id = Admin::Session::shared().create_app({"str_col", "_id"}); Admin::Session::shared().cache_app_id(app_id); From 316bedce7faaadb8a60377123cec4fe5c517f61f Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 10 Jun 2024 12:58:29 +0100 Subject: [PATCH 02/41] Move network code into its own files --- include/cpprealm/app.hpp | 2 + include/cpprealm/networking/networking.hpp | 9 + .../networking/platform_networking.hpp | 52 ++++ src/CMakeLists.txt | 2 + src/cpprealm/app.cpp | 225 +----------------- src/cpprealm/networking/networking.cpp | 21 ++ .../networking/platform_networking.cpp | 82 +++++++ tests/CMakeLists.txt | 3 +- tests/sync/proxy_tests.cpp | 90 +++++++ 9 files changed, 268 insertions(+), 218 deletions(-) create mode 100644 include/cpprealm/networking/platform_networking.hpp create mode 100644 src/cpprealm/networking/platform_networking.cpp create mode 100644 tests/sync/proxy_tests.cpp diff --git a/include/cpprealm/app.hpp b/include/cpprealm/app.hpp index 2711105c7..4353b5bf1 100644 --- a/include/cpprealm/app.hpp +++ b/include/cpprealm/app.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -229,6 +230,7 @@ class App { std::optional> custom_http_headers; std::optional> metadata_encryption_key; std::optional proxy_configuration; + std::shared_ptr<::realm::networking::websocket_event_handler> websocket_event_handler; }; [[deprecated("Use App(const configuration&) instead.")]] diff --git a/include/cpprealm/networking/networking.hpp b/include/cpprealm/networking/networking.hpp index b1d823acd..09a64aa18 100644 --- a/include/cpprealm/networking/networking.hpp +++ b/include/cpprealm/networking/networking.hpp @@ -26,6 +26,9 @@ namespace realm { struct Request; struct Response; } + namespace sync { + struct WebSocketEndpoint; + } } namespace realm::networking { @@ -149,6 +152,9 @@ namespace realm::networking { }; struct websocket_event_handler { + /** + * Used to configure outgoing websocket connections. + */ std::function on_connect; }; @@ -157,6 +163,9 @@ namespace realm::networking { response to_response(const ::realm::app::Response&); ::realm::app::Response to_core_response(const response&); + + ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ws_endpoint& ep); + ws_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& ep); } diff --git a/include/cpprealm/networking/platform_networking.hpp b/include/cpprealm/networking/platform_networking.hpp new file mode 100644 index 000000000..71bde6a34 --- /dev/null +++ b/include/cpprealm/networking/platform_networking.hpp @@ -0,0 +1,52 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef CPPREALM_PLATFORM_NETWORKING_HPP +#define CPPREALM_PLATFORM_NETWORKING_HPP + +#include +#include + +#ifndef REALMCXX_VERSION_MAJOR +#include +#endif + +namespace realm { + namespace app { + struct GenericNetworkTransport; + } + namespace sync { + class SyncSocketProvider; + } + + namespace util { + class Logger; + } +} +namespace realm::networking { + + std::shared_ptr default_sync_socket_provider_factory(const std::shared_ptr& logger, + const std::string& user_agent_binding_info, + const std::string& user_agent_application_info, + const std::shared_ptr&); + + std::shared_ptr default_http_client_factory(const std::optional>& custom_http_headers = std::nullopt, + const std::optional& proxy_config = std::nullopt); +} + +#endif//CPPREALM_PLATFORM_NETWORKING_HPP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 68b4292f0..ee6d4e9d3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -53,6 +53,7 @@ set(SOURCES cpprealm/internal/bridge/uuid.cpp cpprealm/internal/scheduler/realm_core_scheduler.cpp cpprealm/networking/networking.cpp + cpprealm/networking/platform_networking.cpp cpprealm/schedulers/default_scheduler.cpp cpprealm/logger.cpp cpprealm/sdk.cpp) # REALM_SOURCES @@ -115,6 +116,7 @@ set(HEADERS ../include/cpprealm/internal/scheduler/realm_core_scheduler.hpp ../include/cpprealm/schedulers/default_scheduler.hpp ../include/cpprealm/networking/networking.hpp + ../include/cpprealm/networking/platform_networking.hpp ../include/cpprealm/logger.hpp ../include/cpprealm/notifications.hpp ../include/cpprealm/rbool.hpp diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index b7565c567..b6c24ee39 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #ifndef REALMCXX_VERSION_MAJOR #include @@ -11,7 +12,7 @@ #include #include #include -#include +//#include #include #include @@ -21,216 +22,6 @@ #include #endif -namespace realm::networking { - - /// The WebSocket base class that is used by the SyncClient to send data over the - /// WebSocket connection with the server. This is the class that is returned by - /// SyncSocketProvider::connect() when a connection to an endpoint is requested. - /// If an error occurs while establishing the connection, the error is presented - /// to the WebSocketObserver provided when the WebSocket was created. - struct websocket_interface { - /// The destructor must close the websocket connection when the WebSocket object - /// is destroyed - virtual ~websocket_interface() = default; - - using status = internal::bridge::status; - using FunctionHandler = std::function; - - /// Write data asynchronously to the WebSocket connection. The handler function - /// will be called when the data has been sent successfully. The WebSocketOberver - /// provided when the WebSocket was created will be called if any errors occur - /// during the write operation. - /// @param data A util::Span containing the data to be sent to the server. - /// @param handler The handler function to be called when the data has been sent - /// successfully or the websocket has been closed (with - /// ErrorCodes::OperationAborted). If an error occurs during the - /// write operation, the websocket will be closed and the error - /// will be provided via the websocket_closed_handler() function. - virtual void async_write_binary(util::Span data, FunctionHandler&& handler) = 0; - }; - - struct core_websocket_shim : public ::realm::sync::WebSocketInterface { - ~core_websocket_shim() = default; - explicit core_websocket_shim(const std::shared_ptr& ws) : m_interface(ws) {} - - void async_write_binary(util::Span data, sync::SyncSocketProvider::FunctionHandler&& handler) override { - auto handler_ptr = handler.release(); - m_interface->async_write_binary(data, [ptr = std::move(handler_ptr)](websocket_interface::status s) { - auto uf = util::UniqueFunction(std::move(ptr)); - return uf(s.operator ::realm::Status()); - }); - }; - - std::shared_ptr m_interface; - }; - - struct websocket_shim : public websocket_interface { - ~websocket_shim() = default; - void async_write_binary(util::Span data, websocket_interface::FunctionHandler&& handler) override { - m_interface->async_write_binary(data, [fn = std::move(handler)](auto s) { - fn(s); - }); - }; - - std::shared_ptr<::realm::sync::WebSocketInterface> m_interface; - }; - - - /// WebSocket observer interface in the SyncClient that receives the websocket - /// events during operation. - struct websocket_observer { - virtual ~websocket_observer() = default; - - /// Called when the websocket is connected, i.e. after the handshake is done. - /// The Sync Client is not allowed to send messages on the socket before the - /// handshake is complete and no message_received callbacks will be called - /// before the handshake is done. - /// - /// @param protocol The negotiated subprotocol value returned by the server - virtual void websocket_connected_handler(const std::string& protocol) = 0; - - /// Called when an error occurs while establishing the WebSocket connection - /// to the server or during normal operations. No additional binary messages - /// will be processed after this function is called. - virtual void websocket_error_handler() = 0; - - /// Called whenever a full message has arrived. The WebSocket implementation - /// is responsible for defragmenting fragmented messages internally and - /// delivering a full message to the Sync Client. - /// - /// @param data A util::Span containing the data received from the server. - /// The buffer is only valid until the function returns. - /// - /// @return bool designates whether the WebSocket object should continue - /// processing messages. The normal return value is true . False must - /// be returned if the websocket object has been destroyed during - /// execution of the function. - virtual bool websocket_binary_message_received(util::Span data) = 0; - - /// Called whenever the WebSocket connection has been closed, either as a result - /// of a WebSocket error or a normal close. - /// - /// @param was_clean Was the TCP connection closed after the WebSocket closing - /// handshake was completed. - /// @param error_code The error code received or synthesized when the websocket was closed. - /// @param message The message received in the close frame when the websocket was closed. - /// - /// @return bool designates whether the WebSocket object has been destroyed - /// during the execution of this function. The normal return value is - /// True to indicate the WebSocket object is no longer valid. If False - /// is returned, the WebSocket object will be destroyed at some point - /// in the future. - virtual bool websocket_closed_handler(bool was_clean, websocket_err_codes error_code, - std::string_view message) = 0; - }; - - struct core_websocket_observer_shim : public ::realm::sync::WebSocketObserver { - ~core_websocket_observer_shim() = default; - - void websocket_connected_handler(const std::string& protocol) override { - m_observer->websocket_connected_handler(protocol); - } - - void websocket_error_handler() override { - m_observer->websocket_error_handler(); - } - - bool websocket_binary_message_received(util::Span data) override { - return m_observer->websocket_binary_message_received(data); - } - - bool websocket_closed_handler(bool was_clean, ::realm::sync::websocket::WebSocketError error_code, - std::string_view message) override { - return m_observer->websocket_closed_handler(was_clean, static_cast(error_code), message); - } - - std::shared_ptr m_observer; - }; - - struct websocket_observer_shim : public websocket_observer { - ~websocket_observer_shim() = default; - - void websocket_connected_handler(const std::string& protocol) override { - m_observer->websocket_connected_handler(protocol); - } - - void websocket_error_handler() override { - m_observer->websocket_error_handler(); - } - - bool websocket_binary_message_received(util::Span data) override { - return m_observer->websocket_binary_message_received(data); - } - - bool websocket_closed_handler(bool was_clean, websocket_err_codes error_code, - std::string_view message) override { - return m_observer->websocket_closed_handler(was_clean, static_cast<::realm::sync::websocket::WebSocketError>(error_code), message); - } - - std::shared_ptr<::realm::sync::WebSocketObserver> m_observer; - }; - - - struct core_http_transport_shim : app::GenericNetworkTransport { - ~core_http_transport_shim() = default; - core_http_transport_shim(const std::optional>& custom_http_headers = std::nullopt, - const std::optional& proxy_config = std::nullopt) { - m_transport = internal::DefaultTransport(custom_http_headers, proxy_config); - } - - void send_request_to_server(const app::Request& request, - util::UniqueFunction&& completion) { - auto completion_ptr = completion.release(); - m_transport.send_request_to_server(to_request(request), - [f = std::move(completion_ptr)] - (const networking::response& response) { - auto uf = util::UniqueFunction(std::move(f)); - uf(to_core_response(response)); - }); - } - - private: - internal::DefaultTransport m_transport; - }; - - // Internal only wrapper for bridging C++ SDK networking to RealmCore - class core_transport_provider final : public ::realm::sync::SyncSocketProvider{ - public: - explicit core_transport_provider(const std::shared_ptr& logger, - const std::string& user_agent_binding_info, - const std::string& user_agent_application_info) { - auto user_agent = util::format("RealmSync/%1 (%2) %3 %4", REALM_VERSION_STRING, util::get_platform_info(), user_agent_binding_info, user_agent_application_info); - m_default_provider = std::make_unique<::realm::sync::websocket::DefaultSocketProvider>(logger, user_agent); - } - - ~core_transport_provider() = default; - - std::unique_ptr<::realm::sync::WebSocketInterface> connect(std::unique_ptr<::realm::sync::WebSocketObserver> observer, ::realm::sync::WebSocketEndpoint&& endpoint) override { - if (m_websocket_event_handler) { - m_websocket_event_handler->on_connect({});// TODO pass endpoint - } - - return m_default_provider->connect(std::move(observer), std::move(endpoint)); - } - - void post(FunctionHandler&& handler) override { - m_default_provider->post(std::move(handler)); - } - - ::realm::sync::SyncSocketProvider::SyncTimer create_timer(std::chrono::milliseconds delay, ::realm::sync::SyncSocketProvider::FunctionHandler&& handler) override { - return m_default_provider->create_timer(delay, std::move(handler)); - } - - void stop(bool b = false) override { - m_default_provider->stop(b); - } - private: - std::shared_ptr m_websocket_event_handler; -// std::unique_ptr<::realm::sync::websocket::DefaultSocketProvider> m_default_provider; - std::unique_ptr<::realm::sync::websocket::DefaultSocketProvider> m_default_provider; - }; -} - namespace realm { static_assert((int)user::state::logged_in == (int)SyncUser::State::LoggedIn); static_assert((int)user::state::logged_out == (int)SyncUser::State::LoggedOut); @@ -703,15 +494,15 @@ namespace realm { client_config.user_agent_binding_info = std::string("RealmCpp/") + std::string(REALMCXX_VERSION_STRING); client_config.user_agent_application_info = config.app_id; - auto network_transport = std::make_shared<::realm::networking::core_transport_provider>( - client_config.logger_factory(util::Logger::Level::off), // TODO: allow log level to be set from app config - client_config.user_agent_binding_info, - client_config.user_agent_application_info); + auto websocket_provider = ::realm::networking::default_sync_socket_provider_factory(util::Logger::get_default_logger(), + client_config.user_agent_binding_info, + client_config.user_agent_application_info, + config.websocket_event_handler); - client_config.socket_provider = network_transport; + client_config.socket_provider = websocket_provider; app_config.app_id = config.app_id; - app_config.transport = std::make_shared(config.custom_http_headers, config.proxy_configuration); + app_config.transport = ::realm::networking::default_http_client_factory(config.custom_http_headers, config.proxy_configuration); app_config.base_url = config.base_url; app_config.metadata_mode = should_encrypt ? app::AppConfig::MetadataMode::Encryption : app::AppConfig::MetadataMode::NoEncryption; diff --git a/src/cpprealm/networking/networking.cpp b/src/cpprealm/networking/networking.cpp index d37b9f972..4191b9034 100644 --- a/src/cpprealm/networking/networking.cpp +++ b/src/cpprealm/networking/networking.cpp @@ -1,6 +1,7 @@ #include #include +#include namespace realm::networking { request to_request(const ::realm::app::Request& core_request) { @@ -42,4 +43,24 @@ namespace realm::networking { } return core_response; } + + ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ws_endpoint& ep) { + ::realm::sync::WebSocketEndpoint core_ep; + core_ep.address = ep.address; + core_ep.port = ep.port; + core_ep.path = ep.path; + core_ep.protocols = ep.protocols; + core_ep.is_ssl = ep.is_ssl; + return core_ep; + } + + ws_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& core_ep) { + ws_endpoint ep; + ep.address = core_ep.address; + ep.port = core_ep.port; + ep.path = core_ep.path; + ep.protocols = core_ep.protocols; + ep.is_ssl = core_ep.is_ssl; + return ep; + } } \ No newline at end of file diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/platform_networking.cpp new file mode 100644 index 000000000..b3806e26b --- /dev/null +++ b/src/cpprealm/networking/platform_networking.cpp @@ -0,0 +1,82 @@ +#include + +#include +#include +#include + +namespace realm::networking { + std::shared_ptr default_sync_socket_provider_factory(const std::shared_ptr& logger, + const std::string& user_agent_binding_info, + const std::string& user_agent_application_info, + const std::shared_ptr& configuration_handler) { + class default_sync_socket_provider_shim final : public ::realm::sync::SyncSocketProvider { + public: + explicit default_sync_socket_provider_shim(const std::shared_ptr& logger, + const std::string& user_agent_binding_info, + const std::string& user_agent_application_info, + const std::shared_ptr& configuration_handler) { + auto user_agent = util::format("RealmSync/%1 (%2) %3 %4", REALM_VERSION_STRING, util::get_platform_info(), user_agent_binding_info, user_agent_application_info); + m_default_provider = std::make_unique<::realm::sync::websocket::DefaultSocketProvider>(logger, user_agent); + m_websocket_event_handler = configuration_handler; + } + + ~default_sync_socket_provider_shim() = default; + + std::unique_ptr<::realm::sync::WebSocketInterface> connect(std::unique_ptr<::realm::sync::WebSocketObserver> observer, + ::realm::sync::WebSocketEndpoint&& endpoint) override { + if (m_websocket_event_handler) { + auto ep = m_websocket_event_handler->on_connect(to_websocket_endpoint(endpoint)); + return m_default_provider->connect(std::move(observer), to_core_websocket_endpoint(std::move(ep))); + } + + return m_default_provider->connect(std::move(observer), std::move(endpoint)); + } + + void post(FunctionHandler&& handler) override { + m_default_provider->post(std::move(handler)); + } + + ::realm::sync::SyncSocketProvider::SyncTimer create_timer(std::chrono::milliseconds delay, + ::realm::sync::SyncSocketProvider::FunctionHandler&& handler) override { + return m_default_provider->create_timer(delay, std::move(handler)); + } + + void stop(bool b = false) override { + m_default_provider->stop(b); + } + private: + std::shared_ptr m_websocket_event_handler; + std::unique_ptr<::realm::sync::websocket::DefaultSocketProvider> m_default_provider; + }; + + return std::make_shared(logger, user_agent_binding_info, + user_agent_application_info, configuration_handler); + } + + std::shared_ptr default_http_client_factory(const std::optional>& custom_http_headers, + const std::optional& proxy_config) { + struct core_http_transport_shim : app::GenericNetworkTransport { + ~core_http_transport_shim() = default; + core_http_transport_shim(const std::optional>& custom_http_headers = std::nullopt, + const std::optional& proxy_config = std::nullopt) { + m_transport = internal::DefaultTransport(custom_http_headers, proxy_config); + } + + void send_request_to_server(const app::Request& request, + util::UniqueFunction&& completion) { + auto completion_ptr = completion.release(); + m_transport.send_request_to_server(to_request(request), + [f = std::move(completion_ptr)] + (const networking::response& response) { + auto uf = util::UniqueFunction(std::move(f)); + uf(to_core_response(response)); + }); + } + + private: + internal::DefaultTransport m_transport; + }; + + return std::make_shared(custom_http_headers, proxy_config); + }; +} //namespace realm::networking \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f6c6f2e13..7c8cc0f17 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -26,7 +26,8 @@ if (NOT BUILD_FROM_PACKAGE_MANAGER) sync/asymmetric_object_tests.cpp sync/flexible_sync_tests.cpp sync/app_tests.cpp - sync/client_reset_tests.cpp) + sync/client_reset_tests.cpp + sync/proxy_tests.cpp) endif() add_executable(cpprealm_db_tests diff --git a/tests/sync/proxy_tests.cpp b/tests/sync/proxy_tests.cpp new file mode 100644 index 000000000..1c10f654d --- /dev/null +++ b/tests/sync/proxy_tests.cpp @@ -0,0 +1,90 @@ +#include "../admin_utils.hpp" +#include "../main.hpp" +#include "test_objects.hpp" + +using namespace realm; + +TEST_CASE("proxy_enabled_websocket", "[sync]") { + + realm::App::configuration config; + config.app_id = Admin::Session::shared().cached_app_id(); + config.base_url = Admin::Session::shared().base_url(); + + realm::networking::websocket_event_handler handler; + handler.on_connect = [](realm::networking::ws_endpoint&& ep) { + ep.is_ssl = false; + return ep; + }; + config.websocket_event_handler = std::make_shared(handler); + + auto app = realm::App(config); + SECTION("all") { + auto user = app.login(realm::App::credentials::anonymous()).get(); + auto flx_sync_config = user.flexible_sync_configuration(); + auto synced_realm = db(flx_sync_config); + + auto update_success = synced_realm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { + subs.clear(); + }).get(); + CHECK(update_success == true); + CHECK(synced_realm.subscriptions().size() == 0); + + update_success = synced_realm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { + subs.add("foo-strings", [](auto &obj) { + return obj.str_col == "foo"; + }); + subs.add("foo-link"); + }) + .get(); + CHECK(update_success == true); + CHECK(synced_realm.subscriptions().size() == 2); + + auto sub = *synced_realm.subscriptions().find("foo-strings"); + CHECK(sub.name == "foo-strings"); + CHECK(sub.object_class_name == "AllTypesObject"); + CHECK(sub.query_string == "str_col == \"foo\""); + + auto non_existent_sub = synced_realm.subscriptions().find("non-existent"); + CHECK((non_existent_sub == std::nullopt) == true); + + synced_realm.write([&synced_realm]() { + AllTypesObject o; + o._id = 1; + o.str_col = "foo"; + synced_realm.add(std::move(o)); + }); + + synced_realm.get_sync_session()->wait_for_upload_completion().get(); + synced_realm.get_sync_session()->wait_for_download_completion().get(); + + synced_realm.refresh(); + auto objs = synced_realm.objects(); + + CHECK(objs[0]._id == (int64_t) 1); + + synced_realm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { + subs.update_subscription("foo-strings", [](auto &obj) { + return obj.str_col == "bar" && obj._id == (int64_t) 1230; + }); + }).get(); + + auto sub2 = *synced_realm.subscriptions().find("foo-strings"); + CHECK(sub2.name == "foo-strings"); + CHECK(sub2.object_class_name == "AllTypesObject"); + CHECK(sub2.query_string == "str_col == \"bar\" and _id == 1230"); + + synced_realm.write([&synced_realm]() { + AllTypesObject o; + o._id = 1230; + o.str_col = "bar"; + synced_realm.add(std::move(o)); + }); + + synced_realm.get_sync_session()->wait_for_upload_completion().get(); + synced_realm.get_sync_session()->wait_for_download_completion().get(); + + synced_realm.refresh(); + objs = synced_realm.objects(); + CHECK(objs.size() == 1); + } +} \ No newline at end of file From 72f3cd2062e6213e992ab49a6cb7dfff5516d094 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 10 Jun 2024 15:44:34 +0100 Subject: [PATCH 03/41] Add bones for platform networking --- .../networking/platform_networking.hpp | 182 ++++++++++++++++++ src/cpprealm/app.cpp | 13 +- .../networking/platform_networking.cpp | 108 +++++++++++ 3 files changed, 297 insertions(+), 6 deletions(-) diff --git a/include/cpprealm/networking/platform_networking.hpp b/include/cpprealm/networking/platform_networking.hpp index 71bde6a34..de30c9477 100644 --- a/include/cpprealm/networking/platform_networking.hpp +++ b/include/cpprealm/networking/platform_networking.hpp @@ -47,6 +47,188 @@ namespace realm::networking { std::shared_ptr default_http_client_factory(const std::optional>& custom_http_headers = std::nullopt, const std::optional& proxy_config = std::nullopt); + + /// The WebSocket base class that is used by the SyncClient to send data over the + /// WebSocket connection with the server. This is the class that is returned by + /// SyncSocketProvider::connect() when a connection to an endpoint is requested. + /// If an error occurs while establishing the connection, the error is presented + /// to the WebSocketObserver provided when the WebSocket was created. + struct websocket_interface { + /// The destructor must close the websocket connection when the WebSocket object + /// is destroyed + virtual ~websocket_interface() = default; + + using status = internal::bridge::status; + using FunctionHandler = std::function; + + /// Write data asynchronously to the WebSocket connection. The handler function + /// will be called when the data has been sent successfully. The WebSocketOberver + /// provided when the WebSocket was created will be called if any errors occur + /// during the write operation. + /// @param data A std::string_view containing the data to be sent to the server. + /// @param handler The handler function to be called when the data has been sent + /// successfully or the websocket has been closed (with + /// ErrorCodes::OperationAborted). If an error occurs during the + /// write operation, the websocket will be closed and the error + /// will be provided via the websocket_closed_handler() function. + virtual void async_write_binary(std::string_view data, FunctionHandler&& handler) = 0; + }; + + /// WebSocket observer interface in the SyncClient that receives the websocket + /// events during operation. + struct websocket_observer { + virtual ~websocket_observer() = default; + + /// Called when the websocket is connected, i.e. after the handshake is done. + /// The Sync Client is not allowed to send messages on the socket before the + /// handshake is complete and no message_received callbacks will be called + /// before the handshake is done. + /// + /// @param protocol The negotiated subprotocol value returned by the server + virtual void websocket_connected_handler(const std::string& protocol) = 0; + + /// Called when an error occurs while establishing the WebSocket connection + /// to the server or during normal operations. No additional binary messages + /// will be processed after this function is called. + virtual void websocket_error_handler() = 0; + + /// Called whenever a full message has arrived. The WebSocket implementation + /// is responsible for defragmenting fragmented messages internally and + /// delivering a full message to the Sync Client. + /// + /// @param data A util::Span containing the data received from the server. + /// The buffer is only valid until the function returns. + /// + /// @return bool designates whether the WebSocket object should continue + /// processing messages. The normal return value is true . False must + /// be returned if the websocket object has been destroyed during + /// execution of the function. + virtual bool websocket_binary_message_received(std::string_view data) = 0; + + /// Called whenever the WebSocket connection has been closed, either as a result + /// of a WebSocket error or a normal close. + /// + /// @param was_clean Was the TCP connection closed after the WebSocket closing + /// handshake was completed. + /// @param error_code The error code received or synthesized when the websocket was closed. + /// @param message The message received in the close frame when the websocket was closed. + /// + /// @return bool designates whether the WebSocket object has been destroyed + /// during the execution of this function. The normal return value is + /// True to indicate the WebSocket object is no longer valid. If False + /// is returned, the WebSocket object will be destroyed at some point + /// in the future. + virtual bool websocket_closed_handler(bool was_clean, websocket_err_codes error_code, + std::string_view message) = 0; + }; + + /// Sync Socket Provider interface that provides the event loop and WebSocket + /// factory used by the SyncClient. + /// + /// All callback and event operations in the SyncClient must be completed in + /// the order in which they were issued (via post() or timer) to the event + /// loop and cannot be run in parallel. It is up to the custom event loop + /// implementation to determine if these are run on the same thread or a + /// thread pool as long as it is guaranteed that the callback handler + /// functions are processed in order and not run concurrently. + /// + /// The implementation of a SyncSocketProvider must support the following + /// operations that post handler functions (via by the Sync client) onto the + /// event loop: + /// * Post a handler function directly onto the event loop + /// * Post a handler function when the specified timer duration expires + /// + /// The event loop is not required to be a single thread as long as the + /// following requirements are satisfied: + /// * handler functions are called in the order they were posted to the + /// event loop queue, and + /// * a handler function runs to completion before the next handler function + /// is called. + /// + /// The SyncSocketProvider also provides a WebSocket interface for + /// connecting to the server via a WebSocket connection. + class sync_socket_provider { + public: + /// Function handler typedef + using FunctionHandler = std::function; + + /// The Timer object used to track a timer that was started on the event + /// loop. + /// + /// This object provides a cancel() mechanism to cancel the timer. The + /// handler function for this timer must be called with a Status of + /// ErrorCodes::OperationAborted error code if the timer is canceled. + /// + /// Custom event loop implementations will need to create a subclass of + /// Timer that provides access to the underlying implementation to cancel + /// the timer. + struct timer { + /// Cancels the timer and destroys the timer instance. + virtual ~timer() = default; + /// Cancel the timer immediately. Does nothing if the timer has + /// already expired or been cancelled. + virtual void cancel() = 0; + }; + + /// Other class typedefs + using SyncTimer = std::unique_ptr; + + /// The event loop implementation must ensure the event loop is stopped and + /// flushed when the object is destroyed. If the event loop is processed by + /// a thread, the thread must be joined as part of this operation. + virtual ~sync_socket_provider() = default; + + /// Create a new websocket pointed to the server indicated by endpoint and + /// connect to the server. Any events that occur during the execution of the + /// websocket will call directly to the handlers provided by the observer. + /// The WebSocketObserver guarantees that the WebSocket object will be + /// closed/destroyed before the observer is terminated/destroyed. + virtual std::unique_ptr connect(std::unique_ptr observer, + ws_endpoint&& endpoint) = 0; + + /// Submit a handler function to be executed by the event loop (thread). + /// + /// Register the specified handler function to be queued on the event loop + /// for immediate asynchronous execution. The specified handler will be + /// executed by an expression on the form `handler()`. If the the handler + /// object is movable, it will never be copied. Otherwise, it will be + /// copied as necessary. + /// + /// This function is thread-safe and can be called by any thread. It can + /// also be called from other post() handler function. + /// + /// The handler will never be called as part of the execution of post(). If + /// post() is called on a thread separate from the event loop, the handler + /// may be called before post() returns. + /// + /// Handler functions added through post() must be executed in the order + /// they are added. More precisely, if post() is called twice to add two + /// handlers, A and B, and the execution of post(A) ends before the + /// beginning of the execution of post(B), then A is guaranteed to execute + /// before B. + /// + /// @param handler The handler function to be queued on the event loop. + virtual void post(FunctionHandler&& handler) = 0; + + /// Create and register a new timer whose handler function will be posted + /// to the event loop when the provided delay expires. + /// + /// This is a one shot timer and the Timer class returned becomes invalid + /// once the timer has expired. A new timer will need to be created to wait + /// again. + /// + /// @param delay The duration to wait in ms before the timer expires. + /// @param handler The handler function to be called on the event loop + /// when the timer expires. + /// + /// @return A pointer to the Timer object that can be used to cancel the + /// timer. The timer will also be canceled if the Timer object returned is + /// destroyed. + virtual SyncTimer create_timer(std::chrono::milliseconds delay, FunctionHandler&& handler) = 0; + }; + + std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(std::unique_ptr&& provider, + const std::shared_ptr& handler); } #endif//CPPREALM_PLATFORM_NETWORKING_HPP diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index b6c24ee39..c36ffd043 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -494,12 +494,13 @@ namespace realm { client_config.user_agent_binding_info = std::string("RealmCpp/") + std::string(REALMCXX_VERSION_STRING); client_config.user_agent_application_info = config.app_id; - auto websocket_provider = ::realm::networking::default_sync_socket_provider_factory(util::Logger::get_default_logger(), - client_config.user_agent_binding_info, - client_config.user_agent_application_info, - config.websocket_event_handler); - - client_config.socket_provider = websocket_provider; + if (config.websocket_event_handler) { + auto websocket_provider = ::realm::networking::default_sync_socket_provider_factory(util::Logger::get_default_logger(), + client_config.user_agent_binding_info, + client_config.user_agent_application_info, + config.websocket_event_handler); + client_config.socket_provider = websocket_provider; + } app_config.app_id = config.app_id; app_config.transport = ::realm::networking::default_http_client_factory(config.custom_http_headers, config.proxy_configuration); diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/platform_networking.cpp index b3806e26b..c39930e9d 100644 --- a/src/cpprealm/networking/platform_networking.cpp +++ b/src/cpprealm/networking/platform_networking.cpp @@ -79,4 +79,112 @@ namespace realm::networking { return std::make_shared(custom_http_headers, proxy_config); }; + + // Shims + + std::unique_ptr<::realm::sync::WebSocketInterface> create_websocket_interface_shim(std::unique_ptr&& m_interface) { + struct core_websocket_interface_shim : public ::realm::sync::WebSocketInterface { + ~core_websocket_interface_shim() = default; + explicit core_websocket_interface_shim(std::unique_ptr&& ws) : m_interface(std::move(ws)) {} + + void async_write_binary(util::Span data, sync::SyncSocketProvider::FunctionHandler&& handler) override { + auto handler_ptr = handler.release(); + m_interface->async_write_binary(data.data(), [ptr = std::move(handler_ptr)](websocket_interface::status s) { + auto uf = util::UniqueFunction(std::move(ptr)); + return uf(s.operator ::realm::Status()); + }); + }; + + std::unique_ptr m_interface; + }; + + return std::make_unique(std::move(m_interface)); + } + + std::unique_ptr create_websocket_observer_from_core_shim(std::unique_ptr<::realm::sync::WebSocketObserver>&& m_observer) { + struct core_websocket_observer_shim : public websocket_observer{ + explicit core_websocket_observer_shim(std::unique_ptr<::realm::sync::WebSocketObserver>&& ws) : m_observer(std::move(ws)) {} + ~core_websocket_observer_shim() = default; + + void websocket_connected_handler(const std::string& protocol) override { + m_observer->websocket_connected_handler(protocol); + } + + void websocket_error_handler() override { + m_observer->websocket_error_handler(); + } + + bool websocket_binary_message_received(std::string_view data) override { + return m_observer->websocket_binary_message_received(data); + } + + bool websocket_closed_handler(bool was_clean, websocket_err_codes error_code, + std::string_view message) override { + return m_observer->websocket_closed_handler(was_clean, static_cast<::realm::sync::websocket::WebSocketError>(error_code), message); + } + + std::unique_ptr<::realm::sync::WebSocketObserver>&& m_observer; + }; + + return std::make_unique(std::move(m_observer)); + } + + std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(std::unique_ptr&& provider, + const std::shared_ptr& handler) { + + struct sync_timer_shim final : public ::realm::sync::SyncSocketProvider::Timer { + sync_timer_shim(std::unique_ptr&& timer) : m_timer(std::move(timer)) {} + ~sync_timer_shim() = default; + + void cancel() override { + m_timer->cancel(); + } + + private: + std::unique_ptr m_timer; + }; + + struct sync_socket_provider_shim final : public ::realm::sync::SyncSocketProvider { + explicit sync_socket_provider_shim(std::unique_ptr&& provider, + const std::shared_ptr& handler) { + m_provider = std::move(provider); + m_websocket_event_handler = handler; + } + + sync_socket_provider_shim() = delete; + ~sync_socket_provider_shim() = default; + + std::unique_ptr<::realm::sync::WebSocketInterface> connect(std::unique_ptr<::realm::sync::WebSocketObserver> observer, ::realm::sync::WebSocketEndpoint&& ep) override { + if (m_websocket_event_handler) { + auto ws_ep = m_websocket_event_handler->on_connect(to_websocket_endpoint(std::move(ep))); + return create_websocket_interface_shim(m_provider->connect(create_websocket_observer_from_core_shim(std::move(observer)), std::move(ws_ep))); + } + + return create_websocket_interface_shim(m_provider->connect(create_websocket_observer_from_core_shim(std::move(observer)), to_websocket_endpoint(std::move(ep)))); + } + + void post(FunctionHandler&& handler) override { + auto handler_ptr = handler.release(); + m_provider->post([ptr = std::move(handler_ptr)](websocket_interface::status s) { + auto uf = util::UniqueFunction(std::move(ptr)); + return uf(s.operator ::realm::Status()); + }); + } + + ::realm::sync::SyncSocketProvider::SyncTimer create_timer(std::chrono::milliseconds delay, ::realm::sync::SyncSocketProvider::FunctionHandler&& handler) override { + auto handler_ptr = handler.release(); + auto fn = [ptr = std::move(handler_ptr)](websocket_interface::status s) { + auto uf = util::UniqueFunction(std::move(ptr)); + return uf(s.operator ::realm::Status()); + }; + return std::make_unique(m_provider->create_timer(delay, std::move(fn))); + } + private: + std::shared_ptr m_websocket_event_handler; + std::unique_ptr m_provider; + }; + + return std::make_unique(std::move(provider), handler); + } + } //namespace realm::networking \ No newline at end of file From e1a98405a47657263444a0673faffec6e8316ec6 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 10 Jun 2024 17:06:59 +0100 Subject: [PATCH 04/41] Add factory for http transport --- .../internal/generic_network_transport.hpp | 18 +-- include/cpprealm/networking/networking.hpp | 40 ++++--- .../networking/platform_networking.hpp | 27 +++-- src/cpprealm/app.cpp | 14 ++- .../internal/network/network_transport.cpp | 26 ++--- src/cpprealm/networking/networking.cpp | 28 ++--- .../networking/platform_networking.cpp | 109 ++++++++++-------- tests/admin_utils.cpp | 4 +- 8 files changed, 136 insertions(+), 130 deletions(-) diff --git a/include/cpprealm/internal/generic_network_transport.hpp b/include/cpprealm/internal/generic_network_transport.hpp index f8e3b6c59..74495e6cd 100644 --- a/include/cpprealm/internal/generic_network_transport.hpp +++ b/include/cpprealm/internal/generic_network_transport.hpp @@ -22,23 +22,9 @@ #include #include -#include -#include - -namespace realm { - namespace app { - struct Request; - struct Response; - struct GenericNetworkTransport; - } - namespace sync { - class SyncSocketProvider; - } -} namespace realm::internal { - -class DefaultTransport : public networking::http_network_transport_client { -public: + class DefaultTransport : public ::realm::networking::http_transport_client { + public: DefaultTransport(const std::optional>& custom_http_headers = std::nullopt, const std::optional& proxy_config = std::nullopt); ~DefaultTransport() = default; diff --git a/include/cpprealm/networking/networking.hpp b/include/cpprealm/networking/networking.hpp index 09a64aa18..e8b253a74 100644 --- a/include/cpprealm/networking/networking.hpp +++ b/include/cpprealm/networking/networking.hpp @@ -21,16 +21,6 @@ #include -namespace realm { - namespace app { - struct Request; - struct Response; - } - namespace sync { - struct WebSocketEndpoint; - } -} - namespace realm::networking { /** * An HTTP method type. @@ -144,8 +134,8 @@ namespace realm::networking { }; // Interface for providing http transport - struct http_network_transport_client { - virtual ~http_network_transport_client() = default; + struct http_transport_client { + virtual ~http_transport_client() = default; // HTTP virtual void send_request_to_server(const request& request, std::function&& completion) = 0; @@ -158,15 +148,27 @@ namespace realm::networking { std::function on_connect; }; - request to_request(const ::realm::app::Request&); - ::realm::app::Request to_core_request(const request&); - - response to_response(const ::realm::app::Response&); - ::realm::app::Response to_core_response(const response&); +} //namespace realm::networking - ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ws_endpoint& ep); - ws_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& ep); +namespace realm { + namespace app { + struct Request; + struct Response; + } + namespace sync { + struct WebSocketEndpoint; + } } +namespace realm::internal::networking { + ::realm::networking::request to_request(const ::realm::app::Request&); + ::realm::app::Request to_core_request(const ::realm::networking::request&); + + ::realm::networking::response to_response(const ::realm::app::Response&); + ::realm::app::Response to_core_response(const ::realm::networking::response&); + + ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::ws_endpoint& ep); + ::realm::networking::ws_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& ep); +} //namespace internal::networking #endif//CPPREALM_NETWORKING_HPP diff --git a/include/cpprealm/networking/platform_networking.hpp b/include/cpprealm/networking/platform_networking.hpp index de30c9477..4f8c823c0 100644 --- a/include/cpprealm/networking/platform_networking.hpp +++ b/include/cpprealm/networking/platform_networking.hpp @@ -38,15 +38,8 @@ namespace realm { class Logger; } } -namespace realm::networking { - std::shared_ptr default_sync_socket_provider_factory(const std::shared_ptr& logger, - const std::string& user_agent_binding_info, - const std::string& user_agent_application_info, - const std::shared_ptr&); - - std::shared_ptr default_http_client_factory(const std::optional>& custom_http_headers = std::nullopt, - const std::optional& proxy_config = std::nullopt); +namespace realm::networking { /// The WebSocket base class that is used by the SyncClient to send data over the /// WebSocket connection with the server. This is the class that is returned by @@ -227,8 +220,22 @@ namespace realm::networking { virtual SyncTimer create_timer(std::chrono::milliseconds delay, FunctionHandler&& handler) = 0; }; - std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(std::unique_ptr&& provider, - const std::shared_ptr& handler); + struct http_client_factory { + static std::optional> custom_http_headers; + static std::optional proxy_config; + + static std::shared_ptr make_default_http_client(); + static void set_http_client_factory(std::shared_ptr (*factory)()); + }; +} + +namespace realm::internal::networking { + std::shared_ptr default_sync_socket_provider_factory(const std::shared_ptr& logger, + const std::string& user_agent_binding_info, + const std::string& user_agent_application_info, + const std::shared_ptr<::realm::networking::websocket_event_handler>&); + + std::shared_ptr create_http_client_shim(const std::shared_ptr<::realm::networking::http_transport_client>&); } #endif//CPPREALM_PLATFORM_NETWORKING_HPP diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index c36ffd043..1d3f0f1d9 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -12,7 +12,6 @@ #include #include #include -//#include #include #include @@ -495,15 +494,18 @@ namespace realm { client_config.user_agent_application_info = config.app_id; if (config.websocket_event_handler) { - auto websocket_provider = ::realm::networking::default_sync_socket_provider_factory(util::Logger::get_default_logger(), - client_config.user_agent_binding_info, - client_config.user_agent_application_info, - config.websocket_event_handler); + auto websocket_provider = ::realm::internal::networking::default_sync_socket_provider_factory(util::Logger::get_default_logger(), + client_config.user_agent_binding_info, + client_config.user_agent_application_info, + config.websocket_event_handler); client_config.socket_provider = websocket_provider; } + networking::http_client_factory::custom_http_headers = config.custom_http_headers; + networking::http_client_factory::proxy_config = config.proxy_configuration; + app_config.app_id = config.app_id; - app_config.transport = ::realm::networking::default_http_client_factory(config.custom_http_headers, config.proxy_configuration); + app_config.transport = ::realm::internal::networking::create_http_client_shim(networking::http_client_factory::make_default_http_client()); app_config.base_url = config.base_url; app_config.metadata_mode = should_encrypt ? app::AppConfig::MetadataMode::Encryption : app::AppConfig::MetadataMode::NoEncryption; diff --git a/src/cpprealm/internal/network/network_transport.cpp b/src/cpprealm/internal/network/network_transport.cpp index fa8cb2e65..1aa180e68 100644 --- a/src/cpprealm/internal/network/network_transport.cpp +++ b/src/cpprealm/internal/network/network_transport.cpp @@ -130,8 +130,8 @@ namespace realm::internal { } } - void DefaultTransport::send_request_to_server(const networking::request& request, - std::function&& completion_block) { + void DefaultTransport::send_request_to_server(const ::realm::networking::request& request, + std::function&& completion_block) { const auto uri = realm::util::Uri(request.url); std::string userinfo, host, port; uri.get_auth(userinfo, host, port); @@ -155,7 +155,7 @@ namespace realm::internal { std::error_code e; auto address = realm::sync::network::make_address(m_proxy_config->address, e); if (e.value() > 0) { - networking::response response; + ::realm::networking::response response; response.custom_status_code = e.value(); response.body = e.message(); completion_block(std::move(response)); @@ -178,7 +178,7 @@ namespace realm::internal { } socket.connect(ep); } catch (...) { - networking::response response; + ::realm::networking::response response; response.custom_status_code = util::error::operation_aborted; completion_block(std::move(response)); return; @@ -200,14 +200,14 @@ namespace realm::internal { realm::sync::HTTPClient m_proxy_client = realm::sync::HTTPClient(socket, logger); auto handler = [&](realm::sync::HTTPResponse response, std::error_code ec) { if (ec && ec != util::error::operation_aborted) { - networking::response res; + ::realm::networking::response res; res.custom_status_code = util::error::operation_aborted; completion_block(std::move(res)); return; } if (response.status != realm::sync::HTTPStatus::Ok) { - networking::response res; + ::realm::networking::response res; res.http_status_code = static_cast(response.status); completion_block(std::move(res)); return; @@ -253,19 +253,19 @@ namespace realm::internal { realm::sync::HTTPClient m_http_client = realm::sync::HTTPClient(socket, logger); realm::sync::HTTPMethod method; switch (request.method) { - case networking::http_method::get: + case ::realm::networking::http_method::get: method = realm::sync::HTTPMethod::Get; break; - case networking::http_method::put: + case ::realm::networking::http_method::put: method = realm::sync::HTTPMethod::Put; break; - case networking::http_method::post: + case ::realm::networking::http_method::post: method = realm::sync::HTTPMethod::Post; break; - case networking::http_method::patch: + case ::realm::networking::http_method::patch: method = realm::sync::HTTPMethod::Patch; break; - case networking::http_method::del: + case ::realm::networking::http_method::del: method = realm::sync::HTTPMethod::Delete; break; default: @@ -292,7 +292,7 @@ namespace realm::internal { req.body = request.body.empty() ? std::nullopt : std::optional(request.body); m_http_client.async_request(std::move(req), [cb = std::move(completion_block)](const realm::sync::HTTPResponse& r, const std::error_code&) { - networking::response res; + ::realm::networking::response res; res.body = r.body ? *r.body : ""; for (auto& [k, v] : r.headers) { res.headers[k] = v; @@ -302,7 +302,7 @@ namespace realm::internal { cb(res); }); } else { - networking::response response; + ::realm::networking::response response; response.custom_status_code = util::error::operation_aborted; completion_block(std::move(response)); return; diff --git a/src/cpprealm/networking/networking.cpp b/src/cpprealm/networking/networking.cpp index 4191b9034..f4c9620a5 100644 --- a/src/cpprealm/networking/networking.cpp +++ b/src/cpprealm/networking/networking.cpp @@ -3,19 +3,19 @@ #include #include -namespace realm::networking { - request to_request(const ::realm::app::Request& core_request) { - request req; - req.method = static_cast(core_request.method); +namespace realm::internal::networking { + ::realm::networking::request to_request(const ::realm::app::Request& core_request) { + ::realm::networking::request req; + req.method = static_cast<::realm::networking::http_method>(core_request.method); req.url = core_request.url; req.timeout_ms = core_request.timeout_ms; req.headers = core_request.headers; req.body = core_request.body; return req; } - ::realm::app::Request to_core_request(const request& req) { + ::realm::app::Request to_core_request(const ::realm::networking::request& req) { ::realm::app::Request core_request; - core_request.method = static_cast(req.method); + core_request.method = static_cast<::realm::app::HttpMethod>(req.method); core_request.url = req.url; core_request.timeout_ms = req.timeout_ms; core_request.headers = req.headers; @@ -23,8 +23,8 @@ namespace realm::networking { return core_request; } - response to_response(const ::realm::app::Response& core_response) { - response req; + ::realm::networking::response to_response(const ::realm::app::Response& core_response) { + ::realm::networking::response req; req.http_status_code = core_response.http_status_code; req.custom_status_code = core_response.custom_status_code; req.headers = core_response.headers; @@ -32,19 +32,19 @@ namespace realm::networking { req.body = core_response.body; return req; } - ::realm::app::Response to_core_response(const response& req) { + ::realm::app::Response to_core_response(const ::realm::networking::response& req) { ::realm::app::Response core_response; core_response.http_status_code = req.http_status_code; core_response.custom_status_code = req.custom_status_code; core_response.headers = req.headers; core_response.body = req.body; if (req.client_error_code) { - core_response.client_error_code = static_cast(*req.client_error_code); + core_response.client_error_code = static_cast<::realm::ErrorCodes::Error>(*req.client_error_code); } return core_response; } - ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ws_endpoint& ep) { + ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::ws_endpoint& ep) { ::realm::sync::WebSocketEndpoint core_ep; core_ep.address = ep.address; core_ep.port = ep.port; @@ -54,8 +54,8 @@ namespace realm::networking { return core_ep; } - ws_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& core_ep) { - ws_endpoint ep; + ::realm::networking::ws_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& core_ep) { + ::realm::networking::ws_endpoint ep; ep.address = core_ep.address; ep.port = core_ep.port; ep.path = core_ep.path; @@ -63,4 +63,4 @@ namespace realm::networking { ep.is_ssl = core_ep.is_ssl; return ep; } -} \ No newline at end of file +} //namespace internal::networking \ No newline at end of file diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/platform_networking.cpp index c39930e9d..5a4b799de 100644 --- a/src/cpprealm/networking/platform_networking.cpp +++ b/src/cpprealm/networking/platform_networking.cpp @@ -5,16 +5,30 @@ #include namespace realm::networking { + std::shared_ptr (*s_http_client_factory)() = http_client_factory::make_default_http_client; + std::optional> http_client_factory::custom_http_headers = std::nullopt; + std::optional http_client_factory::proxy_config = std::nullopt; + + std::shared_ptr http_client_factory::make_default_http_client() { + return std::make_shared(custom_http_headers, proxy_config); + } + + void http_client_factory::set_http_client_factory(std::shared_ptr (*factory)()) { + s_http_client_factory = std::move(factory); + } +} + +namespace realm::internal::networking { std::shared_ptr default_sync_socket_provider_factory(const std::shared_ptr& logger, const std::string& user_agent_binding_info, const std::string& user_agent_application_info, - const std::shared_ptr& configuration_handler) { + const std::shared_ptr<::realm::networking::websocket_event_handler>& configuration_handler) { class default_sync_socket_provider_shim final : public ::realm::sync::SyncSocketProvider { public: explicit default_sync_socket_provider_shim(const std::shared_ptr& logger, const std::string& user_agent_binding_info, const std::string& user_agent_application_info, - const std::shared_ptr& configuration_handler) { + const std::shared_ptr<::realm::networking::websocket_event_handler>& configuration_handler) { auto user_agent = util::format("RealmSync/%1 (%2) %3 %4", REALM_VERSION_STRING, util::get_platform_info(), user_agent_binding_info, user_agent_application_info); m_default_provider = std::make_unique<::realm::sync::websocket::DefaultSocketProvider>(logger, user_agent); m_websocket_event_handler = configuration_handler; @@ -45,7 +59,7 @@ namespace realm::networking { m_default_provider->stop(b); } private: - std::shared_ptr m_websocket_event_handler; + std::shared_ptr<::realm::networking::websocket_event_handler> m_websocket_event_handler; std::unique_ptr<::realm::sync::websocket::DefaultSocketProvider> m_default_provider; }; @@ -53,56 +67,52 @@ namespace realm::networking { user_agent_application_info, configuration_handler); } - std::shared_ptr default_http_client_factory(const std::optional>& custom_http_headers, - const std::optional& proxy_config) { + std::unique_ptr<::realm::sync::WebSocketInterface> create_websocket_interface_shim(std::unique_ptr<::realm::networking::websocket_interface>&& m_interface) { + struct core_websocket_interface_shim : public ::realm::sync::WebSocketInterface { + ~core_websocket_interface_shim() = default; + explicit core_websocket_interface_shim(std::unique_ptr<::realm::networking::websocket_interface>&& ws) : m_interface(std::move(ws)) {} + + void async_write_binary(util::Span data, sync::SyncSocketProvider::FunctionHandler&& handler) override { + auto handler_ptr = handler.release(); + m_interface->async_write_binary(data.data(), [ptr = std::move(handler_ptr)](::realm::networking::websocket_interface::status s) { + auto uf = util::UniqueFunction(std::move(ptr)); + return uf(s.operator ::realm::Status()); + }); + }; + + std::unique_ptr<::realm::networking::websocket_interface> m_interface; + }; + + return std::make_unique(std::move(m_interface)); + } + + std::shared_ptr create_http_client_shim(const std::shared_ptr<::realm::networking::http_transport_client>& http_client) { struct core_http_transport_shim : app::GenericNetworkTransport { ~core_http_transport_shim() = default; - core_http_transport_shim(const std::optional>& custom_http_headers = std::nullopt, - const std::optional& proxy_config = std::nullopt) { - m_transport = internal::DefaultTransport(custom_http_headers, proxy_config); + core_http_transport_shim(const std::shared_ptr<::realm::networking::http_transport_client>& http_client) { + m_http_client = http_client; } void send_request_to_server(const app::Request& request, util::UniqueFunction&& completion) { auto completion_ptr = completion.release(); - m_transport.send_request_to_server(to_request(request), + m_http_client->send_request_to_server(to_request(request), [f = std::move(completion_ptr)] - (const networking::response& response) { + (const ::realm::networking::response& response) { auto uf = util::UniqueFunction(std::move(f)); uf(to_core_response(response)); }); } - private: - internal::DefaultTransport m_transport; - }; - - return std::make_shared(custom_http_headers, proxy_config); - }; - - // Shims - - std::unique_ptr<::realm::sync::WebSocketInterface> create_websocket_interface_shim(std::unique_ptr&& m_interface) { - struct core_websocket_interface_shim : public ::realm::sync::WebSocketInterface { - ~core_websocket_interface_shim() = default; - explicit core_websocket_interface_shim(std::unique_ptr&& ws) : m_interface(std::move(ws)) {} - - void async_write_binary(util::Span data, sync::SyncSocketProvider::FunctionHandler&& handler) override { - auto handler_ptr = handler.release(); - m_interface->async_write_binary(data.data(), [ptr = std::move(handler_ptr)](websocket_interface::status s) { - auto uf = util::UniqueFunction(std::move(ptr)); - return uf(s.operator ::realm::Status()); - }); - }; - - std::unique_ptr m_interface; + private: + std::shared_ptr<::realm::networking::http_transport_client> m_http_client; }; - return std::make_unique(std::move(m_interface)); - } + return std::make_shared(http_client); + }; - std::unique_ptr create_websocket_observer_from_core_shim(std::unique_ptr<::realm::sync::WebSocketObserver>&& m_observer) { - struct core_websocket_observer_shim : public websocket_observer{ + std::unique_ptr<::realm::networking::websocket_observer> create_websocket_observer_from_core_shim(std::unique_ptr<::realm::sync::WebSocketObserver>&& m_observer) { + struct core_websocket_observer_shim : public ::realm::networking::websocket_observer { explicit core_websocket_observer_shim(std::unique_ptr<::realm::sync::WebSocketObserver>&& ws) : m_observer(std::move(ws)) {} ~core_websocket_observer_shim() = default; @@ -118,7 +128,7 @@ namespace realm::networking { return m_observer->websocket_binary_message_received(data); } - bool websocket_closed_handler(bool was_clean, websocket_err_codes error_code, + bool websocket_closed_handler(bool was_clean, ::realm::networking::websocket_err_codes error_code, std::string_view message) override { return m_observer->websocket_closed_handler(was_clean, static_cast<::realm::sync::websocket::WebSocketError>(error_code), message); } @@ -129,11 +139,11 @@ namespace realm::networking { return std::make_unique(std::move(m_observer)); } - std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(std::unique_ptr&& provider, - const std::shared_ptr& handler) { + std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(std::unique_ptr<::realm::networking::sync_socket_provider>&& provider, + const std::shared_ptr<::realm::networking::websocket_event_handler>& handler) { struct sync_timer_shim final : public ::realm::sync::SyncSocketProvider::Timer { - sync_timer_shim(std::unique_ptr&& timer) : m_timer(std::move(timer)) {} + sync_timer_shim(std::unique_ptr<::realm::networking::sync_socket_provider::timer>&& timer) : m_timer(std::move(timer)) {} ~sync_timer_shim() = default; void cancel() override { @@ -141,12 +151,12 @@ namespace realm::networking { } private: - std::unique_ptr m_timer; + std::unique_ptr<::realm::networking::sync_socket_provider::timer> m_timer; }; struct sync_socket_provider_shim final : public ::realm::sync::SyncSocketProvider { - explicit sync_socket_provider_shim(std::unique_ptr&& provider, - const std::shared_ptr& handler) { + explicit sync_socket_provider_shim(std::unique_ptr<::realm::networking::sync_socket_provider>&& provider, + const std::shared_ptr<::realm::networking::websocket_event_handler>& handler) { m_provider = std::move(provider); m_websocket_event_handler = handler; } @@ -165,7 +175,7 @@ namespace realm::networking { void post(FunctionHandler&& handler) override { auto handler_ptr = handler.release(); - m_provider->post([ptr = std::move(handler_ptr)](websocket_interface::status s) { + m_provider->post([ptr = std::move(handler_ptr)](::realm::networking::websocket_interface::status s) { auto uf = util::UniqueFunction(std::move(ptr)); return uf(s.operator ::realm::Status()); }); @@ -173,18 +183,17 @@ namespace realm::networking { ::realm::sync::SyncSocketProvider::SyncTimer create_timer(std::chrono::milliseconds delay, ::realm::sync::SyncSocketProvider::FunctionHandler&& handler) override { auto handler_ptr = handler.release(); - auto fn = [ptr = std::move(handler_ptr)](websocket_interface::status s) { + auto fn = [ptr = std::move(handler_ptr)](::realm::networking::websocket_interface::status s) { auto uf = util::UniqueFunction(std::move(ptr)); return uf(s.operator ::realm::Status()); }; return std::make_unique(m_provider->create_timer(delay, std::move(fn))); } private: - std::shared_ptr m_websocket_event_handler; - std::unique_ptr m_provider; + std::shared_ptr<::realm::networking::websocket_event_handler> m_websocket_event_handler; + std::unique_ptr<::realm::networking::sync_socket_provider> m_provider; }; return std::make_unique(std::move(provider), handler); } - -} //namespace realm::networking \ No newline at end of file +} //namespace internal::networking \ No newline at end of file diff --git a/tests/admin_utils.cpp b/tests/admin_utils.cpp index 28e7833fb..c4c67ae71 100644 --- a/tests/admin_utils.cpp +++ b/tests/admin_utils.cpp @@ -33,9 +33,9 @@ namespace Admin { internal::DefaultTransport transport; std::promise p; std::future f = p.get_future(); - transport.send_request_to_server(networking::to_request(std::move(request)), + transport.send_request_to_server(::realm::internal::networking::to_request(std::move(request)), [&p](auto&& response) { - p.set_value(networking::to_core_response(std::move(response))); + p.set_value(::realm::internal::networking::to_core_response(std::move(response))); }); return f.get(); } From 5671bddd81b62f09e5cbf0c93fbfef3159326f1b Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 10 Jun 2024 17:55:34 +0100 Subject: [PATCH 05/41] cleanup default network transport --- .../internal/generic_network_transport.hpp | 4 +- src/cpprealm/analytics.cpp | 2 +- .../internal/curl/network_transport.cpp | 39 +++++++++---------- .../internal/network/network_transport.cpp | 4 +- .../networking/platform_networking.cpp | 2 +- tests/admin_utils.cpp | 2 +- 6 files changed, 26 insertions(+), 27 deletions(-) diff --git a/include/cpprealm/internal/generic_network_transport.hpp b/include/cpprealm/internal/generic_network_transport.hpp index 74495e6cd..ba4daf5ff 100644 --- a/include/cpprealm/internal/generic_network_transport.hpp +++ b/include/cpprealm/internal/generic_network_transport.hpp @@ -22,7 +22,7 @@ #include #include -namespace realm::internal { +namespace realm::internal::networking { class DefaultTransport : public ::realm::networking::http_transport_client { public: DefaultTransport(const std::optional>& custom_http_headers = std::nullopt, @@ -36,6 +36,6 @@ namespace realm::internal { std::optional m_proxy_config; }; -} // namespace realm +} // namespace realm::internal::networking #endif //CPPREALM_GENERIC_NETWORK_TRANSPORT_CPP diff --git a/src/cpprealm/analytics.cpp b/src/cpprealm/analytics.cpp index 39b8d93ec..fc8264f72 100644 --- a/src/cpprealm/analytics.cpp +++ b/src/cpprealm/analytics.cpp @@ -273,7 +273,7 @@ namespace realm { std::stringstream json_ss; json_ss << post_data; auto json_str = json_ss.str(); - auto transport = std::make_unique(); + auto transport = std::make_unique(); std::vector buffer; buffer.resize(5000); diff --git a/src/cpprealm/internal/curl/network_transport.cpp b/src/cpprealm/internal/curl/network_transport.cpp index c4ea1524a..35a9b3b62 100644 --- a/src/cpprealm/internal/curl/network_transport.cpp +++ b/src/cpprealm/internal/curl/network_transport.cpp @@ -23,7 +23,7 @@ #include -namespace realm::internal { +namespace realm::internal::networking { namespace { @@ -89,13 +89,13 @@ namespace realm::internal { return nitems * size; } - static app::Response do_http_request(const app::Request& request, + static ::realm::networking::response do_http_request(const ::realm::networking::request& request, const std::optional& proxy_config = std::nullopt) { CurlGlobalGuard curl_global_guard; auto curl = curl_easy_init(); if (!curl) { - return app::Response{500, -1, {}, "", std::nullopt}; + return ::realm::networking::response{500, -1, {}, "", std::nullopt}; } struct curl_slist* list = nullptr; @@ -117,22 +117,22 @@ namespace realm::internal { } /* Now specify the POST data */ - if (request.method == app::HttpMethod::post) { + if (request.method == ::realm::networking::http_method::post) { curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); } - else if (request.method == app::HttpMethod::put) { + else if (request.method == ::realm::networking::http_method::put) { curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); } - else if (request.method == app::HttpMethod::patch) { + else if (request.method == ::realm::networking::http_method::patch) { curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); } - else if (request.method == app::HttpMethod::del) { + else if (request.method == ::realm::networking::http_method::del) { curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); } - else if (request.method == app::HttpMethod::patch) { + else if (request.method == ::realm::networking::http_method::patch) { curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); } @@ -174,17 +174,16 @@ namespace realm::internal { m_proxy_config = proxy_config; } - void DefaultTransport::send_request_to_server(const app::Request& request, - std::function&& completion_block) - { - if (m_custom_http_headers) { - auto req_copy = request; - req_copy.headers.insert(m_custom_http_headers->begin(), m_custom_http_headers->end()); - completion_block(do_http_request(req_copy, m_proxy_config)); - return; + void DefaultTransport::send_request_to_server(const ::realm::networking::request& request, + std::function&& completion_block) { + { + if (m_custom_http_headers) { + auto req_copy = request; + req_copy.headers.insert(m_custom_http_headers->begin(), m_custom_http_headers->end()); + completion_block(do_http_request(req_copy, m_proxy_config)); + return; + } + completion_block(do_http_request(request, m_proxy_config)); } - completion_block(do_http_request(request, m_proxy_config)); } - - -} // namespace realm::internal +} // namespace realm::internal::networking diff --git a/src/cpprealm/internal/network/network_transport.cpp b/src/cpprealm/internal/network/network_transport.cpp index 1aa180e68..51a805b95 100644 --- a/src/cpprealm/internal/network/network_transport.cpp +++ b/src/cpprealm/internal/network/network_transport.cpp @@ -32,7 +32,7 @@ #include -namespace realm::internal { +namespace realm::internal::networking { struct DefaultSocket : realm::sync::network::Socket { DefaultSocket(realm::sync::network::Service& service) : realm::sync::network::Socket(service) @@ -317,5 +317,5 @@ namespace realm::internal { service.run(); } -} +} //namespace realm::internal::networking diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/platform_networking.cpp index 5a4b799de..dae782448 100644 --- a/src/cpprealm/networking/platform_networking.cpp +++ b/src/cpprealm/networking/platform_networking.cpp @@ -10,7 +10,7 @@ namespace realm::networking { std::optional http_client_factory::proxy_config = std::nullopt; std::shared_ptr http_client_factory::make_default_http_client() { - return std::make_shared(custom_http_headers, proxy_config); + return std::make_shared(custom_http_headers, proxy_config); } void http_client_factory::set_http_client_factory(std::shared_ptr (*factory)()) { diff --git a/tests/admin_utils.cpp b/tests/admin_utils.cpp index c4c67ae71..6b1d90149 100644 --- a/tests/admin_utils.cpp +++ b/tests/admin_utils.cpp @@ -30,7 +30,7 @@ namespace Admin { std::mutex Admin::Session::mutex; static app::Response do_http_request(app::Request &&request) { - internal::DefaultTransport transport; + internal::networking::DefaultTransport transport; std::promise p; std::future f = p.get_future(); transport.send_request_to_server(::realm::internal::networking::to_request(std::move(request)), From 6b5ee3c18b8cce2896278088a35b7403954ef9e8 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Thu, 13 Jun 2024 22:29:34 +0100 Subject: [PATCH 06/41] Add platform networking --- include/cpprealm/app.hpp | 2 ++ include/cpprealm/internal/bridge/status.hpp | 2 +- .../cpprealm/networking/platform_networking.hpp | 5 ++++- src/cpprealm/app.cpp | 3 +++ src/cpprealm/internal/bridge/status.cpp | 4 ++++ src/cpprealm/networking/platform_networking.cpp | 14 +++++++------- 6 files changed, 21 insertions(+), 9 deletions(-) diff --git a/include/cpprealm/app.hpp b/include/cpprealm/app.hpp index 4353b5bf1..b9022bdc3 100644 --- a/include/cpprealm/app.hpp +++ b/include/cpprealm/app.hpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -231,6 +232,7 @@ class App { std::optional> metadata_encryption_key; std::optional proxy_configuration; std::shared_ptr<::realm::networking::websocket_event_handler> websocket_event_handler; + std::shared_ptr<::realm::networking::sync_socket_provider> sync_socket_provider; }; [[deprecated("Use App(const configuration&) instead.")]] diff --git a/include/cpprealm/internal/bridge/status.hpp b/include/cpprealm/internal/bridge/status.hpp index eb0a8e00e..d1c7b65b7 100644 --- a/include/cpprealm/internal/bridge/status.hpp +++ b/include/cpprealm/internal/bridge/status.hpp @@ -61,7 +61,7 @@ namespace realm::internal::bridge { }; struct status { - + static status ok(); status(const ::realm::Status&); status(::realm::Status&&); status(const status&); diff --git a/include/cpprealm/networking/platform_networking.hpp b/include/cpprealm/networking/platform_networking.hpp index 4f8c823c0..ccd4bf08a 100644 --- a/include/cpprealm/networking/platform_networking.hpp +++ b/include/cpprealm/networking/platform_networking.hpp @@ -225,7 +225,7 @@ namespace realm::networking { static std::optional proxy_config; static std::shared_ptr make_default_http_client(); - static void set_http_client_factory(std::shared_ptr (*factory)()); + static void set_http_client_factory(std::function()>&&); }; } @@ -236,6 +236,9 @@ namespace realm::internal::networking { const std::shared_ptr<::realm::networking::websocket_event_handler>&); std::shared_ptr create_http_client_shim(const std::shared_ptr<::realm::networking::http_transport_client>&); + + std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider, + const std::shared_ptr<::realm::networking::websocket_event_handler>& handler); } #endif//CPPREALM_PLATFORM_NETWORKING_HPP diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index 1d3f0f1d9..e4e7d34d0 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -500,6 +500,9 @@ namespace realm { config.websocket_event_handler); client_config.socket_provider = websocket_provider; } + if (config.sync_socket_provider) { + client_config.socket_provider = ::realm::internal::networking::create_sync_socket_provider_shim(config.sync_socket_provider, config.websocket_event_handler); + } networking::http_client_factory::custom_http_headers = config.custom_http_headers; networking::http_client_factory::proxy_config = config.proxy_configuration; diff --git a/src/cpprealm/internal/bridge/status.cpp b/src/cpprealm/internal/bridge/status.cpp index 936cd54ea..5fb2b9453 100644 --- a/src/cpprealm/internal/bridge/status.cpp +++ b/src/cpprealm/internal/bridge/status.cpp @@ -24,6 +24,10 @@ namespace realm::internal::bridge { return reinterpret_cast(&m_error_category)->value(); } + status status::ok() { + return ::realm::Status::OK(); + } + status::status(const ::realm::Status& other) { #ifdef CPPREALM_HAVE_GENERATED_BRIDGE_TYPES new (&m_status) Status(other); diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/platform_networking.cpp index dae782448..8834fbf5c 100644 --- a/src/cpprealm/networking/platform_networking.cpp +++ b/src/cpprealm/networking/platform_networking.cpp @@ -5,7 +5,7 @@ #include namespace realm::networking { - std::shared_ptr (*s_http_client_factory)() = http_client_factory::make_default_http_client; + std::function()> s_http_client_factory = http_client_factory::make_default_http_client; std::optional> http_client_factory::custom_http_headers = std::nullopt; std::optional http_client_factory::proxy_config = std::nullopt; @@ -13,8 +13,8 @@ namespace realm::networking { return std::make_shared(custom_http_headers, proxy_config); } - void http_client_factory::set_http_client_factory(std::shared_ptr (*factory)()) { - s_http_client_factory = std::move(factory); + void http_client_factory::set_http_client_factory(std::function()>&& factory_fn) { + s_http_client_factory = std::move(factory_fn); } } @@ -139,7 +139,7 @@ namespace realm::internal::networking { return std::make_unique(std::move(m_observer)); } - std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(std::unique_ptr<::realm::networking::sync_socket_provider>&& provider, + std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider, const std::shared_ptr<::realm::networking::websocket_event_handler>& handler) { struct sync_timer_shim final : public ::realm::sync::SyncSocketProvider::Timer { @@ -155,9 +155,9 @@ namespace realm::internal::networking { }; struct sync_socket_provider_shim final : public ::realm::sync::SyncSocketProvider { - explicit sync_socket_provider_shim(std::unique_ptr<::realm::networking::sync_socket_provider>&& provider, + explicit sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider, const std::shared_ptr<::realm::networking::websocket_event_handler>& handler) { - m_provider = std::move(provider); + m_provider = provider; m_websocket_event_handler = handler; } @@ -191,7 +191,7 @@ namespace realm::internal::networking { } private: std::shared_ptr<::realm::networking::websocket_event_handler> m_websocket_event_handler; - std::unique_ptr<::realm::networking::sync_socket_provider> m_provider; + std::shared_ptr<::realm::networking::sync_socket_provider> m_provider; }; return std::make_unique(std::move(provider), handler); From 2c12b8f416e53dcd9ef76a91809d8820322540b8 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 17 Jun 2024 11:55:38 +0100 Subject: [PATCH 07/41] Cleanup, add docs --- include/cpprealm/app.hpp | 31 +++++++ include/cpprealm/networking/networking.hpp | 18 +--- .../networking/platform_networking.hpp | 20 +++-- src/cpprealm/app.cpp | 14 +-- src/cpprealm/networking/networking.cpp | 6 +- .../networking/platform_networking.cpp | 10 +-- tests/CMakeLists.txt | 2 +- tests/sync/networking_tests.cpp | 31 +++++++ tests/sync/proxy_tests.cpp | 90 ------------------- 9 files changed, 95 insertions(+), 127 deletions(-) create mode 100644 tests/sync/networking_tests.cpp delete mode 100644 tests/sync/proxy_tests.cpp diff --git a/include/cpprealm/app.hpp b/include/cpprealm/app.hpp index b9022bdc3..fc47c9529 100644 --- a/include/cpprealm/app.hpp +++ b/include/cpprealm/app.hpp @@ -224,14 +224,45 @@ namespace app { class App { public: + /** + * Properties representing the configuration of a client + * that communicate with a particular Realm application. + * + * `App::configuration` options cannot be modified once the `App` using it + * is created. App's configuration values are cached when the App is created so any modifications after it + * will not have any effect. + */ struct configuration { + /// The App ID for your Atlas Device Sync Application. std::string app_id; + /// A custom base URL to request against. If not set or set to nil, the default base url for app services will be returned. std::optional base_url; + /// Custom location for Realm files. std::optional path; + // Extra HTTP headers to be set on each request to Atlas Device Sync when using the internal HTTP client. std::optional> custom_http_headers; + // Custom encryption key for the metadata Realm. std::optional> metadata_encryption_key; + // HTTP proxy configuration to be set on each HTTP request when using the internal HTTP client. std::optional proxy_configuration; + /** + * Sets a callback that will be used to configure outgoing WebSocket connections to Atlas Device Sync. + * You can use this to modify the default WebSocket behavior. Normally this is not required to connect to MongoDB Atlas, + * but it can be useful if client devices are behind a corporate firewall or use a more complex networking setup. + */ std::shared_ptr<::realm::networking::websocket_event_handler> websocket_event_handler; + /** + * Optionally provide a custom transport for network calls to the server. + * + * Alternatively use `realm::networking::set_http_client_factory` to globally set + * the default HTTP transport client. + */ + std::shared_ptr<::realm::networking::http_transport_client> http_transport_client; + /** + * Optionally provide a custom WebSocket interface for sync. + * Alternatively use `realm::networking::set_http_client_factory` to globally set + * the default HTTP transport client. + */ std::shared_ptr<::realm::networking::sync_socket_provider> sync_socket_provider; }; diff --git a/include/cpprealm/networking/networking.hpp b/include/cpprealm/networking/networking.hpp index e8b253a74..50d205663 100644 --- a/include/cpprealm/networking/networking.hpp +++ b/include/cpprealm/networking/networking.hpp @@ -93,9 +93,8 @@ namespace realm::networking { }; - struct ws_endpoint { + struct websocket_endpoint { using port_type = std::uint_fast16_t; - std::string address; // Host address port_type port; // Host port number std::string path; // Includes access token in query. @@ -136,18 +135,9 @@ namespace realm::networking { // Interface for providing http transport struct http_transport_client { virtual ~http_transport_client() = default; - // HTTP virtual void send_request_to_server(const request& request, std::function&& completion) = 0; }; - - struct websocket_event_handler { - /** - * Used to configure outgoing websocket connections. - */ - std::function on_connect; - }; - } //namespace realm::networking namespace realm { @@ -167,8 +157,8 @@ namespace realm::internal::networking { ::realm::networking::response to_response(const ::realm::app::Response&); ::realm::app::Response to_core_response(const ::realm::networking::response&); - ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::ws_endpoint& ep); - ::realm::networking::ws_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& ep); -} //namespace internal::networking + ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint & ep); + ::realm::networking::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& ep); +} //namespace realm::internal::networking #endif//CPPREALM_NETWORKING_HPP diff --git a/include/cpprealm/networking/platform_networking.hpp b/include/cpprealm/networking/platform_networking.hpp index ccd4bf08a..626b6ebaf 100644 --- a/include/cpprealm/networking/platform_networking.hpp +++ b/include/cpprealm/networking/platform_networking.hpp @@ -41,6 +41,15 @@ namespace realm { namespace realm::networking { + /** + * Class used for setting a callback which is used + * to pass network configuration options to the sync_socket_provider + * before the websocket establishes a connection. + */ + struct websocket_event_handler { + std::function on_connect; + }; + /// The WebSocket base class that is used by the SyncClient to send data over the /// WebSocket connection with the server. This is the class that is returned by /// SyncSocketProvider::connect() when a connection to an endpoint is requested. @@ -177,7 +186,7 @@ namespace realm::networking { /// The WebSocketObserver guarantees that the WebSocket object will be /// closed/destroyed before the observer is terminated/destroyed. virtual std::unique_ptr connect(std::unique_ptr observer, - ws_endpoint&& endpoint) = 0; + websocket_endpoint && endpoint) = 0; /// Submit a handler function to be executed by the event loop (thread). /// @@ -220,13 +229,8 @@ namespace realm::networking { virtual SyncTimer create_timer(std::chrono::milliseconds delay, FunctionHandler&& handler) = 0; }; - struct http_client_factory { - static std::optional> custom_http_headers; - static std::optional proxy_config; - - static std::shared_ptr make_default_http_client(); - static void set_http_client_factory(std::function()>&&); - }; + std::shared_ptr make_http_client(); + void set_http_client_factory(std::function()>&&); } namespace realm::internal::networking { diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index e4e7d34d0..989fda5ac 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -492,6 +492,7 @@ namespace realm { #endif client_config.user_agent_binding_info = std::string("RealmCpp/") + std::string(REALMCXX_VERSION_STRING); client_config.user_agent_application_info = config.app_id; + app_config.app_id = config.app_id; if (config.websocket_event_handler) { auto websocket_provider = ::realm::internal::networking::default_sync_socket_provider_factory(util::Logger::get_default_logger(), @@ -501,14 +502,17 @@ namespace realm { client_config.socket_provider = websocket_provider; } if (config.sync_socket_provider) { - client_config.socket_provider = ::realm::internal::networking::create_sync_socket_provider_shim(config.sync_socket_provider, config.websocket_event_handler); + client_config.socket_provider = ::realm::internal::networking::create_sync_socket_provider_shim(config.sync_socket_provider, + config.websocket_event_handler); } - networking::http_client_factory::custom_http_headers = config.custom_http_headers; - networking::http_client_factory::proxy_config = config.proxy_configuration; + if (config.http_transport_client) { + app_config.transport = internal::networking::create_http_client_shim(config.http_transport_client); + } else if (config.custom_http_headers || config.proxy_configuration) { + app_config.transport = internal::networking::create_http_client_shim(std::make_shared(config.custom_http_headers, + config.proxy_configuration)); + } - app_config.app_id = config.app_id; - app_config.transport = ::realm::internal::networking::create_http_client_shim(networking::http_client_factory::make_default_http_client()); app_config.base_url = config.base_url; app_config.metadata_mode = should_encrypt ? app::AppConfig::MetadataMode::Encryption : app::AppConfig::MetadataMode::NoEncryption; diff --git a/src/cpprealm/networking/networking.cpp b/src/cpprealm/networking/networking.cpp index f4c9620a5..30748c7a9 100644 --- a/src/cpprealm/networking/networking.cpp +++ b/src/cpprealm/networking/networking.cpp @@ -44,7 +44,7 @@ namespace realm::internal::networking { return core_response; } - ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::ws_endpoint& ep) { + ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint & ep) { ::realm::sync::WebSocketEndpoint core_ep; core_ep.address = ep.address; core_ep.port = ep.port; @@ -54,8 +54,8 @@ namespace realm::internal::networking { return core_ep; } - ::realm::networking::ws_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& core_ep) { - ::realm::networking::ws_endpoint ep; + ::realm::networking::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& core_ep) { + ::realm::networking::websocket_endpoint ep; ep.address = core_ep.address; ep.port = core_ep.port; ep.path = core_ep.path; diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/platform_networking.cpp index 8834fbf5c..293b2107b 100644 --- a/src/cpprealm/networking/platform_networking.cpp +++ b/src/cpprealm/networking/platform_networking.cpp @@ -5,15 +5,13 @@ #include namespace realm::networking { - std::function()> s_http_client_factory = http_client_factory::make_default_http_client; - std::optional> http_client_factory::custom_http_headers = std::nullopt; - std::optional http_client_factory::proxy_config = std::nullopt; - std::shared_ptr http_client_factory::make_default_http_client() { - return std::make_shared(custom_http_headers, proxy_config); + std::shared_ptr make_default_http_client() { + return std::make_shared(); } + std::function()> s_http_client_factory = make_default_http_client; - void http_client_factory::set_http_client_factory(std::function()>&& factory_fn) { + void set_http_client_factory(std::function()>&& factory_fn) { s_http_client_factory = std::move(factory_fn); } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7c8cc0f17..087fc81c8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,7 +27,7 @@ if (NOT BUILD_FROM_PACKAGE_MANAGER) sync/flexible_sync_tests.cpp sync/app_tests.cpp sync/client_reset_tests.cpp - sync/proxy_tests.cpp) + sync/networking_tests.cpp) endif() add_executable(cpprealm_db_tests diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp new file mode 100644 index 000000000..90414e777 --- /dev/null +++ b/tests/sync/networking_tests.cpp @@ -0,0 +1,31 @@ +#include "../admin_utils.hpp" +#include "../main.hpp" +#include "test_objects.hpp" + +using namespace realm; + +TEST_CASE("websocket_handler_called", "[sync]") { + + realm::App::configuration config; + config.app_id = Admin::Session::shared().cached_app_id(); + config.base_url = Admin::Session::shared().base_url(); + + realm::networking::websocket_event_handler handler; + handler.on_connect = [](realm::networking::websocket_endpoint && ep) { + ep.is_ssl = false; + return ep; + }; + config.websocket_event_handler = std::make_shared(handler); + + auto app = realm::App(config); + auto user = app.login(realm::App::credentials::anonymous()).get(); + auto flx_sync_config = user.flexible_sync_configuration(); + auto synced_realm = db(flx_sync_config); + + auto update_success = synced_realm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { + subs.clear(); + }).get(); + CHECK(update_success == true); + CHECK(synced_realm.subscriptions().size() == 0); + +} \ No newline at end of file diff --git a/tests/sync/proxy_tests.cpp b/tests/sync/proxy_tests.cpp deleted file mode 100644 index 1c10f654d..000000000 --- a/tests/sync/proxy_tests.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "../admin_utils.hpp" -#include "../main.hpp" -#include "test_objects.hpp" - -using namespace realm; - -TEST_CASE("proxy_enabled_websocket", "[sync]") { - - realm::App::configuration config; - config.app_id = Admin::Session::shared().cached_app_id(); - config.base_url = Admin::Session::shared().base_url(); - - realm::networking::websocket_event_handler handler; - handler.on_connect = [](realm::networking::ws_endpoint&& ep) { - ep.is_ssl = false; - return ep; - }; - config.websocket_event_handler = std::make_shared(handler); - - auto app = realm::App(config); - SECTION("all") { - auto user = app.login(realm::App::credentials::anonymous()).get(); - auto flx_sync_config = user.flexible_sync_configuration(); - auto synced_realm = db(flx_sync_config); - - auto update_success = synced_realm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { - subs.clear(); - }).get(); - CHECK(update_success == true); - CHECK(synced_realm.subscriptions().size() == 0); - - update_success = synced_realm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { - subs.add("foo-strings", [](auto &obj) { - return obj.str_col == "foo"; - }); - subs.add("foo-link"); - }) - .get(); - CHECK(update_success == true); - CHECK(synced_realm.subscriptions().size() == 2); - - auto sub = *synced_realm.subscriptions().find("foo-strings"); - CHECK(sub.name == "foo-strings"); - CHECK(sub.object_class_name == "AllTypesObject"); - CHECK(sub.query_string == "str_col == \"foo\""); - - auto non_existent_sub = synced_realm.subscriptions().find("non-existent"); - CHECK((non_existent_sub == std::nullopt) == true); - - synced_realm.write([&synced_realm]() { - AllTypesObject o; - o._id = 1; - o.str_col = "foo"; - synced_realm.add(std::move(o)); - }); - - synced_realm.get_sync_session()->wait_for_upload_completion().get(); - synced_realm.get_sync_session()->wait_for_download_completion().get(); - - synced_realm.refresh(); - auto objs = synced_realm.objects(); - - CHECK(objs[0]._id == (int64_t) 1); - - synced_realm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { - subs.update_subscription("foo-strings", [](auto &obj) { - return obj.str_col == "bar" && obj._id == (int64_t) 1230; - }); - }).get(); - - auto sub2 = *synced_realm.subscriptions().find("foo-strings"); - CHECK(sub2.name == "foo-strings"); - CHECK(sub2.object_class_name == "AllTypesObject"); - CHECK(sub2.query_string == "str_col == \"bar\" and _id == 1230"); - - synced_realm.write([&synced_realm]() { - AllTypesObject o; - o._id = 1230; - o.str_col = "bar"; - synced_realm.add(std::move(o)); - }); - - synced_realm.get_sync_session()->wait_for_upload_completion().get(); - synced_realm.get_sync_session()->wait_for_download_completion().get(); - - synced_realm.refresh(); - objs = synced_realm.objects(); - CHECK(objs.size() == 1); - } -} \ No newline at end of file From 143bb35648915230d98ccfd8d5ed15c91bea2702 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Tue, 25 Jun 2024 12:09:29 +0100 Subject: [PATCH 08/41] Playing with proxy --- include/cpprealm/networking/networking.hpp | 18 ++++++++--- src/cpprealm/app.cpp | 5 ++- .../internal/network/network_transport.cpp | 12 ++++--- src/cpprealm/networking/networking.cpp | 15 +++++++++ tests/main.cpp | 2 +- tests/sync/networking_tests.cpp | 31 +++++++++++++++++-- 6 files changed, 67 insertions(+), 16 deletions(-) diff --git a/include/cpprealm/networking/networking.hpp b/include/cpprealm/networking/networking.hpp index 50d205663..edc640fae 100644 --- a/include/cpprealm/networking/networking.hpp +++ b/include/cpprealm/networking/networking.hpp @@ -20,6 +20,7 @@ #define CPPREALM_NETWORKING_HPP #include +#include namespace realm::networking { /** @@ -95,11 +96,18 @@ namespace realm::networking { struct websocket_endpoint { using port_type = std::uint_fast16_t; - std::string address; // Host address - port_type port; // Host port number - std::string path; // Includes access token in query. - std::vector protocols; // Array of one or more websocket protocols - bool is_ssl; // true if SSL should be used + /// Host address + std::string address; + /// Host port number + port_type port; + /// Includes access token in query. + std::string path; + /// Array of one or more websocket protocols + std::vector protocols; + /// true if SSL should be used + bool is_ssl; + /// Optional proxy config + std::optional proxy_configuration; }; enum websocket_err_codes { diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index 989fda5ac..87ea8511e 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -500,15 +500,14 @@ namespace realm { client_config.user_agent_application_info, config.websocket_event_handler); client_config.socket_provider = websocket_provider; - } - if (config.sync_socket_provider) { + } else if (config.sync_socket_provider) { client_config.socket_provider = ::realm::internal::networking::create_sync_socket_provider_shim(config.sync_socket_provider, config.websocket_event_handler); } if (config.http_transport_client) { app_config.transport = internal::networking::create_http_client_shim(config.http_transport_client); - } else if (config.custom_http_headers || config.proxy_configuration) { + } else { app_config.transport = internal::networking::create_http_client_shim(std::make_shared(config.custom_http_headers, config.proxy_configuration)); } diff --git a/src/cpprealm/internal/network/network_transport.cpp b/src/cpprealm/internal/network/network_transport.cpp index 51a805b95..1db1e2dca 100644 --- a/src/cpprealm/internal/network/network_transport.cpp +++ b/src/cpprealm/internal/network/network_transport.cpp @@ -132,6 +132,8 @@ namespace realm::internal::networking { void DefaultTransport::send_request_to_server(const ::realm::networking::request& request, std::function&& completion_block) { + bool disable_ssl = true; + const auto uri = realm::util::Uri(request.url); std::string userinfo, host, port; uri.get_auth(userinfo, host, port); @@ -189,7 +191,7 @@ namespace realm::internal::networking { if (m_proxy_config) { realm::sync::HTTPRequest req; req.method = realm::sync::HTTPMethod::Connect; - req.headers.emplace("Host", util::format("%1:%2", host, "443")); + req.headers.emplace("Host", util::format("%1:%2", host, is_localhost ? "9090" : "443")); if (m_proxy_config->username_password) { auto userpass = util::format("%1:%2", m_proxy_config->username_password->first, m_proxy_config->username_password->second); std::string encoded_userpass; @@ -225,7 +227,7 @@ namespace realm::internal::networking { m_ssl_context.use_included_certificate_roots(); #endif - if (url_scheme == URLScheme::HTTPS) { + if (url_scheme == URLScheme::HTTPS && !disable_ssl) { socket.ssl_stream.emplace(socket, m_ssl_context, Stream::client); socket.ssl_stream->set_host_name(host); // Throws @@ -308,10 +310,10 @@ namespace realm::internal::networking { return; } }; - if (url_scheme != URLScheme::HTTPS) { - handler(std::error_code()); - } else { + if (url_scheme == URLScheme::HTTPS && !disable_ssl) { socket.async_handshake(std::move(handler)); // Throws + } else { + handler(std::error_code()); } }); diff --git a/src/cpprealm/networking/networking.cpp b/src/cpprealm/networking/networking.cpp index 30748c7a9..e63c0e606 100644 --- a/src/cpprealm/networking/networking.cpp +++ b/src/cpprealm/networking/networking.cpp @@ -2,6 +2,7 @@ #include #include +#include namespace realm::internal::networking { ::realm::networking::request to_request(const ::realm::app::Request& core_request) { @@ -51,6 +52,20 @@ namespace realm::internal::networking { core_ep.path = ep.path; core_ep.protocols = ep.protocols; core_ep.is_ssl = ep.is_ssl; + + if (ep.proxy_configuration) { + core_ep.proxy = SyncConfig::ProxyConfig(); + core_ep.proxy->address = ep.proxy_configuration->address; + core_ep.proxy->port = ep.proxy_configuration->port; + if (ep.proxy_configuration->username_password) { + auto userpass = util::format("%1:%2", ep.proxy_configuration->username_password->first, ep.proxy_configuration->username_password->second); + std::string encoded_userpass; + encoded_userpass.resize(realm::util::base64_encoded_size(userpass.length())); + realm::util::base64_encode(userpass, encoded_userpass); + core_ep.headers.emplace("Proxy-Authorization", util::format("Basic %1", encoded_userpass)); + } + } + return core_ep; } diff --git a/tests/main.cpp b/tests/main.cpp index 49f8a8cb3..5c65451fc 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -41,7 +41,7 @@ int main(int argc, char *argv[]) { } #ifdef CPPREALM_ENABLE_SYNC_TESTS - std::optional baas_manager; +// std::optional baas_manager; // if (const char* api_key = getenv("APIKEY")) { // baas_manager.emplace(std::string(api_key)); // baas_manager->start(); diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp index 90414e777..6c2a09f3f 100644 --- a/tests/sync/networking_tests.cpp +++ b/tests/sync/networking_tests.cpp @@ -6,18 +6,30 @@ using namespace realm; TEST_CASE("websocket_handler_called", "[sync]") { + proxy_config pc; + pc.port = 1234; + pc.address = "127.0.0.1"; realm::App::configuration config; - config.app_id = Admin::Session::shared().cached_app_id(); + //config.proxy_configuration = pc; + config.app_id = Admin::Session::shared().cached_app_id(); //"cpp-sdk-vbfxo"; +// c.app_id = "cpp-sdk-vbfxo"; config.base_url = Admin::Session::shared().base_url(); realm::networking::websocket_event_handler handler; - handler.on_connect = [](realm::networking::websocket_endpoint && ep) { + handler.on_connect = [&](realm::networking::websocket_endpoint && ep) { ep.is_ssl = false; +// ep.port = 1234; +// ep.address = "localhost"; + + ep.proxy_configuration = pc; + return ep; }; config.websocket_event_handler = std::make_shared(handler); auto app = realm::App(config); + app.get_sync_manager().set_log_level(logger::level::all); + auto user = app.login(realm::App::credentials::anonymous()).get(); auto flx_sync_config = user.flexible_sync_configuration(); auto synced_realm = db(flx_sync_config); @@ -28,4 +40,19 @@ TEST_CASE("websocket_handler_called", "[sync]") { CHECK(update_success == true); CHECK(synced_realm.subscriptions().size() == 0); + update_success = synced_realm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { + subs.add("foo-strings", [](auto &obj) { + return obj.str_col == "foo"; + }); + subs.add("foo-link"); + }) + .get(); + CHECK(update_success == true); + CHECK(synced_realm.subscriptions().size() == 2); + + auto sub = *synced_realm.subscriptions().find("foo-strings"); + CHECK(sub.name == "foo-strings"); + CHECK(sub.object_class_name == "AllTypesObject"); + CHECK(sub.query_string == "str_col == \"foo\""); + } \ No newline at end of file From 509aefdfa75c05b34e43a42d6a7c91b554baa1e8 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Wed, 3 Jul 2024 18:16:44 +0100 Subject: [PATCH 09/41] Networking checkpoint --- include/cpprealm/app.hpp | 7 +- include/cpprealm/networking/networking.hpp | 17 + .../networking/platform_networking.hpp | 33 +- src/cpprealm/app.cpp | 18 +- .../internal/network/network_transport.cpp | 2 +- .../networking/platform_networking.cpp | 117 +++++- tests/CMakeLists.txt | 13 +- tests/sync/networking_tests.cpp | 32 +- tests/utils/networking/proxy_server.cpp | 351 ++++++++++++++++++ tests/utils/networking/proxy_server.hpp | 34 ++ 10 files changed, 575 insertions(+), 49 deletions(-) create mode 100644 tests/utils/networking/proxy_server.cpp create mode 100644 tests/utils/networking/proxy_server.hpp diff --git a/include/cpprealm/app.hpp b/include/cpprealm/app.hpp index fc47c9529..10baedc6d 100644 --- a/include/cpprealm/app.hpp +++ b/include/cpprealm/app.hpp @@ -245,12 +245,6 @@ class App { std::optional> metadata_encryption_key; // HTTP proxy configuration to be set on each HTTP request when using the internal HTTP client. std::optional proxy_configuration; - /** - * Sets a callback that will be used to configure outgoing WebSocket connections to Atlas Device Sync. - * You can use this to modify the default WebSocket behavior. Normally this is not required to connect to MongoDB Atlas, - * but it can be useful if client devices are behind a corporate firewall or use a more complex networking setup. - */ - std::shared_ptr<::realm::networking::websocket_event_handler> websocket_event_handler; /** * Optionally provide a custom transport for network calls to the server. * @@ -264,6 +258,7 @@ class App { * the default HTTP transport client. */ std::shared_ptr<::realm::networking::sync_socket_provider> sync_socket_provider; + }; [[deprecated("Use App(const configuration&) instead.")]] diff --git a/include/cpprealm/networking/networking.hpp b/include/cpprealm/networking/networking.hpp index edc640fae..d58ca391d 100644 --- a/include/cpprealm/networking/networking.hpp +++ b/include/cpprealm/networking/networking.hpp @@ -94,6 +94,23 @@ namespace realm::networking { }; +// struct websocket_endpoint { +// // using port_type = std::uint_fast16_t; +// // /// Host address +// // std::string address; +// // /// Host port number +// // port_type port; +// // /// Includes access token in query. +// // std::string path; +// // /// Array of one or more websocket protocols +// std::vector protocols; +// // /// true if SSL should be used +// // bool is_ssl; +// std::string url; +// /// Optional proxy config +// std::optional proxy_configuration; +// }; + struct websocket_endpoint { using port_type = std::uint_fast16_t; /// Host address diff --git a/include/cpprealm/networking/platform_networking.hpp b/include/cpprealm/networking/platform_networking.hpp index 626b6ebaf..a3325a86c 100644 --- a/include/cpprealm/networking/platform_networking.hpp +++ b/include/cpprealm/networking/platform_networking.hpp @@ -32,6 +32,7 @@ namespace realm { } namespace sync { class SyncSocketProvider; + struct WebSocketInterface; } namespace util { @@ -231,18 +232,42 @@ namespace realm::networking { std::shared_ptr make_http_client(); void set_http_client_factory(std::function()>&&); + + struct default_socket : public websocket_interface { + default_socket(std::unique_ptr<::realm::sync::WebSocketInterface>&&); + ~default_socket() = default; + + void async_write_binary(std::string_view data, websocket_interface::FunctionHandler&& handler) override; + + private: + std::shared_ptr<::realm::sync::WebSocketInterface> m_ws_interface; + }; + + + struct default_socket_provider : public sync_socket_provider { + + default_socket_provider(); + + ~default_socket_provider() = default; + + std::unique_ptr connect(std::unique_ptr, websocket_endpoint &&) override; + + void post(FunctionHandler&&) override; + + SyncTimer create_timer(std::chrono::milliseconds delay, FunctionHandler&&) override; + private: + std::shared_ptr<::realm::sync::SyncSocketProvider> m_provider; + }; } namespace realm::internal::networking { std::shared_ptr default_sync_socket_provider_factory(const std::shared_ptr& logger, const std::string& user_agent_binding_info, - const std::string& user_agent_application_info, - const std::shared_ptr<::realm::networking::websocket_event_handler>&); + const std::string& user_agent_application_info); std::shared_ptr create_http_client_shim(const std::shared_ptr<::realm::networking::http_transport_client>&); - std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider, - const std::shared_ptr<::realm::networking::websocket_event_handler>& handler); + std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider); } #endif//CPPREALM_PLATFORM_NETWORKING_HPP diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index 87ea8511e..54e03572e 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -494,15 +494,15 @@ namespace realm { client_config.user_agent_application_info = config.app_id; app_config.app_id = config.app_id; - if (config.websocket_event_handler) { - auto websocket_provider = ::realm::internal::networking::default_sync_socket_provider_factory(util::Logger::get_default_logger(), - client_config.user_agent_binding_info, - client_config.user_agent_application_info, - config.websocket_event_handler); - client_config.socket_provider = websocket_provider; - } else if (config.sync_socket_provider) { - client_config.socket_provider = ::realm::internal::networking::create_sync_socket_provider_shim(config.sync_socket_provider, - config.websocket_event_handler); +// if (config.websocket_event_handler) { +// auto websocket_provider = ::realm::internal::networking::default_sync_socket_provider_factory(util::Logger::get_default_logger(), +// client_config.user_agent_binding_info, +// client_config.user_agent_application_info, +// config.websocket_event_handler); +// client_config.socket_provider = websocket_provider; +// } + if (config.sync_socket_provider) { + client_config.socket_provider = ::realm::internal::networking::create_sync_socket_provider_shim(config.sync_socket_provider); } if (config.http_transport_client) { diff --git a/src/cpprealm/internal/network/network_transport.cpp b/src/cpprealm/internal/network/network_transport.cpp index 1db1e2dca..41d6eb392 100644 --- a/src/cpprealm/internal/network/network_transport.cpp +++ b/src/cpprealm/internal/network/network_transport.cpp @@ -132,7 +132,7 @@ namespace realm::internal::networking { void DefaultTransport::send_request_to_server(const ::realm::networking::request& request, std::function&& completion_block) { - bool disable_ssl = true; + bool disable_ssl = true; // TODO: pull this from proxy config const auto uri = realm::util::Uri(request.url); std::string userinfo, host, port; diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/platform_networking.cpp index 293b2107b..b95c9d229 100644 --- a/src/cpprealm/networking/platform_networking.cpp +++ b/src/cpprealm/networking/platform_networking.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -14,6 +15,103 @@ namespace realm::networking { void set_http_client_factory(std::function()>&& factory_fn) { s_http_client_factory = std::move(factory_fn); } + + struct default_websocket_observer_cpprealm : public websocket_observer { + default_websocket_observer_cpprealm(std::unique_ptr<::realm::sync::WebSocketObserver>&& o) : m_observer(std::move(o)) { } + ~default_websocket_observer_cpprealm() = default; + + void websocket_connected_handler(const std::string& protocol) override { + m_observer->websocket_connected_handler(protocol); + } + + void websocket_error_handler() override { + m_observer->websocket_error_handler(); + } + + bool websocket_binary_message_received(std::string_view data) override { + return m_observer->websocket_binary_message_received(data); + } + + bool websocket_closed_handler(bool was_clean, websocket_err_codes error_code, + std::string_view message) override { + return m_observer->websocket_closed_handler(was_clean, static_cast<::realm::sync::websocket::WebSocketError>(error_code), message); + } + + private: + std::shared_ptr<::realm::sync::WebSocketObserver> m_observer; + }; + + struct default_websocket_observer_core : public ::realm::sync::WebSocketObserver { + default_websocket_observer_core(std::unique_ptr&& o) : m_observer(std::move(o)) { } + ~default_websocket_observer_core() = default; + + void websocket_connected_handler(const std::string& protocol) override { + m_observer->websocket_connected_handler(protocol); + } + + void websocket_error_handler() override { + m_observer->websocket_error_handler(); + } + + bool websocket_binary_message_received(util::Span data) override { + return m_observer->websocket_binary_message_received(data.data()); + } + + bool websocket_closed_handler(bool was_clean, ::realm::sync::websocket::WebSocketError error_code, + std::string_view message) override { + return m_observer->websocket_closed_handler(was_clean, static_cast(error_code), message); + } + + private: + std::shared_ptr m_observer; + }; + + struct default_timer : public default_socket_provider::timer { + default_timer(const std::shared_ptr<::realm::sync::SyncSocketProvider::Timer>& t) : m_timer(t) {} + ~default_timer() = default; + void cancel() { + m_timer->cancel(); + }; + + std::shared_ptr<::realm::sync::SyncSocketProvider::Timer> m_timer; + }; + + default_socket_provider::default_socket_provider() { + auto user_agent = util::format("RealmSync/%1 (%2) %3 %4", REALM_VERSION_STRING, util::get_platform_info(), "user_agent_binding_info", "user_agent_application_info"); + m_provider = std::make_unique<::realm::sync::websocket::DefaultSocketProvider>(util::Logger::get_default_logger(), user_agent); + } + + std::unique_ptr default_socket_provider::connect(std::unique_ptr o, websocket_endpoint&& ep) { +// internal::bridge::realm::sync_config::proxy_config pc; +// pc.port = 1234; +// pc.address = "127.0.0.1"; +// ep.proxy_configuration = pc; + + auto ws_interface = m_provider->connect(std::make_unique(std::move(o)), internal::networking::to_core_websocket_endpoint(ep)); + return std::make_unique(std::move(ws_interface)); + } + + void default_socket_provider::post(default_socket_provider::FunctionHandler&& fn) { + m_provider->post([fn = std::move(fn)](::realm::Status s) { + fn(s); + }); + } + + default_socket_provider::SyncTimer default_socket_provider::create_timer(std::chrono::milliseconds delay, FunctionHandler&& fn) { + return std::make_unique(m_provider->create_timer(delay, [fn = std::move(fn)](::realm::Status s) { + fn(s); + })); + } + + default_socket::default_socket(std::unique_ptr<::realm::sync::WebSocketInterface>&& ws) { + m_ws_interface = std::move(ws); + } + + void default_socket::async_write_binary(std::string_view s, websocket_interface::FunctionHandler&& fn) { + m_ws_interface->async_write_binary(s, [fn = std::move(fn)](::realm::Status s) { + fn(s); + }); + } } namespace realm::internal::networking { @@ -131,14 +229,13 @@ namespace realm::internal::networking { return m_observer->websocket_closed_handler(was_clean, static_cast<::realm::sync::websocket::WebSocketError>(error_code), message); } - std::unique_ptr<::realm::sync::WebSocketObserver>&& m_observer; + std::unique_ptr<::realm::sync::WebSocketObserver> m_observer; }; return std::make_unique(std::move(m_observer)); } - std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider, - const std::shared_ptr<::realm::networking::websocket_event_handler>& handler) { + std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider) { struct sync_timer_shim final : public ::realm::sync::SyncSocketProvider::Timer { sync_timer_shim(std::unique_ptr<::realm::networking::sync_socket_provider::timer>&& timer) : m_timer(std::move(timer)) {} @@ -153,22 +250,16 @@ namespace realm::internal::networking { }; struct sync_socket_provider_shim final : public ::realm::sync::SyncSocketProvider { - explicit sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider, - const std::shared_ptr<::realm::networking::websocket_event_handler>& handler) { + explicit sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider) { m_provider = provider; - m_websocket_event_handler = handler; } sync_socket_provider_shim() = delete; ~sync_socket_provider_shim() = default; std::unique_ptr<::realm::sync::WebSocketInterface> connect(std::unique_ptr<::realm::sync::WebSocketObserver> observer, ::realm::sync::WebSocketEndpoint&& ep) override { - if (m_websocket_event_handler) { - auto ws_ep = m_websocket_event_handler->on_connect(to_websocket_endpoint(std::move(ep))); - return create_websocket_interface_shim(m_provider->connect(create_websocket_observer_from_core_shim(std::move(observer)), std::move(ws_ep))); - } - - return create_websocket_interface_shim(m_provider->connect(create_websocket_observer_from_core_shim(std::move(observer)), to_websocket_endpoint(std::move(ep)))); + auto cpprealm_ws_interface = m_provider->connect(create_websocket_observer_from_core_shim(std::move(observer)), to_websocket_endpoint(std::move(ep))); + return create_websocket_interface_shim(std::move(cpprealm_ws_interface)); } void post(FunctionHandler&& handler) override { @@ -192,6 +283,6 @@ namespace realm::internal::networking { std::shared_ptr<::realm::networking::sync_socket_provider> m_provider; }; - return std::make_unique(std::move(provider), handler); + return std::make_unique(std::move(provider)); } } //namespace internal::networking \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 087fc81c8..e3c8f9c80 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -17,6 +17,13 @@ else() endif() if (NOT BUILD_FROM_PACKAGE_MANAGER) + # Find Boost + find_package(Boost REQUIRED COMPONENTS system thread) + include_directories(${Boost_INCLUDE_DIRS}) + # Find OpenSSL + find_package(OpenSSL REQUIRED) + include_directories(${OPENSSL_INCLUDE_DIR}) + add_executable(cpprealm_sync_tests main.hpp main.cpp @@ -27,7 +34,9 @@ if (NOT BUILD_FROM_PACKAGE_MANAGER) sync/flexible_sync_tests.cpp sync/app_tests.cpp sync/client_reset_tests.cpp - sync/networking_tests.cpp) + sync/networking_tests.cpp + utils/networking/proxy_server.hpp + utils/networking/proxy_server.cpp) endif() add_executable(cpprealm_db_tests @@ -101,7 +110,7 @@ if (MSVC AND ENABLE_STATIC) endif() if (NOT BUILD_FROM_PACKAGE_MANAGER) - target_link_libraries(cpprealm_sync_tests PUBLIC ${CPPREALM_TARGET} Catch2::Catch2) + target_link_libraries(cpprealm_sync_tests PUBLIC ${CPPREALM_TARGET} Catch2::Catch2 ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES}) endif() target_link_libraries(cpprealm_db_tests PUBLIC ${CPPREALM_TARGET} Catch2::Catch2) diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp index 6c2a09f3f..58785b362 100644 --- a/tests/sync/networking_tests.cpp +++ b/tests/sync/networking_tests.cpp @@ -1,31 +1,36 @@ #include "../admin_utils.hpp" #include "../main.hpp" #include "test_objects.hpp" - +#include "../utils/networking/proxy_server.hpp" using namespace realm; TEST_CASE("websocket_handler_called", "[sync]") { +// tests::utils::proxy_server::config cfg; +// cfg.port = 1234; +// cfg.client_uses_ssl = false; +// cfg.server_uses_ssl = false; +// tests::utils::proxy_server server(std::move(cfg)); + proxy_config pc; pc.port = 1234; pc.address = "127.0.0.1"; realm::App::configuration config; - //config.proxy_configuration = pc; - config.app_id = Admin::Session::shared().cached_app_id(); //"cpp-sdk-vbfxo"; -// c.app_id = "cpp-sdk-vbfxo"; +// config.proxy_configuration = pc; + config.app_id = Admin::Session::shared().cached_app_id(); config.base_url = Admin::Session::shared().base_url(); - realm::networking::websocket_event_handler handler; - handler.on_connect = [&](realm::networking::websocket_endpoint && ep) { - ep.is_ssl = false; -// ep.port = 1234; -// ep.address = "localhost"; - ep.proxy_configuration = pc; + struct foo_socket_provider : public ::realm::networking::default_socket_provider { + std::unique_ptr<::realm::networking::websocket_interface> connect(std::unique_ptr<::realm::networking::websocket_observer> o, + ::realm::networking::websocket_endpoint&& ep) override { +// url = url.replace("wss://", "ws://"); - return ep; + return ::realm::networking::default_socket_provider::connect(std::move(o), std::move(ep)); + } }; - config.websocket_event_handler = std::make_shared(handler); + + config.sync_socket_provider = std::make_shared(); auto app = realm::App(config); app.get_sync_manager().set_log_level(logger::level::all); @@ -45,8 +50,7 @@ TEST_CASE("websocket_handler_called", "[sync]") { return obj.str_col == "foo"; }); subs.add("foo-link"); - }) - .get(); + }).get(); CHECK(update_success == true); CHECK(synced_realm.subscriptions().size() == 2); diff --git a/tests/utils/networking/proxy_server.cpp b/tests/utils/networking/proxy_server.cpp new file mode 100644 index 000000000..86df8ea45 --- /dev/null +++ b/tests/utils/networking/proxy_server.cpp @@ -0,0 +1,351 @@ +#include "proxy_server.hpp" + +#include +#include +#include +#include + +namespace realm::tests::utils { + + class proxy_session : public std::enable_shared_from_this { + public: + proxy_session(tcp::socket client_socket, std::shared_ptr ctx, bool client_uses_ssl, bool server_uses_ssl) + : m_client_socket(std::move(client_socket)), + m_server_socket(m_client_socket.get_executor()), + m_resolver(m_client_socket.get_executor()), + m_ssl_ctx(ctx), + m_ssl_server_socket(m_client_socket.get_executor(), *ctx), + m_client_uses_ssl(client_uses_ssl), + m_server_uses_ssl(server_uses_ssl) {} + + void start() { + do_read_client_request(); + } + + private: + void do_read_client_request() { + auto self(shared_from_this()); + m_client_socket.async_read_some(boost::asio::buffer(m_buffer), + [this, self](boost::system::error_code ec, std::size_t length) { + if (!ec) { + std::string request(m_buffer.data(), length); + if (request.substr(0, 7) == "CONNECT") { + handle_connect(request); + } else { + std::cerr << "Unsupported method: " << request.substr(0, 7) << std::endl; + } + } else { + std::cerr << "Error reading client request: " << ec.message() << std::endl; + } + }); + } + + void handle_connect(const std::string &request) { + // Extract the host and port from the CONNECT request + std::string::size_type host_start = request.find(" ") + 1; + std::string::size_type host_end = request.find(":", host_start); + std::string host = request.substr(host_start, host_end - host_start); + std::string port = request.substr(host_end + 1, request.find(" ", host_end) - host_end - 1); + + if (host == "127.0.0.1" || host == "localhost") { + tcp::endpoint endpoint(boost::asio::ip::make_address("127.0.0.1"), std::stoi(port)); + connect_to_server(endpoint, host); + } else if (host.find(server_endpoint) != std::string::npos) { + auto self(shared_from_this()); + m_resolver.async_resolve(host, port, + [this, self, host](boost::system::error_code ec, tcp::resolver::results_type results) { + if (!ec) { + connect_to_server(*results.begin(), host); + } else { + std::cerr << "Error resolving host: " << ec.message() << std::endl; + } + }); + } else { + // BAASAAS endpoint + tcp::endpoint endpoint(boost::asio::ip::make_address(host), std::stoi(port)); + connect_to_server(endpoint, host); + } + } + + void connect_to_server(const tcp::endpoint &endpoint, const std::string &host) { + if (m_server_uses_ssl) { + // Handle SSL connection + auto self(shared_from_this()); + m_ssl_server_socket.lowest_layer().async_connect(endpoint, + [this, self, host](boost::system::error_code ec) { + if (!ec) { + do_ssl_handshake_to_server(host); + } else { + std::cerr << "Error connecting to server: " << ec.message() << std::endl; + } + }); + } else { + // Handle non-SSL connection + auto self(shared_from_this()); + m_server_socket.async_connect(endpoint, + [this, self](boost::system::error_code ec) { + if (!ec) { + do_write_connect_response(); + } else { + std::cerr << "Error connecting to server: " << ec.message() << std::endl; + } + }); + } + } + + void do_ssl_handshake_to_server(const std::string &hostname) { + auto self(shared_from_this()); + + m_ssl_server_socket.set_verify_callback(asio::ssl::host_name_verification(hostname)); + m_ssl_server_socket.set_verify_mode(boost::asio::ssl::verify_peer); + + // Set SNI Hostname (many hosts need this to handshake successfully) + if (!SSL_set_tlsext_host_name(m_ssl_server_socket.native_handle(), hostname.c_str())) { + throw std::runtime_error("Could not set hostname"); + } + + m_ssl_server_socket.async_handshake(asio::ssl::stream_base::client, + [this, self](boost::system::error_code ec) { + if (!ec) { + do_write_connect_response(); + } else { + std::cerr << "Error performing SSL handshake: " << ec.message() << std::endl; + } + }); + } + + void do_write_connect_response() { + auto self(shared_from_this()); + asio::async_write(m_client_socket, asio::buffer(std::string("HTTP/1.1 200 Connection Established\r\n\r\n")), + [this, self](boost::system::error_code ec, std::size_t) { + if (!ec) { + do_read_client(); + } else { + std::cerr << "Error writing connect response: " << ec.message() << std::endl; + } + }); + } + + void do_ssl_write_to_server(std::size_t length) { + auto self(shared_from_this()); + boost::asio::async_write(m_ssl_server_socket, boost::asio::buffer(std::string(m_client_buffer.data()), length), + [this, self](boost::system::error_code ec, std::size_t) { + if (!ec) { + do_ssl_read_server(); + } else { + std::cerr << "Error writing to server: " << ec.message() << std::endl; + m_client_socket.close(); + m_server_socket.close(); + } + }); + } + + void do_ssl_read_server() { + auto self(shared_from_this()); + m_ssl_server_socket.async_read_some(boost::asio::buffer(m_server_buffer), + [this, self](boost::system::error_code ec, std::size_t length) { + if (!ec) { + do_write_to_client(length); + } else { + std::cerr << "Error reading from server (SSL): " << ec.message() << std::endl; + m_client_socket.close(); + m_server_socket.close(); + } + }); + } + + void do_read_client() { + auto self(shared_from_this()); + m_client_socket.async_read_some(boost::asio::buffer(m_client_buffer), + [this, self](boost::system::error_code ec, std::size_t length) { + if (!ec) { + auto req = std::string(m_client_buffer.data(), length); + std::cout << "Client to Server: " << req << std::endl; + if (m_server_uses_ssl) { + do_ssl_write_to_server(length); + } else { + do_write_to_server(length); + } + } else { + std::cerr << "Error reading from client: " << ec.message() << std::endl; + m_client_socket.close(); + m_server_socket.close(); + } + }); + } + + void handle_http_req_res() { + auto length = m_client_socket.read_some(boost::asio::buffer(m_client_buffer)); + boost::asio::write(m_server_socket, boost::asio::buffer(std::string(m_client_buffer.data()), length)); + length = m_server_socket.read_some(boost::asio::buffer(m_server_buffer)); + + auto res = std::string(m_server_buffer.data(), length); + bool upgrade_to_websocket = res.find("HTTP/1.1 101 Switching Protocols") != std::string::npos; + auto bytes_written = boost::asio::write(m_client_socket, boost::asio::buffer(std::string(m_server_buffer.data()), length)); + if (upgrade_to_websocket) { + upgrade_client_to_websocket(bytes_written); + } + } + + void do_read_from_websocket_client() { + auto self(shared_from_this()); + m_client_socket.async_read_some(boost::asio::buffer(m_client_buffer), + [this, self](boost::system::error_code ec, std::size_t length) { + if (!ec) { + do_write_to_websocket_server(length); + do_read_from_websocket_client(); + } else { + // Handle error + } + }); + } + + void do_write_to_websocket_server(std::size_t length) { + auto self(shared_from_this()); + boost::asio::async_write(m_server_socket, boost::asio::buffer(m_client_buffer.data(), length), + [this, self](boost::system::error_code ec, std::size_t /*length*/) { + if (!ec) { + do_read_from_websocket_client(); + } else { + // Handle error + } + }); + } + + void do_read_from_websocket_server() { + auto self(shared_from_this()); + m_server_socket.async_read_some(boost::asio::buffer(m_server_buffer), + [this, self](boost::system::error_code ec, std::size_t length) { + if (!ec) { + do_write_to_websocket_client(length); + do_read_from_websocket_server(); + } else { + // Handle error + } + }); + } + + void do_write_to_websocket_client(std::size_t length) { + auto self(shared_from_this()); + boost::asio::async_write(m_client_socket, boost::asio::buffer(m_server_buffer.data(), length), + [this, self](boost::system::error_code ec, std::size_t /*length*/) { + if (!ec) { + do_read_from_websocket_server(); + } else { + // Handle error + } + }); + } + + + void upgrade_client_to_websocket(std::size_t length) { + auto self(shared_from_this()); + do_read_from_websocket_client(); + do_read_from_websocket_server(); + } + + void do_write_to_server(std::size_t length) { + auto self(shared_from_this()); + boost::asio::async_write(m_server_socket, boost::asio::buffer(std::string(m_client_buffer.data()), length), + [this, self](boost::system::error_code ec, std::size_t) { + if (!ec) { + do_read_server(); + } else { + std::cerr << "Error writing to server: " << ec.message() << std::endl; + m_client_socket.close(); + m_server_socket.close(); + } + }); + } + + void do_read_server() { + auto self(shared_from_this()); + m_server_socket.async_read_some(boost::asio::buffer(m_server_buffer), + [this, self](boost::system::error_code ec, std::size_t length) { + if (!ec) { + do_write_to_client(length); + } else { + std::cerr << "Error reading from server: " << ec.message() << std::endl; + m_client_socket.close(); + m_server_socket.close(); + } + }); + } + + void do_write_to_client(std::size_t length) { + auto self(shared_from_this()); + auto res = std::string(m_server_buffer.data(), length); + std::cout << "Server to Client: " << res << std::endl; + bool upgrade_to_websocket = res.find("HTTP/1.1 101 Switching Protocols") != std::string::npos; + + boost::asio::async_write(m_client_socket, boost::asio::buffer(std::string(m_server_buffer.data()), length), + [this, self, upgrade_to_websocket](boost::system::error_code ec, std::size_t bytes_written) { + if (!ec) { + std::cout << "BYTES WRITTEN: " << bytes_written << std::endl; + if (upgrade_to_websocket) { + upgrade_client_to_websocket(bytes_written); + } else { + if (m_server_uses_ssl) { + do_ssl_read_server(); + } else { + do_read_server(); + } + } + } else { + std::cerr << "Error writing to client: " << ec.message() << std::endl; + m_client_socket.close(); + m_server_socket.close(); + } + }); + } + + bool m_client_uses_ssl = false; + bool m_server_uses_ssl = false; + + tcp::socket m_client_socket; + tcp::socket m_server_socket; + tcp::resolver m_resolver; + + std::shared_ptr m_ssl_ctx; + asio::ssl::stream m_ssl_server_socket; + + std::array m_buffer; + std::array m_client_buffer; + std::array m_server_buffer; + + const std::string server_endpoint = "services.cloud.mongodb.com"; + }; + + proxy_server::proxy_server(const config &cfg) : m_config(cfg), m_strand(m_io_context) { + m_acceptor = std::make_unique(m_io_context, tcp::endpoint(tcp::v4(), m_config.port)); + asio::ssl::context ctx(asio::ssl::context::tlsv13_client); + + m_ssl_ctx = std::make_shared(asio::ssl::context::tlsv13_client); + m_ssl_ctx->set_default_verify_paths(); + + do_accept(); + + m_io_thread = std::thread([this] { + m_io_context.run(); + }); + } + + proxy_server::~proxy_server() { + m_io_context.stop(); + if (m_io_thread.joinable()) { + m_io_thread.join(); + } + } + + void proxy_server::do_accept() { + std::lock_guard lock(m_mutex); + m_acceptor->async_accept( + asio::bind_executor(m_strand, + [this](std::error_code ec, asio::ip::tcp::socket socket) { + if (!ec) { + std::make_shared(std::move(socket), m_ssl_ctx, m_config.client_uses_ssl, m_config.server_uses_ssl)->start(); + } + do_accept(); + })); + } +} // namespace realm::tests::utils \ No newline at end of file diff --git a/tests/utils/networking/proxy_server.hpp b/tests/utils/networking/proxy_server.hpp new file mode 100644 index 000000000..93655e80e --- /dev/null +++ b/tests/utils/networking/proxy_server.hpp @@ -0,0 +1,34 @@ +#ifndef CPPREALM_PROXY_SERVER_HPP +#define CPPREALM_PROXY_SERVER_HPP + +#include +#include +#include + +namespace asio = boost::asio; +using tcp = asio::ip::tcp; + +namespace realm::tests::utils { + class proxy_server { + public: + struct config { + short port = 1234; + bool client_uses_ssl = false; + bool server_uses_ssl = false; + }; + + proxy_server(const config &cfg); + ~proxy_server(); + private: + void do_accept(); + boost::asio::io_context m_io_context; + std::unique_ptr m_acceptor; + std::shared_ptr m_ssl_ctx; + config m_config; + std::mutex m_mutex; + std::thread m_io_thread; + asio::io_context::strand m_strand; + }; +} + +#endif //CPPREALM_PROXY_SERVER_HPP From aec653a847bf4747b30e497cf5a27116bda3c589 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Fri, 5 Jul 2024 17:32:05 +0100 Subject: [PATCH 10/41] Networking checkpoint --- .../internal/generic_network_transport.hpp | 41 ----- include/cpprealm/networking/networking.hpp | 50 +++--- .../networking/platform_networking.hpp | 32 ++-- src/CMakeLists.txt | 1 - src/cpprealm/analytics.cpp | 4 +- src/cpprealm/app.cpp | 23 +-- .../internal/curl/network_transport.cpp | 18 +-- .../internal/network/network_transport.cpp | 34 ++--- src/cpprealm/networking/networking.cpp | 59 +++++-- .../networking/platform_networking.cpp | 105 +++---------- tests/admin_utils.cpp | 4 +- tests/main.cpp | 16 +- tests/sync/networking_tests.cpp | 39 +++-- tests/utils/networking/proxy_server.cpp | 144 +++++++++--------- tests/utils/networking/proxy_server.hpp | 1 - 15 files changed, 239 insertions(+), 332 deletions(-) delete mode 100644 include/cpprealm/internal/generic_network_transport.hpp diff --git a/include/cpprealm/internal/generic_network_transport.hpp b/include/cpprealm/internal/generic_network_transport.hpp deleted file mode 100644 index ba4daf5ff..000000000 --- a/include/cpprealm/internal/generic_network_transport.hpp +++ /dev/null @@ -1,41 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef CPPREALM_GENERIC_NETWORK_TRANSPORT_CPP -#define CPPREALM_GENERIC_NETWORK_TRANSPORT_CPP - -#include -#include - -namespace realm::internal::networking { - class DefaultTransport : public ::realm::networking::http_transport_client { - public: - DefaultTransport(const std::optional>& custom_http_headers = std::nullopt, - const std::optional& proxy_config = std::nullopt); - ~DefaultTransport() = default; - - void send_request_to_server(const ::realm::networking::request& request, - std::function&& completion); -private: - std::optional> m_custom_http_headers; - std::optional m_proxy_config; -}; - -} // namespace realm::internal::networking - -#endif //CPPREALM_GENERIC_NETWORK_TRANSPORT_CPP diff --git a/include/cpprealm/networking/networking.hpp b/include/cpprealm/networking/networking.hpp index d58ca391d..024d38d1d 100644 --- a/include/cpprealm/networking/networking.hpp +++ b/include/cpprealm/networking/networking.hpp @@ -94,36 +94,12 @@ namespace realm::networking { }; -// struct websocket_endpoint { -// // using port_type = std::uint_fast16_t; -// // /// Host address -// // std::string address; -// // /// Host port number -// // port_type port; -// // /// Includes access token in query. -// // std::string path; -// // /// Array of one or more websocket protocols -// std::vector protocols; -// // /// true if SSL should be used -// // bool is_ssl; -// std::string url; -// /// Optional proxy config -// std::optional proxy_configuration; -// }; - struct websocket_endpoint { - using port_type = std::uint_fast16_t; - /// Host address - std::string address; - /// Host port number - port_type port; - /// Includes access token in query. - std::string path; - /// Array of one or more websocket protocols + /// Array of one or more websocket protocols. std::vector protocols; - /// true if SSL should be used - bool is_ssl; - /// Optional proxy config + /// The websocket url to connect to. + std::string url; + /// Optional proxy config taken in from `realm::App::config`. std::optional proxy_configuration; }; @@ -157,12 +133,25 @@ namespace realm::networking { RLM_ERR_WEBSOCKET_FATAL_ERROR = 4405, }; - // Interface for providing http transport + // Interface for providing http transport struct http_transport_client { virtual ~http_transport_client() = default; virtual void send_request_to_server(const request& request, std::function&& completion) = 0; + + void set_proxy_configuration(::realm::internal::bridge::realm::sync_config::proxy_config proxy_config) { + m_proxy_config = proxy_config; + }; + + void set_custom_http_headers(std::map http_headers) { + m_custom_http_headers = http_headers; + }; + + protected: + std::optional> m_custom_http_headers; + std::optional<::realm::internal::bridge::realm::sync_config::proxy_config> m_proxy_config; }; + } //namespace realm::networking namespace realm { @@ -183,7 +172,8 @@ namespace realm::internal::networking { ::realm::app::Response to_core_response(const ::realm::networking::response&); ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint & ep); - ::realm::networking::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& ep); + ::realm::networking::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& ep, + std::optional<::realm::internal::bridge::realm::sync_config::proxy_config> pc); } //namespace realm::internal::networking #endif//CPPREALM_NETWORKING_HPP diff --git a/include/cpprealm/networking/platform_networking.hpp b/include/cpprealm/networking/platform_networking.hpp index a3325a86c..d072661ee 100644 --- a/include/cpprealm/networking/platform_networking.hpp +++ b/include/cpprealm/networking/platform_networking.hpp @@ -20,7 +20,7 @@ #define CPPREALM_PLATFORM_NETWORKING_HPP #include -#include +#include #ifndef REALMCXX_VERSION_MAJOR #include @@ -42,15 +42,6 @@ namespace realm { namespace realm::networking { - /** - * Class used for setting a callback which is used - * to pass network configuration options to the sync_socket_provider - * before the websocket establishes a connection. - */ - struct websocket_event_handler { - std::function on_connect; - }; - /// The WebSocket base class that is used by the SyncClient to send data over the /// WebSocket connection with the server. This is the class that is returned by /// SyncSocketProvider::connect() when a connection to an endpoint is requested. @@ -233,6 +224,14 @@ namespace realm::networking { std::shared_ptr make_http_client(); void set_http_client_factory(std::function()>&&); + struct default_http_transport : public ::realm::networking::http_transport_client { + default_http_transport() = default; + ~default_http_transport() = default; + + void send_request_to_server(const ::realm::networking::request& request, + std::function&& completion); + }; + struct default_socket : public websocket_interface { default_socket(std::unique_ptr<::realm::sync::WebSocketInterface>&&); ~default_socket() = default; @@ -243,31 +242,24 @@ namespace realm::networking { std::shared_ptr<::realm::sync::WebSocketInterface> m_ws_interface; }; - struct default_socket_provider : public sync_socket_provider { - default_socket_provider(); - ~default_socket_provider() = default; std::unique_ptr connect(std::unique_ptr, websocket_endpoint &&) override; - void post(FunctionHandler&&) override; - SyncTimer create_timer(std::chrono::milliseconds delay, FunctionHandler&&) override; + private: std::shared_ptr<::realm::sync::SyncSocketProvider> m_provider; }; } namespace realm::internal::networking { - std::shared_ptr default_sync_socket_provider_factory(const std::shared_ptr& logger, - const std::string& user_agent_binding_info, - const std::string& user_agent_application_info); - std::shared_ptr create_http_client_shim(const std::shared_ptr<::realm::networking::http_transport_client>&); - std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider); + std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider, + std::optional proxy); } #endif//CPPREALM_PLATFORM_NETWORKING_HPP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ee6d4e9d3..c0ded2c85 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -111,7 +111,6 @@ set(HEADERS ../include/cpprealm/internal/bridge/timestamp.hpp ../include/cpprealm/internal/bridge/utils.hpp ../include/cpprealm/internal/bridge/uuid.hpp - ../include/cpprealm/internal/generic_network_transport.hpp ../include/cpprealm/internal/type_info.hpp ../include/cpprealm/internal/scheduler/realm_core_scheduler.hpp ../include/cpprealm/schedulers/default_scheduler.hpp diff --git a/src/cpprealm/analytics.cpp b/src/cpprealm/analytics.cpp index fc8264f72..bd52efb84 100644 --- a/src/cpprealm/analytics.cpp +++ b/src/cpprealm/analytics.cpp @@ -37,7 +37,7 @@ #endif #include -#include +#include #include #include @@ -273,7 +273,7 @@ namespace realm { std::stringstream json_ss; json_ss << post_data; auto json_str = json_ss.str(); - auto transport = std::make_unique(); + auto transport = std::make_unique(); std::vector buffer; buffer.resize(5000); diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index 54e03572e..f45b40ace 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #ifndef REALMCXX_VERSION_MAJOR @@ -494,22 +493,24 @@ namespace realm { client_config.user_agent_application_info = config.app_id; app_config.app_id = config.app_id; -// if (config.websocket_event_handler) { -// auto websocket_provider = ::realm::internal::networking::default_sync_socket_provider_factory(util::Logger::get_default_logger(), -// client_config.user_agent_binding_info, -// client_config.user_agent_application_info, -// config.websocket_event_handler); -// client_config.socket_provider = websocket_provider; -// } + // Websocket provider configuration if (config.sync_socket_provider) { - client_config.socket_provider = ::realm::internal::networking::create_sync_socket_provider_shim(config.sync_socket_provider); + client_config.socket_provider = ::realm::internal::networking::create_sync_socket_provider_shim(config.sync_socket_provider, + config.proxy_configuration); } + // HTTP Transport configuration if (config.http_transport_client) { app_config.transport = internal::networking::create_http_client_shim(config.http_transport_client); } else { - app_config.transport = internal::networking::create_http_client_shim(std::make_shared(config.custom_http_headers, - config.proxy_configuration)); + app_config.transport = internal::networking::create_http_client_shim(std::make_shared()); + } + + if (config.proxy_configuration) { + config.http_transport_client->set_proxy_configuration(*config.proxy_configuration); + } + if (config.custom_http_headers) { + config.http_transport_client->set_custom_http_headers(*config.custom_http_headers); } app_config.base_url = config.base_url; diff --git a/src/cpprealm/internal/curl/network_transport.cpp b/src/cpprealm/internal/curl/network_transport.cpp index 35a9b3b62..4d28fc6ef 100644 --- a/src/cpprealm/internal/curl/network_transport.cpp +++ b/src/cpprealm/internal/curl/network_transport.cpp @@ -17,13 +17,13 @@ //////////////////////////////////////////////////////////////////////////// #include -#include +#include #include #include -namespace realm::internal::networking { +namespace realm::networking { namespace { @@ -90,7 +90,7 @@ namespace realm::internal::networking { } static ::realm::networking::response do_http_request(const ::realm::networking::request& request, - const std::optional& proxy_config = std::nullopt) + const std::optional& proxy_config = std::nullopt) { CurlGlobalGuard curl_global_guard; auto curl = curl_easy_init(); @@ -168,14 +168,8 @@ namespace realm::internal::networking { } } // namespace - DefaultTransport::DefaultTransport(const std::optional>& custom_http_headers, - const std::optional& proxy_config) { - m_custom_http_headers = custom_http_headers; - m_proxy_config = proxy_config; - } - - void DefaultTransport::send_request_to_server(const ::realm::networking::request& request, - std::function&& completion_block) { + void default_http_transport::send_request_to_server(const ::realm::networking::request& request, + std::function&& completion_block) { { if (m_custom_http_headers) { auto req_copy = request; @@ -186,4 +180,4 @@ namespace realm::internal::networking { completion_block(do_http_request(request, m_proxy_config)); } } -} // namespace realm::internal::networking +} // namespace realm::networking diff --git a/src/cpprealm/internal/network/network_transport.cpp b/src/cpprealm/internal/network/network_transport.cpp index 41d6eb392..1a25834f0 100644 --- a/src/cpprealm/internal/network/network_transport.cpp +++ b/src/cpprealm/internal/network/network_transport.cpp @@ -20,7 +20,7 @@ #include #endif #include -#include +#include #include #include @@ -32,7 +32,7 @@ #include -namespace realm::internal::networking { +namespace realm::networking { struct DefaultSocket : realm::sync::network::Socket { DefaultSocket(realm::sync::network::Service& service) : realm::sync::network::Socket(service) @@ -84,12 +84,6 @@ namespace realm::internal::networking { realm::sync::network::ReadAheadBuffer m_read_buffer; }; - DefaultTransport::DefaultTransport(const std::optional>& custom_http_headers, - const std::optional& proxy_config) { - m_custom_http_headers = custom_http_headers; - m_proxy_config = proxy_config; - } - inline bool is_valid_ipv4(const std::string& ip) { std::stringstream ss(ip); std::string segment; @@ -130,10 +124,8 @@ namespace realm::internal::networking { } } - void DefaultTransport::send_request_to_server(const ::realm::networking::request& request, - std::function&& completion_block) { - bool disable_ssl = true; // TODO: pull this from proxy config - + void default_http_transport::send_request_to_server(const ::realm::networking::request& request, + std::function&& completion_block) { const auto uri = realm::util::Uri(request.url); std::string userinfo, host, port; uri.get_auth(userinfo, host, port); @@ -191,7 +183,13 @@ namespace realm::internal::networking { if (m_proxy_config) { realm::sync::HTTPRequest req; req.method = realm::sync::HTTPMethod::Connect; - req.headers.emplace("Host", util::format("%1:%2", host, is_localhost ? "9090" : "443")); + + if (is_ipv4) { + req.headers.emplace("Host", util::format("%1:%2", host, port)); + } else { + req.headers.emplace("Host", util::format("%1:%2", host, is_localhost ? "9090" : "443")); + } + if (m_proxy_config->username_password) { auto userpass = util::format("%1:%2", m_proxy_config->username_password->first, m_proxy_config->username_password->second); std::string encoded_userpass; @@ -227,7 +225,7 @@ namespace realm::internal::networking { m_ssl_context.use_included_certificate_roots(); #endif - if (url_scheme == URLScheme::HTTPS && !disable_ssl) { + if (url_scheme == URLScheme::HTTPS) { socket.ssl_stream.emplace(socket, m_ssl_context, Stream::client); socket.ssl_stream->set_host_name(host); // Throws @@ -246,8 +244,8 @@ namespace realm::internal::networking { headers["Content-Length"] = util::to_string(request.body.size()); } - if (m_custom_http_headers) { - for (auto& header : *m_custom_http_headers) { + if (this->m_custom_http_headers) { + for (auto& header : *this->m_custom_http_headers) { headers.emplace(header); } } @@ -310,7 +308,7 @@ namespace realm::internal::networking { return; } }; - if (url_scheme == URLScheme::HTTPS && !disable_ssl) { + if (url_scheme == URLScheme::HTTPS) { socket.async_handshake(std::move(handler)); // Throws } else { handler(std::error_code()); @@ -319,5 +317,5 @@ namespace realm::internal::networking { service.run(); } -} //namespace realm::internal::networking +} //namespace realm::networking diff --git a/src/cpprealm/networking/networking.cpp b/src/cpprealm/networking/networking.cpp index e63c0e606..005d85240 100644 --- a/src/cpprealm/networking/networking.cpp +++ b/src/cpprealm/networking/networking.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include namespace realm::internal::networking { ::realm::networking::request to_request(const ::realm::app::Request& core_request) { @@ -45,13 +47,48 @@ namespace realm::internal::networking { return core_response; } - ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint & ep) { + ::realm::sync::ProtocolEnvelope to_protocol_envelope(const std::string& protocol) noexcept + { + if (protocol == "realm:") { + return ::realm::sync::ProtocolEnvelope::realm; + } else if (protocol == "realms:") { + return ::realm::sync::ProtocolEnvelope::realms; + } else if (protocol == "ws:") { + return ::realm::sync::ProtocolEnvelope::ws; + } else if (protocol == "wss:") { + return ::realm::sync::ProtocolEnvelope::wss; + } + REALM_TERMINATE("Unrecognized websocket protocol"); + } + + ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint& ep) { ::realm::sync::WebSocketEndpoint core_ep; - core_ep.address = ep.address; - core_ep.port = ep.port; - core_ep.path = ep.path; + + auto uri = util::Uri(ep.url); + auto protocol = to_protocol_envelope(uri.get_scheme()); + + auto uri_path = uri.get_auth(); + if (uri_path.find("//") == 0) { + uri_path = uri_path.substr(2); + } + + std::string address; + std::string port; + size_t colon_pos = uri_path.find(':'); + if (colon_pos != std::string::npos) { + // Extract the address + address = uri_path.substr(0, colon_pos); + // Extract the port + port = uri_path.substr(colon_pos + 1); + } else { + REALM_TERMINATE("Invalid URL"); + } + + core_ep.address = address; + core_ep.port = std::stoi(port); + core_ep.path = uri.get_path() + uri.get_query(); core_ep.protocols = ep.protocols; - core_ep.is_ssl = ep.is_ssl; + core_ep.is_ssl = ::realm::sync::is_ssl(protocol); if (ep.proxy_configuration) { core_ep.proxy = SyncConfig::ProxyConfig(); @@ -69,13 +106,13 @@ namespace realm::internal::networking { return core_ep; } - ::realm::networking::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& core_ep) { + ::realm::networking::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& core_ep, + std::optional pc) { ::realm::networking::websocket_endpoint ep; - ep.address = core_ep.address; - ep.port = core_ep.port; - ep.path = core_ep.path; ep.protocols = core_ep.protocols; - ep.is_ssl = core_ep.is_ssl; + const auto& port = core_ep.proxy ? core_ep.proxy->port : core_ep.port; + ep.url = util::format("%1://%2:%3%4", core_ep.is_ssl ? "wss" : "ws", core_ep.proxy ? core_ep.proxy->address : core_ep.address, port, core_ep.path); + ep.proxy_configuration = pc; return ep; } -} //namespace internal::networking \ No newline at end of file +} //namespace internal::networking diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/platform_networking.cpp index b95c9d229..9d2961247 100644 --- a/src/cpprealm/networking/platform_networking.cpp +++ b/src/cpprealm/networking/platform_networking.cpp @@ -8,7 +8,7 @@ namespace realm::networking { std::shared_ptr make_default_http_client() { - return std::make_shared(); + return std::make_shared(); } std::function()> s_http_client_factory = make_default_http_client; @@ -16,31 +16,6 @@ namespace realm::networking { s_http_client_factory = std::move(factory_fn); } - struct default_websocket_observer_cpprealm : public websocket_observer { - default_websocket_observer_cpprealm(std::unique_ptr<::realm::sync::WebSocketObserver>&& o) : m_observer(std::move(o)) { } - ~default_websocket_observer_cpprealm() = default; - - void websocket_connected_handler(const std::string& protocol) override { - m_observer->websocket_connected_handler(protocol); - } - - void websocket_error_handler() override { - m_observer->websocket_error_handler(); - } - - bool websocket_binary_message_received(std::string_view data) override { - return m_observer->websocket_binary_message_received(data); - } - - bool websocket_closed_handler(bool was_clean, websocket_err_codes error_code, - std::string_view message) override { - return m_observer->websocket_closed_handler(was_clean, static_cast<::realm::sync::websocket::WebSocketError>(error_code), message); - } - - private: - std::shared_ptr<::realm::sync::WebSocketObserver> m_observer; - }; - struct default_websocket_observer_core : public ::realm::sync::WebSocketObserver { default_websocket_observer_core(std::unique_ptr&& o) : m_observer(std::move(o)) { } ~default_websocket_observer_core() = default; @@ -54,7 +29,7 @@ namespace realm::networking { } bool websocket_binary_message_received(util::Span data) override { - return m_observer->websocket_binary_message_received(data.data()); + return m_observer->websocket_binary_message_received(std::string_view(data.data(), data.size())); } bool websocket_closed_handler(bool was_clean, ::realm::sync::websocket::WebSocketError error_code, @@ -77,16 +52,14 @@ namespace realm::networking { }; default_socket_provider::default_socket_provider() { - auto user_agent = util::format("RealmSync/%1 (%2) %3 %4", REALM_VERSION_STRING, util::get_platform_info(), "user_agent_binding_info", "user_agent_application_info"); + auto user_agent_binding_info = std::string("RealmCpp/") + std::string(REALMCXX_VERSION_STRING); + auto user_agent_application_info = "";//app_id; TODO: Should we pass the app id? + + auto user_agent = util::format("RealmSync/%1 (%2) %3 %4", REALM_VERSION_STRING, util::get_platform_info(), user_agent_binding_info, user_agent_application_info); m_provider = std::make_unique<::realm::sync::websocket::DefaultSocketProvider>(util::Logger::get_default_logger(), user_agent); } std::unique_ptr default_socket_provider::connect(std::unique_ptr o, websocket_endpoint&& ep) { -// internal::bridge::realm::sync_config::proxy_config pc; -// pc.port = 1234; -// pc.address = "127.0.0.1"; -// ep.proxy_configuration = pc; - auto ws_interface = m_provider->connect(std::make_unique(std::move(o)), internal::networking::to_core_websocket_endpoint(ep)); return std::make_unique(std::move(ws_interface)); } @@ -115,53 +88,6 @@ namespace realm::networking { } namespace realm::internal::networking { - std::shared_ptr default_sync_socket_provider_factory(const std::shared_ptr& logger, - const std::string& user_agent_binding_info, - const std::string& user_agent_application_info, - const std::shared_ptr<::realm::networking::websocket_event_handler>& configuration_handler) { - class default_sync_socket_provider_shim final : public ::realm::sync::SyncSocketProvider { - public: - explicit default_sync_socket_provider_shim(const std::shared_ptr& logger, - const std::string& user_agent_binding_info, - const std::string& user_agent_application_info, - const std::shared_ptr<::realm::networking::websocket_event_handler>& configuration_handler) { - auto user_agent = util::format("RealmSync/%1 (%2) %3 %4", REALM_VERSION_STRING, util::get_platform_info(), user_agent_binding_info, user_agent_application_info); - m_default_provider = std::make_unique<::realm::sync::websocket::DefaultSocketProvider>(logger, user_agent); - m_websocket_event_handler = configuration_handler; - } - - ~default_sync_socket_provider_shim() = default; - - std::unique_ptr<::realm::sync::WebSocketInterface> connect(std::unique_ptr<::realm::sync::WebSocketObserver> observer, - ::realm::sync::WebSocketEndpoint&& endpoint) override { - if (m_websocket_event_handler) { - auto ep = m_websocket_event_handler->on_connect(to_websocket_endpoint(endpoint)); - return m_default_provider->connect(std::move(observer), to_core_websocket_endpoint(std::move(ep))); - } - - return m_default_provider->connect(std::move(observer), std::move(endpoint)); - } - - void post(FunctionHandler&& handler) override { - m_default_provider->post(std::move(handler)); - } - - ::realm::sync::SyncSocketProvider::SyncTimer create_timer(std::chrono::milliseconds delay, - ::realm::sync::SyncSocketProvider::FunctionHandler&& handler) override { - return m_default_provider->create_timer(delay, std::move(handler)); - } - - void stop(bool b = false) override { - m_default_provider->stop(b); - } - private: - std::shared_ptr<::realm::networking::websocket_event_handler> m_websocket_event_handler; - std::unique_ptr<::realm::sync::websocket::DefaultSocketProvider> m_default_provider; - }; - - return std::make_shared(logger, user_agent_binding_info, - user_agent_application_info, configuration_handler); - } std::unique_ptr<::realm::sync::WebSocketInterface> create_websocket_interface_shim(std::unique_ptr<::realm::networking::websocket_interface>&& m_interface) { struct core_websocket_interface_shim : public ::realm::sync::WebSocketInterface { @@ -170,7 +96,8 @@ namespace realm::internal::networking { void async_write_binary(util::Span data, sync::SyncSocketProvider::FunctionHandler&& handler) override { auto handler_ptr = handler.release(); - m_interface->async_write_binary(data.data(), [ptr = std::move(handler_ptr)](::realm::networking::websocket_interface::status s) { + auto b = std::string_view(data.data(), data.size()); + m_interface->async_write_binary(b, [ptr = std::move(handler_ptr)](::realm::networking::websocket_interface::status s) { auto uf = util::UniqueFunction(std::move(ptr)); return uf(s.operator ::realm::Status()); }); @@ -235,8 +162,8 @@ namespace realm::internal::networking { return std::make_unique(std::move(m_observer)); } - std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider) { - + std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider, + std::optional pc) { struct sync_timer_shim final : public ::realm::sync::SyncSocketProvider::Timer { sync_timer_shim(std::unique_ptr<::realm::networking::sync_socket_provider::timer>&& timer) : m_timer(std::move(timer)) {} ~sync_timer_shim() = default; @@ -250,15 +177,17 @@ namespace realm::internal::networking { }; struct sync_socket_provider_shim final : public ::realm::sync::SyncSocketProvider { - explicit sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider) { + explicit sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider, + std::optional proxy_config) { m_provider = provider; + m_proxy_config = proxy_config; } sync_socket_provider_shim() = delete; ~sync_socket_provider_shim() = default; std::unique_ptr<::realm::sync::WebSocketInterface> connect(std::unique_ptr<::realm::sync::WebSocketObserver> observer, ::realm::sync::WebSocketEndpoint&& ep) override { - auto cpprealm_ws_interface = m_provider->connect(create_websocket_observer_from_core_shim(std::move(observer)), to_websocket_endpoint(std::move(ep))); + auto cpprealm_ws_interface = m_provider->connect(create_websocket_observer_from_core_shim(std::move(observer)), to_websocket_endpoint(std::move(ep), m_proxy_config)); return create_websocket_interface_shim(std::move(cpprealm_ws_interface)); } @@ -279,10 +208,10 @@ namespace realm::internal::networking { return std::make_unique(m_provider->create_timer(delay, std::move(fn))); } private: - std::shared_ptr<::realm::networking::websocket_event_handler> m_websocket_event_handler; std::shared_ptr<::realm::networking::sync_socket_provider> m_provider; + std::optional m_proxy_config; }; - return std::make_unique(std::move(provider)); + return std::make_unique(std::move(provider), pc); } -} //namespace internal::networking \ No newline at end of file +} //namespace internal::networking diff --git a/tests/admin_utils.cpp b/tests/admin_utils.cpp index 6b1d90149..a64c6865a 100644 --- a/tests/admin_utils.cpp +++ b/tests/admin_utils.cpp @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include "admin_utils.hpp" #include "external/json/json.hpp" @@ -30,7 +30,7 @@ namespace Admin { std::mutex Admin::Session::mutex; static app::Response do_http_request(app::Request &&request) { - internal::networking::DefaultTransport transport; + networking::default_http_transport transport; std::promise p; std::future f = p.get_future(); transport.send_request_to_server(::realm::internal::networking::to_request(std::move(request)), diff --git a/tests/main.cpp b/tests/main.cpp index 5c65451fc..2210994ee 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -41,15 +41,15 @@ int main(int argc, char *argv[]) { } #ifdef CPPREALM_ENABLE_SYNC_TESTS -// std::optional baas_manager; -// if (const char* api_key = getenv("APIKEY")) { -// baas_manager.emplace(std::string(api_key)); -// baas_manager->start(); -// auto url = baas_manager->wait_for_container(); -// Admin::Session::shared().prepare(url); -// } else { + std::optional baas_manager; + if (const char* api_key = getenv("APIKEY")) { + baas_manager.emplace(std::string(api_key)); + baas_manager->start(); + auto url = baas_manager->wait_for_container(); + Admin::Session::shared().prepare(url); + } else { Admin::Session::shared().prepare(); -// } + } auto app_id = Admin::Session::shared().create_app({"str_col", "_id"}); Admin::Session::shared().cache_app_id(app_id); diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp index 58785b362..a4c71d526 100644 --- a/tests/sync/networking_tests.cpp +++ b/tests/sync/networking_tests.cpp @@ -4,34 +4,50 @@ #include "../utils/networking/proxy_server.hpp" using namespace realm; -TEST_CASE("websocket_handler_called", "[sync]") { +TEST_CASE("sends plaintext data to proxy", "[proxy]") { -// tests::utils::proxy_server::config cfg; -// cfg.port = 1234; -// cfg.client_uses_ssl = false; -// cfg.server_uses_ssl = false; -// tests::utils::proxy_server server(std::move(cfg)); + tests::utils::proxy_server::config cfg; + cfg.port = 1234; + cfg.server_uses_ssl = false; // Set to true if using services.cloud.mongodb.com + tests::utils::proxy_server server(std::move(cfg)); proxy_config pc; pc.port = 1234; pc.address = "127.0.0.1"; realm::App::configuration config; -// config.proxy_configuration = pc; - config.app_id = Admin::Session::shared().cached_app_id(); + config.proxy_configuration = pc; + config.app_id =Admin::Session::shared().cached_app_id(); config.base_url = Admin::Session::shared().base_url(); - struct foo_socket_provider : public ::realm::networking::default_socket_provider { std::unique_ptr<::realm::networking::websocket_interface> connect(std::unique_ptr<::realm::networking::websocket_observer> o, ::realm::networking::websocket_endpoint&& ep) override { -// url = url.replace("wss://", "ws://"); + const std::string from = "wss:"; + const std::string to = "ws:"; + if (ep.url.find(from) == 0) { + ep.url.replace(0, from.length(), to); + } return ::realm::networking::default_socket_provider::connect(std::move(o), std::move(ep)); } }; - config.sync_socket_provider = std::make_shared(); + struct foo_http_transport : public ::realm::networking::default_http_transport { + void send_request_to_server(const ::realm::networking::request& request, + std::function&& completion) override { + auto req_copy = request; + const std::string from = "https:"; + const std::string to = "http:"; + if (req_copy.url.find(from) == 0) { + req_copy.url.replace(0, from.length(), to); + } + + return ::realm::networking::default_http_transport::send_request_to_server(req_copy, std::move(completion)); + } + }; + config.http_transport_client = std::make_shared(); + auto app = realm::App(config); app.get_sync_manager().set_log_level(logger::level::all); @@ -58,5 +74,4 @@ TEST_CASE("websocket_handler_called", "[sync]") { CHECK(sub.name == "foo-strings"); CHECK(sub.object_class_name == "AllTypesObject"); CHECK(sub.query_string == "str_col == \"foo\""); - } \ No newline at end of file diff --git a/tests/utils/networking/proxy_server.cpp b/tests/utils/networking/proxy_server.cpp index 86df8ea45..68fc8a79b 100644 --- a/tests/utils/networking/proxy_server.cpp +++ b/tests/utils/networking/proxy_server.cpp @@ -1,4 +1,5 @@ #include "proxy_server.hpp" +#include "realm/util/terminate.hpp" #include #include @@ -9,13 +10,12 @@ namespace realm::tests::utils { class proxy_session : public std::enable_shared_from_this { public: - proxy_session(tcp::socket client_socket, std::shared_ptr ctx, bool client_uses_ssl, bool server_uses_ssl) + proxy_session(tcp::socket client_socket, std::shared_ptr ctx, bool server_uses_ssl) : m_client_socket(std::move(client_socket)), m_server_socket(m_client_socket.get_executor()), m_resolver(m_client_socket.get_executor()), m_ssl_ctx(ctx), m_ssl_server_socket(m_client_socket.get_executor(), *ctx), - m_client_uses_ssl(client_uses_ssl), m_server_uses_ssl(server_uses_ssl) {} void start() { @@ -32,10 +32,10 @@ namespace realm::tests::utils { if (request.substr(0, 7) == "CONNECT") { handle_connect(request); } else { - std::cerr << "Unsupported method: " << request.substr(0, 7) << std::endl; + REALM_TERMINATE("Unsupported HTTP method in proxy"); } } else { - std::cerr << "Error reading client request: " << ec.message() << std::endl; + REALM_TERMINATE("Error reading client request"); } }); } @@ -57,7 +57,7 @@ namespace realm::tests::utils { if (!ec) { connect_to_server(*results.begin(), host); } else { - std::cerr << "Error resolving host: " << ec.message() << std::endl; + REALM_TERMINATE("Proxy: Error resolving host"); } }); } else { @@ -76,7 +76,7 @@ namespace realm::tests::utils { if (!ec) { do_ssl_handshake_to_server(host); } else { - std::cerr << "Error connecting to server: " << ec.message() << std::endl; + REALM_TERMINATE("Proxy: Error connecting to server"); } }); } else { @@ -87,7 +87,7 @@ namespace realm::tests::utils { if (!ec) { do_write_connect_response(); } else { - std::cerr << "Error connecting to server: " << ec.message() << std::endl; + REALM_TERMINATE("Proxy: Error connecting to server"); } }); } @@ -101,7 +101,7 @@ namespace realm::tests::utils { // Set SNI Hostname (many hosts need this to handshake successfully) if (!SSL_set_tlsext_host_name(m_ssl_server_socket.native_handle(), hostname.c_str())) { - throw std::runtime_error("Could not set hostname"); + REALM_TERMINATE("Proxy: Could not set host name"); } m_ssl_server_socket.async_handshake(asio::ssl::stream_base::client, @@ -109,7 +109,7 @@ namespace realm::tests::utils { if (!ec) { do_write_connect_response(); } else { - std::cerr << "Error performing SSL handshake: " << ec.message() << std::endl; + REALM_TERMINATE("Proxy: Error performing SSL handshake."); } }); } @@ -121,7 +121,7 @@ namespace realm::tests::utils { if (!ec) { do_read_client(); } else { - std::cerr << "Error writing connect response: " << ec.message() << std::endl; + REALM_TERMINATE("Proxy: Error writing CONNECT response."); } }); } @@ -133,9 +133,7 @@ namespace realm::tests::utils { if (!ec) { do_ssl_read_server(); } else { - std::cerr << "Error writing to server: " << ec.message() << std::endl; - m_client_socket.close(); - m_server_socket.close(); + REALM_TERMINATE("Proxy: Error writing to server."); } }); } @@ -147,9 +145,7 @@ namespace realm::tests::utils { if (!ec) { do_write_to_client(length); } else { - std::cerr << "Error reading from server (SSL): " << ec.message() << std::endl; - m_client_socket.close(); - m_server_socket.close(); + REALM_TERMINATE("Proxy: Error reading from server."); } }); } @@ -167,26 +163,11 @@ namespace realm::tests::utils { do_write_to_server(length); } } else { - std::cerr << "Error reading from client: " << ec.message() << std::endl; - m_client_socket.close(); - m_server_socket.close(); + REALM_TERMINATE("Proxy: Error reading from client."); } }); } - void handle_http_req_res() { - auto length = m_client_socket.read_some(boost::asio::buffer(m_client_buffer)); - boost::asio::write(m_server_socket, boost::asio::buffer(std::string(m_client_buffer.data()), length)); - length = m_server_socket.read_some(boost::asio::buffer(m_server_buffer)); - - auto res = std::string(m_server_buffer.data(), length); - bool upgrade_to_websocket = res.find("HTTP/1.1 101 Switching Protocols") != std::string::npos; - auto bytes_written = boost::asio::write(m_client_socket, boost::asio::buffer(std::string(m_server_buffer.data()), length)); - if (upgrade_to_websocket) { - upgrade_client_to_websocket(bytes_written); - } - } - void do_read_from_websocket_client() { auto self(shared_from_this()); m_client_socket.async_read_some(boost::asio::buffer(m_client_buffer), @@ -195,51 +176,74 @@ namespace realm::tests::utils { do_write_to_websocket_server(length); do_read_from_websocket_client(); } else { - // Handle error + REALM_TERMINATE("Proxy: Error reading from websocket client."); } }); + } void do_write_to_websocket_server(std::size_t length) { auto self(shared_from_this()); - boost::asio::async_write(m_server_socket, boost::asio::buffer(m_client_buffer.data(), length), - [this, self](boost::system::error_code ec, std::size_t /*length*/) { - if (!ec) { - do_read_from_websocket_client(); - } else { - // Handle error - } - }); + if (m_server_uses_ssl) { + boost::asio::async_write(m_ssl_server_socket, boost::asio::buffer(m_client_buffer.data(), length), + [this, self](boost::system::error_code ec, std::size_t) { + if (!ec) { + do_read_from_websocket_client(); + } else { + REALM_TERMINATE("Proxy: Error writing to websocket server."); + } + }); + } else { + boost::asio::async_write(m_server_socket, boost::asio::buffer(m_client_buffer.data(), length), + [this, self](boost::system::error_code ec, std::size_t /*length*/) { + if (!ec) { + do_read_from_websocket_client(); + } else { + REALM_TERMINATE("Proxy: Error writing to websocket server."); + } + }); + } } void do_read_from_websocket_server() { auto self(shared_from_this()); - m_server_socket.async_read_some(boost::asio::buffer(m_server_buffer), - [this, self](boost::system::error_code ec, std::size_t length) { - if (!ec) { - do_write_to_websocket_client(length); - do_read_from_websocket_server(); - } else { - // Handle error - } - }); + if (m_server_uses_ssl) { + m_ssl_server_socket.async_read_some(boost::asio::buffer(m_server_buffer), + [this, self](boost::system::error_code ec, std::size_t length) { + if (!ec) { + do_write_to_websocket_client(length); + do_read_from_websocket_server(); + } else { + REALM_TERMINATE("Proxy: Error reading from websocket server."); + } + }); + } else { + m_server_socket.async_read_some(boost::asio::buffer(m_server_buffer), + [this, self](boost::system::error_code ec, std::size_t length) { + if (!ec) { + do_write_to_websocket_client(length); + do_read_from_websocket_server(); + } else { + REALM_TERMINATE("Proxy: Error reading from websocket server."); + } + }); + } } void do_write_to_websocket_client(std::size_t length) { auto self(shared_from_this()); - boost::asio::async_write(m_client_socket, boost::asio::buffer(m_server_buffer.data(), length), - [this, self](boost::system::error_code ec, std::size_t /*length*/) { - if (!ec) { - do_read_from_websocket_server(); - } else { - // Handle error - } - }); - } + boost::asio::async_write(m_client_socket, boost::asio::buffer(m_server_buffer.data(), length), + [this, self](boost::system::error_code ec, std::size_t) { + if (!ec) { + do_read_from_websocket_server(); + } else { + REALM_TERMINATE("Proxy: Error writing from websocket client."); + } + }); + } void upgrade_client_to_websocket(std::size_t length) { - auto self(shared_from_this()); do_read_from_websocket_client(); do_read_from_websocket_server(); } @@ -251,9 +255,7 @@ namespace realm::tests::utils { if (!ec) { do_read_server(); } else { - std::cerr << "Error writing to server: " << ec.message() << std::endl; - m_client_socket.close(); - m_server_socket.close(); + REALM_TERMINATE("Proxy: Error writing to server."); } }); } @@ -265,9 +267,7 @@ namespace realm::tests::utils { if (!ec) { do_write_to_client(length); } else { - std::cerr << "Error reading from server: " << ec.message() << std::endl; - m_client_socket.close(); - m_server_socket.close(); + REALM_TERMINATE("Proxy: Error reading from server."); } }); } @@ -275,13 +275,11 @@ namespace realm::tests::utils { void do_write_to_client(std::size_t length) { auto self(shared_from_this()); auto res = std::string(m_server_buffer.data(), length); - std::cout << "Server to Client: " << res << std::endl; bool upgrade_to_websocket = res.find("HTTP/1.1 101 Switching Protocols") != std::string::npos; boost::asio::async_write(m_client_socket, boost::asio::buffer(std::string(m_server_buffer.data()), length), [this, self, upgrade_to_websocket](boost::system::error_code ec, std::size_t bytes_written) { if (!ec) { - std::cout << "BYTES WRITTEN: " << bytes_written << std::endl; if (upgrade_to_websocket) { upgrade_client_to_websocket(bytes_written); } else { @@ -292,14 +290,11 @@ namespace realm::tests::utils { } } } else { - std::cerr << "Error writing to client: " << ec.message() << std::endl; - m_client_socket.close(); - m_server_socket.close(); + REALM_TERMINATE("Proxy: Error writing to client."); } }); } - bool m_client_uses_ssl = false; bool m_server_uses_ssl = false; tcp::socket m_client_socket; @@ -318,7 +313,6 @@ namespace realm::tests::utils { proxy_server::proxy_server(const config &cfg) : m_config(cfg), m_strand(m_io_context) { m_acceptor = std::make_unique(m_io_context, tcp::endpoint(tcp::v4(), m_config.port)); - asio::ssl::context ctx(asio::ssl::context::tlsv13_client); m_ssl_ctx = std::make_shared(asio::ssl::context::tlsv13_client); m_ssl_ctx->set_default_verify_paths(); @@ -343,9 +337,9 @@ namespace realm::tests::utils { asio::bind_executor(m_strand, [this](std::error_code ec, asio::ip::tcp::socket socket) { if (!ec) { - std::make_shared(std::move(socket), m_ssl_ctx, m_config.client_uses_ssl, m_config.server_uses_ssl)->start(); + std::make_shared(std::move(socket), m_ssl_ctx, m_config.server_uses_ssl)->start(); } do_accept(); })); } -} // namespace realm::tests::utils \ No newline at end of file +} // namespace realm::tests::utils diff --git a/tests/utils/networking/proxy_server.hpp b/tests/utils/networking/proxy_server.hpp index 93655e80e..764f6d122 100644 --- a/tests/utils/networking/proxy_server.hpp +++ b/tests/utils/networking/proxy_server.hpp @@ -13,7 +13,6 @@ namespace realm::tests::utils { public: struct config { short port = 1234; - bool client_uses_ssl = false; bool server_uses_ssl = false; }; From 81034b1d3b04b731186144928fdb341288e66d1b Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 8 Jul 2024 11:23:45 +0100 Subject: [PATCH 11/41] Cleanup --- include/cpprealm/app.hpp | 7 ++--- include/cpprealm/db.hpp | 11 +++---- .../networking/platform_networking.hpp | 30 +++++++++++-------- src/cpprealm/app.cpp | 19 +++++++----- .../internal/network/network_transport.cpp | 8 ++--- src/cpprealm/networking/networking.cpp | 2 +- .../networking/platform_networking.cpp | 15 +++++++--- 7 files changed, 52 insertions(+), 40 deletions(-) diff --git a/include/cpprealm/app.hpp b/include/cpprealm/app.hpp index 10baedc6d..3b09aaa47 100644 --- a/include/cpprealm/app.hpp +++ b/include/cpprealm/app.hpp @@ -34,6 +34,7 @@ #include namespace realm { + using proxy_config = sync_config::proxy_config; using sync_session = internal::bridge::sync_session; @@ -243,10 +244,10 @@ class App { std::optional> custom_http_headers; // Custom encryption key for the metadata Realm. std::optional> metadata_encryption_key; - // HTTP proxy configuration to be set on each HTTP request when using the internal HTTP client. + // Network proxy configuration to be set on each HTTP and WebSocket request. std::optional proxy_configuration; /** - * Optionally provide a custom transport for network calls to the server. + * Optionally provide a custom HTTP transport for network calls to the server. * * Alternatively use `realm::networking::set_http_client_factory` to globally set * the default HTTP transport client. @@ -254,8 +255,6 @@ class App { std::shared_ptr<::realm::networking::http_transport_client> http_transport_client; /** * Optionally provide a custom WebSocket interface for sync. - * Alternatively use `realm::networking::set_http_client_factory` to globally set - * the default HTTP transport client. */ std::shared_ptr<::realm::networking::sync_socket_provider> sync_socket_provider; diff --git a/include/cpprealm/db.hpp b/include/cpprealm/db.hpp index 697765d14..b30794a90 100644 --- a/include/cpprealm/db.hpp +++ b/include/cpprealm/db.hpp @@ -20,19 +20,16 @@ #define CPPREALM_DB_HPP #include - +#include +#include +#include #include +#include #include #include #include -#include - -#include -#include -#include - #include #include #include diff --git a/include/cpprealm/networking/platform_networking.hpp b/include/cpprealm/networking/platform_networking.hpp index d072661ee..c7edbc258 100644 --- a/include/cpprealm/networking/platform_networking.hpp +++ b/include/cpprealm/networking/platform_networking.hpp @@ -44,9 +44,9 @@ namespace realm::networking { /// The WebSocket base class that is used by the SyncClient to send data over the /// WebSocket connection with the server. This is the class that is returned by - /// SyncSocketProvider::connect() when a connection to an endpoint is requested. + /// sync_socket_provider::connect() when a connection to an endpoint is requested. /// If an error occurs while establishing the connection, the error is presented - /// to the WebSocketObserver provided when the WebSocket was created. + /// to the web_socket_observer provided when the WebSocket was created. struct websocket_interface { /// The destructor must close the websocket connection when the WebSocket object /// is destroyed @@ -56,7 +56,7 @@ namespace realm::networking { using FunctionHandler = std::function; /// Write data asynchronously to the WebSocket connection. The handler function - /// will be called when the data has been sent successfully. The WebSocketOberver + /// will be called when the data has been sent successfully. The web_socket_oberver /// provided when the WebSocket was created will be called if any errors occur /// during the write operation. /// @param data A std::string_view containing the data to be sent to the server. @@ -68,12 +68,12 @@ namespace realm::networking { virtual void async_write_binary(std::string_view data, FunctionHandler&& handler) = 0; }; - /// WebSocket observer interface in the SyncClient that receives the websocket + /// WebSocket observer interface in the Sync Client that receives the websocket /// events during operation. struct websocket_observer { virtual ~websocket_observer() = default; - /// Called when the websocket is connected, i.e. after the handshake is done. + /// Called when the WebSocket is connected, i.e. after the handshake is done. /// The Sync Client is not allowed to send messages on the socket before the /// handshake is complete and no message_received callbacks will be called /// before the handshake is done. @@ -126,7 +126,7 @@ namespace realm::networking { /// thread pool as long as it is guaranteed that the callback handler /// functions are processed in order and not run concurrently. /// - /// The implementation of a SyncSocketProvider must support the following + /// The implementation of a sync_socket_provider must support the following /// operations that post handler functions (via by the Sync client) onto the /// event loop: /// * Post a handler function directly onto the event loop @@ -139,14 +139,14 @@ namespace realm::networking { /// * a handler function runs to completion before the next handler function /// is called. /// - /// The SyncSocketProvider also provides a WebSocket interface for + /// The sync_socket_provider also provides a WebSocket interface for /// connecting to the server via a WebSocket connection. class sync_socket_provider { public: /// Function handler typedef using FunctionHandler = std::function; - /// The Timer object used to track a timer that was started on the event + /// The timer object used to track a timer that was started on the event /// loop. /// /// This object provides a cancel() mechanism to cancel the timer. The @@ -165,7 +165,7 @@ namespace realm::networking { }; /// Other class typedefs - using SyncTimer = std::unique_ptr; + using sync_timer = std::unique_ptr; /// The event loop implementation must ensure the event loop is stopped and /// flushed when the object is destroyed. If the event loop is processed by @@ -175,7 +175,7 @@ namespace realm::networking { /// Create a new websocket pointed to the server indicated by endpoint and /// connect to the server. Any events that occur during the execution of the /// websocket will call directly to the handlers provided by the observer. - /// The WebSocketObserver guarantees that the WebSocket object will be + /// The web_socket_observer guarantees that the WebSocket object will be /// closed/destroyed before the observer is terminated/destroyed. virtual std::unique_ptr connect(std::unique_ptr observer, websocket_endpoint && endpoint) = 0; @@ -218,12 +218,15 @@ namespace realm::networking { /// @return A pointer to the Timer object that can be used to cancel the /// timer. The timer will also be canceled if the Timer object returned is /// destroyed. - virtual SyncTimer create_timer(std::chrono::milliseconds delay, FunctionHandler&& handler) = 0; + virtual sync_timer create_timer(std::chrono::milliseconds delay, FunctionHandler&& handler) = 0; }; + /// Produces a http transport client from the factory. std::shared_ptr make_http_client(); + /// Globally overwrites the default http transport client factory void set_http_client_factory(std::function()>&&); + /// Built in HTTP transport client. struct default_http_transport : public ::realm::networking::http_transport_client { default_http_transport() = default; ~default_http_transport() = default; @@ -232,23 +235,24 @@ namespace realm::networking { std::function&& completion); }; + /// Built in websocket client. struct default_socket : public websocket_interface { default_socket(std::unique_ptr<::realm::sync::WebSocketInterface>&&); ~default_socket() = default; void async_write_binary(std::string_view data, websocket_interface::FunctionHandler&& handler) override; - private: std::shared_ptr<::realm::sync::WebSocketInterface> m_ws_interface; }; + /// Built in websocket provider struct default_socket_provider : public sync_socket_provider { default_socket_provider(); ~default_socket_provider() = default; std::unique_ptr connect(std::unique_ptr, websocket_endpoint &&) override; void post(FunctionHandler&&) override; - SyncTimer create_timer(std::chrono::milliseconds delay, FunctionHandler&&) override; + sync_timer create_timer(std::chrono::milliseconds delay, FunctionHandler&&) override; private: std::shared_ptr<::realm::sync::SyncSocketProvider> m_provider; diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index f45b40ace..27bfdc608 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -491,29 +491,34 @@ namespace realm { #endif client_config.user_agent_binding_info = std::string("RealmCpp/") + std::string(REALMCXX_VERSION_STRING); client_config.user_agent_application_info = config.app_id; + app_config.app_id = config.app_id; + app_config.base_url = config.base_url; - // Websocket provider configuration + // Sync socket provider configuration if (config.sync_socket_provider) { client_config.socket_provider = ::realm::internal::networking::create_sync_socket_provider_shim(config.sync_socket_provider, config.proxy_configuration); + } else { + client_config.socket_provider = ::realm::internal::networking::create_sync_socket_provider_shim(std::make_shared(), + config.proxy_configuration); } + std::shared_ptr http_client; // HTTP Transport configuration if (config.http_transport_client) { - app_config.transport = internal::networking::create_http_client_shim(config.http_transport_client); + http_client = config.http_transport_client; } else { - app_config.transport = internal::networking::create_http_client_shim(std::make_shared()); + http_client = networking::make_http_client(); } if (config.proxy_configuration) { - config.http_transport_client->set_proxy_configuration(*config.proxy_configuration); + http_client->set_proxy_configuration(*config.proxy_configuration); } if (config.custom_http_headers) { - config.http_transport_client->set_custom_http_headers(*config.custom_http_headers); + http_client->set_custom_http_headers(*config.custom_http_headers); } - - app_config.base_url = config.base_url; + app_config.transport = internal::networking::create_http_client_shim(http_client); app_config.metadata_mode = should_encrypt ? app::AppConfig::MetadataMode::Encryption : app::AppConfig::MetadataMode::NoEncryption; if (config.metadata_encryption_key) { diff --git a/src/cpprealm/internal/network/network_transport.cpp b/src/cpprealm/internal/network/network_transport.cpp index 1a25834f0..5b1d706a5 100644 --- a/src/cpprealm/internal/network/network_transport.cpp +++ b/src/cpprealm/internal/network/network_transport.cpp @@ -184,11 +184,11 @@ namespace realm::networking { realm::sync::HTTPRequest req; req.method = realm::sync::HTTPMethod::Connect; - if (is_ipv4) { +// if (is_ipv4) { req.headers.emplace("Host", util::format("%1:%2", host, port)); - } else { - req.headers.emplace("Host", util::format("%1:%2", host, is_localhost ? "9090" : "443")); - } +// } else { +// req.headers.emplace("Host", util::format("%1:%2", host, is_localhost ? "9090" : port)); +// } if (m_proxy_config->username_password) { auto userpass = util::format("%1:%2", m_proxy_config->username_password->first, m_proxy_config->username_password->second); diff --git a/src/cpprealm/networking/networking.cpp b/src/cpprealm/networking/networking.cpp index 005d85240..7603940ed 100644 --- a/src/cpprealm/networking/networking.cpp +++ b/src/cpprealm/networking/networking.cpp @@ -1,10 +1,10 @@ #include #include +#include #include #include #include -#include namespace realm::internal::networking { ::realm::networking::request to_request(const ::realm::app::Request& core_request) { diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/platform_networking.cpp index 9d2961247..fc18525b1 100644 --- a/src/cpprealm/networking/platform_networking.cpp +++ b/src/cpprealm/networking/platform_networking.cpp @@ -16,6 +16,10 @@ namespace realm::networking { s_http_client_factory = std::move(factory_fn); } + std::shared_ptr make_http_client() { + return s_http_client_factory(); + } + struct default_websocket_observer_core : public ::realm::sync::WebSocketObserver { default_websocket_observer_core(std::unique_ptr&& o) : m_observer(std::move(o)) { } ~default_websocket_observer_core() = default; @@ -55,12 +59,14 @@ namespace realm::networking { auto user_agent_binding_info = std::string("RealmCpp/") + std::string(REALMCXX_VERSION_STRING); auto user_agent_application_info = "";//app_id; TODO: Should we pass the app id? - auto user_agent = util::format("RealmSync/%1 (%2) %3 %4", REALM_VERSION_STRING, util::get_platform_info(), user_agent_binding_info, user_agent_application_info); + auto user_agent = util::format("RealmSync/%1 (%2) %3 %4", REALM_VERSION_STRING, util::get_platform_info(), + user_agent_binding_info, user_agent_application_info); m_provider = std::make_unique<::realm::sync::websocket::DefaultSocketProvider>(util::Logger::get_default_logger(), user_agent); } std::unique_ptr default_socket_provider::connect(std::unique_ptr o, websocket_endpoint&& ep) { - auto ws_interface = m_provider->connect(std::make_unique(std::move(o)), internal::networking::to_core_websocket_endpoint(ep)); + auto ws_interface = m_provider->connect(std::make_unique(std::move(o)), + internal::networking::to_core_websocket_endpoint(ep)); return std::make_unique(std::move(ws_interface)); } @@ -70,7 +76,7 @@ namespace realm::networking { }); } - default_socket_provider::SyncTimer default_socket_provider::create_timer(std::chrono::milliseconds delay, FunctionHandler&& fn) { + default_socket_provider::sync_timer default_socket_provider::create_timer(std::chrono::milliseconds delay, FunctionHandler&& fn) { return std::make_unique(m_provider->create_timer(delay, [fn = std::move(fn)](::realm::Status s) { fn(s); })); @@ -187,7 +193,8 @@ namespace realm::internal::networking { ~sync_socket_provider_shim() = default; std::unique_ptr<::realm::sync::WebSocketInterface> connect(std::unique_ptr<::realm::sync::WebSocketObserver> observer, ::realm::sync::WebSocketEndpoint&& ep) override { - auto cpprealm_ws_interface = m_provider->connect(create_websocket_observer_from_core_shim(std::move(observer)), to_websocket_endpoint(std::move(ep), m_proxy_config)); + auto cpprealm_ws_interface = m_provider->connect(create_websocket_observer_from_core_shim(std::move(observer)), + to_websocket_endpoint(std::move(ep), m_proxy_config)); return create_websocket_interface_shim(std::move(cpprealm_ws_interface)); } From df847517baba4367bfa51e7a96228476f030e79c Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 8 Jul 2024 13:08:39 +0100 Subject: [PATCH 12/41] Cleanup --- .../internal/network/network_transport.cpp | 11 +-- src/cpprealm/networking/networking.cpp | 21 +--- tests/sync/networking_tests.cpp | 97 ++++++++++++++++++- tests/utils/networking/proxy_server.cpp | 66 ++++++++++--- tests/utils/networking/proxy_server.hpp | 16 +++ 5 files changed, 170 insertions(+), 41 deletions(-) diff --git a/src/cpprealm/internal/network/network_transport.cpp b/src/cpprealm/internal/network/network_transport.cpp index 5b1d706a5..a0bba685f 100644 --- a/src/cpprealm/internal/network/network_transport.cpp +++ b/src/cpprealm/internal/network/network_transport.cpp @@ -139,6 +139,10 @@ namespace realm::networking { const bool is_ipv4 = is_valid_ipv4(host); const URLScheme url_scheme = get_url_scheme(uri.get_scheme()); + if (port.empty()) { + port = is_localhost ? "9090" : "443"; + } + try { auto resolver = realm::sync::network::Resolver{service}; if (m_proxy_config) { @@ -183,12 +187,7 @@ namespace realm::networking { if (m_proxy_config) { realm::sync::HTTPRequest req; req.method = realm::sync::HTTPMethod::Connect; - -// if (is_ipv4) { - req.headers.emplace("Host", util::format("%1:%2", host, port)); -// } else { -// req.headers.emplace("Host", util::format("%1:%2", host, is_localhost ? "9090" : port)); -// } + req.headers.emplace("Host", util::format("%1:%2", host, port)); if (m_proxy_config->username_password) { auto userpass = util::format("%1:%2", m_proxy_config->username_password->first, m_proxy_config->username_password->second); diff --git a/src/cpprealm/networking/networking.cpp b/src/cpprealm/networking/networking.cpp index 7603940ed..8828acff4 100644 --- a/src/cpprealm/networking/networking.cpp +++ b/src/cpprealm/networking/networking.cpp @@ -63,28 +63,13 @@ namespace realm::internal::networking { ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint& ep) { ::realm::sync::WebSocketEndpoint core_ep; - auto uri = util::Uri(ep.url); auto protocol = to_protocol_envelope(uri.get_scheme()); - auto uri_path = uri.get_auth(); - if (uri_path.find("//") == 0) { - uri_path = uri_path.substr(2); - } - - std::string address; - std::string port; - size_t colon_pos = uri_path.find(':'); - if (colon_pos != std::string::npos) { - // Extract the address - address = uri_path.substr(0, colon_pos); - // Extract the port - port = uri_path.substr(colon_pos + 1); - } else { - REALM_TERMINATE("Invalid URL"); - } + std::string userinfo, host, port; + uri.get_auth(userinfo, host, port); - core_ep.address = address; + core_ep.address = host; core_ep.port = std::stoi(port); core_ep.path = uri.get_path() + uri.get_query(); core_ep.protocols = ep.protocols; diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp index a4c71d526..0da8c45b1 100644 --- a/tests/sync/networking_tests.cpp +++ b/tests/sync/networking_tests.cpp @@ -11,15 +11,24 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { cfg.server_uses_ssl = false; // Set to true if using services.cloud.mongodb.com tests::utils::proxy_server server(std::move(cfg)); + std::set proxy_events; + server.set_callback([&proxy_events](tests::utils::proxy_server::event e) { + proxy_events.insert(e); + }); + proxy_config pc; pc.port = 1234; pc.address = "127.0.0.1"; realm::App::configuration config; config.proxy_configuration = pc; - config.app_id =Admin::Session::shared().cached_app_id(); + config.app_id = Admin::Session::shared().cached_app_id(); config.base_url = Admin::Session::shared().base_url(); + bool provider_called, http_transport_called = false; + struct foo_socket_provider : public ::realm::networking::default_socket_provider { + foo_socket_provider(bool& provider_called) : m_called(provider_called) { } + std::unique_ptr<::realm::networking::websocket_interface> connect(std::unique_ptr<::realm::networking::websocket_observer> o, ::realm::networking::websocket_endpoint&& ep) override { const std::string from = "wss:"; @@ -27,13 +36,18 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { if (ep.url.find(from) == 0) { ep.url.replace(0, from.length(), to); } - + m_called = true; return ::realm::networking::default_socket_provider::connect(std::move(o), std::move(ep)); } + + private: + bool& m_called; }; - config.sync_socket_provider = std::make_shared(); + config.sync_socket_provider = std::make_shared(provider_called); struct foo_http_transport : public ::realm::networking::default_http_transport { + foo_http_transport(bool& provider_called) : m_called(provider_called) { } + void send_request_to_server(const ::realm::networking::request& request, std::function&& completion) override { auto req_copy = request; @@ -42,11 +56,14 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { if (req_copy.url.find(from) == 0) { req_copy.url.replace(0, from.length(), to); } - + m_called = true; return ::realm::networking::default_http_transport::send_request_to_server(req_copy, std::move(completion)); } + + private: + bool& m_called; }; - config.http_transport_client = std::make_shared(); + config.http_transport_client = std::make_shared(http_transport_called); auto app = realm::App(config); app.get_sync_manager().set_log_level(logger::level::all); @@ -74,4 +91,74 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { CHECK(sub.name == "foo-strings"); CHECK(sub.object_class_name == "AllTypesObject"); CHECK(sub.query_string == "str_col == \"foo\""); + + std::set expected_events; + expected_events.insert(tests::utils::proxy_server::event::connect); + expected_events.insert(tests::utils::proxy_server::event::client); + expected_events.insert(tests::utils::proxy_server::event::nonssl); + expected_events.insert(tests::utils::proxy_server::event::websocket_upgrade); + expected_events.insert(tests::utils::proxy_server::event::websocket); + + bool is_subset = std::includes(expected_events.begin(), expected_events.end(), proxy_events.begin(), proxy_events.end()); + CHECK(is_subset); + CHECK(provider_called); + CHECK(http_transport_called); +} + +TEST_CASE("proxy roundtrip", "[proxy]") { + + tests::utils::proxy_server::config cfg; + cfg.port = 1234; + cfg.server_uses_ssl = false; // Set to true if using services.cloud.mongodb.com + tests::utils::proxy_server server(std::move(cfg)); + + std::set proxy_events; + server.set_callback([&proxy_events](tests::utils::proxy_server::event e) { + proxy_events.insert(e); + }); + + proxy_config pc; + pc.port = 1234; + pc.address = "127.0.0.1"; + realm::App::configuration config; + config.proxy_configuration = pc; + config.app_id = Admin::Session::shared().cached_app_id(); + config.base_url = Admin::Session::shared().base_url(); + + auto app = realm::App(config); + app.get_sync_manager().set_log_level(logger::level::all); + + auto user = app.login(realm::App::credentials::anonymous()).get(); + auto flx_sync_config = user.flexible_sync_configuration(); + auto synced_realm = db(flx_sync_config); + + auto update_success = synced_realm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { + subs.clear(); + }).get(); + CHECK(update_success == true); + CHECK(synced_realm.subscriptions().size() == 0); + + update_success = synced_realm.subscriptions().update([](realm::mutable_sync_subscription_set &subs) { + subs.add("foo-strings", [](auto &obj) { + return obj.str_col == "foo"; + }); + subs.add("foo-link"); + }).get(); + CHECK(update_success == true); + CHECK(synced_realm.subscriptions().size() == 2); + + auto sub = *synced_realm.subscriptions().find("foo-strings"); + CHECK(sub.name == "foo-strings"); + CHECK(sub.object_class_name == "AllTypesObject"); + CHECK(sub.query_string == "str_col == \"foo\""); + + std::set expected_events; + expected_events.insert(tests::utils::proxy_server::event::connect); + expected_events.insert(tests::utils::proxy_server::event::client); + expected_events.insert(tests::utils::proxy_server::event::nonssl); + expected_events.insert(tests::utils::proxy_server::event::websocket_upgrade); + expected_events.insert(tests::utils::proxy_server::event::websocket); + + bool is_subset = std::includes(expected_events.begin(), expected_events.end(), proxy_events.begin(), proxy_events.end()); + CHECK(is_subset); } \ No newline at end of file diff --git a/tests/utils/networking/proxy_server.cpp b/tests/utils/networking/proxy_server.cpp index 68fc8a79b..1781cbf1b 100644 --- a/tests/utils/networking/proxy_server.cpp +++ b/tests/utils/networking/proxy_server.cpp @@ -10,13 +10,14 @@ namespace realm::tests::utils { class proxy_session : public std::enable_shared_from_this { public: - proxy_session(tcp::socket client_socket, std::shared_ptr ctx, bool server_uses_ssl) + proxy_session(tcp::socket client_socket, std::shared_ptr ctx, bool server_uses_ssl, std::function fn) : m_client_socket(std::move(client_socket)), m_server_socket(m_client_socket.get_executor()), m_resolver(m_client_socket.get_executor()), m_ssl_ctx(ctx), m_ssl_server_socket(m_client_socket.get_executor(), *ctx), - m_server_uses_ssl(server_uses_ssl) {} + m_server_uses_ssl(server_uses_ssl), + m_event_handler(std::move(fn)) {} void start() { do_read_client_request(); @@ -47,6 +48,10 @@ namespace realm::tests::utils { std::string host = request.substr(host_start, host_end - host_start); std::string port = request.substr(host_end + 1, request.find(" ", host_end) - host_end - 1); + if (m_event_handler) { + m_event_handler(proxy_server::event::connect); + } + if (host == "127.0.0.1" || host == "localhost") { tcp::endpoint endpoint(boost::asio::ip::make_address("127.0.0.1"), std::stoi(port)); connect_to_server(endpoint, host); @@ -96,6 +101,10 @@ namespace realm::tests::utils { void do_ssl_handshake_to_server(const std::string &hostname) { auto self(shared_from_this()); + if (m_event_handler) { + m_event_handler(proxy_server::event::ssl_handshake); + } + m_ssl_server_socket.set_verify_callback(asio::ssl::host_name_verification(hostname)); m_ssl_server_socket.set_verify_mode(boost::asio::ssl::verify_peer); @@ -128,6 +137,9 @@ namespace realm::tests::utils { void do_ssl_write_to_server(std::size_t length) { auto self(shared_from_this()); + if (m_event_handler) { + m_event_handler(proxy_server::event::ssl); + } boost::asio::async_write(m_ssl_server_socket, boost::asio::buffer(std::string(m_client_buffer.data()), length), [this, self](boost::system::error_code ec, std::size_t) { if (!ec) { @@ -140,6 +152,9 @@ namespace realm::tests::utils { void do_ssl_read_server() { auto self(shared_from_this()); + if (m_event_handler) { + m_event_handler(proxy_server::event::ssl); + } m_ssl_server_socket.async_read_some(boost::asio::buffer(m_server_buffer), [this, self](boost::system::error_code ec, std::size_t length) { if (!ec) { @@ -152,6 +167,9 @@ namespace realm::tests::utils { void do_read_client() { auto self(shared_from_this()); + if (m_event_handler) { + m_event_handler(proxy_server::event::client); + } m_client_socket.async_read_some(boost::asio::buffer(m_client_buffer), [this, self](boost::system::error_code ec, std::size_t length) { if (!ec) { @@ -170,6 +188,9 @@ namespace realm::tests::utils { void do_read_from_websocket_client() { auto self(shared_from_this()); + if (m_event_handler) { + m_event_handler(proxy_server::event::websocket); + } m_client_socket.async_read_some(boost::asio::buffer(m_client_buffer), [this, self](boost::system::error_code ec, std::size_t length) { if (!ec) { @@ -184,6 +205,9 @@ namespace realm::tests::utils { void do_write_to_websocket_server(std::size_t length) { auto self(shared_from_this()); + if (m_event_handler) { + m_event_handler(proxy_server::event::websocket); + } if (m_server_uses_ssl) { boost::asio::async_write(m_ssl_server_socket, boost::asio::buffer(m_client_buffer.data(), length), [this, self](boost::system::error_code ec, std::size_t) { @@ -207,6 +231,9 @@ namespace realm::tests::utils { void do_read_from_websocket_server() { auto self(shared_from_this()); + if (m_event_handler) { + m_event_handler(proxy_server::event::websocket); + } if (m_server_uses_ssl) { m_ssl_server_socket.async_read_some(boost::asio::buffer(m_server_buffer), [this, self](boost::system::error_code ec, std::size_t length) { @@ -232,24 +259,32 @@ namespace realm::tests::utils { void do_write_to_websocket_client(std::size_t length) { auto self(shared_from_this()); - boost::asio::async_write(m_client_socket, boost::asio::buffer(m_server_buffer.data(), length), - [this, self](boost::system::error_code ec, std::size_t) { - if (!ec) { - do_read_from_websocket_server(); - } else { - REALM_TERMINATE("Proxy: Error writing from websocket client."); - } - }); - + if (m_event_handler) { + m_event_handler(proxy_server::event::websocket); + } + boost::asio::async_write(m_client_socket, boost::asio::buffer(m_server_buffer.data(), length), + [this, self](boost::system::error_code ec, std::size_t) { + if (!ec) { + do_read_from_websocket_server(); + } else { + REALM_TERMINATE("Proxy: Error writing from websocket client."); + } + }); } void upgrade_client_to_websocket(std::size_t length) { + if (m_event_handler) { + m_event_handler(proxy_server::event::websocket_upgrade); + } do_read_from_websocket_client(); do_read_from_websocket_server(); } void do_write_to_server(std::size_t length) { auto self(shared_from_this()); + if (m_event_handler) { + m_event_handler(proxy_server::event::nonssl); + } boost::asio::async_write(m_server_socket, boost::asio::buffer(std::string(m_client_buffer.data()), length), [this, self](boost::system::error_code ec, std::size_t) { if (!ec) { @@ -262,6 +297,9 @@ namespace realm::tests::utils { void do_read_server() { auto self(shared_from_this()); + if (m_event_handler) { + m_event_handler(proxy_server::event::nonssl); + } m_server_socket.async_read_some(boost::asio::buffer(m_server_buffer), [this, self](boost::system::error_code ec, std::size_t length) { if (!ec) { @@ -274,6 +312,9 @@ namespace realm::tests::utils { void do_write_to_client(std::size_t length) { auto self(shared_from_this()); + if (m_event_handler) { + m_event_handler(proxy_server::event::client); + } auto res = std::string(m_server_buffer.data(), length); bool upgrade_to_websocket = res.find("HTTP/1.1 101 Switching Protocols") != std::string::npos; @@ -309,6 +350,7 @@ namespace realm::tests::utils { std::array m_server_buffer; const std::string server_endpoint = "services.cloud.mongodb.com"; + std::function m_event_handler; }; proxy_server::proxy_server(const config &cfg) : m_config(cfg), m_strand(m_io_context) { @@ -337,7 +379,7 @@ namespace realm::tests::utils { asio::bind_executor(m_strand, [this](std::error_code ec, asio::ip::tcp::socket socket) { if (!ec) { - std::make_shared(std::move(socket), m_ssl_ctx, m_config.server_uses_ssl)->start(); + std::make_shared(std::move(socket), m_ssl_ctx, m_config.server_uses_ssl, std::move(m_event_handler))->start(); } do_accept(); })); diff --git a/tests/utils/networking/proxy_server.hpp b/tests/utils/networking/proxy_server.hpp index 764f6d122..345de9141 100644 --- a/tests/utils/networking/proxy_server.hpp +++ b/tests/utils/networking/proxy_server.hpp @@ -18,6 +18,21 @@ namespace realm::tests::utils { proxy_server(const config &cfg); ~proxy_server(); + + enum event { + connect, + ssl_handshake, + client, + nonssl, + ssl, + websocket_upgrade, + websocket, + }; + + // Sets an event handler callback + void set_callback(std::function fn) { + m_event_handler = std::move(fn); + } private: void do_accept(); boost::asio::io_context m_io_context; @@ -27,6 +42,7 @@ namespace realm::tests::utils { std::mutex m_mutex; std::thread m_io_thread; asio::io_context::strand m_strand; + std::function m_event_handler; }; } From 0768165d5237e54a443c3f4805a113710d738535 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 8 Jul 2024 14:09:07 +0100 Subject: [PATCH 13/41] Add boost to GHA runners --- .github/workflows/cmake.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 9defdb9fc..204747eda 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -62,6 +62,9 @@ jobs: with: xcode-version: ${{ matrix.xcode }} + - name: Install Boost + run: brew install boost + - uses: ammaraskar/gcc-problem-matcher@master - name: Configure @@ -132,7 +135,8 @@ jobs: ninja-build \ sudo \ zlib1g-dev \ - valgrind + valgrind \ + libboost-all-dev - name: Setup Ccache uses: hendrikmuhs/ccache-action@v1.1 @@ -226,8 +230,12 @@ jobs: with: cmake-version: latest + - name: Install Boost + run: | + choco install boost-msvc-14.2 + - name: Configure - run: cmake --preset windows-x64 -DENABLE_STATIC=1 -DREALM_ENABLE_EXPERIMENTAL=1 -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake --preset windows-x64 -DENABLE_STATIC=1 -DREALM_ENABLE_EXPERIMENTAL=1 -DCMAKE_TOOLCHAIN_FILE=%VCPKG_INSTALLATION_ROOT%\scripts\buildsystems\vcpkg.cmake -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Compile run: cmake --build --preset windows-x64 --config ${{ matrix.configuration }} @@ -250,4 +258,4 @@ jobs: with: report_paths: '.build/**/TestResults.xml' annotate_only: true - require_tests: true \ No newline at end of file + require_tests: true From 7c9068806689ee037c02f519769cfd16a1762c8f Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 8 Jul 2024 14:41:17 +0100 Subject: [PATCH 14/41] Dont use boost for asio --- .github/workflows/cmake.yml | 14 +--- tests/CMakeLists.txt | 20 ++++- tests/sync/networking_tests.cpp | 32 ++++---- tests/utils/networking/proxy_server.cpp | 104 ++++++++++++------------ tests/utils/networking/proxy_server.hpp | 8 +- 5 files changed, 93 insertions(+), 85 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 204747eda..9defdb9fc 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -62,9 +62,6 @@ jobs: with: xcode-version: ${{ matrix.xcode }} - - name: Install Boost - run: brew install boost - - uses: ammaraskar/gcc-problem-matcher@master - name: Configure @@ -135,8 +132,7 @@ jobs: ninja-build \ sudo \ zlib1g-dev \ - valgrind \ - libboost-all-dev + valgrind - name: Setup Ccache uses: hendrikmuhs/ccache-action@v1.1 @@ -230,12 +226,8 @@ jobs: with: cmake-version: latest - - name: Install Boost - run: | - choco install boost-msvc-14.2 - - name: Configure - run: cmake --preset windows-x64 -DENABLE_STATIC=1 -DREALM_ENABLE_EXPERIMENTAL=1 -DCMAKE_TOOLCHAIN_FILE=%VCPKG_INSTALLATION_ROOT%\scripts\buildsystems\vcpkg.cmake -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake --preset windows-x64 -DENABLE_STATIC=1 -DREALM_ENABLE_EXPERIMENTAL=1 -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Compile run: cmake --build --preset windows-x64 --config ${{ matrix.configuration }} @@ -258,4 +250,4 @@ jobs: with: report_paths: '.build/**/TestResults.xml' annotate_only: true - require_tests: true + require_tests: true \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e3c8f9c80..1175a04a4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,8 +18,16 @@ endif() if (NOT BUILD_FROM_PACKAGE_MANAGER) # Find Boost - find_package(Boost REQUIRED COMPONENTS system thread) - include_directories(${Boost_INCLUDE_DIRS}) + include(FetchContent) + + # Fetch ASIO + FetchContent_Declare( + asio + GIT_REPOSITORY https://github.com/chriskohlhoff/asio.git + GIT_TAG asio-1-30-2 + ) + FetchContent_MakeAvailable(asio) + # Find OpenSSL find_package(OpenSSL REQUIRED) include_directories(${OPENSSL_INCLUDE_DIR}) @@ -37,6 +45,12 @@ if (NOT BUILD_FROM_PACKAGE_MANAGER) sync/networking_tests.cpp utils/networking/proxy_server.hpp utils/networking/proxy_server.cpp) + + target_include_directories(cpprealm_sync_tests PRIVATE ${asio_SOURCE_DIR}/asio/include) + + if(UNIX) + set(ASIO_deps pthread) + endif() endif() add_executable(cpprealm_db_tests @@ -110,7 +124,7 @@ if (MSVC AND ENABLE_STATIC) endif() if (NOT BUILD_FROM_PACKAGE_MANAGER) - target_link_libraries(cpprealm_sync_tests PUBLIC ${CPPREALM_TARGET} Catch2::Catch2 ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES}) + target_link_libraries(cpprealm_sync_tests PUBLIC ${CPPREALM_TARGET} Catch2::Catch2 ${ASIO_deps} ${OPENSSL_LIBRARIES}) endif() target_link_libraries(cpprealm_db_tests PUBLIC ${CPPREALM_TARGET} Catch2::Catch2) diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp index 0da8c45b1..8778c1880 100644 --- a/tests/sync/networking_tests.cpp +++ b/tests/sync/networking_tests.cpp @@ -24,11 +24,7 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { config.app_id = Admin::Session::shared().cached_app_id(); config.base_url = Admin::Session::shared().base_url(); - bool provider_called, http_transport_called = false; - struct foo_socket_provider : public ::realm::networking::default_socket_provider { - foo_socket_provider(bool& provider_called) : m_called(provider_called) { } - std::unique_ptr<::realm::networking::websocket_interface> connect(std::unique_ptr<::realm::networking::websocket_observer> o, ::realm::networking::websocket_endpoint&& ep) override { const std::string from = "wss:"; @@ -40,14 +36,18 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { return ::realm::networking::default_socket_provider::connect(std::move(o), std::move(ep)); } + bool was_called() const { + return m_called; + } + private: - bool& m_called; + bool m_called = false; }; - config.sync_socket_provider = std::make_shared(provider_called); - struct foo_http_transport : public ::realm::networking::default_http_transport { - foo_http_transport(bool& provider_called) : m_called(provider_called) { } + auto foo_socket = std::make_shared(); + config.sync_socket_provider = foo_socket; + struct foo_http_transport : public ::realm::networking::default_http_transport { void send_request_to_server(const ::realm::networking::request& request, std::function&& completion) override { auto req_copy = request; @@ -60,13 +60,18 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { return ::realm::networking::default_http_transport::send_request_to_server(req_copy, std::move(completion)); } + bool was_called() const { + return m_called; + } + private: - bool& m_called; + bool m_called = false; }; - config.http_transport_client = std::make_shared(http_transport_called); + + auto foo_transport = std::make_shared(); + config.http_transport_client = foo_transport; auto app = realm::App(config); - app.get_sync_manager().set_log_level(logger::level::all); auto user = app.login(realm::App::credentials::anonymous()).get(); auto flx_sync_config = user.flexible_sync_configuration(); @@ -101,8 +106,8 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { bool is_subset = std::includes(expected_events.begin(), expected_events.end(), proxy_events.begin(), proxy_events.end()); CHECK(is_subset); - CHECK(provider_called); - CHECK(http_transport_called); + CHECK(foo_transport->was_called()); + CHECK(foo_socket->was_called()); } TEST_CASE("proxy roundtrip", "[proxy]") { @@ -126,7 +131,6 @@ TEST_CASE("proxy roundtrip", "[proxy]") { config.base_url = Admin::Session::shared().base_url(); auto app = realm::App(config); - app.get_sync_manager().set_log_level(logger::level::all); auto user = app.login(realm::App::credentials::anonymous()).get(); auto flx_sync_config = user.flexible_sync_configuration(); diff --git a/tests/utils/networking/proxy_server.cpp b/tests/utils/networking/proxy_server.cpp index 1781cbf1b..4329fc239 100644 --- a/tests/utils/networking/proxy_server.cpp +++ b/tests/utils/networking/proxy_server.cpp @@ -26,8 +26,8 @@ namespace realm::tests::utils { private: void do_read_client_request() { auto self(shared_from_this()); - m_client_socket.async_read_some(boost::asio::buffer(m_buffer), - [this, self](boost::system::error_code ec, std::size_t length) { + m_client_socket.async_read_some(asio::buffer(m_buffer), + [this, self](auto ec, std::size_t length) { if (!ec) { std::string request(m_buffer.data(), length); if (request.substr(0, 7) == "CONNECT") { @@ -53,12 +53,12 @@ namespace realm::tests::utils { } if (host == "127.0.0.1" || host == "localhost") { - tcp::endpoint endpoint(boost::asio::ip::make_address("127.0.0.1"), std::stoi(port)); + tcp::endpoint endpoint(asio::ip::make_address("127.0.0.1"), std::stoi(port)); connect_to_server(endpoint, host); } else if (host.find(server_endpoint) != std::string::npos) { auto self(shared_from_this()); m_resolver.async_resolve(host, port, - [this, self, host](boost::system::error_code ec, tcp::resolver::results_type results) { + [this, self, host](auto ec, tcp::resolver::results_type results) { if (!ec) { connect_to_server(*results.begin(), host); } else { @@ -67,7 +67,7 @@ namespace realm::tests::utils { }); } else { // BAASAAS endpoint - tcp::endpoint endpoint(boost::asio::ip::make_address(host), std::stoi(port)); + tcp::endpoint endpoint(asio::ip::make_address(host), std::stoi(port)); connect_to_server(endpoint, host); } } @@ -77,7 +77,7 @@ namespace realm::tests::utils { // Handle SSL connection auto self(shared_from_this()); m_ssl_server_socket.lowest_layer().async_connect(endpoint, - [this, self, host](boost::system::error_code ec) { + [this, self, host](auto ec) { if (!ec) { do_ssl_handshake_to_server(host); } else { @@ -88,7 +88,7 @@ namespace realm::tests::utils { // Handle non-SSL connection auto self(shared_from_this()); m_server_socket.async_connect(endpoint, - [this, self](boost::system::error_code ec) { + [this, self](auto ec) { if (!ec) { do_write_connect_response(); } else { @@ -106,7 +106,7 @@ namespace realm::tests::utils { } m_ssl_server_socket.set_verify_callback(asio::ssl::host_name_verification(hostname)); - m_ssl_server_socket.set_verify_mode(boost::asio::ssl::verify_peer); + m_ssl_server_socket.set_verify_mode(asio::ssl::verify_peer); // Set SNI Hostname (many hosts need this to handshake successfully) if (!SSL_set_tlsext_host_name(m_ssl_server_socket.native_handle(), hostname.c_str())) { @@ -114,7 +114,7 @@ namespace realm::tests::utils { } m_ssl_server_socket.async_handshake(asio::ssl::stream_base::client, - [this, self](boost::system::error_code ec) { + [this, self](auto ec) { if (!ec) { do_write_connect_response(); } else { @@ -126,7 +126,7 @@ namespace realm::tests::utils { void do_write_connect_response() { auto self(shared_from_this()); asio::async_write(m_client_socket, asio::buffer(std::string("HTTP/1.1 200 Connection Established\r\n\r\n")), - [this, self](boost::system::error_code ec, std::size_t) { + [this, self](auto ec, std::size_t) { if (!ec) { do_read_client(); } else { @@ -140,14 +140,14 @@ namespace realm::tests::utils { if (m_event_handler) { m_event_handler(proxy_server::event::ssl); } - boost::asio::async_write(m_ssl_server_socket, boost::asio::buffer(std::string(m_client_buffer.data()), length), - [this, self](boost::system::error_code ec, std::size_t) { - if (!ec) { - do_ssl_read_server(); - } else { - REALM_TERMINATE("Proxy: Error writing to server."); - } - }); + asio::async_write(m_ssl_server_socket, asio::buffer(std::string(m_client_buffer.data()), length), + [this, self](auto ec, std::size_t) { + if (!ec) { + do_ssl_read_server(); + } else { + REALM_TERMINATE("Proxy: Error writing to server."); + } + }); } void do_ssl_read_server() { @@ -155,8 +155,8 @@ namespace realm::tests::utils { if (m_event_handler) { m_event_handler(proxy_server::event::ssl); } - m_ssl_server_socket.async_read_some(boost::asio::buffer(m_server_buffer), - [this, self](boost::system::error_code ec, std::size_t length) { + m_ssl_server_socket.async_read_some(asio::buffer(m_server_buffer), + [this, self](auto ec, std::size_t length) { if (!ec) { do_write_to_client(length); } else { @@ -170,8 +170,8 @@ namespace realm::tests::utils { if (m_event_handler) { m_event_handler(proxy_server::event::client); } - m_client_socket.async_read_some(boost::asio::buffer(m_client_buffer), - [this, self](boost::system::error_code ec, std::size_t length) { + m_client_socket.async_read_some(asio::buffer(m_client_buffer), + [this, self](auto ec, std::size_t length) { if (!ec) { auto req = std::string(m_client_buffer.data(), length); std::cout << "Client to Server: " << req << std::endl; @@ -191,8 +191,8 @@ namespace realm::tests::utils { if (m_event_handler) { m_event_handler(proxy_server::event::websocket); } - m_client_socket.async_read_some(boost::asio::buffer(m_client_buffer), - [this, self](boost::system::error_code ec, std::size_t length) { + m_client_socket.async_read_some(asio::buffer(m_client_buffer), + [this, self](auto ec, std::size_t length) { if (!ec) { do_write_to_websocket_server(length); do_read_from_websocket_client(); @@ -209,8 +209,8 @@ namespace realm::tests::utils { m_event_handler(proxy_server::event::websocket); } if (m_server_uses_ssl) { - boost::asio::async_write(m_ssl_server_socket, boost::asio::buffer(m_client_buffer.data(), length), - [this, self](boost::system::error_code ec, std::size_t) { + asio::async_write(m_ssl_server_socket, asio::buffer(m_client_buffer.data(), length), + [this, self](auto ec, std::size_t) { if (!ec) { do_read_from_websocket_client(); } else { @@ -218,8 +218,8 @@ namespace realm::tests::utils { } }); } else { - boost::asio::async_write(m_server_socket, boost::asio::buffer(m_client_buffer.data(), length), - [this, self](boost::system::error_code ec, std::size_t /*length*/) { + asio::async_write(m_server_socket, asio::buffer(m_client_buffer.data(), length), + [this, self](auto ec, std::size_t /*length*/) { if (!ec) { do_read_from_websocket_client(); } else { @@ -235,8 +235,8 @@ namespace realm::tests::utils { m_event_handler(proxy_server::event::websocket); } if (m_server_uses_ssl) { - m_ssl_server_socket.async_read_some(boost::asio::buffer(m_server_buffer), - [this, self](boost::system::error_code ec, std::size_t length) { + m_ssl_server_socket.async_read_some(asio::buffer(m_server_buffer), + [this, self](auto ec, std::size_t length) { if (!ec) { do_write_to_websocket_client(length); do_read_from_websocket_server(); @@ -245,8 +245,8 @@ namespace realm::tests::utils { } }); } else { - m_server_socket.async_read_some(boost::asio::buffer(m_server_buffer), - [this, self](boost::system::error_code ec, std::size_t length) { + m_server_socket.async_read_some(asio::buffer(m_server_buffer), + [this, self](auto ec, std::size_t length) { if (!ec) { do_write_to_websocket_client(length); do_read_from_websocket_server(); @@ -262,8 +262,8 @@ namespace realm::tests::utils { if (m_event_handler) { m_event_handler(proxy_server::event::websocket); } - boost::asio::async_write(m_client_socket, boost::asio::buffer(m_server_buffer.data(), length), - [this, self](boost::system::error_code ec, std::size_t) { + asio::async_write(m_client_socket, asio::buffer(m_server_buffer.data(), length), + [this, self](auto ec, std::size_t) { if (!ec) { do_read_from_websocket_server(); } else { @@ -285,8 +285,8 @@ namespace realm::tests::utils { if (m_event_handler) { m_event_handler(proxy_server::event::nonssl); } - boost::asio::async_write(m_server_socket, boost::asio::buffer(std::string(m_client_buffer.data()), length), - [this, self](boost::system::error_code ec, std::size_t) { + asio::async_write(m_server_socket, asio::buffer(std::string(m_client_buffer.data()), length), + [this, self](auto ec, std::size_t) { if (!ec) { do_read_server(); } else { @@ -300,8 +300,8 @@ namespace realm::tests::utils { if (m_event_handler) { m_event_handler(proxy_server::event::nonssl); } - m_server_socket.async_read_some(boost::asio::buffer(m_server_buffer), - [this, self](boost::system::error_code ec, std::size_t length) { + m_server_socket.async_read_some(asio::buffer(m_server_buffer), + [this, self](auto ec, std::size_t length) { if (!ec) { do_write_to_client(length); } else { @@ -318,22 +318,22 @@ namespace realm::tests::utils { auto res = std::string(m_server_buffer.data(), length); bool upgrade_to_websocket = res.find("HTTP/1.1 101 Switching Protocols") != std::string::npos; - boost::asio::async_write(m_client_socket, boost::asio::buffer(std::string(m_server_buffer.data()), length), - [this, self, upgrade_to_websocket](boost::system::error_code ec, std::size_t bytes_written) { - if (!ec) { - if (upgrade_to_websocket) { - upgrade_client_to_websocket(bytes_written); - } else { - if (m_server_uses_ssl) { - do_ssl_read_server(); - } else { - do_read_server(); - } - } + asio::async_write(m_client_socket, asio::buffer(std::string(m_server_buffer.data()), length), + [this, self, upgrade_to_websocket](auto ec, std::size_t bytes_written) { + if (!ec) { + if (upgrade_to_websocket) { + upgrade_client_to_websocket(bytes_written); + } else { + if (m_server_uses_ssl) { + do_ssl_read_server(); } else { - REALM_TERMINATE("Proxy: Error writing to client."); + do_read_server(); } - }); + } + } else { + REALM_TERMINATE("Proxy: Error writing to client."); + } + }); } bool m_server_uses_ssl = false; diff --git a/tests/utils/networking/proxy_server.hpp b/tests/utils/networking/proxy_server.hpp index 345de9141..54a071046 100644 --- a/tests/utils/networking/proxy_server.hpp +++ b/tests/utils/networking/proxy_server.hpp @@ -1,11 +1,9 @@ #ifndef CPPREALM_PROXY_SERVER_HPP #define CPPREALM_PROXY_SERVER_HPP -#include -#include -#include +#include +#include -namespace asio = boost::asio; using tcp = asio::ip::tcp; namespace realm::tests::utils { @@ -35,7 +33,7 @@ namespace realm::tests::utils { } private: void do_accept(); - boost::asio::io_context m_io_context; + asio::io_context m_io_context; std::unique_ptr m_acceptor; std::shared_ptr m_ssl_ctx; config m_config; From 621bb1c6355128d9276ac6c6c9243e3744e4c52b Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 8 Jul 2024 15:19:20 +0100 Subject: [PATCH 15/41] Fix tests --- .../internal/apple/network_transport.mm | 28 ++++++++----------- tests/sync/networking_tests.cpp | 10 +++++-- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/cpprealm/internal/apple/network_transport.mm b/src/cpprealm/internal/apple/network_transport.mm index 436f67950..f99be6590 100644 --- a/src/cpprealm/internal/apple/network_transport.mm +++ b/src/cpprealm/internal/apple/network_transport.mm @@ -17,7 +17,7 @@ //////////////////////////////////////////////////////////////////////////// #include -#include +#include #include #include @@ -28,34 +28,28 @@ #include #include -namespace realm::internal { +namespace realm::networking { - DefaultTransport::DefaultTransport(const std::optional>& custom_http_headers, - const std::optional& proxy_config) { - m_custom_http_headers = custom_http_headers; - m_proxy_config = proxy_config; - } - - void DefaultTransport::send_request_to_server(const app::Request& request, - std::function&& completion_block) { + void default_http_transport::send_request_to_server(const request& request, + std::function&& completion_block) { NSURL* url = [NSURL URLWithString:[NSString stringWithCString:request.url.c_str() encoding:NSUTF8StringEncoding]]; NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:url]; switch (request.method) { - case app::HttpMethod::get: + case http_method::get: [urlRequest setHTTPMethod:@"GET"]; break; - case app::HttpMethod::post: + case http_method::post: [urlRequest setHTTPMethod:@"POST"]; break; - case app::HttpMethod::put: + case http_method::put: [urlRequest setHTTPMethod:@"PUT"]; break; - case app::HttpMethod::patch: + case http_method::patch: [urlRequest setHTTPMethod:@"PATCH"]; break; - case app::HttpMethod::del: + case http_method::del: [urlRequest setHTTPMethod:@"DELETE"]; break; } @@ -70,7 +64,7 @@ forHTTPHeaderField:[NSString stringWithCString:header.first.c_str() encoding:NSUTF8StringEncoding]]; } } - if (request.method != app::HttpMethod::get && !request.body.empty()) { + if (request.method != http_method::get && !request.body.empty()) { [urlRequest setHTTPBody:[[NSString stringWithCString:request.body.c_str() encoding:NSUTF8StringEncoding] dataUsingEncoding:NSUTF8StringEncoding]]; } @@ -117,7 +111,7 @@ status_code = static_cast(httpResponse.statusCode); } - completion(app::Response { + completion({ .http_status_code=status_code, .body=body }); diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp index 8778c1880..58b468b78 100644 --- a/tests/sync/networking_tests.cpp +++ b/tests/sync/networking_tests.cpp @@ -1,7 +1,11 @@ #include "../admin_utils.hpp" #include "../main.hpp" #include "test_objects.hpp" + #include "../utils/networking/proxy_server.hpp" + +#include + using namespace realm; TEST_CASE("sends plaintext data to proxy", "[proxy]") { @@ -33,6 +37,7 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { ep.url.replace(0, from.length(), to); } m_called = true; + std::cout << "foo_socket_provider: called" << "\n"; return ::realm::networking::default_socket_provider::connect(std::move(o), std::move(ep)); } @@ -57,6 +62,7 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { req_copy.url.replace(0, from.length(), to); } m_called = true; + std::cout << "foo_http_transport: called" << "\n"; return ::realm::networking::default_http_transport::send_request_to_server(req_copy, std::move(completion)); } @@ -106,8 +112,8 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { bool is_subset = std::includes(expected_events.begin(), expected_events.end(), proxy_events.begin(), proxy_events.end()); CHECK(is_subset); - CHECK(foo_transport->was_called()); - CHECK(foo_socket->was_called()); +// CHECK(foo_transport->was_called()); +// CHECK(foo_socket->was_called()); } TEST_CASE("proxy roundtrip", "[proxy]") { From 05077748a8ce106e465e0f999f2b24e045b46e99 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 8 Jul 2024 15:59:17 +0100 Subject: [PATCH 16/41] Fix tests --- src/cpprealm/networking/platform_networking.cpp | 2 ++ tests/sync/networking_tests.cpp | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/platform_networking.cpp index fc18525b1..d96356e71 100644 --- a/src/cpprealm/networking/platform_networking.cpp +++ b/src/cpprealm/networking/platform_networking.cpp @@ -5,6 +5,8 @@ #include #include +#include + namespace realm::networking { std::shared_ptr make_default_http_client() { diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp index 58b468b78..6434c44c5 100644 --- a/tests/sync/networking_tests.cpp +++ b/tests/sync/networking_tests.cpp @@ -28,6 +28,8 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { config.app_id = Admin::Session::shared().cached_app_id(); config.base_url = Admin::Session::shared().base_url(); + std::cout << "should call foo_socket_provider" << "\n"; + struct foo_socket_provider : public ::realm::networking::default_socket_provider { std::unique_ptr<::realm::networking::websocket_interface> connect(std::unique_ptr<::realm::networking::websocket_observer> o, ::realm::networking::websocket_endpoint&& ep) override { @@ -51,6 +53,7 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { auto foo_socket = std::make_shared(); config.sync_socket_provider = foo_socket; + std::cout << "should call foo_http_transport" << "\n"; struct foo_http_transport : public ::realm::networking::default_http_transport { void send_request_to_server(const ::realm::networking::request& request, From b4c3031328ecd7e773d8123e20c8db3957055acf Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 8 Jul 2024 16:00:19 +0100 Subject: [PATCH 17/41] Fix tests --- tests/sync/networking_tests.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp index 6434c44c5..acf2e671c 100644 --- a/tests/sync/networking_tests.cpp +++ b/tests/sync/networking_tests.cpp @@ -115,6 +115,9 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { bool is_subset = std::includes(expected_events.begin(), expected_events.end(), proxy_events.begin(), proxy_events.end()); CHECK(is_subset); + std::cout << "foo_http_transport: called" << foo_transport->was_called() <<"\n"; + std::cout << "foo_socket: called" << foo_socket->was_called() <<"\n"; + // CHECK(foo_transport->was_called()); // CHECK(foo_socket->was_called()); } From e28f6136de79693cbfe1d5479ebb8a07d1d71560 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 8 Jul 2024 16:57:30 +0100 Subject: [PATCH 18/41] Fix tests --- include/cpprealm/app.hpp | 8 +++++--- .../networking/platform_networking.hpp | 2 ++ src/cpprealm/app.cpp | 3 ++- .../networking/platform_networking.cpp | 2 -- tests/sync/networking_tests.cpp | 19 +++++-------------- 5 files changed, 14 insertions(+), 20 deletions(-) diff --git a/include/cpprealm/app.hpp b/include/cpprealm/app.hpp index 3b09aaa47..95fbaa2b5 100644 --- a/include/cpprealm/app.hpp +++ b/include/cpprealm/app.hpp @@ -240,11 +240,13 @@ class App { std::optional base_url; /// Custom location for Realm files. std::optional path; - // Extra HTTP headers to be set on each request to Atlas Device Sync when using the internal HTTP client. + /// Extra HTTP headers to be set on each request to Atlas Device Sync when using the internal HTTP client. std::optional> custom_http_headers; - // Custom encryption key for the metadata Realm. + /// Custom encryption key for the metadata Realm. std::optional> metadata_encryption_key; - // Network proxy configuration to be set on each HTTP and WebSocket request. + /// Caches an App and its configuration for a given App ID. On by default. + bool enable_caching = true; + /// Network proxy configuration to be set on each HTTP and WebSocket request. std::optional proxy_configuration; /** * Optionally provide a custom HTTP transport for network calls to the server. diff --git a/include/cpprealm/networking/platform_networking.hpp b/include/cpprealm/networking/platform_networking.hpp index c7edbc258..8fd6f79f9 100644 --- a/include/cpprealm/networking/platform_networking.hpp +++ b/include/cpprealm/networking/platform_networking.hpp @@ -26,6 +26,8 @@ #include #endif +#include + namespace realm { namespace app { struct GenericNetworkTransport; diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index 27bfdc608..27567d6f3 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -537,7 +537,8 @@ namespace realm { app_config.device_info = std::move(device_info); app_config.sync_client_config = client_config; - m_app = app::App::get_app(app::App::CacheMode::Enabled, std::move(app_config)); + m_app = app::App::get_app(config.enable_caching ? app::App::CacheMode::Enabled : app::App::CacheMode::Disabled, + std::move(app_config)); } App::App(const std::string &app_id, diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/platform_networking.cpp index d96356e71..fc18525b1 100644 --- a/src/cpprealm/networking/platform_networking.cpp +++ b/src/cpprealm/networking/platform_networking.cpp @@ -5,8 +5,6 @@ #include #include -#include - namespace realm::networking { std::shared_ptr make_default_http_client() { diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp index acf2e671c..3b506b329 100644 --- a/tests/sync/networking_tests.cpp +++ b/tests/sync/networking_tests.cpp @@ -4,8 +4,6 @@ #include "../utils/networking/proxy_server.hpp" -#include - using namespace realm; TEST_CASE("sends plaintext data to proxy", "[proxy]") { @@ -27,9 +25,8 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { config.proxy_configuration = pc; config.app_id = Admin::Session::shared().cached_app_id(); config.base_url = Admin::Session::shared().base_url(); - - std::cout << "should call foo_socket_provider" << "\n"; - + config.enable_caching = false; + struct foo_socket_provider : public ::realm::networking::default_socket_provider { std::unique_ptr<::realm::networking::websocket_interface> connect(std::unique_ptr<::realm::networking::websocket_observer> o, ::realm::networking::websocket_endpoint&& ep) override { @@ -39,7 +36,6 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { ep.url.replace(0, from.length(), to); } m_called = true; - std::cout << "foo_socket_provider: called" << "\n"; return ::realm::networking::default_socket_provider::connect(std::move(o), std::move(ep)); } @@ -53,7 +49,6 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { auto foo_socket = std::make_shared(); config.sync_socket_provider = foo_socket; - std::cout << "should call foo_http_transport" << "\n"; struct foo_http_transport : public ::realm::networking::default_http_transport { void send_request_to_server(const ::realm::networking::request& request, @@ -65,7 +60,6 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { req_copy.url.replace(0, from.length(), to); } m_called = true; - std::cout << "foo_http_transport: called" << "\n"; return ::realm::networking::default_http_transport::send_request_to_server(req_copy, std::move(completion)); } @@ -115,11 +109,8 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { bool is_subset = std::includes(expected_events.begin(), expected_events.end(), proxy_events.begin(), proxy_events.end()); CHECK(is_subset); - std::cout << "foo_http_transport: called" << foo_transport->was_called() <<"\n"; - std::cout << "foo_socket: called" << foo_socket->was_called() <<"\n"; - -// CHECK(foo_transport->was_called()); -// CHECK(foo_socket->was_called()); + CHECK(foo_transport->was_called()); + CHECK(foo_socket->was_called()); } TEST_CASE("proxy roundtrip", "[proxy]") { @@ -177,4 +168,4 @@ TEST_CASE("proxy roundtrip", "[proxy]") { bool is_subset = std::includes(expected_events.begin(), expected_events.end(), proxy_events.begin(), proxy_events.end()); CHECK(is_subset); -} \ No newline at end of file +} From ba0320eaaea2c21c2fff381385dd5ec95f215963 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 8 Jul 2024 17:24:44 +0100 Subject: [PATCH 19/41] Fix tests --- src/cpprealm/networking/platform_networking.cpp | 2 +- tests/sync/networking_tests.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/platform_networking.cpp index fc18525b1..b984da480 100644 --- a/src/cpprealm/networking/platform_networking.cpp +++ b/src/cpprealm/networking/platform_networking.cpp @@ -138,7 +138,7 @@ namespace realm::internal::networking { }; return std::make_shared(http_client); - }; + } std::unique_ptr<::realm::networking::websocket_observer> create_websocket_observer_from_core_shim(std::unique_ptr<::realm::sync::WebSocketObserver>&& m_observer) { struct core_websocket_observer_shim : public ::realm::networking::websocket_observer { diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp index 3b506b329..4b1df8ac6 100644 --- a/tests/sync/networking_tests.cpp +++ b/tests/sync/networking_tests.cpp @@ -132,6 +132,7 @@ TEST_CASE("proxy roundtrip", "[proxy]") { config.proxy_configuration = pc; config.app_id = Admin::Session::shared().cached_app_id(); config.base_url = Admin::Session::shared().base_url(); + config.enable_caching = false; auto app = realm::App(config); From a9d57ea11d6df3f253e0b3b04f218050ca02a143 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Wed, 10 Jul 2024 10:53:01 +0100 Subject: [PATCH 20/41] Begin cleanup --- CHANGELOG.md | 14 ++ include/cpprealm/app.hpp | 24 ++- .../cpprealm/internal/networking/shims.hpp | 41 +++++ include/cpprealm/networking/networking.hpp | 21 +-- .../networking/platform_networking.hpp | 43 +++-- src/CMakeLists.txt | 2 + src/cpprealm/app.cpp | 15 +- .../internal/network/network_transport.cpp | 21 +-- src/cpprealm/internal/networking/shims.cpp | 131 ++++++++++++++++ src/cpprealm/networking/networking.cpp | 24 +-- .../networking/platform_networking.cpp | 148 ++---------------- tests/sync/client_reset_tests.cpp | 16 +- 12 files changed, 284 insertions(+), 216 deletions(-) create mode 100644 include/cpprealm/internal/networking/shims.hpp create mode 100644 src/cpprealm/internal/networking/shims.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 41dc2be78..0e195bc97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,20 @@ NEXT-RELEASE Release notes (YYYY-MM-DD) Supported operators are `==`, `!=`, `>`, `<`, `>=`, `<=` and `contains(const std::string&)`. * Add `managed>::contains_key` for conveniently checking if a managed map contains a given key. Use this method in the Type Safe Query API instead of `managed>::find`. +* Add `realm::networking` namespace which contains interfaces for providing your own custom network transport + implementations. The following interfaces are exposed: + * `websocket_interface` + * `websocket_observer` + * `sync_socket_provider` + * `sync_socket_provider::timer` + * `http_transport_client` +* Add `default_http_transport` for built-in HTTP transport. +* Add `default_socket_provider` a built-in class for providing the components necessary for transport via WebSocket. +* A custom WebSocket & HTTP transport implementation can be used by passing + the instance via `realm::app::App::configuration.http_transport_client` & `realm::app::App::configuration.sync_socket_provider`. +* Proxy and custom header configuration for built-in transport must be supplied via the constructor on + `realm::networking::default_http_transport` & `realm::networking::default_socket_provider` using the + `realm::networking::default_transport_configuration` struct. ### Compatibility * Fileformat: Generates files with format v24. Reads and automatically upgrade from fileformat v10. diff --git a/include/cpprealm/app.hpp b/include/cpprealm/app.hpp index 95fbaa2b5..9e0d6189c 100644 --- a/include/cpprealm/app.hpp +++ b/include/cpprealm/app.hpp @@ -240,13 +240,29 @@ class App { std::optional base_url; /// Custom location for Realm files. std::optional path; - /// Extra HTTP headers to be set on each request to Atlas Device Sync when using the internal HTTP client. + /** + * Extra HTTP headers to be set on each request to Atlas Device Sync when using the internal HTTP client. + * + * Note: This has been deprecated and custom network options must now be supplied by either: + * + * - Your own subclass of `realm::networking::http_transport_client` / `realm::networking::sync_socket_provider` + * - Use default_http_transport and pass custom headers & proxy via the constructor. Set via `configuration.http_transport_client`. + */ + [[deprecated("Network options must be supplied via custom network implementations.")]] std::optional> custom_http_headers; /// Custom encryption key for the metadata Realm. std::optional> metadata_encryption_key; /// Caches an App and its configuration for a given App ID. On by default. bool enable_caching = true; - /// Network proxy configuration to be set on each HTTP and WebSocket request. + /** + * Network proxy configuration to be set on each HTTP and WebSocket request. + * + * Note: This has been deprecated and custom network options must now be supplied by either: + * + * - Your own subclass of `realm::networking::http_transport_client` / `realm::networking::sync_socket_provider` + * - Use default_http_transport and pass custom headers & proxy via the constructor. Set via `configuration.http_transport_client`. + */ + [[deprecated("Network options must be supplied via custom network implementations.")]] std::optional proxy_configuration; /** * Optionally provide a custom HTTP transport for network calls to the server. @@ -254,11 +270,11 @@ class App { * Alternatively use `realm::networking::set_http_client_factory` to globally set * the default HTTP transport client. */ - std::shared_ptr<::realm::networking::http_transport_client> http_transport_client; + std::shared_ptr http_transport_client; /** * Optionally provide a custom WebSocket interface for sync. */ - std::shared_ptr<::realm::networking::sync_socket_provider> sync_socket_provider; + std::shared_ptr sync_socket_provider; }; diff --git a/include/cpprealm/internal/networking/shims.hpp b/include/cpprealm/internal/networking/shims.hpp new file mode 100644 index 000000000..6b0dc976f --- /dev/null +++ b/include/cpprealm/internal/networking/shims.hpp @@ -0,0 +1,41 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef CPPREALM_SHIMS_HPP +#define CPPREALM_SHIMS_HPP + +#include + +namespace realm { + namespace sync { + class SyncSocketProvider; + } + namespace app { + struct GenericNetworkTransport; + } +} + +namespace realm::internal::networking { + + std::shared_ptr create_http_client_shim(const std::shared_ptr<::realm::networking::http_transport_client>&); + + std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider); + +} + +#endif //CPPREALM_SHIMS_HPP diff --git a/include/cpprealm/networking/networking.hpp b/include/cpprealm/networking/networking.hpp index 024d38d1d..31b3d14c7 100644 --- a/include/cpprealm/networking/networking.hpp +++ b/include/cpprealm/networking/networking.hpp @@ -99,8 +99,6 @@ namespace realm::networking { std::vector protocols; /// The websocket url to connect to. std::string url; - /// Optional proxy config taken in from `realm::App::config`. - std::optional proxy_configuration; }; enum websocket_err_codes { @@ -138,18 +136,6 @@ namespace realm::networking { virtual ~http_transport_client() = default; virtual void send_request_to_server(const request& request, std::function&& completion) = 0; - - void set_proxy_configuration(::realm::internal::bridge::realm::sync_config::proxy_config proxy_config) { - m_proxy_config = proxy_config; - }; - - void set_custom_http_headers(std::map http_headers) { - m_custom_http_headers = http_headers; - }; - - protected: - std::optional> m_custom_http_headers; - std::optional<::realm::internal::bridge::realm::sync_config::proxy_config> m_proxy_config; }; } //namespace realm::networking @@ -171,9 +157,10 @@ namespace realm::internal::networking { ::realm::networking::response to_response(const ::realm::app::Response&); ::realm::app::Response to_core_response(const ::realm::networking::response&); - ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint & ep); - ::realm::networking::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& ep, - std::optional<::realm::internal::bridge::realm::sync_config::proxy_config> pc); + ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint& ep, + const std::optional& pc, + const std::optional>& custom_headers); + ::realm::networking::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& ep); } //namespace realm::internal::networking #endif//CPPREALM_NETWORKING_HPP diff --git a/include/cpprealm/networking/platform_networking.hpp b/include/cpprealm/networking/platform_networking.hpp index 8fd6f79f9..bb29ba4c6 100644 --- a/include/cpprealm/networking/platform_networking.hpp +++ b/include/cpprealm/networking/platform_networking.hpp @@ -29,12 +29,11 @@ #include namespace realm { - namespace app { - struct GenericNetworkTransport; - } + namespace sync { class SyncSocketProvider; struct WebSocketInterface; + struct WebSocketObserver; } namespace util { @@ -43,6 +42,8 @@ namespace realm { } namespace realm::networking { + + using status = internal::bridge::status; /// The WebSocket base class that is used by the SyncClient to send data over the /// WebSocket connection with the server. This is the class that is returned by @@ -54,8 +55,8 @@ namespace realm::networking { /// is destroyed virtual ~websocket_interface() = default; - using status = internal::bridge::status; - using FunctionHandler = std::function; + using status = status; + using FunctionHandler = std::function; /// Write data asynchronously to the WebSocket connection. The handler function /// will be called when the data has been sent successfully. The web_socket_oberver @@ -146,7 +147,7 @@ namespace realm::networking { class sync_socket_provider { public: /// Function handler typedef - using FunctionHandler = std::function; + using FunctionHandler = std::function; /// The timer object used to track a timer that was started on the event /// loop. @@ -228,44 +229,40 @@ namespace realm::networking { /// Globally overwrites the default http transport client factory void set_http_client_factory(std::function()>&&); + struct default_transport_configuraton { + std::optional> custom_http_headers; + std::optional<::realm::internal::bridge::realm::sync_config::proxy_config> proxy_config; + }; + /// Built in HTTP transport client. - struct default_http_transport : public ::realm::networking::http_transport_client { + struct default_http_transport : public http_transport_client { default_http_transport() = default; + default_http_transport(const default_transport_configuraton& c) : m_configuration(c) {} + ~default_http_transport() = default; void send_request_to_server(const ::realm::networking::request& request, std::function&& completion); - }; - /// Built in websocket client. - struct default_socket : public websocket_interface { - default_socket(std::unique_ptr<::realm::sync::WebSocketInterface>&&); - ~default_socket() = default; - - void async_write_binary(std::string_view data, websocket_interface::FunctionHandler&& handler) override; - private: - std::shared_ptr<::realm::sync::WebSocketInterface> m_ws_interface; + protected: + default_transport_configuraton m_configuration; }; /// Built in websocket provider struct default_socket_provider : public sync_socket_provider { default_socket_provider(); + default_socket_provider(const default_transport_configuraton& c) : m_configuration(c) {} ~default_socket_provider() = default; std::unique_ptr connect(std::unique_ptr, websocket_endpoint &&) override; void post(FunctionHandler&&) override; sync_timer create_timer(std::chrono::milliseconds delay, FunctionHandler&&) override; + protected: + default_transport_configuraton m_configuration; private: std::shared_ptr<::realm::sync::SyncSocketProvider> m_provider; }; } -namespace realm::internal::networking { - std::shared_ptr create_http_client_shim(const std::shared_ptr<::realm::networking::http_transport_client>&); - - std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider, - std::optional proxy); -} - #endif//CPPREALM_PLATFORM_NETWORKING_HPP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c0ded2c85..7c911d733 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -51,6 +51,7 @@ set(SOURCES cpprealm/internal/bridge/thread_safe_reference.cpp cpprealm/internal/bridge/timestamp.cpp cpprealm/internal/bridge/uuid.cpp + cpprealm/internal/networking/shims.cpp cpprealm/internal/scheduler/realm_core_scheduler.cpp cpprealm/networking/networking.cpp cpprealm/networking/platform_networking.cpp @@ -112,6 +113,7 @@ set(HEADERS ../include/cpprealm/internal/bridge/utils.hpp ../include/cpprealm/internal/bridge/uuid.hpp ../include/cpprealm/internal/type_info.hpp + ../include/cpprealm/internal/networking/shims.hpp ../include/cpprealm/internal/scheduler/realm_core_scheduler.hpp ../include/cpprealm/schedulers/default_scheduler.hpp ../include/cpprealm/networking/networking.hpp diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index 27567d6f3..2c9231ae1 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #ifndef REALMCXX_VERSION_MAJOR #include @@ -497,27 +498,17 @@ namespace realm { // Sync socket provider configuration if (config.sync_socket_provider) { - client_config.socket_provider = ::realm::internal::networking::create_sync_socket_provider_shim(config.sync_socket_provider, - config.proxy_configuration); - } else { - client_config.socket_provider = ::realm::internal::networking::create_sync_socket_provider_shim(std::make_shared(), - config.proxy_configuration); + client_config.socket_provider = ::realm::internal::networking::create_sync_socket_provider_shim(config.sync_socket_provider); } - std::shared_ptr http_client; // HTTP Transport configuration + std::shared_ptr http_client; if (config.http_transport_client) { http_client = config.http_transport_client; } else { http_client = networking::make_http_client(); } - if (config.proxy_configuration) { - http_client->set_proxy_configuration(*config.proxy_configuration); - } - if (config.custom_http_headers) { - http_client->set_custom_http_headers(*config.custom_http_headers); - } app_config.transport = internal::networking::create_http_client_shim(http_client); app_config.metadata_mode = should_encrypt ? app::AppConfig::MetadataMode::Encryption : app::AppConfig::MetadataMode::NoEncryption; diff --git a/src/cpprealm/internal/network/network_transport.cpp b/src/cpprealm/internal/network/network_transport.cpp index a0bba685f..00305c407 100644 --- a/src/cpprealm/internal/network/network_transport.cpp +++ b/src/cpprealm/internal/network/network_transport.cpp @@ -33,6 +33,7 @@ #include namespace realm::networking { + struct DefaultSocket : realm::sync::network::Socket { DefaultSocket(realm::sync::network::Service& service) : realm::sync::network::Socket(service) @@ -145,13 +146,13 @@ namespace realm::networking { try { auto resolver = realm::sync::network::Resolver{service}; - if (m_proxy_config) { + if (m_configuration.proxy_config) { std::string proxy_userinfo, proxy_host, proxy_port; - const auto proxy_uri = realm::util::Uri(m_proxy_config->address); + const auto proxy_uri = realm::util::Uri(m_configuration.proxy_config->address); proxy_uri.get_auth(proxy_userinfo, proxy_host, proxy_port); if (proxy_host.empty()) { std::error_code e; - auto address = realm::sync::network::make_address(m_proxy_config->address, e); + auto address = realm::sync::network::make_address(m_configuration.proxy_config->address, e); if (e.value() > 0) { ::realm::networking::response response; response.custom_status_code = e.value(); @@ -159,9 +160,9 @@ namespace realm::networking { completion_block(std::move(response)); return; } - ep = realm::sync::network::Endpoint(address, m_proxy_config->port); + ep = realm::sync::network::Endpoint(address, m_configuration.proxy_config->port); } else { - auto resolved = resolver.resolve(sync::network::Resolver::Query(proxy_host, std::to_string(m_proxy_config->port))); + auto resolved = resolver.resolve(sync::network::Resolver::Query(proxy_host, std::to_string(m_configuration.proxy_config->port))); ep = *resolved.begin(); } } else { @@ -184,13 +185,13 @@ namespace realm::networking { auto logger = util::Logger::get_default_logger(); - if (m_proxy_config) { + if (m_configuration.proxy_config) { realm::sync::HTTPRequest req; req.method = realm::sync::HTTPMethod::Connect; req.headers.emplace("Host", util::format("%1:%2", host, port)); - if (m_proxy_config->username_password) { - auto userpass = util::format("%1:%2", m_proxy_config->username_password->first, m_proxy_config->username_password->second); + if (m_configuration.proxy_config->username_password) { + auto userpass = util::format("%1:%2", m_configuration.proxy_config->username_password->first, m_configuration.proxy_config->username_password->second); std::string encoded_userpass; encoded_userpass.resize(realm::util::base64_encoded_size(userpass.length())); realm::util::base64_encode(userpass, encoded_userpass); @@ -243,8 +244,8 @@ namespace realm::networking { headers["Content-Length"] = util::to_string(request.body.size()); } - if (this->m_custom_http_headers) { - for (auto& header : *this->m_custom_http_headers) { + if (m_configuration.custom_http_headers) { + for (auto& header : *m_configuration.custom_http_headers) { headers.emplace(header); } } diff --git a/src/cpprealm/internal/networking/shims.cpp b/src/cpprealm/internal/networking/shims.cpp new file mode 100644 index 000000000..9b09b531a --- /dev/null +++ b/src/cpprealm/internal/networking/shims.cpp @@ -0,0 +1,131 @@ +#include + +#include +#include +#include + +namespace realm::internal::networking { + + std::unique_ptr<::realm::sync::WebSocketInterface> create_websocket_interface_shim(std::unique_ptr<::realm::networking::websocket_interface>&& m_interface) { + struct core_websocket_interface_shim : public ::realm::sync::WebSocketInterface { + ~core_websocket_interface_shim() = default; + explicit core_websocket_interface_shim(std::unique_ptr<::realm::networking::websocket_interface>&& ws) : m_interface(std::move(ws)) {} + + void async_write_binary(util::Span data, sync::SyncSocketProvider::FunctionHandler&& handler) override { + auto handler_ptr = handler.release(); + auto b = std::string_view(data.data(), data.size()); + m_interface->async_write_binary(b, [ptr = std::move(handler_ptr)](::realm::networking::websocket_interface::status s) { + auto uf = util::UniqueFunction(std::move(ptr)); + return uf(s.operator ::realm::Status()); + }); + }; + + std::unique_ptr<::realm::networking::websocket_interface> m_interface; + }; + + return std::make_unique(std::move(m_interface)); + } + + std::shared_ptr create_http_client_shim(const std::shared_ptr<::realm::networking::http_transport_client>& http_client) { + struct core_http_transport_shim : app::GenericNetworkTransport { + ~core_http_transport_shim() = default; + core_http_transport_shim(const std::shared_ptr<::realm::networking::http_transport_client>& http_client) { + m_http_client = http_client; + } + + void send_request_to_server(const app::Request& request, + util::UniqueFunction&& completion) { + auto completion_ptr = completion.release(); + m_http_client->send_request_to_server(to_request(request), + [f = std::move(completion_ptr)] + (const ::realm::networking::response& response) { + auto uf = util::UniqueFunction(std::move(f)); + uf(to_core_response(response)); + }); + } + + private: + std::shared_ptr<::realm::networking::http_transport_client> m_http_client; + }; + + return std::make_shared(http_client); + } + + std::unique_ptr<::realm::networking::websocket_observer> create_websocket_observer_from_core_shim(std::unique_ptr<::realm::sync::WebSocketObserver>&& m_observer) { + struct core_websocket_observer_shim : public ::realm::networking::websocket_observer { + explicit core_websocket_observer_shim(std::unique_ptr<::realm::sync::WebSocketObserver>&& ws) : m_observer(std::move(ws)) {} + ~core_websocket_observer_shim() = default; + + void websocket_connected_handler(const std::string& protocol) override { + m_observer->websocket_connected_handler(protocol); + } + + void websocket_error_handler() override { + m_observer->websocket_error_handler(); + } + + bool websocket_binary_message_received(std::string_view data) override { + return m_observer->websocket_binary_message_received(data); + } + + bool websocket_closed_handler(bool was_clean, ::realm::networking::websocket_err_codes error_code, + std::string_view message) override { + return m_observer->websocket_closed_handler(was_clean, static_cast<::realm::sync::websocket::WebSocketError>(error_code), message); + } + + std::unique_ptr<::realm::sync::WebSocketObserver> m_observer; + }; + + return std::make_unique(std::move(m_observer)); + } + + std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider) { + struct sync_timer_shim final : public ::realm::sync::SyncSocketProvider::Timer { + sync_timer_shim(std::unique_ptr<::realm::networking::sync_socket_provider::timer>&& timer) : m_timer(std::move(timer)) {} + ~sync_timer_shim() = default; + + void cancel() override { + m_timer->cancel(); + } + + private: + std::unique_ptr<::realm::networking::sync_socket_provider::timer> m_timer; + }; + + struct sync_socket_provider_shim final : public ::realm::sync::SyncSocketProvider { + explicit sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider) { + m_provider = provider; + } + + sync_socket_provider_shim() = delete; + ~sync_socket_provider_shim() = default; + + std::unique_ptr<::realm::sync::WebSocketInterface> connect(std::unique_ptr<::realm::sync::WebSocketObserver> observer, ::realm::sync::WebSocketEndpoint&& ep) override { + auto cpprealm_ws_interface = m_provider->connect(create_websocket_observer_from_core_shim(std::move(observer)), + to_websocket_endpoint(std::move(ep))); + return create_websocket_interface_shim(std::move(cpprealm_ws_interface)); + } + + void post(FunctionHandler&& handler) override { + auto handler_ptr = handler.release(); + m_provider->post([ptr = std::move(handler_ptr)](::realm::networking::websocket_interface::status s) { + auto uf = util::UniqueFunction(std::move(ptr)); + return uf(s.operator ::realm::Status()); + }); + } + + ::realm::sync::SyncSocketProvider::SyncTimer create_timer(std::chrono::milliseconds delay, ::realm::sync::SyncSocketProvider::FunctionHandler&& handler) override { + auto handler_ptr = handler.release(); + auto fn = [ptr = std::move(handler_ptr)](::realm::networking::websocket_interface::status s) { + auto uf = util::UniqueFunction(std::move(ptr)); + return uf(s.operator ::realm::Status()); + }; + return std::make_unique(m_provider->create_timer(delay, std::move(fn))); + } + private: + std::shared_ptr<::realm::networking::sync_socket_provider> m_provider; + }; + + return std::make_unique(std::move(provider)); + } +} //namespace internal::networking \ No newline at end of file diff --git a/src/cpprealm/networking/networking.cpp b/src/cpprealm/networking/networking.cpp index 8828acff4..c020e6420 100644 --- a/src/cpprealm/networking/networking.cpp +++ b/src/cpprealm/networking/networking.cpp @@ -61,7 +61,9 @@ namespace realm::internal::networking { REALM_TERMINATE("Unrecognized websocket protocol"); } - ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint& ep) { + ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint& ep, + const std::optional& pc, + const std::optional>& custom_headers) { ::realm::sync::WebSocketEndpoint core_ep; auto uri = util::Uri(ep.url); auto protocol = to_protocol_envelope(uri.get_scheme()); @@ -75,12 +77,12 @@ namespace realm::internal::networking { core_ep.protocols = ep.protocols; core_ep.is_ssl = ::realm::sync::is_ssl(protocol); - if (ep.proxy_configuration) { + if (pc) { core_ep.proxy = SyncConfig::ProxyConfig(); - core_ep.proxy->address = ep.proxy_configuration->address; - core_ep.proxy->port = ep.proxy_configuration->port; - if (ep.proxy_configuration->username_password) { - auto userpass = util::format("%1:%2", ep.proxy_configuration->username_password->first, ep.proxy_configuration->username_password->second); + core_ep.proxy->address = pc->address; + core_ep.proxy->port = pc->port; + if (pc->username_password) { + auto userpass = util::format("%1:%2", pc->username_password->first, pc->username_password->second); std::string encoded_userpass; encoded_userpass.resize(realm::util::base64_encoded_size(userpass.length())); realm::util::base64_encode(userpass, encoded_userpass); @@ -88,16 +90,18 @@ namespace realm::internal::networking { } } + if (custom_headers) { + core_ep.headers = *custom_headers; + } + return core_ep; } - ::realm::networking::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& core_ep, - std::optional pc) { + ::realm::networking::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& core_ep) { ::realm::networking::websocket_endpoint ep; ep.protocols = core_ep.protocols; const auto& port = core_ep.proxy ? core_ep.proxy->port : core_ep.port; - ep.url = util::format("%1://%2:%3%4", core_ep.is_ssl ? "wss" : "ws", core_ep.proxy ? core_ep.proxy->address : core_ep.address, port, core_ep.path); - ep.proxy_configuration = pc; + ep.url = util::format("%1://%2:%3%4", core_ep.is_ssl ? "wss" : "ws", core_ep.address, port, core_ep.path); return ep; } } //namespace internal::networking diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/platform_networking.cpp index b984da480..49f4c7cbe 100644 --- a/src/cpprealm/networking/platform_networking.cpp +++ b/src/cpprealm/networking/platform_networking.cpp @@ -52,9 +52,20 @@ namespace realm::networking { m_timer->cancel(); }; + private: std::shared_ptr<::realm::sync::SyncSocketProvider::Timer> m_timer; }; + /// Built in websocket client. + struct default_socket : public websocket_interface { + default_socket(std::unique_ptr<::realm::sync::WebSocketInterface>&&); + ~default_socket() = default; + + void async_write_binary(std::string_view data, websocket_interface::FunctionHandler&& handler) override; + private: + std::shared_ptr<::realm::sync::WebSocketInterface> m_ws_interface; + }; + default_socket_provider::default_socket_provider() { auto user_agent_binding_info = std::string("RealmCpp/") + std::string(REALMCXX_VERSION_STRING); auto user_agent_application_info = "";//app_id; TODO: Should we pass the app id? @@ -65,8 +76,11 @@ namespace realm::networking { } std::unique_ptr default_socket_provider::connect(std::unique_ptr o, websocket_endpoint&& ep) { - auto ws_interface = m_provider->connect(std::make_unique(std::move(o)), - internal::networking::to_core_websocket_endpoint(ep)); + + auto core_ep = internal::networking::to_core_websocket_endpoint(ep, + m_configuration.proxy_config, + m_configuration.custom_http_headers); + auto ws_interface = m_provider->connect(std::make_unique(std::move(o)), std::move(core_ep)); return std::make_unique(std::move(ws_interface)); } @@ -92,133 +106,3 @@ namespace realm::networking { }); } } - -namespace realm::internal::networking { - - std::unique_ptr<::realm::sync::WebSocketInterface> create_websocket_interface_shim(std::unique_ptr<::realm::networking::websocket_interface>&& m_interface) { - struct core_websocket_interface_shim : public ::realm::sync::WebSocketInterface { - ~core_websocket_interface_shim() = default; - explicit core_websocket_interface_shim(std::unique_ptr<::realm::networking::websocket_interface>&& ws) : m_interface(std::move(ws)) {} - - void async_write_binary(util::Span data, sync::SyncSocketProvider::FunctionHandler&& handler) override { - auto handler_ptr = handler.release(); - auto b = std::string_view(data.data(), data.size()); - m_interface->async_write_binary(b, [ptr = std::move(handler_ptr)](::realm::networking::websocket_interface::status s) { - auto uf = util::UniqueFunction(std::move(ptr)); - return uf(s.operator ::realm::Status()); - }); - }; - - std::unique_ptr<::realm::networking::websocket_interface> m_interface; - }; - - return std::make_unique(std::move(m_interface)); - } - - std::shared_ptr create_http_client_shim(const std::shared_ptr<::realm::networking::http_transport_client>& http_client) { - struct core_http_transport_shim : app::GenericNetworkTransport { - ~core_http_transport_shim() = default; - core_http_transport_shim(const std::shared_ptr<::realm::networking::http_transport_client>& http_client) { - m_http_client = http_client; - } - - void send_request_to_server(const app::Request& request, - util::UniqueFunction&& completion) { - auto completion_ptr = completion.release(); - m_http_client->send_request_to_server(to_request(request), - [f = std::move(completion_ptr)] - (const ::realm::networking::response& response) { - auto uf = util::UniqueFunction(std::move(f)); - uf(to_core_response(response)); - }); - } - - private: - std::shared_ptr<::realm::networking::http_transport_client> m_http_client; - }; - - return std::make_shared(http_client); - } - - std::unique_ptr<::realm::networking::websocket_observer> create_websocket_observer_from_core_shim(std::unique_ptr<::realm::sync::WebSocketObserver>&& m_observer) { - struct core_websocket_observer_shim : public ::realm::networking::websocket_observer { - explicit core_websocket_observer_shim(std::unique_ptr<::realm::sync::WebSocketObserver>&& ws) : m_observer(std::move(ws)) {} - ~core_websocket_observer_shim() = default; - - void websocket_connected_handler(const std::string& protocol) override { - m_observer->websocket_connected_handler(protocol); - } - - void websocket_error_handler() override { - m_observer->websocket_error_handler(); - } - - bool websocket_binary_message_received(std::string_view data) override { - return m_observer->websocket_binary_message_received(data); - } - - bool websocket_closed_handler(bool was_clean, ::realm::networking::websocket_err_codes error_code, - std::string_view message) override { - return m_observer->websocket_closed_handler(was_clean, static_cast<::realm::sync::websocket::WebSocketError>(error_code), message); - } - - std::unique_ptr<::realm::sync::WebSocketObserver> m_observer; - }; - - return std::make_unique(std::move(m_observer)); - } - - std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider, - std::optional pc) { - struct sync_timer_shim final : public ::realm::sync::SyncSocketProvider::Timer { - sync_timer_shim(std::unique_ptr<::realm::networking::sync_socket_provider::timer>&& timer) : m_timer(std::move(timer)) {} - ~sync_timer_shim() = default; - - void cancel() override { - m_timer->cancel(); - } - - private: - std::unique_ptr<::realm::networking::sync_socket_provider::timer> m_timer; - }; - - struct sync_socket_provider_shim final : public ::realm::sync::SyncSocketProvider { - explicit sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider, - std::optional proxy_config) { - m_provider = provider; - m_proxy_config = proxy_config; - } - - sync_socket_provider_shim() = delete; - ~sync_socket_provider_shim() = default; - - std::unique_ptr<::realm::sync::WebSocketInterface> connect(std::unique_ptr<::realm::sync::WebSocketObserver> observer, ::realm::sync::WebSocketEndpoint&& ep) override { - auto cpprealm_ws_interface = m_provider->connect(create_websocket_observer_from_core_shim(std::move(observer)), - to_websocket_endpoint(std::move(ep), m_proxy_config)); - return create_websocket_interface_shim(std::move(cpprealm_ws_interface)); - } - - void post(FunctionHandler&& handler) override { - auto handler_ptr = handler.release(); - m_provider->post([ptr = std::move(handler_ptr)](::realm::networking::websocket_interface::status s) { - auto uf = util::UniqueFunction(std::move(ptr)); - return uf(s.operator ::realm::Status()); - }); - } - - ::realm::sync::SyncSocketProvider::SyncTimer create_timer(std::chrono::milliseconds delay, ::realm::sync::SyncSocketProvider::FunctionHandler&& handler) override { - auto handler_ptr = handler.release(); - auto fn = [ptr = std::move(handler_ptr)](::realm::networking::websocket_interface::status s) { - auto uf = util::UniqueFunction(std::move(ptr)); - return uf(s.operator ::realm::Status()); - }; - return std::make_unique(m_provider->create_timer(delay, std::move(fn))); - } - private: - std::shared_ptr<::realm::networking::sync_socket_provider> m_provider; - std::optional m_proxy_config; - }; - - return std::make_unique(std::move(provider), pc); - } -} //namespace internal::networking diff --git a/tests/sync/client_reset_tests.cpp b/tests/sync/client_reset_tests.cpp index c32623aa0..713c54c9c 100644 --- a/tests/sync/client_reset_tests.cpp +++ b/tests/sync/client_reset_tests.cpp @@ -121,7 +121,7 @@ TEST_CASE("client_reset", "[sync]") { }); sync_session->resume(); CHECK(flx_sync_config.get_client_reset_mode() == realm::client_reset_mode::manual); - CHECK(f.wait_for(std::chrono::milliseconds(60000)) == std::future_status::ready); + CHECK(f.wait_for(std::chrono::seconds(180)) == std::future_status::ready); } SECTION("discard_unsynced_changes") { @@ -162,8 +162,8 @@ TEST_CASE("client_reset", "[sync]") { // The client_reset_object created locally with _id=2 should have been discarded, // while the one from the server _id=1 should be present CHECK(flx_sync_config.get_client_reset_mode() == realm::client_reset_mode::discard_unsynced); - CHECK(before_handler_future.wait_for(std::chrono::milliseconds(60000)) == std::future_status::ready); - CHECK(after_handler_future.wait_for(std::chrono::milliseconds(60000)) == std::future_status::ready); + CHECK(before_handler_future.wait_for(std::chrono::seconds(180)) == std::future_status::ready); + CHECK(after_handler_future.wait_for(std::chrono::seconds(180)) == std::future_status::ready); } SECTION("recover_or_discard_unsynced_changes") { @@ -203,8 +203,8 @@ TEST_CASE("client_reset", "[sync]") { // The client_reset_object created locally with _id=2 should be present as it should be recovered, // while the one from the server _id=1 should be present CHECK(flx_sync_config.get_client_reset_mode() == realm::client_reset_mode::recover_or_discard); - CHECK(before_handler_future.wait_for(std::chrono::milliseconds(60000)) == std::future_status::ready); - CHECK(after_handler_future.wait_for(std::chrono::milliseconds(60000)) == std::future_status::ready); + CHECK(before_handler_future.wait_for(std::chrono::seconds(180)) == std::future_status::ready); + CHECK(after_handler_future.wait_for(std::chrono::seconds(180)) == std::future_status::ready); } SECTION("recover_unsynced_changes") { @@ -244,8 +244,8 @@ TEST_CASE("client_reset", "[sync]") { // The object created locally and the object created on the server // should both be integrated into the new realm file. CHECK(flx_sync_config.get_client_reset_mode() == realm::client_reset_mode::recover); - CHECK(before_handler_future.wait_for(std::chrono::milliseconds(60000)) == std::future_status::ready); - CHECK(after_handler_future.wait_for(std::chrono::milliseconds(60000)) == std::future_status::ready); + CHECK(before_handler_future.wait_for(std::chrono::seconds(180)) == std::future_status::ready); + CHECK(after_handler_future.wait_for(std::chrono::seconds(180)) == std::future_status::ready); } SECTION("recover_unsynced_changes_with_failure") { @@ -278,7 +278,7 @@ TEST_CASE("client_reset", "[sync]") { auto synced_realm2 = db(flx_sync_config); synced_realm2.refresh(); CHECK(flx_sync_config.get_client_reset_mode() == realm::client_reset_mode::recover); - CHECK(error_handler_future.wait_for(std::chrono::milliseconds(60000)) == std::future_status::ready); + CHECK(error_handler_future.wait_for(std::chrono::seconds(180)) == std::future_status::ready); } } \ No newline at end of file From 85796b89acd012cb27e22d611389bbfc6d670b84 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Wed, 10 Jul 2024 17:29:13 +0100 Subject: [PATCH 21/41] Add SSL configuration --- include/cpprealm/networking/networking.hpp | 7 ++- .../networking/platform_networking.hpp | 18 ++++-- .../internal/curl/network_transport.cpp | 23 +++---- .../internal/network/network_transport.cpp | 6 +- src/cpprealm/networking/networking.cpp | 44 ++++++++------ .../networking/platform_networking.cpp | 12 +++- tests/sync/flexible_sync_tests.cpp | 15 ++++- tests/utils/networking/proxy_server.cpp | 60 +++++++++++++++++++ 8 files changed, 139 insertions(+), 46 deletions(-) diff --git a/include/cpprealm/networking/networking.hpp b/include/cpprealm/networking/networking.hpp index 31b3d14c7..61bc0e8be 100644 --- a/include/cpprealm/networking/networking.hpp +++ b/include/cpprealm/networking/networking.hpp @@ -148,6 +148,10 @@ namespace realm { namespace sync { struct WebSocketEndpoint; } + + namespace networking { + struct default_transport_configuration; + } } namespace realm::internal::networking { @@ -158,8 +162,7 @@ namespace realm::internal::networking { ::realm::app::Response to_core_response(const ::realm::networking::response&); ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint& ep, - const std::optional& pc, - const std::optional>& custom_headers); + const std::optional<::realm::networking::default_transport_configuration>& config); ::realm::networking::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& ep); } //namespace realm::internal::networking diff --git a/include/cpprealm/networking/platform_networking.hpp b/include/cpprealm/networking/platform_networking.hpp index bb29ba4c6..ff6f41332 100644 --- a/include/cpprealm/networking/platform_networking.hpp +++ b/include/cpprealm/networking/platform_networking.hpp @@ -229,15 +229,22 @@ namespace realm::networking { /// Globally overwrites the default http transport client factory void set_http_client_factory(std::function()>&&); - struct default_transport_configuraton { + struct default_transport_configuration { std::optional> custom_http_headers; std::optional<::realm::internal::bridge::realm::sync_config::proxy_config> proxy_config; + + using SSLVerifyCallback = bool(const std::string& server_address, + internal::bridge::realm::sync_config::proxy_config::port_type server_port, + const char* pem_data, size_t pem_size, int preverify_ok, int depth); + bool client_validate_ssl = true; + std::optional ssl_trust_certificate_path; + std::function ssl_verify_callback; }; /// Built in HTTP transport client. struct default_http_transport : public http_transport_client { default_http_transport() = default; - default_http_transport(const default_transport_configuraton& c) : m_configuration(c) {} + default_http_transport(const default_transport_configuration& c) : m_configuration(c) {} ~default_http_transport() = default; @@ -245,13 +252,13 @@ namespace realm::networking { std::function&& completion); protected: - default_transport_configuraton m_configuration; + default_transport_configuration m_configuration; }; /// Built in websocket provider struct default_socket_provider : public sync_socket_provider { default_socket_provider(); - default_socket_provider(const default_transport_configuraton& c) : m_configuration(c) {} + default_socket_provider(const default_transport_configuration& c); ~default_socket_provider() = default; std::unique_ptr connect(std::unique_ptr, websocket_endpoint &&) override; @@ -259,8 +266,9 @@ namespace realm::networking { sync_timer create_timer(std::chrono::milliseconds delay, FunctionHandler&&) override; protected: - default_transport_configuraton m_configuration; + default_transport_configuration m_configuration; private: + void initialize(); std::shared_ptr<::realm::sync::SyncSocketProvider> m_provider; }; } diff --git a/src/cpprealm/internal/curl/network_transport.cpp b/src/cpprealm/internal/curl/network_transport.cpp index 4d28fc6ef..cd9ec953c 100644 --- a/src/cpprealm/internal/curl/network_transport.cpp +++ b/src/cpprealm/internal/curl/network_transport.cpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include @@ -60,7 +60,6 @@ namespace realm::networking { static size_t curl_write_cb(char* ptr, size_t size, size_t nmemb, std::string* response) { - REALM_ASSERT(response); size_t realsize = size * nmemb; response->append(ptr, realsize); return realsize; @@ -68,7 +67,6 @@ namespace realm::networking { static size_t curl_header_cb(char* buffer, size_t size, size_t nitems, std::map* response_headers) { - REALM_ASSERT(response_headers); std::string combined(buffer, size * nitems); if (auto pos = combined.find(':'); pos != std::string::npos) { std::string key = combined.substr(0, pos); @@ -101,11 +99,8 @@ namespace realm::networking { struct curl_slist* list = nullptr; std::string response; - app::HttpHeaders response_headers; + ::realm::networking::http_headers response_headers; - /* First set the URL that is about to receive our POST. This URL can - just as well be a https:// URL if that is what should receive the - data. */ curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str()); if (proxy_config) { @@ -170,14 +165,12 @@ namespace realm::networking { void default_http_transport::send_request_to_server(const ::realm::networking::request& request, std::function&& completion_block) { - { - if (m_custom_http_headers) { - auto req_copy = request; - req_copy.headers.insert(m_custom_http_headers->begin(), m_custom_http_headers->end()); - completion_block(do_http_request(req_copy, m_proxy_config)); - return; - } - completion_block(do_http_request(request, m_proxy_config)); + if (m_configuration.custom_http_headers) { + auto req_copy = request; + req_copy.headers.insert(m_configuration.custom_http_headers->begin(), m_configuration.custom_http_headers->end()); + completion_block(do_http_request(req_copy, m_configuration.proxy_config)); + return; } + completion_block(do_http_request(request, m_configuration.proxy_config)); } } // namespace realm::networking diff --git a/src/cpprealm/internal/network/network_transport.cpp b/src/cpprealm/internal/network/network_transport.cpp index 00305c407..2110c0c61 100644 --- a/src/cpprealm/internal/network/network_transport.cpp +++ b/src/cpprealm/internal/network/network_transport.cpp @@ -221,9 +221,13 @@ namespace realm::networking { service.reset(); } + if (m_configuration.ssl_trust_certificate_path) { + m_ssl_context.use_certificate_chain_file(*m_configuration.ssl_trust_certificate_path); + } else { #if REALM_INCLUDE_CERTS - m_ssl_context.use_included_certificate_roots(); + m_ssl_context.use_included_certificate_roots(); #endif + } if (url_scheme == URLScheme::HTTPS) { socket.ssl_stream.emplace(socket, m_ssl_context, Stream::client); diff --git a/src/cpprealm/networking/networking.cpp b/src/cpprealm/networking/networking.cpp index c020e6420..66810df09 100644 --- a/src/cpprealm/networking/networking.cpp +++ b/src/cpprealm/networking/networking.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -62,36 +63,45 @@ namespace realm::internal::networking { } ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint& ep, - const std::optional& pc, - const std::optional>& custom_headers) { + const std::optional<::realm::networking::default_transport_configuration>& config) { ::realm::sync::WebSocketEndpoint core_ep; auto uri = util::Uri(ep.url); auto protocol = to_protocol_envelope(uri.get_scheme()); std::string userinfo, host, port; uri.get_auth(userinfo, host, port); + core_ep.is_ssl = ::realm::sync::is_ssl(protocol); core_ep.address = host; - core_ep.port = std::stoi(port); + if (port.empty()) { + core_ep.port = core_ep.is_ssl ? 443 : 80; + } else { + core_ep.port = std::stoi(port); + } core_ep.path = uri.get_path() + uri.get_query(); core_ep.protocols = ep.protocols; - core_ep.is_ssl = ::realm::sync::is_ssl(protocol); - if (pc) { - core_ep.proxy = SyncConfig::ProxyConfig(); - core_ep.proxy->address = pc->address; - core_ep.proxy->port = pc->port; - if (pc->username_password) { - auto userpass = util::format("%1:%2", pc->username_password->first, pc->username_password->second); - std::string encoded_userpass; - encoded_userpass.resize(realm::util::base64_encoded_size(userpass.length())); - realm::util::base64_encode(userpass, encoded_userpass); - core_ep.headers.emplace("Proxy-Authorization", util::format("Basic %1", encoded_userpass)); + if (config) { + if (config->proxy_config) { + core_ep.proxy = SyncConfig::ProxyConfig(); + core_ep.proxy->address = config->proxy_config->address; + core_ep.proxy->port = config->proxy_config->port; + if (config->proxy_config->username_password) { + auto userpass = util::format("%1:%2", config->proxy_config->username_password->first, config->proxy_config->username_password->second); + std::string encoded_userpass; + encoded_userpass.resize(realm::util::base64_encoded_size(userpass.length())); + realm::util::base64_encode(userpass, encoded_userpass); + core_ep.headers.emplace("Proxy-Authorization", util::format("Basic %1", encoded_userpass)); + } + } + + if (config->custom_http_headers) { + core_ep.headers = *config->custom_http_headers; } - } - if (custom_headers) { - core_ep.headers = *custom_headers; + core_ep.verify_servers_ssl_certificate = config->client_validate_ssl; + core_ep.ssl_trust_certificate_path = config->ssl_trust_certificate_path; + core_ep.ssl_verify_callback = config->ssl_verify_callback; } return core_ep; diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/platform_networking.cpp index 49f4c7cbe..442856979 100644 --- a/src/cpprealm/networking/platform_networking.cpp +++ b/src/cpprealm/networking/platform_networking.cpp @@ -67,6 +67,14 @@ namespace realm::networking { }; default_socket_provider::default_socket_provider() { + initialize(); + } + + default_socket_provider::default_socket_provider(const default_transport_configuration& c) : m_configuration(c) { + initialize(); + } + + void default_socket_provider::initialize() { auto user_agent_binding_info = std::string("RealmCpp/") + std::string(REALMCXX_VERSION_STRING); auto user_agent_application_info = "";//app_id; TODO: Should we pass the app id? @@ -77,9 +85,7 @@ namespace realm::networking { std::unique_ptr default_socket_provider::connect(std::unique_ptr o, websocket_endpoint&& ep) { - auto core_ep = internal::networking::to_core_websocket_endpoint(ep, - m_configuration.proxy_config, - m_configuration.custom_http_headers); + auto core_ep = internal::networking::to_core_websocket_endpoint(ep, m_configuration); auto ws_interface = m_provider->connect(std::make_unique(std::move(o)), std::move(core_ep)); return std::make_unique(std::move(ws_interface)); } diff --git a/tests/sync/flexible_sync_tests.cpp b/tests/sync/flexible_sync_tests.cpp index a14ec4f13..9f7c5cb65 100644 --- a/tests/sync/flexible_sync_tests.cpp +++ b/tests/sync/flexible_sync_tests.cpp @@ -5,7 +5,10 @@ using namespace realm; TEST_CASE("flexible_sync", "[sync]") { - auto app = realm::App(realm::App::configuration({Admin::Session::shared().cached_app_id(), Admin::Session::shared().base_url()})); + auto config = realm::App::configuration(); + config.app_id = Admin::Session::shared().cached_app_id(); + config.base_url = Admin::Session::shared().base_url(); + auto app = realm::App(config); SECTION("all") { auto user = app.login(realm::App::credentials::anonymous()).get(); auto flx_sync_config = user.flexible_sync_configuration(); @@ -108,7 +111,10 @@ void test_set(realm::managed* property, Func f, } TEST_CASE("set collection sync", "[set]") { - auto app = realm::App(realm::App::configuration({Admin::Session::shared().cached_app_id(), Admin::Session::shared().base_url()})); + auto config = realm::App::configuration(); + config.app_id = Admin::Session::shared().cached_app_id(); + config.base_url = Admin::Session::shared().base_url(); + auto app = realm::App(config); SECTION("insert") { auto user = app.login(realm::App::credentials::anonymous()).get(); auto flx_sync_config = user.flexible_sync_configuration(); @@ -185,7 +191,10 @@ TEST_CASE("set collection sync", "[set]") { } TEST_CASE("pause_resume_sync", "[sync]") { - auto app = realm::App(realm::App::configuration({Admin::Session::shared().cached_app_id(), Admin::Session::shared().base_url()})); + auto config = realm::App::configuration(); + config.app_id = Admin::Session::shared().cached_app_id(); + config.base_url = Admin::Session::shared().base_url(); + auto app = realm::App(config); SECTION("pause_resume") { auto user = app.login(realm::App::credentials::anonymous()).get(); diff --git a/tests/utils/networking/proxy_server.cpp b/tests/utils/networking/proxy_server.cpp index 4329fc239..2e2344d51 100644 --- a/tests/utils/networking/proxy_server.cpp +++ b/tests/utils/networking/proxy_server.cpp @@ -36,6 +36,10 @@ namespace realm::tests::utils { REALM_TERMINATE("Unsupported HTTP method in proxy"); } } else { + if (ec == asio::error::eof) { + std::cout << "Connection closed by peer (EOF)." << std::endl; + return; + } REALM_TERMINATE("Error reading client request"); } }); @@ -62,6 +66,10 @@ namespace realm::tests::utils { if (!ec) { connect_to_server(*results.begin(), host); } else { + if (ec == asio::error::eof) { + std::cout << "Connection closed by peer (EOF)." << std::endl; + return; + } REALM_TERMINATE("Proxy: Error resolving host"); } }); @@ -81,6 +89,10 @@ namespace realm::tests::utils { if (!ec) { do_ssl_handshake_to_server(host); } else { + if (ec == asio::error::eof) { + std::cout << "Connection closed by peer (EOF)." << std::endl; + return; + } REALM_TERMINATE("Proxy: Error connecting to server"); } }); @@ -92,6 +104,10 @@ namespace realm::tests::utils { if (!ec) { do_write_connect_response(); } else { + if (ec == asio::error::eof) { + std::cout << "Connection closed by peer (EOF)." << std::endl; + return; + } REALM_TERMINATE("Proxy: Error connecting to server"); } }); @@ -145,6 +161,10 @@ namespace realm::tests::utils { if (!ec) { do_ssl_read_server(); } else { + if (ec == asio::error::eof) { + std::cout << "Connection closed by peer (EOF)." << std::endl; + return; + } REALM_TERMINATE("Proxy: Error writing to server."); } }); @@ -160,6 +180,10 @@ namespace realm::tests::utils { if (!ec) { do_write_to_client(length); } else { + if (ec == asio::error::eof) { + std::cout << "Connection closed by peer (EOF)." << std::endl; + return; + } REALM_TERMINATE("Proxy: Error reading from server."); } }); @@ -181,6 +205,10 @@ namespace realm::tests::utils { do_write_to_server(length); } } else { + if (ec == asio::error::eof) { + std::cout << "Connection closed by peer (EOF)." << std::endl; + return; + } REALM_TERMINATE("Proxy: Error reading from client."); } }); @@ -197,6 +225,10 @@ namespace realm::tests::utils { do_write_to_websocket_server(length); do_read_from_websocket_client(); } else { + if (ec == asio::error::eof) { + std::cout << "Connection closed by peer (EOF)." << std::endl; + return; + } REALM_TERMINATE("Proxy: Error reading from websocket client."); } }); @@ -223,6 +255,10 @@ namespace realm::tests::utils { if (!ec) { do_read_from_websocket_client(); } else { + if (ec == asio::error::eof) { + std::cout << "Connection closed by peer (EOF)." << std::endl; + return; + } REALM_TERMINATE("Proxy: Error writing to websocket server."); } }); @@ -241,6 +277,10 @@ namespace realm::tests::utils { do_write_to_websocket_client(length); do_read_from_websocket_server(); } else { + if (ec == asio::error::eof) { + std::cout << "Connection closed by peer (EOF)." << std::endl; + return; + } REALM_TERMINATE("Proxy: Error reading from websocket server."); } }); @@ -251,6 +291,10 @@ namespace realm::tests::utils { do_write_to_websocket_client(length); do_read_from_websocket_server(); } else { + if (ec == asio::error::eof) { + std::cout << "Connection closed by peer (EOF)." << std::endl; + return; + } REALM_TERMINATE("Proxy: Error reading from websocket server."); } }); @@ -267,6 +311,10 @@ namespace realm::tests::utils { if (!ec) { do_read_from_websocket_server(); } else { + if (ec == asio::error::eof) { + std::cout << "Connection closed by peer (EOF)." << std::endl; + return; + } REALM_TERMINATE("Proxy: Error writing from websocket client."); } }); @@ -290,6 +338,10 @@ namespace realm::tests::utils { if (!ec) { do_read_server(); } else { + if (ec == asio::error::eof) { + std::cout << "Connection closed by peer (EOF)." << std::endl; + return; + } REALM_TERMINATE("Proxy: Error writing to server."); } }); @@ -305,6 +357,10 @@ namespace realm::tests::utils { if (!ec) { do_write_to_client(length); } else { + if (ec == asio::error::eof) { + std::cout << "Connection closed by peer (EOF)." << std::endl; + return; + } REALM_TERMINATE("Proxy: Error reading from server."); } }); @@ -331,6 +387,10 @@ namespace realm::tests::utils { } } } else { + if (ec == asio::error::eof) { + std::cout << "Connection closed by peer (EOF)." << std::endl; + return; + } REALM_TERMINATE("Proxy: Error writing to client."); } }); From bfeb61c5b3112cbe1744f3c1ad1ace117434750b Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Wed, 10 Jul 2024 17:29:26 +0100 Subject: [PATCH 22/41] Update tests --- tests/sync/networking_tests.cpp | 190 ++++++++++++++++++++++++++++---- 1 file changed, 167 insertions(+), 23 deletions(-) diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp index 4b1df8ac6..920e6b646 100644 --- a/tests/sync/networking_tests.cpp +++ b/tests/sync/networking_tests.cpp @@ -6,7 +6,7 @@ using namespace realm; -TEST_CASE("sends plaintext data to proxy", "[proxy]") { +TEST_CASE("custom transport to proxy", "[proxy]") { tests::utils::proxy_server::config cfg; cfg.port = 1234; @@ -18,23 +18,24 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { proxy_events.insert(e); }); + realm::App::configuration config; + config.app_id = Admin::Session::shared().create_app({"str_col", "_id"}, "test", false, false); + config.base_url = Admin::Session::shared().base_url(); + config.enable_caching = true; + + auto transport_config = ::realm::networking::default_transport_configuration(); proxy_config pc; pc.port = 1234; pc.address = "127.0.0.1"; - realm::App::configuration config; - config.proxy_configuration = pc; - config.app_id = Admin::Session::shared().cached_app_id(); - config.base_url = Admin::Session::shared().base_url(); - config.enable_caching = false; + transport_config.proxy_config = pc; struct foo_socket_provider : public ::realm::networking::default_socket_provider { + + foo_socket_provider(const ::realm::networking::default_transport_configuration& configuration) { + m_configuration = configuration; + } std::unique_ptr<::realm::networking::websocket_interface> connect(std::unique_ptr<::realm::networking::websocket_observer> o, ::realm::networking::websocket_endpoint&& ep) override { - const std::string from = "wss:"; - const std::string to = "ws:"; - if (ep.url.find(from) == 0) { - ep.url.replace(0, from.length(), to); - } m_called = true; return ::realm::networking::default_socket_provider::connect(std::move(o), std::move(ep)); } @@ -47,10 +48,15 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { bool m_called = false; }; - auto foo_socket = std::make_shared(); + auto foo_socket = std::make_shared(transport_config); config.sync_socket_provider = foo_socket; struct foo_http_transport : public ::realm::networking::default_http_transport { + + foo_http_transport(const ::realm::networking::default_transport_configuration& configuration) { + m_configuration = configuration; + } + void send_request_to_server(const ::realm::networking::request& request, std::function&& completion) override { auto req_copy = request; @@ -71,7 +77,7 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { bool m_called = false; }; - auto foo_transport = std::make_shared(); + auto foo_transport = std::make_shared(transport_config); config.http_transport_client = foo_transport; auto app = realm::App(config); @@ -107,13 +113,12 @@ TEST_CASE("sends plaintext data to proxy", "[proxy]") { expected_events.insert(tests::utils::proxy_server::event::websocket_upgrade); expected_events.insert(tests::utils::proxy_server::event::websocket); - bool is_subset = std::includes(expected_events.begin(), expected_events.end(), proxy_events.begin(), proxy_events.end()); - CHECK(is_subset); + CHECK(proxy_events == expected_events); CHECK(foo_transport->was_called()); CHECK(foo_socket->was_called()); } -TEST_CASE("proxy roundtrip", "[proxy]") { +TEST_CASE("built in transport to proxy roundtrip", "[proxy]") { tests::utils::proxy_server::config cfg; cfg.port = 1234; @@ -125,15 +130,20 @@ TEST_CASE("proxy roundtrip", "[proxy]") { proxy_events.insert(e); }); - proxy_config pc; - pc.port = 1234; - pc.address = "127.0.0.1"; realm::App::configuration config; - config.proxy_configuration = pc; - config.app_id = Admin::Session::shared().cached_app_id(); + config.app_id = Admin::Session::shared().create_app({"str_col", "_id"}, "test", false, false); config.base_url = Admin::Session::shared().base_url(); config.enable_caching = false; + auto transport_config = ::realm::networking::default_transport_configuration(); + proxy_config pc; + pc.port = 1234; + pc.address = "127.0.0.1"; + transport_config.proxy_config = pc; + + config.sync_socket_provider = std::make_shared(transport_config); + config.http_transport_client = std::make_shared(transport_config); + auto app = realm::App(config); auto user = app.login(realm::App::credentials::anonymous()).get(); @@ -167,6 +177,140 @@ TEST_CASE("proxy roundtrip", "[proxy]") { expected_events.insert(tests::utils::proxy_server::event::websocket_upgrade); expected_events.insert(tests::utils::proxy_server::event::websocket); - bool is_subset = std::includes(expected_events.begin(), expected_events.end(), proxy_events.begin(), proxy_events.end()); - CHECK(is_subset); + CHECK(proxy_events == expected_events); } + +TEST_CASE("WebsocketEndpoint", "[proxy]") { + realm::networking::websocket_endpoint ep; + ep.url = "wss://my-server.com:443"; + ep.protocols = std::vector({"test_protocol"}); + + ::realm::networking::default_transport_configuration config; + config.proxy_config = ::realm::proxy_config(); + config.proxy_config->address = "127.0.0.1"; + config.proxy_config->port = 1234; + config.proxy_config->username_password = std::pair("foo", "bar"); + + config.ssl_trust_certificate_path = "test_path"; + + auto res = ::realm::internal::networking::to_core_websocket_endpoint(ep, config); + CHECK(res.is_ssl); + CHECK(res.protocols.size() == 1); + CHECK(res.protocols[0] == "test_protocol"); + CHECK(res.address == "my-server.com"); + + CHECK(res.proxy); + CHECK(res.proxy->port == 1234); + CHECK(res.proxy->address == "127.0.0.1"); + CHECK(res.headers.find("Proxy-Authorization") != res.headers.end()); + + CHECK(res.ssl_trust_certificate_path == "test_path"); + + ep.url = "ws://my-server.com:80"; + res = ::realm::internal::networking::to_core_websocket_endpoint(ep, std::nullopt); + CHECK_FALSE(res.is_ssl); +} + +TEST_CASE("proxy custom impl", "[proxy]") { + // Naive round-trip test to ensure custom socket impl's succeed. + realm::App::configuration config; + config.app_id = Admin::Session::shared().create_app({"str_col", "_id"}, "test", false, false); + config.base_url = Admin::Session::shared().base_url(); + config.enable_caching = false; + + std::promise websocket_promise; + std::future websocket_future = websocket_promise.get_future(); + + struct mock_websocket : public ::realm::networking::websocket_interface { + mock_websocket(std::unique_ptr<::realm::networking::websocket_observer>&& observer, std::promise&& p) : m_websocket_promise(std::move(p)) { + std::thread([o = std::move(observer)]() { + std::this_thread::sleep_for(std::chrono::seconds(1)); + o->websocket_connected_handler("com.mongodb.realm-query-sync#13"); + }).detach(); + } + + void async_write_binary(std::string_view data, FunctionHandler&& handler) override { + m_websocket_promise.set_value(); + } + + private: + std::promise m_websocket_promise; + }; + + struct mock_socket_provider : public ::realm::networking::sync_socket_provider { + std::unique_ptr<::realm::networking::websocket_interface> connect( + std::unique_ptr<::realm::networking::websocket_observer> observer, + ::realm::networking::websocket_endpoint&& endpoint) override { + m_called = true; + return std::make_unique(std::move(observer), std::move(m_websocket_promise)); + } + + mock_socket_provider(std::promise&& p) : m_websocket_promise(std::move(p)) { + worker_thread = std::thread([this]() { + while (running) { + std::vector local_queue; + { + std::unique_lock lock(mtx); + if (m_queue.empty()) { + lock.unlock(); + std::this_thread::sleep_for(std::chrono::seconds(1)); + continue; + } + local_queue.swap(m_queue); + } + + for (auto fn : local_queue) { + fn(::realm::networking::status::ok()); + } + } + }); + worker_thread.detach(); + } + + ~mock_socket_provider() { + running = false; + if (worker_thread.joinable()) { + worker_thread.join(); + } + } + + void post(FunctionHandler&& handler) override { + std::lock_guard guard(mtx); + m_queue.push_back(std::move(handler)); + } + + struct timer : sync_socket_provider::timer { + ~timer() = default; + void cancel() override { } + }; + + std::unique_ptr create_timer(std::chrono::milliseconds delay, FunctionHandler&& handler) override { + static bool did_issue_connect = false; + if (!did_issue_connect) + m_queue.push_back(std::move(handler)); + did_issue_connect = true; + return std::make_unique(); + } + + bool was_called() const { + return m_called; + } + + private: + std::vector m_queue; + std::mutex mtx; + bool m_called = false; + std::thread worker_thread; + std::atomic running{true}; + std::promise m_websocket_promise; + }; + + config.sync_socket_provider = std::make_shared(std::move(websocket_promise)); + + auto app = realm::App(config); + + auto user = app.login(realm::App::credentials::anonymous()).get(); + auto flx_sync_config = user.flexible_sync_configuration(); + auto synced_realm = db(flx_sync_config); + websocket_future.get(); +} \ No newline at end of file From 3cc3d0b9a2a26c919b32ac975c586304b6962c94 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Wed, 10 Jul 2024 18:14:30 +0100 Subject: [PATCH 23/41] Update tests --- .../networking/platform_networking.hpp | 2 +- .../networking/platform_networking.cpp | 8 ++--- tests/sync/app_tests.cpp | 5 ++- tests/sync/client_reset_tests.cpp | 32 +++++++++++++++---- tests/sync/networking_tests.cpp | 4 +-- tests/utils/networking/proxy_server.cpp | 28 ---------------- 6 files changed, 37 insertions(+), 42 deletions(-) diff --git a/include/cpprealm/networking/platform_networking.hpp b/include/cpprealm/networking/platform_networking.hpp index ff6f41332..1ecec328e 100644 --- a/include/cpprealm/networking/platform_networking.hpp +++ b/include/cpprealm/networking/platform_networking.hpp @@ -269,7 +269,7 @@ namespace realm::networking { default_transport_configuration m_configuration; private: void initialize(); - std::shared_ptr<::realm::sync::SyncSocketProvider> m_provider; + std::unique_ptr<::realm::sync::SyncSocketProvider> m_provider; }; } diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/platform_networking.cpp index 442856979..82d895632 100644 --- a/src/cpprealm/networking/platform_networking.cpp +++ b/src/cpprealm/networking/platform_networking.cpp @@ -42,18 +42,18 @@ namespace realm::networking { } private: - std::shared_ptr m_observer; + std::unique_ptr m_observer; }; struct default_timer : public default_socket_provider::timer { - default_timer(const std::shared_ptr<::realm::sync::SyncSocketProvider::Timer>& t) : m_timer(t) {} + default_timer(std::unique_ptr<::realm::sync::SyncSocketProvider::Timer>&& t) : m_timer(std::move(t)) {} ~default_timer() = default; void cancel() { m_timer->cancel(); }; private: - std::shared_ptr<::realm::sync::SyncSocketProvider::Timer> m_timer; + std::unique_ptr<::realm::sync::SyncSocketProvider::Timer> m_timer; }; /// Built in websocket client. @@ -63,7 +63,7 @@ namespace realm::networking { void async_write_binary(std::string_view data, websocket_interface::FunctionHandler&& handler) override; private: - std::shared_ptr<::realm::sync::WebSocketInterface> m_ws_interface; + std::unique_ptr<::realm::sync::WebSocketInterface> m_ws_interface; }; default_socket_provider::default_socket_provider() { diff --git a/tests/sync/app_tests.cpp b/tests/sync/app_tests.cpp index 7b9f17c3c..42196fb46 100644 --- a/tests/sync/app_tests.cpp +++ b/tests/sync/app_tests.cpp @@ -70,7 +70,10 @@ static std::string create_jwt(const std::string& appId) using namespace realm; TEST_CASE("app", "[app]") { - auto app = realm::App(realm::App::configuration({Admin::Session::shared().cached_app_id(), Admin::Session::shared().base_url()})); + auto config = realm::App::configuration(); + config.app_id = Admin::Session::shared().cached_app_id(); + config.base_url = Admin::Session::shared().base_url(); + auto app = realm::App(config); SECTION("base_url") { auto no_url_provided_app = realm::App(realm::App::configuration({"NA"})); diff --git a/tests/sync/client_reset_tests.cpp b/tests/sync/client_reset_tests.cpp index 713c54c9c..b9000b621 100644 --- a/tests/sync/client_reset_tests.cpp +++ b/tests/sync/client_reset_tests.cpp @@ -53,7 +53,11 @@ void prepare_realm(const realm::db_config& flx_sync_config, const user& sync_use TEST_CASE("client_reset", "[sync]") { SECTION("error handler") { - auto app = realm::App(realm::App::configuration({Admin::Session::shared().create_app({"str_col", "_id"}), Admin::Session::shared().base_url()})); + auto config = realm::App::configuration(); + config.app_id = Admin::Session::shared().create_app({"str_col", "_id"}); + config.base_url = Admin::Session::shared().base_url(); + auto app = realm::App(config); + app.get_sync_manager().set_log_level(logger::level::all); auto user = app.login(realm::App::credentials::anonymous()).get(); auto flx_sync_config = user.flexible_sync_configuration(); @@ -76,7 +80,11 @@ TEST_CASE("client_reset", "[sync]") { } SECTION("manual handler") { - auto app = realm::App(realm::App::configuration({Admin::Session::shared().create_app({"_id", "str_col"}, "test", false, false), Admin::Session::shared().base_url()})); + auto config = realm::App::configuration(); + config.app_id = Admin::Session::shared().create_app({"_id", "str_col"}, "test", false, false); + config.base_url = Admin::Session::shared().base_url(); + auto app = realm::App(config); + app.get_sync_manager().set_log_level(logger::level::all); auto user = app.login(realm::App::credentials::anonymous()).get(); auto flx_sync_config = user.flexible_sync_configuration(); @@ -125,7 +133,10 @@ TEST_CASE("client_reset", "[sync]") { } SECTION("discard_unsynced_changes") { - auto app = realm::App(realm::App::configuration({Admin::Session::shared().create_app({"str_col", "_id"}, "test", false, false), Admin::Session::shared().base_url()})); + auto config = realm::App::configuration(); + config.app_id = Admin::Session::shared().create_app({"_id", "str_col"}, "test", false, false); + config.base_url = Admin::Session::shared().base_url(); + auto app = realm::App(config); app.get_sync_manager().set_log_level(logger::level::all); auto user = app.login(realm::App::credentials::anonymous()).get(); @@ -167,7 +178,10 @@ TEST_CASE("client_reset", "[sync]") { } SECTION("recover_or_discard_unsynced_changes") { - auto app = realm::App(realm::App::configuration({Admin::Session::shared().create_app({"str_col", "_id"}, "test", false, false), Admin::Session::shared().base_url()})); + auto config = realm::App::configuration(); + config.app_id = Admin::Session::shared().create_app({"_id", "str_col"}, "test", false, false); + config.base_url = Admin::Session::shared().base_url(); + auto app = realm::App(config); app.get_sync_manager().set_log_level(logger::level::all); auto user = app.login(realm::App::credentials::anonymous()).get(); auto flx_sync_config = user.flexible_sync_configuration(); @@ -208,7 +222,10 @@ TEST_CASE("client_reset", "[sync]") { } SECTION("recover_unsynced_changes") { - auto app = realm::App(realm::App::configuration({Admin::Session::shared().create_app({"str_col", "_id"}, "test", false, false), Admin::Session::shared().base_url()})); + auto config = realm::App::configuration(); + config.app_id = Admin::Session::shared().create_app({"_id", "str_col"}, "test", false, false); + config.base_url = Admin::Session::shared().base_url(); + auto app = realm::App(config); app.get_sync_manager().set_log_level(logger::level::all); auto user = app.login(realm::App::credentials::anonymous()).get(); auto flx_sync_config = user.flexible_sync_configuration(); @@ -249,7 +266,10 @@ TEST_CASE("client_reset", "[sync]") { } SECTION("recover_unsynced_changes_with_failure") { - auto app = realm::App(realm::App::configuration({Admin::Session::shared().create_app({"str_col", "_id"}, "test", false, true), Admin::Session::shared().base_url()})); + auto config = realm::App::configuration(); + config.app_id = Admin::Session::shared().create_app({"_id", "str_col"}, "test", false, true); + config.base_url = Admin::Session::shared().base_url(); + auto app = realm::App(config); app.get_sync_manager().set_log_level(logger::level::all); auto user = app.login(realm::App::credentials::anonymous()).get(); auto flx_sync_config = user.flexible_sync_configuration(); diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp index 920e6b646..af7056e17 100644 --- a/tests/sync/networking_tests.cpp +++ b/tests/sync/networking_tests.cpp @@ -121,7 +121,7 @@ TEST_CASE("custom transport to proxy", "[proxy]") { TEST_CASE("built in transport to proxy roundtrip", "[proxy]") { tests::utils::proxy_server::config cfg; - cfg.port = 1234; + cfg.port = 1235; cfg.server_uses_ssl = false; // Set to true if using services.cloud.mongodb.com tests::utils::proxy_server server(std::move(cfg)); @@ -137,7 +137,7 @@ TEST_CASE("built in transport to proxy roundtrip", "[proxy]") { auto transport_config = ::realm::networking::default_transport_configuration(); proxy_config pc; - pc.port = 1234; + pc.port = 1235; pc.address = "127.0.0.1"; transport_config.proxy_config = pc; diff --git a/tests/utils/networking/proxy_server.cpp b/tests/utils/networking/proxy_server.cpp index 2e2344d51..61947dbd3 100644 --- a/tests/utils/networking/proxy_server.cpp +++ b/tests/utils/networking/proxy_server.cpp @@ -68,9 +68,7 @@ namespace realm::tests::utils { } else { if (ec == asio::error::eof) { std::cout << "Connection closed by peer (EOF)." << std::endl; - return; } - REALM_TERMINATE("Proxy: Error resolving host"); } }); } else { @@ -91,9 +89,7 @@ namespace realm::tests::utils { } else { if (ec == asio::error::eof) { std::cout << "Connection closed by peer (EOF)." << std::endl; - return; } - REALM_TERMINATE("Proxy: Error connecting to server"); } }); } else { @@ -106,9 +102,7 @@ namespace realm::tests::utils { } else { if (ec == asio::error::eof) { std::cout << "Connection closed by peer (EOF)." << std::endl; - return; } - REALM_TERMINATE("Proxy: Error connecting to server"); } }); } @@ -163,9 +157,7 @@ namespace realm::tests::utils { } else { if (ec == asio::error::eof) { std::cout << "Connection closed by peer (EOF)." << std::endl; - return; } - REALM_TERMINATE("Proxy: Error writing to server."); } }); } @@ -182,9 +174,7 @@ namespace realm::tests::utils { } else { if (ec == asio::error::eof) { std::cout << "Connection closed by peer (EOF)." << std::endl; - return; } - REALM_TERMINATE("Proxy: Error reading from server."); } }); } @@ -207,9 +197,7 @@ namespace realm::tests::utils { } else { if (ec == asio::error::eof) { std::cout << "Connection closed by peer (EOF)." << std::endl; - return; } - REALM_TERMINATE("Proxy: Error reading from client."); } }); } @@ -227,9 +215,7 @@ namespace realm::tests::utils { } else { if (ec == asio::error::eof) { std::cout << "Connection closed by peer (EOF)." << std::endl; - return; } - REALM_TERMINATE("Proxy: Error reading from websocket client."); } }); @@ -257,9 +243,7 @@ namespace realm::tests::utils { } else { if (ec == asio::error::eof) { std::cout << "Connection closed by peer (EOF)." << std::endl; - return; } - REALM_TERMINATE("Proxy: Error writing to websocket server."); } }); } @@ -279,9 +263,7 @@ namespace realm::tests::utils { } else { if (ec == asio::error::eof) { std::cout << "Connection closed by peer (EOF)." << std::endl; - return; } - REALM_TERMINATE("Proxy: Error reading from websocket server."); } }); } else { @@ -293,9 +275,7 @@ namespace realm::tests::utils { } else { if (ec == asio::error::eof) { std::cout << "Connection closed by peer (EOF)." << std::endl; - return; } - REALM_TERMINATE("Proxy: Error reading from websocket server."); } }); } @@ -313,9 +293,7 @@ namespace realm::tests::utils { } else { if (ec == asio::error::eof) { std::cout << "Connection closed by peer (EOF)." << std::endl; - return; } - REALM_TERMINATE("Proxy: Error writing from websocket client."); } }); } @@ -340,9 +318,7 @@ namespace realm::tests::utils { } else { if (ec == asio::error::eof) { std::cout << "Connection closed by peer (EOF)." << std::endl; - return; } - REALM_TERMINATE("Proxy: Error writing to server."); } }); } @@ -359,9 +335,7 @@ namespace realm::tests::utils { } else { if (ec == asio::error::eof) { std::cout << "Connection closed by peer (EOF)." << std::endl; - return; } - REALM_TERMINATE("Proxy: Error reading from server."); } }); } @@ -389,9 +363,7 @@ namespace realm::tests::utils { } else { if (ec == asio::error::eof) { std::cout << "Connection closed by peer (EOF)." << std::endl; - return; } - REALM_TERMINATE("Proxy: Error writing to client."); } }); } From c93dfcdcfb3ffd55c898ea5f515e145a9aa82c84 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Thu, 11 Jul 2024 12:59:40 +0100 Subject: [PATCH 24/41] Cleanup --- .../cpprealm/internal/networking/utils.hpp | 53 +++++++++++++++++++ include/cpprealm/networking/networking.hpp | 26 --------- .../networking/platform_networking.hpp | 3 +- src/CMakeLists.txt | 3 +- .../internal/apple/network_transport.mm | 15 +++--- src/cpprealm/internal/networking/shims.cpp | 7 +-- .../networking/utils.cpp} | 2 +- .../networking/platform_networking.cpp | 1 + tests/admin_utils.cpp | 1 + tests/sync/asymmetric_object_tests.cpp | 5 +- tests/sync/networking_tests.cpp | 3 ++ 11 files changed, 78 insertions(+), 41 deletions(-) create mode 100644 include/cpprealm/internal/networking/utils.hpp rename src/cpprealm/{networking/networking.cpp => internal/networking/utils.cpp} (99%) diff --git a/include/cpprealm/internal/networking/utils.hpp b/include/cpprealm/internal/networking/utils.hpp new file mode 100644 index 000000000..ac2e4cfe2 --- /dev/null +++ b/include/cpprealm/internal/networking/utils.hpp @@ -0,0 +1,53 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef CPPREALM_NETWORKING_UTILS_HPP +#define CPPREALM_NETWORKING_UTILS_HPP + +#include + +namespace realm { + namespace app { + struct Request; + struct Response; + } + namespace sync { + struct WebSocketEndpoint; + } + + namespace networking { + struct default_transport_configuration; + struct request; + struct response; + struct websocket_endpoint; + } +} + +namespace realm::internal::networking { + ::realm::networking::request to_request(const ::realm::app::Request&); + ::realm::app::Request to_core_request(const ::realm::networking::request&); + + ::realm::networking::response to_response(const ::realm::app::Response&); + ::realm::app::Response to_core_response(const ::realm::networking::response&); + + ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint& ep, + const std::optional<::realm::networking::default_transport_configuration>& config); + ::realm::networking::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& ep); +} //namespace realm::internal::networking + +#endif //CPPREALM_NETWORKING_UTILS_HPP diff --git a/include/cpprealm/networking/networking.hpp b/include/cpprealm/networking/networking.hpp index 61bc0e8be..d153c6768 100644 --- a/include/cpprealm/networking/networking.hpp +++ b/include/cpprealm/networking/networking.hpp @@ -140,30 +140,4 @@ namespace realm::networking { } //namespace realm::networking -namespace realm { - namespace app { - struct Request; - struct Response; - } - namespace sync { - struct WebSocketEndpoint; - } - - namespace networking { - struct default_transport_configuration; - } -} - -namespace realm::internal::networking { - ::realm::networking::request to_request(const ::realm::app::Request&); - ::realm::app::Request to_core_request(const ::realm::networking::request&); - - ::realm::networking::response to_response(const ::realm::app::Response&); - ::realm::app::Response to_core_response(const ::realm::networking::response&); - - ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint& ep, - const std::optional<::realm::networking::default_transport_configuration>& config); - ::realm::networking::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& ep); -} //namespace realm::internal::networking - #endif//CPPREALM_NETWORKING_HPP diff --git a/include/cpprealm/networking/platform_networking.hpp b/include/cpprealm/networking/platform_networking.hpp index 1ecec328e..a472850f8 100644 --- a/include/cpprealm/networking/platform_networking.hpp +++ b/include/cpprealm/networking/platform_networking.hpp @@ -43,7 +43,7 @@ namespace realm { namespace realm::networking { - using status = internal::bridge::status; + using status = ::realm::internal::bridge::status; /// The WebSocket base class that is used by the SyncClient to send data over the /// WebSocket connection with the server. This is the class that is returned by @@ -55,7 +55,6 @@ namespace realm::networking { /// is destroyed virtual ~websocket_interface() = default; - using status = status; using FunctionHandler = std::function; /// Write data asynchronously to the WebSocket connection. The handler function diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7c911d733..994018679 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -52,8 +52,8 @@ set(SOURCES cpprealm/internal/bridge/timestamp.cpp cpprealm/internal/bridge/uuid.cpp cpprealm/internal/networking/shims.cpp + cpprealm/internal/networking/utils.cpp cpprealm/internal/scheduler/realm_core_scheduler.cpp - cpprealm/networking/networking.cpp cpprealm/networking/platform_networking.cpp cpprealm/schedulers/default_scheduler.cpp cpprealm/logger.cpp @@ -114,6 +114,7 @@ set(HEADERS ../include/cpprealm/internal/bridge/uuid.hpp ../include/cpprealm/internal/type_info.hpp ../include/cpprealm/internal/networking/shims.hpp + ../include/cpprealm/internal/networking/utils.hpp ../include/cpprealm/internal/scheduler/realm_core_scheduler.hpp ../include/cpprealm/schedulers/default_scheduler.hpp ../include/cpprealm/networking/networking.hpp diff --git a/src/cpprealm/internal/apple/network_transport.mm b/src/cpprealm/internal/apple/network_transport.mm index f99be6590..8841f2f9b 100644 --- a/src/cpprealm/internal/apple/network_transport.mm +++ b/src/cpprealm/internal/apple/network_transport.mm @@ -58,8 +58,8 @@ [urlRequest addValue:[NSString stringWithCString:header.second.c_str() encoding:NSUTF8StringEncoding] forHTTPHeaderField:[NSString stringWithCString:header.first.c_str() encoding:NSUTF8StringEncoding]]; } - if (m_custom_http_headers) { - for (auto& header : *m_custom_http_headers) { + if (m_configuration.custom_http_headers) { + for (auto& header : *m_configuration.custom_http_headers) { [urlRequest addValue:[NSString stringWithCString:header.second.c_str() encoding:NSUTF8StringEncoding] forHTTPHeaderField:[NSString stringWithCString:header.first.c_str() encoding:NSUTF8StringEncoding]]; } @@ -70,10 +70,10 @@ } NSURLSession *session; - if (m_proxy_config) { + if (m_configuration.proxy_config) { NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSString *proxyHost = @(m_proxy_config->address.c_str()); - NSInteger proxyPort = m_proxy_config->port; + NSString *proxyHost = @(m_configuration.proxy_config->address.c_str()); + NSInteger proxyPort = m_configuration.proxy_config->port; sessionConfiguration.connectionProxyDictionary = @{ @"HTTPSEnable": @YES, @"HTTPSProxy": @(proxyPort), @@ -84,8 +84,9 @@ [[NSURLCache sharedURLCache] removeAllCachedResponses]; session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; - if (m_proxy_config->username_password) { - auto userpass = util::format("%1:%2", m_proxy_config->username_password->first, m_proxy_config->username_password->second); + if (m_configuration.proxy_config->username_password) { + auto userpass = util::format("%1:%2", m_configuration.proxy_config->username_password->first, + m_configuration.proxy_config->username_password->second); std::string encoded_userpass; encoded_userpass.resize(realm::util::base64_encoded_size(userpass.length())); realm::util::base64_encode(userpass, encoded_userpass); diff --git a/src/cpprealm/internal/networking/shims.cpp b/src/cpprealm/internal/networking/shims.cpp index 9b09b531a..19c5b249d 100644 --- a/src/cpprealm/internal/networking/shims.cpp +++ b/src/cpprealm/internal/networking/shims.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -14,7 +15,7 @@ namespace realm::internal::networking { void async_write_binary(util::Span data, sync::SyncSocketProvider::FunctionHandler&& handler) override { auto handler_ptr = handler.release(); auto b = std::string_view(data.data(), data.size()); - m_interface->async_write_binary(b, [ptr = std::move(handler_ptr)](::realm::networking::websocket_interface::status s) { + m_interface->async_write_binary(b, [ptr = std::move(handler_ptr)](::realm::networking::status s) { auto uf = util::UniqueFunction(std::move(ptr)); return uf(s.operator ::realm::Status()); }); @@ -108,7 +109,7 @@ namespace realm::internal::networking { void post(FunctionHandler&& handler) override { auto handler_ptr = handler.release(); - m_provider->post([ptr = std::move(handler_ptr)](::realm::networking::websocket_interface::status s) { + m_provider->post([ptr = std::move(handler_ptr)](::realm::networking::status s) { auto uf = util::UniqueFunction(std::move(ptr)); return uf(s.operator ::realm::Status()); }); @@ -116,7 +117,7 @@ namespace realm::internal::networking { ::realm::sync::SyncSocketProvider::SyncTimer create_timer(std::chrono::milliseconds delay, ::realm::sync::SyncSocketProvider::FunctionHandler&& handler) override { auto handler_ptr = handler.release(); - auto fn = [ptr = std::move(handler_ptr)](::realm::networking::websocket_interface::status s) { + auto fn = [ptr = std::move(handler_ptr)](::realm::networking::status s) { auto uf = util::UniqueFunction(std::move(ptr)); return uf(s.operator ::realm::Status()); }; diff --git a/src/cpprealm/networking/networking.cpp b/src/cpprealm/internal/networking/utils.cpp similarity index 99% rename from src/cpprealm/networking/networking.cpp rename to src/cpprealm/internal/networking/utils.cpp index 66810df09..95f5023ca 100644 --- a/src/cpprealm/networking/networking.cpp +++ b/src/cpprealm/internal/networking/utils.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/platform_networking.cpp index 82d895632..7103d9831 100644 --- a/src/cpprealm/networking/platform_networking.cpp +++ b/src/cpprealm/networking/platform_networking.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include diff --git a/tests/admin_utils.cpp b/tests/admin_utils.cpp index a64c6865a..2a7627046 100644 --- a/tests/admin_utils.cpp +++ b/tests/admin_utils.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "admin_utils.hpp" #include "external/json/json.hpp" diff --git a/tests/sync/asymmetric_object_tests.cpp b/tests/sync/asymmetric_object_tests.cpp index 9b1754fc7..6b33734d1 100644 --- a/tests/sync/asymmetric_object_tests.cpp +++ b/tests/sync/asymmetric_object_tests.cpp @@ -7,7 +7,10 @@ using namespace realm; TEST_CASE("asymmetric object", "[sync]") { SECTION("basic", "[sync]") { auto asymmetric_app_id = Admin::Session::shared().create_app({}, "test", true); - auto app = realm::App(realm::App::configuration({asymmetric_app_id, Admin::Session::shared().base_url()})); + auto config = realm::App::configuration(); + config.app_id = asymmetric_app_id; + config.base_url = Admin::Session::shared().base_url(); + auto app = realm::App(config); auto user = app.login(realm::App::credentials::anonymous()).get(); auto synced_realm = open(user.flexible_sync_configuration()); diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp index af7056e17..b7b26bd3f 100644 --- a/tests/sync/networking_tests.cpp +++ b/tests/sync/networking_tests.cpp @@ -3,6 +3,9 @@ #include "test_objects.hpp" #include "../utils/networking/proxy_server.hpp" +#include + +#include using namespace realm; From f874f9aae230a25e70482389426cdcaac22eac0a Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Thu, 11 Jul 2024 13:35:23 +0100 Subject: [PATCH 25/41] Cleanup --- include/cpprealm/app.hpp | 5 ----- src/cpprealm/app.cpp | 12 ------------ tests/sync/app_tests.cpp | 25 ++++++++++++++++++++----- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/include/cpprealm/app.hpp b/include/cpprealm/app.hpp index 9e0d6189c..2b5856f26 100644 --- a/include/cpprealm/app.hpp +++ b/include/cpprealm/app.hpp @@ -278,11 +278,6 @@ class App { }; - [[deprecated("Use App(const configuration&) instead.")]] - explicit App(const std::string& app_id, - const std::optional& base_url = {}, - const std::optional& path = {}, - const std::optional>& custom_http_headers = {}); App(const configuration&); diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index 2c9231ae1..8cbdadec4 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -532,18 +532,6 @@ namespace realm { std::move(app_config)); } - App::App(const std::string &app_id, - const std::optional &base_url, - const std::optional &path, - const std::optional> &custom_http_headers) { - configuration c; - c.app_id = app_id; - c.base_url = base_url; - c.path = path; - c.custom_http_headers = custom_http_headers; - *this = App(std::move(c)); - } - std::string App::get_base_url() const { return m_app->get_base_url(); } diff --git a/tests/sync/app_tests.cpp b/tests/sync/app_tests.cpp index 42196fb46..add855d61 100644 --- a/tests/sync/app_tests.cpp +++ b/tests/sync/app_tests.cpp @@ -76,15 +76,23 @@ TEST_CASE("app", "[app]") { auto app = realm::App(config); SECTION("base_url") { - auto no_url_provided_app = realm::App(realm::App::configuration({"NA"})); + auto config = realm::App::configuration(); + config.app_id = "NA"; + auto no_url_provided_app = realm::App(config); CHECK(no_url_provided_app.get_base_url() == "https://services.cloud.mongodb.com"); - auto with_url_provided_app = realm::App(realm::App::configuration({"NA", "https://foobar.com"})); + + auto config2 = realm::App::configuration(); + config2.app_id = "NA"; + config2.base_url = "https://foobar.com"; + auto with_url_provided_app = realm::App(config2); CHECK(with_url_provided_app.get_base_url() == "https://foobar.com"); } #ifdef REALM_ENABLE_EXPERIMENTAL SECTION("update_base_url") { - auto no_url_provided_app = realm::App(realm::App::configuration({"NA"})); + auto config = realm::App::configuration(); + config.app_id = "NA"; + auto no_url_provided_app = realm::App(config); CHECK(no_url_provided_app.get_base_url() == "https://services.cloud.mongodb.com"); REQUIRE_THROWS_AS(no_url_provided_app.update_base_url("https://foobar.com").get(), realm::app_error); CHECK(no_url_provided_app.get_base_url() == "https://services.cloud.mongodb.com"); @@ -126,7 +134,11 @@ TEST_CASE("app", "[app]") { SECTION("clear_cached_apps") { auto temp_app_id = Admin::Session::shared().create_app({"str_col", "_id"}); - auto temp_app = realm::App(realm::App::configuration({temp_app_id, Admin::Session::shared().base_url()})); + auto config = realm::App::configuration(); + config.app_id = temp_app_id; + config.base_url = Admin::Session::shared().base_url(); + + auto temp_app = realm::App(config); auto cached_app = temp_app.get_cached_app(temp_app_id, Admin::Session::shared().base_url()); CHECK(cached_app.has_value()); app.clear_cached_apps(); @@ -135,7 +147,10 @@ TEST_CASE("app", "[app]") { } SECTION("error handling") { - auto dead_app = realm::App(realm::App::configuration({"NA", Admin::Session::shared().base_url()})); + auto config = realm::App::configuration(); + config.app_id = "NA"; + config.base_url = Admin::Session::shared().base_url(); + auto dead_app = realm::App(config); REQUIRE_THROWS_AS(dead_app.login(realm::App::credentials::anonymous()).get(), realm::app_error); REQUIRE_THROWS_AS(dead_app.register_user("", "").get(), realm::app_error); From c62ef0ae2b10794d3ea75b6a668b334e4b7d2233 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Thu, 11 Jul 2024 19:12:56 +0100 Subject: [PATCH 26/41] Fix tests --- tests/utils/networking/proxy_server.cpp | 46 +++++++++++++------------ tests/utils/networking/proxy_server.hpp | 4 +-- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/tests/utils/networking/proxy_server.cpp b/tests/utils/networking/proxy_server.cpp index 61947dbd3..b83840034 100644 --- a/tests/utils/networking/proxy_server.cpp +++ b/tests/utils/networking/proxy_server.cpp @@ -10,14 +10,16 @@ namespace realm::tests::utils { class proxy_session : public std::enable_shared_from_this { public: - proxy_session(tcp::socket client_socket, std::shared_ptr ctx, bool server_uses_ssl, std::function fn) + proxy_session(tcp::socket client_socket, std::shared_ptr ctx, + bool server_uses_ssl, + const std::shared_ptr>& fn) : m_client_socket(std::move(client_socket)), m_server_socket(m_client_socket.get_executor()), m_resolver(m_client_socket.get_executor()), m_ssl_ctx(ctx), m_ssl_server_socket(m_client_socket.get_executor(), *ctx), m_server_uses_ssl(server_uses_ssl), - m_event_handler(std::move(fn)) {} + m_event_handler(fn) {} void start() { do_read_client_request(); @@ -53,7 +55,7 @@ namespace realm::tests::utils { std::string port = request.substr(host_end + 1, request.find(" ", host_end) - host_end - 1); if (m_event_handler) { - m_event_handler(proxy_server::event::connect); + m_event_handler->operator()(proxy_server::event::connect); } if (host == "127.0.0.1" || host == "localhost") { @@ -112,7 +114,7 @@ namespace realm::tests::utils { auto self(shared_from_this()); if (m_event_handler) { - m_event_handler(proxy_server::event::ssl_handshake); + m_event_handler->operator()(proxy_server::event::ssl_handshake); } m_ssl_server_socket.set_verify_callback(asio::ssl::host_name_verification(hostname)); @@ -135,7 +137,7 @@ namespace realm::tests::utils { void do_write_connect_response() { auto self(shared_from_this()); - asio::async_write(m_client_socket, asio::buffer(std::string("HTTP/1.1 200 Connection Established\r\n\r\n")), + asio::async_write(m_client_socket, asio::buffer(std::string_view("HTTP/1.1 200 Connection Established\r\n\r\n")), [this, self](auto ec, std::size_t) { if (!ec) { do_read_client(); @@ -148,9 +150,9 @@ namespace realm::tests::utils { void do_ssl_write_to_server(std::size_t length) { auto self(shared_from_this()); if (m_event_handler) { - m_event_handler(proxy_server::event::ssl); + m_event_handler->operator()(proxy_server::event::ssl); } - asio::async_write(m_ssl_server_socket, asio::buffer(std::string(m_client_buffer.data()), length), + asio::async_write(m_ssl_server_socket, asio::buffer(std::string_view(m_client_buffer.data()), length), [this, self](auto ec, std::size_t) { if (!ec) { do_ssl_read_server(); @@ -165,7 +167,7 @@ namespace realm::tests::utils { void do_ssl_read_server() { auto self(shared_from_this()); if (m_event_handler) { - m_event_handler(proxy_server::event::ssl); + m_event_handler->operator()(proxy_server::event::ssl); } m_ssl_server_socket.async_read_some(asio::buffer(m_server_buffer), [this, self](auto ec, std::size_t length) { @@ -182,12 +184,12 @@ namespace realm::tests::utils { void do_read_client() { auto self(shared_from_this()); if (m_event_handler) { - m_event_handler(proxy_server::event::client); + m_event_handler->operator()(proxy_server::event::client); } m_client_socket.async_read_some(asio::buffer(m_client_buffer), [this, self](auto ec, std::size_t length) { if (!ec) { - auto req = std::string(m_client_buffer.data(), length); + auto req = std::string_view(m_client_buffer.data(), length); std::cout << "Client to Server: " << req << std::endl; if (m_server_uses_ssl) { do_ssl_write_to_server(length); @@ -205,7 +207,7 @@ namespace realm::tests::utils { void do_read_from_websocket_client() { auto self(shared_from_this()); if (m_event_handler) { - m_event_handler(proxy_server::event::websocket); + m_event_handler->operator()(proxy_server::event::websocket); } m_client_socket.async_read_some(asio::buffer(m_client_buffer), [this, self](auto ec, std::size_t length) { @@ -224,7 +226,7 @@ namespace realm::tests::utils { void do_write_to_websocket_server(std::size_t length) { auto self(shared_from_this()); if (m_event_handler) { - m_event_handler(proxy_server::event::websocket); + m_event_handler->operator()(proxy_server::event::websocket); } if (m_server_uses_ssl) { asio::async_write(m_ssl_server_socket, asio::buffer(m_client_buffer.data(), length), @@ -237,7 +239,7 @@ namespace realm::tests::utils { }); } else { asio::async_write(m_server_socket, asio::buffer(m_client_buffer.data(), length), - [this, self](auto ec, std::size_t /*length*/) { + [this, self](auto ec, std::size_t) { if (!ec) { do_read_from_websocket_client(); } else { @@ -252,7 +254,7 @@ namespace realm::tests::utils { void do_read_from_websocket_server() { auto self(shared_from_this()); if (m_event_handler) { - m_event_handler(proxy_server::event::websocket); + m_event_handler->operator()(proxy_server::event::websocket); } if (m_server_uses_ssl) { m_ssl_server_socket.async_read_some(asio::buffer(m_server_buffer), @@ -284,7 +286,7 @@ namespace realm::tests::utils { void do_write_to_websocket_client(std::size_t length) { auto self(shared_from_this()); if (m_event_handler) { - m_event_handler(proxy_server::event::websocket); + m_event_handler->operator()(proxy_server::event::websocket); } asio::async_write(m_client_socket, asio::buffer(m_server_buffer.data(), length), [this, self](auto ec, std::size_t) { @@ -300,7 +302,7 @@ namespace realm::tests::utils { void upgrade_client_to_websocket(std::size_t length) { if (m_event_handler) { - m_event_handler(proxy_server::event::websocket_upgrade); + m_event_handler->operator()(proxy_server::event::websocket_upgrade); } do_read_from_websocket_client(); do_read_from_websocket_server(); @@ -309,9 +311,9 @@ namespace realm::tests::utils { void do_write_to_server(std::size_t length) { auto self(shared_from_this()); if (m_event_handler) { - m_event_handler(proxy_server::event::nonssl); + m_event_handler->operator()(proxy_server::event::nonssl); } - asio::async_write(m_server_socket, asio::buffer(std::string(m_client_buffer.data()), length), + asio::async_write(m_server_socket, asio::buffer(std::string_view(m_client_buffer.data()), length), [this, self](auto ec, std::size_t) { if (!ec) { do_read_server(); @@ -326,7 +328,7 @@ namespace realm::tests::utils { void do_read_server() { auto self(shared_from_this()); if (m_event_handler) { - m_event_handler(proxy_server::event::nonssl); + m_event_handler->operator()(proxy_server::event::nonssl); } m_server_socket.async_read_some(asio::buffer(m_server_buffer), [this, self](auto ec, std::size_t length) { @@ -343,12 +345,12 @@ namespace realm::tests::utils { void do_write_to_client(std::size_t length) { auto self(shared_from_this()); if (m_event_handler) { - m_event_handler(proxy_server::event::client); + m_event_handler->operator()(proxy_server::event::client); } auto res = std::string(m_server_buffer.data(), length); bool upgrade_to_websocket = res.find("HTTP/1.1 101 Switching Protocols") != std::string::npos; - asio::async_write(m_client_socket, asio::buffer(std::string(m_server_buffer.data()), length), + asio::async_write(m_client_socket, asio::buffer(m_server_buffer.data(), length), [this, self, upgrade_to_websocket](auto ec, std::size_t bytes_written) { if (!ec) { if (upgrade_to_websocket) { @@ -382,7 +384,7 @@ namespace realm::tests::utils { std::array m_server_buffer; const std::string server_endpoint = "services.cloud.mongodb.com"; - std::function m_event_handler; + std::shared_ptr> m_event_handler; }; proxy_server::proxy_server(const config &cfg) : m_config(cfg), m_strand(m_io_context) { diff --git a/tests/utils/networking/proxy_server.hpp b/tests/utils/networking/proxy_server.hpp index 54a071046..99ac0fb5e 100644 --- a/tests/utils/networking/proxy_server.hpp +++ b/tests/utils/networking/proxy_server.hpp @@ -29,7 +29,7 @@ namespace realm::tests::utils { // Sets an event handler callback void set_callback(std::function fn) { - m_event_handler = std::move(fn); + m_event_handler = std::make_shared>(std::move(fn)); } private: void do_accept(); @@ -40,7 +40,7 @@ namespace realm::tests::utils { std::mutex m_mutex; std::thread m_io_thread; asio::io_context::strand m_strand; - std::function m_event_handler; + std::shared_ptr> m_event_handler; }; } From 0bfd4bbeb64c6cd7438dc7947e70226096c1ecbc Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Thu, 11 Jul 2024 19:38:29 +0100 Subject: [PATCH 27/41] point to yg/openssl-native-ca --- realm-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/realm-core b/realm-core index f3d7ae5f9..c0bea32d6 160000 --- a/realm-core +++ b/realm-core @@ -1 +1 @@ -Subproject commit f3d7ae5f9f31d90b327a64536bb7801cc69fd85b +Subproject commit c0bea32d6c425d9da50258696bec2ddc63d0b623 From 9a2e507a5a4258ca7ab4a8adac12cb7ba66b0eb8 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Thu, 11 Jul 2024 21:08:27 +0100 Subject: [PATCH 28/41] Try out use_default_verify --- src/cpprealm/internal/network/network_transport.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/cpprealm/internal/network/network_transport.cpp b/src/cpprealm/internal/network/network_transport.cpp index 2110c0c61..bcdc8e708 100644 --- a/src/cpprealm/internal/network/network_transport.cpp +++ b/src/cpprealm/internal/network/network_transport.cpp @@ -224,9 +224,7 @@ namespace realm::networking { if (m_configuration.ssl_trust_certificate_path) { m_ssl_context.use_certificate_chain_file(*m_configuration.ssl_trust_certificate_path); } else { -#if REALM_INCLUDE_CERTS - m_ssl_context.use_included_certificate_roots(); -#endif + m_ssl_context.use_default_verify(); } if (url_scheme == URLScheme::HTTPS) { From 9ffec41dac2dc02d1f8a6734de0c6404c57a476e Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Fri, 12 Jul 2024 14:09:16 +0100 Subject: [PATCH 29/41] Add back use_included_certificate_roots --- .../internal/network/network_transport.cpp | 3 + tests/sync/networking_tests.cpp | 104 ------------------ 2 files changed, 3 insertions(+), 104 deletions(-) diff --git a/src/cpprealm/internal/network/network_transport.cpp b/src/cpprealm/internal/network/network_transport.cpp index bcdc8e708..f35721820 100644 --- a/src/cpprealm/internal/network/network_transport.cpp +++ b/src/cpprealm/internal/network/network_transport.cpp @@ -221,6 +221,9 @@ namespace realm::networking { service.reset(); } +#if REALM_INCLUDE_CERTS + m_ssl_context.use_included_certificate_roots(); +#endif if (m_configuration.ssl_trust_certificate_path) { m_ssl_context.use_certificate_chain_file(*m_configuration.ssl_trust_certificate_path); } else { diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp index b7b26bd3f..7adf61bf4 100644 --- a/tests/sync/networking_tests.cpp +++ b/tests/sync/networking_tests.cpp @@ -212,108 +212,4 @@ TEST_CASE("WebsocketEndpoint", "[proxy]") { ep.url = "ws://my-server.com:80"; res = ::realm::internal::networking::to_core_websocket_endpoint(ep, std::nullopt); CHECK_FALSE(res.is_ssl); -} - -TEST_CASE("proxy custom impl", "[proxy]") { - // Naive round-trip test to ensure custom socket impl's succeed. - realm::App::configuration config; - config.app_id = Admin::Session::shared().create_app({"str_col", "_id"}, "test", false, false); - config.base_url = Admin::Session::shared().base_url(); - config.enable_caching = false; - - std::promise websocket_promise; - std::future websocket_future = websocket_promise.get_future(); - - struct mock_websocket : public ::realm::networking::websocket_interface { - mock_websocket(std::unique_ptr<::realm::networking::websocket_observer>&& observer, std::promise&& p) : m_websocket_promise(std::move(p)) { - std::thread([o = std::move(observer)]() { - std::this_thread::sleep_for(std::chrono::seconds(1)); - o->websocket_connected_handler("com.mongodb.realm-query-sync#13"); - }).detach(); - } - - void async_write_binary(std::string_view data, FunctionHandler&& handler) override { - m_websocket_promise.set_value(); - } - - private: - std::promise m_websocket_promise; - }; - - struct mock_socket_provider : public ::realm::networking::sync_socket_provider { - std::unique_ptr<::realm::networking::websocket_interface> connect( - std::unique_ptr<::realm::networking::websocket_observer> observer, - ::realm::networking::websocket_endpoint&& endpoint) override { - m_called = true; - return std::make_unique(std::move(observer), std::move(m_websocket_promise)); - } - - mock_socket_provider(std::promise&& p) : m_websocket_promise(std::move(p)) { - worker_thread = std::thread([this]() { - while (running) { - std::vector local_queue; - { - std::unique_lock lock(mtx); - if (m_queue.empty()) { - lock.unlock(); - std::this_thread::sleep_for(std::chrono::seconds(1)); - continue; - } - local_queue.swap(m_queue); - } - - for (auto fn : local_queue) { - fn(::realm::networking::status::ok()); - } - } - }); - worker_thread.detach(); - } - - ~mock_socket_provider() { - running = false; - if (worker_thread.joinable()) { - worker_thread.join(); - } - } - - void post(FunctionHandler&& handler) override { - std::lock_guard guard(mtx); - m_queue.push_back(std::move(handler)); - } - - struct timer : sync_socket_provider::timer { - ~timer() = default; - void cancel() override { } - }; - - std::unique_ptr create_timer(std::chrono::milliseconds delay, FunctionHandler&& handler) override { - static bool did_issue_connect = false; - if (!did_issue_connect) - m_queue.push_back(std::move(handler)); - did_issue_connect = true; - return std::make_unique(); - } - - bool was_called() const { - return m_called; - } - - private: - std::vector m_queue; - std::mutex mtx; - bool m_called = false; - std::thread worker_thread; - std::atomic running{true}; - std::promise m_websocket_promise; - }; - - config.sync_socket_provider = std::make_shared(std::move(websocket_promise)); - - auto app = realm::App(config); - - auto user = app.login(realm::App::credentials::anonymous()).get(); - auto flx_sync_config = user.flexible_sync_configuration(); - auto synced_realm = db(flx_sync_config); - websocket_future.get(); } \ No newline at end of file From b61d9feb050bb6e9b122c767d155bef5202495e9 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Fri, 12 Jul 2024 16:26:53 +0100 Subject: [PATCH 30/41] Cleanup TODO --- src/cpprealm/networking/platform_networking.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/platform_networking.cpp index 7103d9831..d7742e523 100644 --- a/src/cpprealm/networking/platform_networking.cpp +++ b/src/cpprealm/networking/platform_networking.cpp @@ -77,10 +77,8 @@ namespace realm::networking { void default_socket_provider::initialize() { auto user_agent_binding_info = std::string("RealmCpp/") + std::string(REALMCXX_VERSION_STRING); - auto user_agent_application_info = "";//app_id; TODO: Should we pass the app id? - - auto user_agent = util::format("RealmSync/%1 (%2) %3 %4", REALM_VERSION_STRING, util::get_platform_info(), - user_agent_binding_info, user_agent_application_info); + auto user_agent = util::format("RealmSync/%1 (%2) %3", REALM_VERSION_STRING, util::get_platform_info(), + user_agent_binding_info); m_provider = std::make_unique<::realm::sync::websocket::DefaultSocketProvider>(util::Logger::get_default_logger(), user_agent); } From 65af4a1189b0e8c17b025ba0a8216629d237ea41 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 15 Jul 2024 09:58:01 +0100 Subject: [PATCH 31/41] Cleanup, add SSL verify callback --- include/cpprealm/app.hpp | 16 -- .../networking/platform_networking.hpp | 17 ++ src/cpprealm/app.cpp | 3 +- .../internal/curl/network_transport.cpp | 176 +++++++++++------- .../internal/network/network_transport.cpp | 8 +- 5 files changed, 129 insertions(+), 91 deletions(-) diff --git a/include/cpprealm/app.hpp b/include/cpprealm/app.hpp index 2b5856f26..d5740771b 100644 --- a/include/cpprealm/app.hpp +++ b/include/cpprealm/app.hpp @@ -240,28 +240,12 @@ class App { std::optional base_url; /// Custom location for Realm files. std::optional path; - /** - * Extra HTTP headers to be set on each request to Atlas Device Sync when using the internal HTTP client. - * - * Note: This has been deprecated and custom network options must now be supplied by either: - * - * - Your own subclass of `realm::networking::http_transport_client` / `realm::networking::sync_socket_provider` - * - Use default_http_transport and pass custom headers & proxy via the constructor. Set via `configuration.http_transport_client`. - */ [[deprecated("Network options must be supplied via custom network implementations.")]] std::optional> custom_http_headers; /// Custom encryption key for the metadata Realm. std::optional> metadata_encryption_key; /// Caches an App and its configuration for a given App ID. On by default. bool enable_caching = true; - /** - * Network proxy configuration to be set on each HTTP and WebSocket request. - * - * Note: This has been deprecated and custom network options must now be supplied by either: - * - * - Your own subclass of `realm::networking::http_transport_client` / `realm::networking::sync_socket_provider` - * - Use default_http_transport and pass custom headers & proxy via the constructor. Set via `configuration.http_transport_client`. - */ [[deprecated("Network options must be supplied via custom network implementations.")]] std::optional proxy_configuration; /** diff --git a/include/cpprealm/networking/platform_networking.hpp b/include/cpprealm/networking/platform_networking.hpp index a472850f8..3e140bbaa 100644 --- a/include/cpprealm/networking/platform_networking.hpp +++ b/include/cpprealm/networking/platform_networking.hpp @@ -229,14 +229,31 @@ namespace realm::networking { void set_http_client_factory(std::function()>&&); struct default_transport_configuration { + /** + * Extra HTTP headers to be set on each request to Atlas Device Sync when using the internal HTTP client. + */ std::optional> custom_http_headers; + /** + * Network proxy configuration to be set on each request. + */ std::optional<::realm::internal::bridge::realm::sync_config::proxy_config> proxy_config; using SSLVerifyCallback = bool(const std::string& server_address, internal::bridge::realm::sync_config::proxy_config::port_type server_port, const char* pem_data, size_t pem_size, int preverify_ok, int depth); + /** + * If set to false, no validation will take place and the client will accept any certificate. + */ bool client_validate_ssl = true; + /** + * Used for providing your own root certificate. + */ std::optional ssl_trust_certificate_path; + /** + * `ssl_verify_callback` is used to implement custom SSL certificate + * verification. It is only used if the protocol is SSL & `ssl_trust_certificate_path` + * is not set. + */ std::function ssl_verify_callback; }; diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index 8cbdadec4..1325f4d4b 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -1,7 +1,7 @@ #include #include -#include #include +#include #ifndef REALMCXX_VERSION_MAJOR #include @@ -13,7 +13,6 @@ #include #include #include -#include #include diff --git a/src/cpprealm/internal/curl/network_transport.cpp b/src/cpprealm/internal/curl/network_transport.cpp index cd9ec953c..d3dfd549b 100644 --- a/src/cpprealm/internal/curl/network_transport.cpp +++ b/src/cpprealm/internal/curl/network_transport.cpp @@ -86,91 +86,123 @@ namespace realm::networking { } return nitems * size; } + } // namespace - static ::realm::networking::response do_http_request(const ::realm::networking::request& request, - const std::optional& proxy_config = std::nullopt) - { - CurlGlobalGuard curl_global_guard; - auto curl = curl_easy_init(); - if (!curl) { - return ::realm::networking::response{500, -1, {}, "", std::nullopt}; - } + using SSLVerifyCallback = std::function; - struct curl_slist* list = nullptr; + CURLcode ssl_ctx_callback(CURL *curl, void */*sslctx*/, SSLVerifyCallback *parm) { + auto verify_callback = (SSLVerifyCallback)(*parm); - std::string response; - ::realm::networking::http_headers response_headers; + char *url; + curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url); + std::string server_address(url); - curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str()); + long port; + curl_easy_getinfo(curl, CURLINFO_PRIMARY_PORT, &port); - if (proxy_config) { - curl_easy_setopt(curl, CURLOPT_PROXY, util::format("%1:%2", proxy_config->address, proxy_config->port).c_str()); - curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L); - if (proxy_config->username_password) { - curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, util::format("%1:%2", proxy_config->username_password->first, proxy_config->username_password->second).c_str()); - } - } + const char *pem_data = "-----BEGIN CERTIFICATE-----\n...certificate data...\n-----END CERTIFICATE-----\n"; + size_t pem_size = strlen(pem_data); - /* Now specify the POST data */ - if (request.method == ::realm::networking::http_method::post) { - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); - } - else if (request.method == ::realm::networking::http_method::put) { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); - } - else if (request.method == ::realm::networking::http_method::patch) { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); - } - else if (request.method == ::realm::networking::http_method::del) { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); - } - else if (request.method == ::realm::networking::http_method::patch) { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); + int preverify_ok = 1; + int depth = 0; + + bool result = verify_callback(server_address, port, pem_data, pem_size, preverify_ok, depth); + return result ? CURLE_OK : CURLE_SSL_CERTPROBLEM; + } + + void default_http_transport::send_request_to_server(const ::realm::networking::request& request, + std::function&& completion_block) { + CurlGlobalGuard curl_global_guard; + auto curl = curl_easy_init(); + if (!curl) { + completion_block(::realm::networking::response{500, -1, {}, "", std::nullopt}); + return; + } + + struct curl_slist* list = nullptr; + + std::string response; + ::realm::networking::http_headers response_headers; + + curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str()); + + + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, m_configuration.client_validate_ssl); + + if (m_configuration.ssl_verify_callback) { + curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_callback); + curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA, &m_configuration.ssl_verify_callback); + } + + if (m_configuration.ssl_trust_certificate_path) { + curl_easy_setopt(curl, CURLOPT_CAINFO, m_configuration.ssl_trust_certificate_path->c_str()); + } + + if (m_configuration.proxy_config) { + curl_easy_setopt(curl, CURLOPT_PROXY, util::format("%1:%2", m_configuration.proxy_config->address, m_configuration.proxy_config->port).c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L); + if (m_configuration.proxy_config->username_password) { + curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, util::format("%1:%2", m_configuration.proxy_config->username_password->first, m_configuration.proxy_config->username_password->second).c_str()); } + } - curl_easy_setopt(curl, CURLOPT_TIMEOUT, request.timeout_ms); + /* Now specify the POST data */ + if (request.method == ::realm::networking::http_method::post) { + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); + } + else if (request.method == ::realm::networking::http_method::put) { + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); + } + else if (request.method == ::realm::networking::http_method::patch) { + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); + } + else if (request.method == ::realm::networking::http_method::del) { + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); + } + else if (request.method == ::realm::networking::http_method::patch) { + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); + } - for (auto header : request.headers) { + curl_easy_setopt(curl, CURLOPT_TIMEOUT, request.timeout_ms); + + for (auto header : request.headers) { + auto header_str = util::format("%1: %2", header.first, header.second); + list = curl_slist_append(list, header_str.c_str()); + } + if (m_configuration.custom_http_headers) { + for (auto header : *m_configuration.custom_http_headers) { auto header_str = util::format("%1: %2", header.first, header.second); list = curl_slist_append(list, header_str.c_str()); } - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); - curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_cb); - curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response_headers); - - auto response_code = curl_easy_perform(curl); - if (response_code != CURLE_OK) { - fprintf(stderr, "curl_easy_perform() failed when sending request to '%s' with body '%s': %s\n", - request.url.c_str(), request.body.c_str(), curl_easy_strerror(response_code)); - } - long http_code = 0; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); - curl_easy_cleanup(curl); - curl_slist_free_all(list); - return { - static_cast(http_code), - 0, // binding_response_code - std::move(response_headers), - std::move(response), - std::nullopt - }; } - } // namespace - - void default_http_transport::send_request_to_server(const ::realm::networking::request& request, - std::function&& completion_block) { - if (m_configuration.custom_http_headers) { - auto req_copy = request; - req_copy.headers.insert(m_configuration.custom_http_headers->begin(), m_configuration.custom_http_headers->end()); - completion_block(do_http_request(req_copy, m_configuration.proxy_config)); - return; + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_cb); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response_headers); + + auto response_code = curl_easy_perform(curl); + if (response_code != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed when sending request to '%s' with body '%s': %s\n", + request.url.c_str(), request.body.c_str(), curl_easy_strerror(response_code)); } - completion_block(do_http_request(request, m_configuration.proxy_config)); + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + curl_easy_cleanup(curl); + curl_slist_free_all(list); + completion_block({ + static_cast(http_code), + 0, // binding_response_code + std::move(response_headers), + std::move(response), + std::nullopt + }); } } // namespace realm::networking diff --git a/src/cpprealm/internal/network/network_transport.cpp b/src/cpprealm/internal/network/network_transport.cpp index f35721820..e2f6b6087 100644 --- a/src/cpprealm/internal/network/network_transport.cpp +++ b/src/cpprealm/internal/network/network_transport.cpp @@ -230,11 +230,17 @@ namespace realm::networking { m_ssl_context.use_default_verify(); } + if (m_configuration.ssl_verify_callback) { + socket.ssl_stream->use_verify_callback(std::move(m_configuration.ssl_verify_callback)); + } + if (url_scheme == URLScheme::HTTPS) { socket.ssl_stream.emplace(socket, m_ssl_context, Stream::client); socket.ssl_stream->set_host_name(host); // Throws - socket.ssl_stream->set_verify_mode(VerifyMode::peer); + if (m_configuration.client_validate_ssl) { + socket.ssl_stream->set_verify_mode(VerifyMode::peer); + } socket.ssl_stream->set_logger(logger.get()); } From 5bd2e6bd274502b912e4db4d4561cb8cb2ec537b Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 15 Jul 2024 10:13:26 +0100 Subject: [PATCH 32/41] Fix compilation --- src/cpprealm/internal/curl/network_transport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cpprealm/internal/curl/network_transport.cpp b/src/cpprealm/internal/curl/network_transport.cpp index d3dfd549b..f3ef755c5 100644 --- a/src/cpprealm/internal/curl/network_transport.cpp +++ b/src/cpprealm/internal/curl/network_transport.cpp @@ -104,7 +104,7 @@ namespace realm::networking { curl_easy_getinfo(curl, CURLINFO_PRIMARY_PORT, &port); const char *pem_data = "-----BEGIN CERTIFICATE-----\n...certificate data...\n-----END CERTIFICATE-----\n"; - size_t pem_size = strlen(pem_data); + size_t pem_size = std::strlen(pem_data); int preverify_ok = 1; int depth = 0; From 8cf8fd22c7979066debae76e865ef0d9a38a18d4 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 15 Jul 2024 10:23:07 +0100 Subject: [PATCH 33/41] Fix compilation --- src/cpprealm/internal/curl/network_transport.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cpprealm/internal/curl/network_transport.cpp b/src/cpprealm/internal/curl/network_transport.cpp index f3ef755c5..778abe151 100644 --- a/src/cpprealm/internal/curl/network_transport.cpp +++ b/src/cpprealm/internal/curl/network_transport.cpp @@ -22,6 +22,7 @@ #include #include +#include namespace realm::networking { From cf7119da44ee0eb492eef7a23d63009d9aad03c0 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 15 Jul 2024 16:59:31 +0100 Subject: [PATCH 34/41] split interfaces into http and websocket headers --- include/cpprealm/app.hpp | 3 +- .../cpprealm/internal/networking/shims.hpp | 6 +- .../cpprealm/internal/networking/utils.hpp | 14 +- include/cpprealm/networking/http.hpp | 87 ++++++ include/cpprealm/networking/networking.hpp | 45 --- ...{platform_networking.hpp => websocket.hpp} | 264 ++++++++++-------- realm-core | 2 +- src/CMakeLists.txt | 6 +- src/cpprealm/analytics.cpp | 2 +- src/cpprealm/app.cpp | 2 +- .../internal/network/network_transport.cpp | 2 +- src/cpprealm/internal/networking/shims.cpp | 2 +- src/cpprealm/internal/networking/utils.cpp | 29 +- src/cpprealm/networking/http.cpp | 22 ++ ...{platform_networking.cpp => websocket.cpp} | 20 +- tests/admin_utils.cpp | 2 +- tests/sync/networking_tests.cpp | 34 ++- 17 files changed, 301 insertions(+), 241 deletions(-) create mode 100644 include/cpprealm/networking/http.hpp rename include/cpprealm/networking/{platform_networking.hpp => websocket.hpp} (77%) create mode 100644 src/cpprealm/networking/http.cpp rename src/cpprealm/networking/{platform_networking.cpp => websocket.cpp} (83%) diff --git a/include/cpprealm/app.hpp b/include/cpprealm/app.hpp index d5740771b..f459f5d11 100644 --- a/include/cpprealm/app.hpp +++ b/include/cpprealm/app.hpp @@ -27,8 +27,9 @@ #include #include #include +#include #include -#include +#include #include #include diff --git a/include/cpprealm/internal/networking/shims.hpp b/include/cpprealm/internal/networking/shims.hpp index 6b0dc976f..74ad95c02 100644 --- a/include/cpprealm/internal/networking/shims.hpp +++ b/include/cpprealm/internal/networking/shims.hpp @@ -19,7 +19,8 @@ #ifndef CPPREALM_SHIMS_HPP #define CPPREALM_SHIMS_HPP -#include +#include +#include namespace realm { namespace sync { @@ -31,11 +32,8 @@ namespace realm { } namespace realm::internal::networking { - std::shared_ptr create_http_client_shim(const std::shared_ptr<::realm::networking::http_transport_client>&); - std::unique_ptr<::realm::sync::SyncSocketProvider> create_sync_socket_provider_shim(const std::shared_ptr<::realm::networking::sync_socket_provider>& provider); - } #endif //CPPREALM_SHIMS_HPP diff --git a/include/cpprealm/internal/networking/utils.hpp b/include/cpprealm/internal/networking/utils.hpp index ac2e4cfe2..4da834f16 100644 --- a/include/cpprealm/internal/networking/utils.hpp +++ b/include/cpprealm/internal/networking/utils.hpp @@ -19,6 +19,8 @@ #ifndef CPPREALM_NETWORKING_UTILS_HPP #define CPPREALM_NETWORKING_UTILS_HPP +#include + #include namespace realm { @@ -31,23 +33,21 @@ namespace realm { } namespace networking { - struct default_transport_configuration; struct request; struct response; - struct websocket_endpoint; } } namespace realm::internal::networking { ::realm::networking::request to_request(const ::realm::app::Request&); - ::realm::app::Request to_core_request(const ::realm::networking::request&); +// ::realm::app::Request to_core_request(const ::realm::networking::request&); - ::realm::networking::response to_response(const ::realm::app::Response&); +// ::realm::networking::response to_response(const ::realm::app::Response&); ::realm::app::Response to_core_response(const ::realm::networking::response&); - ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint& ep, - const std::optional<::realm::networking::default_transport_configuration>& config); - ::realm::networking::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& ep); + ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::sync_socket_provider::websocket_endpoint& ep, + const std::optional<::realm::networking::default_socket_provider::configuration>& config); + ::realm::networking::sync_socket_provider::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& ep); } //namespace realm::internal::networking #endif //CPPREALM_NETWORKING_UTILS_HPP diff --git a/include/cpprealm/networking/http.hpp b/include/cpprealm/networking/http.hpp new file mode 100644 index 000000000..f192f8c49 --- /dev/null +++ b/include/cpprealm/networking/http.hpp @@ -0,0 +1,87 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef CPPREALM_NETWORKING_HTTP_HPP +#define CPPREALM_NETWORKING_HTTP_HPP + +#include +#include + +#ifndef REALMCXX_VERSION_MAJOR +#include +#endif + +namespace realm::networking { + + // Interface for providing http transport + struct http_transport_client { + virtual ~http_transport_client() = default; + virtual void send_request_to_server(const request& request, + std::function&& completion) = 0; + }; + + /// Produces a http transport client from the factory. + std::shared_ptr make_http_client(); + /// Globally overwrites the default http transport client factory + [[maybe_unused]] void set_http_client_factory(std::function()>&&); + + /// Built in HTTP transport client. + struct default_http_transport : public http_transport_client { + struct configuration { + /** + * Extra HTTP headers to be set on each request to Atlas Device Sync when using the internal HTTP client. + */ + std::optional> custom_http_headers; + /** + * Network proxy configuration to be set on each request. + */ + std::optional<::realm::internal::bridge::realm::sync_config::proxy_config> proxy_config; + + using SSLVerifyCallback = bool(const std::string& server_address, + internal::bridge::realm::sync_config::proxy_config::port_type server_port, + const char* pem_data, size_t pem_size, int preverify_ok, int depth); + /** + * If set to false, no validation will take place and the client will accept any certificate. + */ + bool client_validate_ssl = true; + /** + * Used for providing your own root certificate. + */ + std::optional ssl_trust_certificate_path; + /** + * `ssl_verify_callback` is used to implement custom SSL certificate + * verification. It is only used if the protocol is SSL & `ssl_trust_certificate_path` + * is not set. + */ + std::function ssl_verify_callback; + }; + + default_http_transport() = default; + default_http_transport(const configuration& c) : m_configuration(c) {} + + ~default_http_transport() = default; + + void send_request_to_server(const ::realm::networking::request& request, + std::function&& completion); + + protected: + configuration m_configuration; + }; +} + +#endif //CPPREALM_NETWORKING_HTTP_HPP diff --git a/include/cpprealm/networking/networking.hpp b/include/cpprealm/networking/networking.hpp index d153c6768..1e340cce7 100644 --- a/include/cpprealm/networking/networking.hpp +++ b/include/cpprealm/networking/networking.hpp @@ -20,7 +20,6 @@ #define CPPREALM_NETWORKING_HPP #include -#include namespace realm::networking { /** @@ -94,50 +93,6 @@ namespace realm::networking { }; - struct websocket_endpoint { - /// Array of one or more websocket protocols. - std::vector protocols; - /// The websocket url to connect to. - std::string url; - }; - - enum websocket_err_codes { - RLM_ERR_WEBSOCKET_OK = 1000, - RLM_ERR_WEBSOCKET_GOINGAWAY = 1001, - RLM_ERR_WEBSOCKET_PROTOCOLERROR = 1002, - RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA = 1003, - RLM_ERR_WEBSOCKET_RESERVED = 1004, - RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED = 1005, - RLM_ERR_WEBSOCKET_ABNORMALCLOSURE = 1006, - RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA = 1007, - RLM_ERR_WEBSOCKET_POLICYVIOLATION = 1008, - RLM_ERR_WEBSOCKET_MESSAGETOOBIG = 1009, - RLM_ERR_WEBSOCKET_INAVALIDEXTENSION = 1010, - RLM_ERR_WEBSOCKET_INTERNALSERVERERROR = 1011, - RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED = 1015, - - RLM_ERR_WEBSOCKET_UNAUTHORIZED = 4001, - RLM_ERR_WEBSOCKET_FORBIDDEN = 4002, - RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY = 4003, - RLM_ERR_WEBSOCKET_CLIENT_TOO_OLD = 4004, - RLM_ERR_WEBSOCKET_CLIENT_TOO_NEW = 4005, - RLM_ERR_WEBSOCKET_PROTOCOL_MISMATCH = 4006, - - RLM_ERR_WEBSOCKET_RESOLVE_FAILED = 4400, - RLM_ERR_WEBSOCKET_CONNECTION_FAILED = 4401, - RLM_ERR_WEBSOCKET_READ_ERROR = 4402, - RLM_ERR_WEBSOCKET_WRITE_ERROR = 4403, - RLM_ERR_WEBSOCKET_RETRY_ERROR = 4404, - RLM_ERR_WEBSOCKET_FATAL_ERROR = 4405, - }; - - // Interface for providing http transport - struct http_transport_client { - virtual ~http_transport_client() = default; - virtual void send_request_to_server(const request& request, - std::function&& completion) = 0; - }; - } //namespace realm::networking #endif//CPPREALM_NETWORKING_HPP diff --git a/include/cpprealm/networking/platform_networking.hpp b/include/cpprealm/networking/websocket.hpp similarity index 77% rename from include/cpprealm/networking/platform_networking.hpp rename to include/cpprealm/networking/websocket.hpp index 3e140bbaa..b716eed95 100644 --- a/include/cpprealm/networking/platform_networking.hpp +++ b/include/cpprealm/networking/websocket.hpp @@ -16,11 +16,12 @@ // //////////////////////////////////////////////////////////////////////////// -#ifndef CPPREALM_PLATFORM_NETWORKING_HPP -#define CPPREALM_PLATFORM_NETWORKING_HPP +#ifndef CPPREALM_NETWORKING_WEBSOCKET_HPP +#define CPPREALM_NETWORKING_WEBSOCKET_HPP -#include #include +#include +#include #ifndef REALMCXX_VERSION_MAJOR #include @@ -29,7 +30,6 @@ #include namespace realm { - namespace sync { class SyncSocketProvider; struct WebSocketInterface; @@ -42,81 +42,10 @@ namespace realm { } namespace realm::networking { - - using status = ::realm::internal::bridge::status; - - /// The WebSocket base class that is used by the SyncClient to send data over the - /// WebSocket connection with the server. This is the class that is returned by - /// sync_socket_provider::connect() when a connection to an endpoint is requested. - /// If an error occurs while establishing the connection, the error is presented - /// to the web_socket_observer provided when the WebSocket was created. - struct websocket_interface { - /// The destructor must close the websocket connection when the WebSocket object - /// is destroyed - virtual ~websocket_interface() = default; - - using FunctionHandler = std::function; - - /// Write data asynchronously to the WebSocket connection. The handler function - /// will be called when the data has been sent successfully. The web_socket_oberver - /// provided when the WebSocket was created will be called if any errors occur - /// during the write operation. - /// @param data A std::string_view containing the data to be sent to the server. - /// @param handler The handler function to be called when the data has been sent - /// successfully or the websocket has been closed (with - /// ErrorCodes::OperationAborted). If an error occurs during the - /// write operation, the websocket will be closed and the error - /// will be provided via the websocket_closed_handler() function. - virtual void async_write_binary(std::string_view data, FunctionHandler&& handler) = 0; - }; - - /// WebSocket observer interface in the Sync Client that receives the websocket - /// events during operation. - struct websocket_observer { - virtual ~websocket_observer() = default; - - /// Called when the WebSocket is connected, i.e. after the handshake is done. - /// The Sync Client is not allowed to send messages on the socket before the - /// handshake is complete and no message_received callbacks will be called - /// before the handshake is done. - /// - /// @param protocol The negotiated subprotocol value returned by the server - virtual void websocket_connected_handler(const std::string& protocol) = 0; - - /// Called when an error occurs while establishing the WebSocket connection - /// to the server or during normal operations. No additional binary messages - /// will be processed after this function is called. - virtual void websocket_error_handler() = 0; - /// Called whenever a full message has arrived. The WebSocket implementation - /// is responsible for defragmenting fragmented messages internally and - /// delivering a full message to the Sync Client. - /// - /// @param data A util::Span containing the data received from the server. - /// The buffer is only valid until the function returns. - /// - /// @return bool designates whether the WebSocket object should continue - /// processing messages. The normal return value is true . False must - /// be returned if the websocket object has been destroyed during - /// execution of the function. - virtual bool websocket_binary_message_received(std::string_view data) = 0; - - /// Called whenever the WebSocket connection has been closed, either as a result - /// of a WebSocket error or a normal close. - /// - /// @param was_clean Was the TCP connection closed after the WebSocket closing - /// handshake was completed. - /// @param error_code The error code received or synthesized when the websocket was closed. - /// @param message The message received in the close frame when the websocket was closed. - /// - /// @return bool designates whether the WebSocket object has been destroyed - /// during the execution of this function. The normal return value is - /// True to indicate the WebSocket object is no longer valid. If False - /// is returned, the WebSocket object will be destroyed at some point - /// in the future. - virtual bool websocket_closed_handler(bool was_clean, websocket_err_codes error_code, - std::string_view message) = 0; - }; + using status = ::realm::internal::bridge::status; + struct websocket_interface; + struct websocket_observer; /// Sync Socket Provider interface that provides the event loop and WebSocket /// factory used by the SyncClient. @@ -145,6 +74,44 @@ namespace realm::networking { /// connecting to the server via a WebSocket connection. class sync_socket_provider { public: + + struct websocket_endpoint { + /// Array of one or more websocket protocols. + std::vector protocols; + /// The websocket url to connect to. + std::string url; + }; + + enum websocket_err_codes { + RLM_ERR_WEBSOCKET_OK = 1000, + RLM_ERR_WEBSOCKET_GOINGAWAY = 1001, + RLM_ERR_WEBSOCKET_PROTOCOLERROR = 1002, + RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA = 1003, + RLM_ERR_WEBSOCKET_RESERVED = 1004, + RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED = 1005, + RLM_ERR_WEBSOCKET_ABNORMALCLOSURE = 1006, + RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA = 1007, + RLM_ERR_WEBSOCKET_POLICYVIOLATION = 1008, + RLM_ERR_WEBSOCKET_MESSAGETOOBIG = 1009, + RLM_ERR_WEBSOCKET_INAVALIDEXTENSION = 1010, + RLM_ERR_WEBSOCKET_INTERNALSERVERERROR = 1011, + RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED = 1015, + + RLM_ERR_WEBSOCKET_UNAUTHORIZED = 4001, + RLM_ERR_WEBSOCKET_FORBIDDEN = 4002, + RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY = 4003, + RLM_ERR_WEBSOCKET_CLIENT_TOO_OLD = 4004, + RLM_ERR_WEBSOCKET_CLIENT_TOO_NEW = 4005, + RLM_ERR_WEBSOCKET_PROTOCOL_MISMATCH = 4006, + + RLM_ERR_WEBSOCKET_RESOLVE_FAILED = 4400, + RLM_ERR_WEBSOCKET_CONNECTION_FAILED = 4401, + RLM_ERR_WEBSOCKET_READ_ERROR = 4402, + RLM_ERR_WEBSOCKET_WRITE_ERROR = 4403, + RLM_ERR_WEBSOCKET_RETRY_ERROR = 4404, + RLM_ERR_WEBSOCKET_FATAL_ERROR = 4405, + }; + /// Function handler typedef using FunctionHandler = std::function; @@ -223,58 +190,111 @@ namespace realm::networking { virtual sync_timer create_timer(std::chrono::milliseconds delay, FunctionHandler&& handler) = 0; }; - /// Produces a http transport client from the factory. - std::shared_ptr make_http_client(); - /// Globally overwrites the default http transport client factory - void set_http_client_factory(std::function()>&&); - - struct default_transport_configuration { - /** - * Extra HTTP headers to be set on each request to Atlas Device Sync when using the internal HTTP client. - */ - std::optional> custom_http_headers; - /** - * Network proxy configuration to be set on each request. - */ - std::optional<::realm::internal::bridge::realm::sync_config::proxy_config> proxy_config; - - using SSLVerifyCallback = bool(const std::string& server_address, - internal::bridge::realm::sync_config::proxy_config::port_type server_port, - const char* pem_data, size_t pem_size, int preverify_ok, int depth); - /** - * If set to false, no validation will take place and the client will accept any certificate. - */ - bool client_validate_ssl = true; - /** - * Used for providing your own root certificate. - */ - std::optional ssl_trust_certificate_path; - /** - * `ssl_verify_callback` is used to implement custom SSL certificate - * verification. It is only used if the protocol is SSL & `ssl_trust_certificate_path` - * is not set. - */ - std::function ssl_verify_callback; + /// The WebSocket base class that is used by the SyncClient to send data over the + /// WebSocket connection with the server. This is the class that is returned by + /// sync_socket_provider::connect() when a connection to an endpoint is requested. + /// If an error occurs while establishing the connection, the error is presented + /// to the web_socket_observer provided when the WebSocket was created. + struct websocket_interface { + /// The destructor must close the websocket connection when the WebSocket object + /// is destroyed + virtual ~websocket_interface() = default; + + using FunctionHandler = std::function; + + /// Write data asynchronously to the WebSocket connection. The handler function + /// will be called when the data has been sent successfully. The web_socket_oberver + /// provided when the WebSocket was created will be called if any errors occur + /// during the write operation. + /// @param data A std::string_view containing the data to be sent to the server. + /// @param handler The handler function to be called when the data has been sent + /// successfully or the websocket has been closed (with + /// ErrorCodes::OperationAborted). If an error occurs during the + /// write operation, the websocket will be closed and the error + /// will be provided via the websocket_closed_handler() function. + virtual void async_write_binary(std::string_view data, FunctionHandler&& handler) = 0; }; - /// Built in HTTP transport client. - struct default_http_transport : public http_transport_client { - default_http_transport() = default; - default_http_transport(const default_transport_configuration& c) : m_configuration(c) {} + /// WebSocket observer interface in the Sync Client that receives the websocket + /// events during operation. + struct websocket_observer { + virtual ~websocket_observer() = default; + + /// Called when the WebSocket is connected, i.e. after the handshake is done. + /// The Sync Client is not allowed to send messages on the socket before the + /// handshake is complete and no message_received callbacks will be called + /// before the handshake is done. + /// + /// @param protocol The negotiated subprotocol value returned by the server + virtual void websocket_connected_handler(const std::string& protocol) = 0; - ~default_http_transport() = default; + /// Called when an error occurs while establishing the WebSocket connection + /// to the server or during normal operations. No additional binary messages + /// will be processed after this function is called. + virtual void websocket_error_handler() = 0; - void send_request_to_server(const ::realm::networking::request& request, - std::function&& completion); + /// Called whenever a full message has arrived. The WebSocket implementation + /// is responsible for defragmenting fragmented messages internally and + /// delivering a full message to the Sync Client. + /// + /// @param data A util::Span containing the data received from the server. + /// The buffer is only valid until the function returns. + /// + /// @return bool designates whether the WebSocket object should continue + /// processing messages. The normal return value is true . False must + /// be returned if the websocket object has been destroyed during + /// execution of the function. + virtual bool websocket_binary_message_received(std::string_view data) = 0; - protected: - default_transport_configuration m_configuration; + /// Called whenever the WebSocket connection has been closed, either as a result + /// of a WebSocket error or a normal close. + /// + /// @param was_clean Was the TCP connection closed after the WebSocket closing + /// handshake was completed. + /// @param error_code The error code received or synthesized when the websocket was closed. + /// @param message The message received in the close frame when the websocket was closed. + /// + /// @return bool designates whether the WebSocket object has been destroyed + /// during the execution of this function. The normal return value is + /// True to indicate the WebSocket object is no longer valid. If False + /// is returned, the WebSocket object will be destroyed at some point + /// in the future. + virtual bool websocket_closed_handler(bool was_clean, sync_socket_provider::websocket_err_codes error_code, + std::string_view message) = 0; }; /// Built in websocket provider struct default_socket_provider : public sync_socket_provider { + struct configuration { + /** + * Extra HTTP headers to be set on each request to Atlas Device Sync when using the internal HTTP client. + */ + std::optional> custom_http_headers; + /** + * Network proxy configuration to be set on each request. + */ + std::optional<::realm::internal::bridge::realm::sync_config::proxy_config> proxy_config; + + using SSLVerifyCallback = bool(const std::string& server_address, + internal::bridge::realm::sync_config::proxy_config::port_type server_port, + const char* pem_data, size_t pem_size, int preverify_ok, int depth); + /** + * If set to false, no validation will take place and the client will accept any certificate. + */ + bool client_validate_ssl = true; + /** + * Used for providing your own root certificate. + */ + std::optional ssl_trust_certificate_path; + /** + * `ssl_verify_callback` is used to implement custom SSL certificate + * verification. It is only used if the protocol is SSL & `ssl_trust_certificate_path` + * is not set. + */ + std::function ssl_verify_callback; + }; default_socket_provider(); - default_socket_provider(const default_transport_configuration& c); + default_socket_provider(const configuration& c); ~default_socket_provider() = default; std::unique_ptr connect(std::unique_ptr, websocket_endpoint &&) override; @@ -282,11 +302,11 @@ namespace realm::networking { sync_timer create_timer(std::chrono::milliseconds delay, FunctionHandler&&) override; protected: - default_transport_configuration m_configuration; + configuration m_configuration; private: void initialize(); std::unique_ptr<::realm::sync::SyncSocketProvider> m_provider; }; } -#endif//CPPREALM_PLATFORM_NETWORKING_HPP +#endif //CPPREALM_NETWORKING_WEBSOCKET_HPP diff --git a/realm-core b/realm-core index c0bea32d6..fac06bf58 160000 --- a/realm-core +++ b/realm-core @@ -1 +1 @@ -Subproject commit c0bea32d6c425d9da50258696bec2ddc63d0b623 +Subproject commit fac06bf58c4b631d0218fec3cb01fddf2ab62f3d diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 994018679..a06d6fb58 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,7 +54,8 @@ set(SOURCES cpprealm/internal/networking/shims.cpp cpprealm/internal/networking/utils.cpp cpprealm/internal/scheduler/realm_core_scheduler.cpp - cpprealm/networking/platform_networking.cpp + cpprealm/networking/http.cpp + cpprealm/networking/websocket.cpp cpprealm/schedulers/default_scheduler.cpp cpprealm/logger.cpp cpprealm/sdk.cpp) # REALM_SOURCES @@ -118,7 +119,8 @@ set(HEADERS ../include/cpprealm/internal/scheduler/realm_core_scheduler.hpp ../include/cpprealm/schedulers/default_scheduler.hpp ../include/cpprealm/networking/networking.hpp - ../include/cpprealm/networking/platform_networking.hpp + ../include/cpprealm/networking/http.hpp + ../include/cpprealm/networking/websocket.hpp ../include/cpprealm/logger.hpp ../include/cpprealm/notifications.hpp ../include/cpprealm/rbool.hpp diff --git a/src/cpprealm/analytics.cpp b/src/cpprealm/analytics.cpp index bd52efb84..1536449cf 100644 --- a/src/cpprealm/analytics.cpp +++ b/src/cpprealm/analytics.cpp @@ -37,7 +37,7 @@ #endif #include -#include +#include #include #include diff --git a/src/cpprealm/app.cpp b/src/cpprealm/app.cpp index 1325f4d4b..972ba7fd3 100644 --- a/src/cpprealm/app.cpp +++ b/src/cpprealm/app.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #ifndef REALMCXX_VERSION_MAJOR #include diff --git a/src/cpprealm/internal/network/network_transport.cpp b/src/cpprealm/internal/network/network_transport.cpp index e2f6b6087..eb11a6f7b 100644 --- a/src/cpprealm/internal/network/network_transport.cpp +++ b/src/cpprealm/internal/network/network_transport.cpp @@ -20,7 +20,7 @@ #include #endif #include -#include +#include #include #include diff --git a/src/cpprealm/internal/networking/shims.cpp b/src/cpprealm/internal/networking/shims.cpp index 19c5b249d..46b53608d 100644 --- a/src/cpprealm/internal/networking/shims.cpp +++ b/src/cpprealm/internal/networking/shims.cpp @@ -69,7 +69,7 @@ namespace realm::internal::networking { return m_observer->websocket_binary_message_received(data); } - bool websocket_closed_handler(bool was_clean, ::realm::networking::websocket_err_codes error_code, + bool websocket_closed_handler(bool was_clean, ::realm::networking::sync_socket_provider::websocket_err_codes error_code, std::string_view message) override { return m_observer->websocket_closed_handler(was_clean, static_cast<::realm::sync::websocket::WebSocketError>(error_code), message); } diff --git a/src/cpprealm/internal/networking/utils.cpp b/src/cpprealm/internal/networking/utils.cpp index 95f5023ca..b73d3bd08 100644 --- a/src/cpprealm/internal/networking/utils.cpp +++ b/src/cpprealm/internal/networking/utils.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include @@ -8,6 +8,7 @@ #include namespace realm::internal::networking { + ::realm::networking::request to_request(const ::realm::app::Request& core_request) { ::realm::networking::request req; req.method = static_cast<::realm::networking::http_method>(core_request.method); @@ -17,25 +18,7 @@ namespace realm::internal::networking { req.body = core_request.body; return req; } - ::realm::app::Request to_core_request(const ::realm::networking::request& req) { - ::realm::app::Request core_request; - core_request.method = static_cast<::realm::app::HttpMethod>(req.method); - core_request.url = req.url; - core_request.timeout_ms = req.timeout_ms; - core_request.headers = req.headers; - core_request.body = req.body; - return core_request; - } - ::realm::networking::response to_response(const ::realm::app::Response& core_response) { - ::realm::networking::response req; - req.http_status_code = core_response.http_status_code; - req.custom_status_code = core_response.custom_status_code; - req.headers = core_response.headers; - req.client_error_code = core_response.client_error_code; - req.body = core_response.body; - return req; - } ::realm::app::Response to_core_response(const ::realm::networking::response& req) { ::realm::app::Response core_response; core_response.http_status_code = req.http_status_code; @@ -62,8 +45,8 @@ namespace realm::internal::networking { REALM_TERMINATE("Unrecognized websocket protocol"); } - ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint& ep, - const std::optional<::realm::networking::default_transport_configuration>& config) { + ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::sync_socket_provider::websocket_endpoint& ep, + const std::optional<::realm::networking::default_socket_provider::configuration>& config) { ::realm::sync::WebSocketEndpoint core_ep; auto uri = util::Uri(ep.url); auto protocol = to_protocol_envelope(uri.get_scheme()); @@ -107,8 +90,8 @@ namespace realm::internal::networking { return core_ep; } - ::realm::networking::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& core_ep) { - ::realm::networking::websocket_endpoint ep; + ::realm::networking::sync_socket_provider::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& core_ep) { + ::realm::networking::sync_socket_provider::websocket_endpoint ep; ep.protocols = core_ep.protocols; const auto& port = core_ep.proxy ? core_ep.proxy->port : core_ep.port; ep.url = util::format("%1://%2:%3%4", core_ep.is_ssl ? "wss" : "ws", core_ep.address, port, core_ep.path); diff --git a/src/cpprealm/networking/http.cpp b/src/cpprealm/networking/http.cpp new file mode 100644 index 000000000..ab3c91e2a --- /dev/null +++ b/src/cpprealm/networking/http.cpp @@ -0,0 +1,22 @@ +#include +#include + +#include +#include +#include + +namespace realm::networking { + std::shared_ptr make_default_http_client() { + return std::make_shared(); + } + std::function()> s_http_client_factory = make_default_http_client; + + [[maybe_unused]] + void set_http_client_factory(std::function()>&& factory_fn) { + s_http_client_factory = std::move(factory_fn); + } + + std::shared_ptr make_http_client() { + return s_http_client_factory(); + } +} diff --git a/src/cpprealm/networking/platform_networking.cpp b/src/cpprealm/networking/websocket.cpp similarity index 83% rename from src/cpprealm/networking/platform_networking.cpp rename to src/cpprealm/networking/websocket.cpp index d7742e523..4e8c40da4 100644 --- a/src/cpprealm/networking/platform_networking.cpp +++ b/src/cpprealm/networking/websocket.cpp @@ -1,5 +1,4 @@ -#include -#include +#include #include #include @@ -8,19 +7,6 @@ namespace realm::networking { - std::shared_ptr make_default_http_client() { - return std::make_shared(); - } - std::function()> s_http_client_factory = make_default_http_client; - - void set_http_client_factory(std::function()>&& factory_fn) { - s_http_client_factory = std::move(factory_fn); - } - - std::shared_ptr make_http_client() { - return s_http_client_factory(); - } - struct default_websocket_observer_core : public ::realm::sync::WebSocketObserver { default_websocket_observer_core(std::unique_ptr&& o) : m_observer(std::move(o)) { } ~default_websocket_observer_core() = default; @@ -39,7 +25,7 @@ namespace realm::networking { bool websocket_closed_handler(bool was_clean, ::realm::sync::websocket::WebSocketError error_code, std::string_view message) override { - return m_observer->websocket_closed_handler(was_clean, static_cast(error_code), message); + return m_observer->websocket_closed_handler(was_clean, static_cast(error_code), message); } private: @@ -71,7 +57,7 @@ namespace realm::networking { initialize(); } - default_socket_provider::default_socket_provider(const default_transport_configuration& c) : m_configuration(c) { + default_socket_provider::default_socket_provider(const configuration& c) : m_configuration(c) { initialize(); } diff --git a/tests/admin_utils.cpp b/tests/admin_utils.cpp index 2a7627046..5762b59ba 100644 --- a/tests/admin_utils.cpp +++ b/tests/admin_utils.cpp @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include "admin_utils.hpp" diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp index 7adf61bf4..105f9e377 100644 --- a/tests/sync/networking_tests.cpp +++ b/tests/sync/networking_tests.cpp @@ -26,19 +26,19 @@ TEST_CASE("custom transport to proxy", "[proxy]") { config.base_url = Admin::Session::shared().base_url(); config.enable_caching = true; - auto transport_config = ::realm::networking::default_transport_configuration(); + auto socket_config = ::realm::networking::default_socket_provider::configuration(); proxy_config pc; pc.port = 1234; pc.address = "127.0.0.1"; - transport_config.proxy_config = pc; + socket_config.proxy_config = pc; struct foo_socket_provider : public ::realm::networking::default_socket_provider { - foo_socket_provider(const ::realm::networking::default_transport_configuration& configuration) { + foo_socket_provider(const ::realm::networking::default_socket_provider::configuration& configuration) { m_configuration = configuration; } std::unique_ptr<::realm::networking::websocket_interface> connect(std::unique_ptr<::realm::networking::websocket_observer> o, - ::realm::networking::websocket_endpoint&& ep) override { + ::realm::networking::sync_socket_provider::websocket_endpoint&& ep) override { m_called = true; return ::realm::networking::default_socket_provider::connect(std::move(o), std::move(ep)); } @@ -51,12 +51,12 @@ TEST_CASE("custom transport to proxy", "[proxy]") { bool m_called = false; }; - auto foo_socket = std::make_shared(transport_config); + auto foo_socket = std::make_shared(socket_config); config.sync_socket_provider = foo_socket; struct foo_http_transport : public ::realm::networking::default_http_transport { - foo_http_transport(const ::realm::networking::default_transport_configuration& configuration) { + foo_http_transport(const ::realm::networking::default_http_transport::configuration& configuration) { m_configuration = configuration; } @@ -80,7 +80,10 @@ TEST_CASE("custom transport to proxy", "[proxy]") { bool m_called = false; }; - auto foo_transport = std::make_shared(transport_config); + auto http_config = ::realm::networking::default_http_transport::configuration(); + http_config.proxy_config = pc; + + auto foo_transport = std::make_shared(http_config); config.http_transport_client = foo_transport; auto app = realm::App(config); @@ -138,14 +141,17 @@ TEST_CASE("built in transport to proxy roundtrip", "[proxy]") { config.base_url = Admin::Session::shared().base_url(); config.enable_caching = false; - auto transport_config = ::realm::networking::default_transport_configuration(); + auto socket_config = ::realm::networking::default_socket_provider::configuration(); proxy_config pc; - pc.port = 1235; + pc.port = 1234; pc.address = "127.0.0.1"; - transport_config.proxy_config = pc; + socket_config.proxy_config = pc; + + auto http_config = ::realm::networking::default_http_transport::configuration(); + http_config.proxy_config = pc; - config.sync_socket_provider = std::make_shared(transport_config); - config.http_transport_client = std::make_shared(transport_config); + config.sync_socket_provider = std::make_shared(socket_config); + config.http_transport_client = std::make_shared(http_config); auto app = realm::App(config); @@ -184,11 +190,11 @@ TEST_CASE("built in transport to proxy roundtrip", "[proxy]") { } TEST_CASE("WebsocketEndpoint", "[proxy]") { - realm::networking::websocket_endpoint ep; + realm::networking::sync_socket_provider::websocket_endpoint ep; ep.url = "wss://my-server.com:443"; ep.protocols = std::vector({"test_protocol"}); - ::realm::networking::default_transport_configuration config; + ::realm::networking::default_socket_provider::configuration config; config.proxy_config = ::realm::proxy_config(); config.proxy_config->address = "127.0.0.1"; config.proxy_config->port = 1234; From 4389342da5f50e48087a9029957864f2bacb64cd Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 15 Jul 2024 17:13:12 +0100 Subject: [PATCH 35/41] split interfaces into http and websocket headers --- src/cpprealm/internal/apple/network_transport.mm | 2 +- src/cpprealm/internal/curl/network_transport.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cpprealm/internal/apple/network_transport.mm b/src/cpprealm/internal/apple/network_transport.mm index 8841f2f9b..c4c5da1db 100644 --- a/src/cpprealm/internal/apple/network_transport.mm +++ b/src/cpprealm/internal/apple/network_transport.mm @@ -17,7 +17,7 @@ //////////////////////////////////////////////////////////////////////////// #include -#include +#include #include #include diff --git a/src/cpprealm/internal/curl/network_transport.cpp b/src/cpprealm/internal/curl/network_transport.cpp index 778abe151..c668018f4 100644 --- a/src/cpprealm/internal/curl/network_transport.cpp +++ b/src/cpprealm/internal/curl/network_transport.cpp @@ -17,7 +17,7 @@ //////////////////////////////////////////////////////////////////////////// #include -#include +#include #include From 4fb16ec991c421d8768fb7db06fa887ab033f1bd Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 15 Jul 2024 17:33:16 +0100 Subject: [PATCH 36/41] split interfaces into http and websocket headers --- tests/sync/networking_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp index 105f9e377..f15257aac 100644 --- a/tests/sync/networking_tests.cpp +++ b/tests/sync/networking_tests.cpp @@ -143,7 +143,7 @@ TEST_CASE("built in transport to proxy roundtrip", "[proxy]") { auto socket_config = ::realm::networking::default_socket_provider::configuration(); proxy_config pc; - pc.port = 1234; + pc.port = 1235; pc.address = "127.0.0.1"; socket_config.proxy_config = pc; From e443f39a50cb205a969835cd61d774c96dd7fb2d Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Mon, 15 Jul 2024 18:23:21 +0100 Subject: [PATCH 37/41] split interfaces into http and websocket headers --- include/cpprealm/networking/networking.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/cpprealm/networking/networking.hpp b/include/cpprealm/networking/networking.hpp index 1e340cce7..e04d5f737 100644 --- a/include/cpprealm/networking/networking.hpp +++ b/include/cpprealm/networking/networking.hpp @@ -20,6 +20,7 @@ #define CPPREALM_NETWORKING_HPP #include +#include namespace realm::networking { /** From 5ddcab137ad5d6f999544dea182c73341b8d54a5 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Tue, 16 Jul 2024 08:41:58 +0100 Subject: [PATCH 38/41] split interfaces into http and websocket headers --- include/cpprealm/internal/networking/utils.hpp | 3 --- include/cpprealm/networking/http.hpp | 2 ++ include/cpprealm/networking/networking.hpp | 1 + include/cpprealm/networking/websocket.hpp | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/cpprealm/internal/networking/utils.hpp b/include/cpprealm/internal/networking/utils.hpp index 4da834f16..9cb9be8c5 100644 --- a/include/cpprealm/internal/networking/utils.hpp +++ b/include/cpprealm/internal/networking/utils.hpp @@ -40,9 +40,6 @@ namespace realm { namespace realm::internal::networking { ::realm::networking::request to_request(const ::realm::app::Request&); -// ::realm::app::Request to_core_request(const ::realm::networking::request&); - -// ::realm::networking::response to_response(const ::realm::app::Response&); ::realm::app::Response to_core_response(const ::realm::networking::response&); ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::sync_socket_provider::websocket_endpoint& ep, diff --git a/include/cpprealm/networking/http.hpp b/include/cpprealm/networking/http.hpp index f192f8c49..5352befd5 100644 --- a/include/cpprealm/networking/http.hpp +++ b/include/cpprealm/networking/http.hpp @@ -26,6 +26,8 @@ #include #endif +#include + namespace realm::networking { // Interface for providing http transport diff --git a/include/cpprealm/networking/networking.hpp b/include/cpprealm/networking/networking.hpp index e04d5f737..822d02941 100644 --- a/include/cpprealm/networking/networking.hpp +++ b/include/cpprealm/networking/networking.hpp @@ -19,6 +19,7 @@ #ifndef CPPREALM_NETWORKING_HPP #define CPPREALM_NETWORKING_HPP +#include #include #include diff --git a/include/cpprealm/networking/websocket.hpp b/include/cpprealm/networking/websocket.hpp index b716eed95..04816d396 100644 --- a/include/cpprealm/networking/websocket.hpp +++ b/include/cpprealm/networking/websocket.hpp @@ -28,6 +28,7 @@ #endif #include +#include namespace realm { namespace sync { From 8ec63169054c91296d453131d6a348748736799a Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Tue, 16 Jul 2024 09:33:28 +0100 Subject: [PATCH 39/41] split interfaces into http and websocket headers --- src/cpprealm/networking/http.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cpprealm/networking/http.cpp b/src/cpprealm/networking/http.cpp index ab3c91e2a..0e814f214 100644 --- a/src/cpprealm/networking/http.cpp +++ b/src/cpprealm/networking/http.cpp @@ -11,7 +11,6 @@ namespace realm::networking { } std::function()> s_http_client_factory = make_default_http_client; - [[maybe_unused]] void set_http_client_factory(std::function()>&& factory_fn) { s_http_client_factory = std::move(factory_fn); } From 97ea1342537f759f279a78953b5626c61eaa044c Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Tue, 16 Jul 2024 16:37:31 +0100 Subject: [PATCH 40/41] Address feedback --- CHANGELOG.md | 12 ++- Package.swift | 2 +- include/cpprealm/app.hpp | 1 - .../cpprealm/internal/networking/utils.hpp | 4 +- include/cpprealm/networking/http.hpp | 74 ++++++++++++- include/cpprealm/networking/networking.hpp | 100 ------------------ include/cpprealm/networking/websocket.hpp | 77 +++++++------- src/CMakeLists.txt | 1 - .../internal/network/network_transport.cpp | 1 - src/cpprealm/internal/networking/shims.cpp | 2 +- src/cpprealm/internal/networking/utils.cpp | 6 +- src/cpprealm/networking/websocket.cpp | 2 +- tests/sync/networking_tests.cpp | 4 +- 13 files changed, 129 insertions(+), 157 deletions(-) delete mode 100644 include/cpprealm/networking/networking.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e195bc97..27e70dd1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,15 +22,19 @@ NEXT-RELEASE Release notes (YYYY-MM-DD) * Add `default_socket_provider` a built-in class for providing the components necessary for transport via WebSocket. * A custom WebSocket & HTTP transport implementation can be used by passing the instance via `realm::app::App::configuration.http_transport_client` & `realm::app::App::configuration.sync_socket_provider`. -* Proxy and custom header configuration for built-in transport must be supplied via the constructor on - `realm::networking::default_http_transport` & `realm::networking::default_socket_provider` using the - `realm::networking::default_transport_configuration` struct. +* Network configuration for the built-in http transport must be supplied via it's constructor using the + `realm::networking::default_http_transport::configuration` struct. +* Network configuration for the built-in websocket provider must be supplied via it's constructor using the + `realm::networking::default_socket_provider::configuration` struct. + +### Deprecations +* Proxy and custom http headers should no longer be set via `realm::app::App::configuration`. ### Compatibility * Fileformat: Generates files with format v24. Reads and automatically upgrade from fileformat v10. ### Internals -* None +* Upgraded to Core v14.10.4 2.1.0 Release notes (2024-06-27) ============================================================= diff --git a/Package.swift b/Package.swift index 4c77b6da9..2bffd245f 100644 --- a/Package.swift +++ b/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let sdkVersion = Version("2.1.0") -let coreVersion = Version("14.9.0") +let coreVersion = Version("14.10.4") var cxxSettings: [CXXSetting] = [ .define("REALM_ENABLE_SYNC", to: "1"), diff --git a/include/cpprealm/app.hpp b/include/cpprealm/app.hpp index f459f5d11..5fe600a4d 100644 --- a/include/cpprealm/app.hpp +++ b/include/cpprealm/app.hpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include diff --git a/include/cpprealm/internal/networking/utils.hpp b/include/cpprealm/internal/networking/utils.hpp index 9cb9be8c5..9c48efb3b 100644 --- a/include/cpprealm/internal/networking/utils.hpp +++ b/include/cpprealm/internal/networking/utils.hpp @@ -42,9 +42,9 @@ namespace realm::internal::networking { ::realm::networking::request to_request(const ::realm::app::Request&); ::realm::app::Response to_core_response(const ::realm::networking::response&); - ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::sync_socket_provider::websocket_endpoint& ep, + ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint& ep, const std::optional<::realm::networking::default_socket_provider::configuration>& config); - ::realm::networking::sync_socket_provider::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& ep); + ::realm::networking::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& ep); } //namespace realm::internal::networking #endif //CPPREALM_NETWORKING_UTILS_HPP diff --git a/include/cpprealm/networking/http.hpp b/include/cpprealm/networking/http.hpp index 5352befd5..a01acb7d5 100644 --- a/include/cpprealm/networking/http.hpp +++ b/include/cpprealm/networking/http.hpp @@ -19,7 +19,6 @@ #ifndef CPPREALM_NETWORKING_HTTP_HPP #define CPPREALM_NETWORKING_HTTP_HPP -#include #include #ifndef REALMCXX_VERSION_MAJOR @@ -27,9 +26,82 @@ #endif #include +#include +#include namespace realm::networking { + /** + * A HTTP method type. + */ + enum class http_method { get, post, patch, put, del }; + /** + * Request/Response headers type + */ + using http_headers = std::map; + + /** + * An HTTP request that can be made to an arbitrary server. + */ + struct request { + /** + * The HTTP method of this request. + */ + http_method method = http_method::get; + + /** + * The URL to which this request will be made. + */ + std::string url; + + /** + * The number of milliseconds that the underlying transport should spend on an HTTP round trip before failing with + * an error. + */ + uint64_t timeout_ms = 0; + + /** + * The HTTP headers of this request - keys are case insensitive. + */ + http_headers headers; + + /** + * The body of the request. + */ + std::string body; + }; + + /** + * The contents of an HTTP response. + */ + struct response { + /** + * The status code of the HTTP response. + */ + int http_status_code; + + /** + * A custom status code provided by the language binding (SDK). + */ + int custom_status_code; + + /** + * The headers of the HTTP response - keys are case insensitive. + */ + http_headers headers; + + /** + * The body of the HTTP response. + */ + std::string body; + + /** + * An error code used by the client to report http processing errors. + */ + std::optional client_error_code; + + }; + // Interface for providing http transport struct http_transport_client { virtual ~http_transport_client() = default; diff --git a/include/cpprealm/networking/networking.hpp b/include/cpprealm/networking/networking.hpp deleted file mode 100644 index 822d02941..000000000 --- a/include/cpprealm/networking/networking.hpp +++ /dev/null @@ -1,100 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2024 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#ifndef CPPREALM_NETWORKING_HPP -#define CPPREALM_NETWORKING_HPP - -#include -#include -#include - -namespace realm::networking { - /** - * An HTTP method type. - */ - enum class http_method { get, post, patch, put, del }; - /** - * Request/Response headers type - */ - using http_headers = std::map; - - /** - * An HTTP request that can be made to an arbitrary server. - */ - struct request { - /** - * The HTTP method of this request. - */ - http_method method = http_method::get; - - /** - * The URL to which this request will be made. - */ - std::string url; - - /** - * The number of milliseconds that the underlying transport should spend on an HTTP round trip before failing with - * an error. - */ - uint64_t timeout_ms = 0; - - /** - * The HTTP headers of this request - keys are case insensitive. - */ - http_headers headers; - - /** - * The body of the request. - */ - std::string body; - }; - - /** - * The contents of an HTTP response. - */ - struct response { - /** - * The status code of the HTTP response. - */ - int http_status_code; - - /** - * A custom status code provided by the language binding (SDK). - */ - int custom_status_code; - - /** - * The headers of the HTTP response - keys are case insensitive. - */ - http_headers headers; - - /** - * The body of the HTTP response. - */ - std::string body; - - /** - * An error code used by the client to report http processing errors. - */ - std::optional client_error_code; - - }; - -} //namespace realm::networking - -#endif//CPPREALM_NETWORKING_HPP diff --git a/include/cpprealm/networking/websocket.hpp b/include/cpprealm/networking/websocket.hpp index 04816d396..1fabb51e5 100644 --- a/include/cpprealm/networking/websocket.hpp +++ b/include/cpprealm/networking/websocket.hpp @@ -19,7 +19,6 @@ #ifndef CPPREALM_NETWORKING_WEBSOCKET_HPP #define CPPREALM_NETWORKING_WEBSOCKET_HPP -#include #include #include @@ -44,6 +43,43 @@ namespace realm { namespace realm::networking { + struct websocket_endpoint { + /// Array of one or more websocket protocols. + std::vector protocols; + /// The websocket url to connect to. + std::string url; + }; + + enum class websocket_err_codes { + ok = 1000, + going_away = 1001, + protocol_error = 1002, + unsupported_data = 1003, + websocket_reserved = 1004, + no_status_received = 1005, + abnormal_closure = 1006, + invalid_payload_data = 1007, + policy_violation = 1008, + message_too_big = 1009, + invalid_extension = 1010, + invalid_server_error = 1011, + TLS_handshake_failed = 1015, + + unauthorized = 4001, + forbidden = 4002, + moved_permanently = 4003, + client_too_old = 4004, + client_too_new = 4005, + protocol_mismatch = 4006, + + resolve_failed = 4400, + connection_failed = 4401, + read_error = 4402, + write_error = 4403, + retry_error = 4404, + fata_error = 4405, + }; + using status = ::realm::internal::bridge::status; struct websocket_interface; struct websocket_observer; @@ -76,43 +112,6 @@ namespace realm::networking { class sync_socket_provider { public: - struct websocket_endpoint { - /// Array of one or more websocket protocols. - std::vector protocols; - /// The websocket url to connect to. - std::string url; - }; - - enum websocket_err_codes { - RLM_ERR_WEBSOCKET_OK = 1000, - RLM_ERR_WEBSOCKET_GOINGAWAY = 1001, - RLM_ERR_WEBSOCKET_PROTOCOLERROR = 1002, - RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA = 1003, - RLM_ERR_WEBSOCKET_RESERVED = 1004, - RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED = 1005, - RLM_ERR_WEBSOCKET_ABNORMALCLOSURE = 1006, - RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA = 1007, - RLM_ERR_WEBSOCKET_POLICYVIOLATION = 1008, - RLM_ERR_WEBSOCKET_MESSAGETOOBIG = 1009, - RLM_ERR_WEBSOCKET_INAVALIDEXTENSION = 1010, - RLM_ERR_WEBSOCKET_INTERNALSERVERERROR = 1011, - RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED = 1015, - - RLM_ERR_WEBSOCKET_UNAUTHORIZED = 4001, - RLM_ERR_WEBSOCKET_FORBIDDEN = 4002, - RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY = 4003, - RLM_ERR_WEBSOCKET_CLIENT_TOO_OLD = 4004, - RLM_ERR_WEBSOCKET_CLIENT_TOO_NEW = 4005, - RLM_ERR_WEBSOCKET_PROTOCOL_MISMATCH = 4006, - - RLM_ERR_WEBSOCKET_RESOLVE_FAILED = 4400, - RLM_ERR_WEBSOCKET_CONNECTION_FAILED = 4401, - RLM_ERR_WEBSOCKET_READ_ERROR = 4402, - RLM_ERR_WEBSOCKET_WRITE_ERROR = 4403, - RLM_ERR_WEBSOCKET_RETRY_ERROR = 4404, - RLM_ERR_WEBSOCKET_FATAL_ERROR = 4405, - }; - /// Function handler typedef using FunctionHandler = std::function; @@ -260,7 +259,7 @@ namespace realm::networking { /// True to indicate the WebSocket object is no longer valid. If False /// is returned, the WebSocket object will be destroyed at some point /// in the future. - virtual bool websocket_closed_handler(bool was_clean, sync_socket_provider::websocket_err_codes error_code, + virtual bool websocket_closed_handler(bool was_clean, websocket_err_codes error_code, std::string_view message) = 0; }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a06d6fb58..54acc4e4e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -118,7 +118,6 @@ set(HEADERS ../include/cpprealm/internal/networking/utils.hpp ../include/cpprealm/internal/scheduler/realm_core_scheduler.hpp ../include/cpprealm/schedulers/default_scheduler.hpp - ../include/cpprealm/networking/networking.hpp ../include/cpprealm/networking/http.hpp ../include/cpprealm/networking/websocket.hpp ../include/cpprealm/logger.hpp diff --git a/src/cpprealm/internal/network/network_transport.cpp b/src/cpprealm/internal/network/network_transport.cpp index eb11a6f7b..6054a70d3 100644 --- a/src/cpprealm/internal/network/network_transport.cpp +++ b/src/cpprealm/internal/network/network_transport.cpp @@ -21,7 +21,6 @@ #endif #include #include -#include #include #include diff --git a/src/cpprealm/internal/networking/shims.cpp b/src/cpprealm/internal/networking/shims.cpp index 46b53608d..19c5b249d 100644 --- a/src/cpprealm/internal/networking/shims.cpp +++ b/src/cpprealm/internal/networking/shims.cpp @@ -69,7 +69,7 @@ namespace realm::internal::networking { return m_observer->websocket_binary_message_received(data); } - bool websocket_closed_handler(bool was_clean, ::realm::networking::sync_socket_provider::websocket_err_codes error_code, + bool websocket_closed_handler(bool was_clean, ::realm::networking::websocket_err_codes error_code, std::string_view message) override { return m_observer->websocket_closed_handler(was_clean, static_cast<::realm::sync::websocket::WebSocketError>(error_code), message); } diff --git a/src/cpprealm/internal/networking/utils.cpp b/src/cpprealm/internal/networking/utils.cpp index b73d3bd08..13128d3c4 100644 --- a/src/cpprealm/internal/networking/utils.cpp +++ b/src/cpprealm/internal/networking/utils.cpp @@ -45,7 +45,7 @@ namespace realm::internal::networking { REALM_TERMINATE("Unrecognized websocket protocol"); } - ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::sync_socket_provider::websocket_endpoint& ep, + ::realm::sync::WebSocketEndpoint to_core_websocket_endpoint(const ::realm::networking::websocket_endpoint& ep, const std::optional<::realm::networking::default_socket_provider::configuration>& config) { ::realm::sync::WebSocketEndpoint core_ep; auto uri = util::Uri(ep.url); @@ -90,8 +90,8 @@ namespace realm::internal::networking { return core_ep; } - ::realm::networking::sync_socket_provider::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& core_ep) { - ::realm::networking::sync_socket_provider::websocket_endpoint ep; + ::realm::networking::websocket_endpoint to_websocket_endpoint(const ::realm::sync::WebSocketEndpoint& core_ep) { + ::realm::networking::websocket_endpoint ep; ep.protocols = core_ep.protocols; const auto& port = core_ep.proxy ? core_ep.proxy->port : core_ep.port; ep.url = util::format("%1://%2:%3%4", core_ep.is_ssl ? "wss" : "ws", core_ep.address, port, core_ep.path); diff --git a/src/cpprealm/networking/websocket.cpp b/src/cpprealm/networking/websocket.cpp index 4e8c40da4..76851891a 100644 --- a/src/cpprealm/networking/websocket.cpp +++ b/src/cpprealm/networking/websocket.cpp @@ -25,7 +25,7 @@ namespace realm::networking { bool websocket_closed_handler(bool was_clean, ::realm::sync::websocket::WebSocketError error_code, std::string_view message) override { - return m_observer->websocket_closed_handler(was_clean, static_cast(error_code), message); + return m_observer->websocket_closed_handler(was_clean, static_cast(error_code), message); } private: diff --git a/tests/sync/networking_tests.cpp b/tests/sync/networking_tests.cpp index f15257aac..82d2209bd 100644 --- a/tests/sync/networking_tests.cpp +++ b/tests/sync/networking_tests.cpp @@ -38,7 +38,7 @@ TEST_CASE("custom transport to proxy", "[proxy]") { m_configuration = configuration; } std::unique_ptr<::realm::networking::websocket_interface> connect(std::unique_ptr<::realm::networking::websocket_observer> o, - ::realm::networking::sync_socket_provider::websocket_endpoint&& ep) override { + ::realm::networking::websocket_endpoint&& ep) override { m_called = true; return ::realm::networking::default_socket_provider::connect(std::move(o), std::move(ep)); } @@ -190,7 +190,7 @@ TEST_CASE("built in transport to proxy roundtrip", "[proxy]") { } TEST_CASE("WebsocketEndpoint", "[proxy]") { - realm::networking::sync_socket_provider::websocket_endpoint ep; + realm::networking::websocket_endpoint ep; ep.url = "wss://my-server.com:443"; ep.protocols = std::vector({"test_protocol"}); From 71e658cf4ba8b0f650a1a6429a1287b76eecd8e9 Mon Sep 17 00:00:00 2001 From: Lee Maguire Date: Wed, 17 Jul 2024 11:51:22 +0100 Subject: [PATCH 41/41] Address feedback, remove curl and NSURLSession transport --- CMakeLists.txt | 20 +- Package.swift | 2 - include/cpprealm/networking/websocket.hpp | 2 +- src/CMakeLists.txt | 1 + .../internal/apple/network_transport.mm | 122 ---------- .../internal/curl/network_transport.cpp | 209 ------------------ .../network_transport.cpp | 20 +- tools/cmake/Config.cmake.in | 1 - 8 files changed, 14 insertions(+), 363 deletions(-) delete mode 100644 src/cpprealm/internal/apple/network_transport.mm delete mode 100644 src/cpprealm/internal/curl/network_transport.cpp rename src/cpprealm/internal/{network => networking}/network_transport.cpp (97%) diff --git a/CMakeLists.txt b/CMakeLists.txt index d6abfe709..f8b067590 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,24 +122,8 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang" target_compile_options(cpprealm PRIVATE -Wall -Wextra -pedantic -Werror) endif() -if(APPLE OR MSVC) - target_sources(cpprealm PRIVATE src/cpprealm/internal/network/network_transport.cpp) -elseif(ANDROID) - set(REALM_ANDROID) - target_sources(cpprealm PRIVATE src/cpprealm/internal/network/network_transport.cpp) -else() - find_package(CURL) - if(NOT CURL_FOUND) - message(WARNING "CURL not found. Realm C++ will use internal networking components instead.") - target_sources(cpprealm PRIVATE src/cpprealm/internal/network/network_transport.cpp) - else() - target_link_libraries(cpprealm PUBLIC CURL::libcurl) - target_sources(cpprealm PRIVATE src/cpprealm/internal/curl/network_transport.cpp) - endif() - - if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0) - target_link_libraries(cpprealm PRIVATE stdc++fs) - endif() +if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0) + target_link_libraries(cpprealm PRIVATE stdc++fs) endif() if(MSVC AND NOT DEFINED CMAKE_MSVC_RUNTIME_LIBRARY) diff --git a/Package.swift b/Package.swift index 2bffd245f..644a19c07 100644 --- a/Package.swift +++ b/Package.swift @@ -41,8 +41,6 @@ let cppSdkTarget: Target = .target( ], path: ".", exclude: [ - "src/cpprealm/internal/curl", - "src/cpprealm/internal/network", "src/cpprealm/util/config.in.h", "realm-core" ], diff --git a/include/cpprealm/networking/websocket.hpp b/include/cpprealm/networking/websocket.hpp index 1fabb51e5..1e4ea9889 100644 --- a/include/cpprealm/networking/websocket.hpp +++ b/include/cpprealm/networking/websocket.hpp @@ -77,7 +77,7 @@ namespace realm::networking { read_error = 4402, write_error = 4403, retry_error = 4404, - fata_error = 4405, + fatal_error = 4405, }; using status = ::realm::internal::bridge::status; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 54acc4e4e..654279808 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -51,6 +51,7 @@ set(SOURCES cpprealm/internal/bridge/thread_safe_reference.cpp cpprealm/internal/bridge/timestamp.cpp cpprealm/internal/bridge/uuid.cpp + cpprealm/internal/networking/network_transport.cpp cpprealm/internal/networking/shims.cpp cpprealm/internal/networking/utils.cpp cpprealm/internal/scheduler/realm_core_scheduler.cpp diff --git a/src/cpprealm/internal/apple/network_transport.mm b/src/cpprealm/internal/apple/network_transport.mm deleted file mode 100644 index c4c5da1db..000000000 --- a/src/cpprealm/internal/apple/network_transport.mm +++ /dev/null @@ -1,122 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -namespace realm::networking { - - void default_http_transport::send_request_to_server(const request& request, - std::function&& completion_block) { - NSURL* url = [NSURL URLWithString:[NSString stringWithCString:request.url.c_str() - encoding:NSUTF8StringEncoding]]; - NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:url]; - - switch (request.method) { - case http_method::get: - [urlRequest setHTTPMethod:@"GET"]; - break; - case http_method::post: - [urlRequest setHTTPMethod:@"POST"]; - break; - case http_method::put: - [urlRequest setHTTPMethod:@"PUT"]; - break; - case http_method::patch: - [urlRequest setHTTPMethod:@"PATCH"]; - break; - case http_method::del: - [urlRequest setHTTPMethod:@"DELETE"]; - break; - } - - for (auto& header : request.headers) { - [urlRequest addValue:[NSString stringWithCString:header.second.c_str() encoding:NSUTF8StringEncoding] - forHTTPHeaderField:[NSString stringWithCString:header.first.c_str() encoding:NSUTF8StringEncoding]]; - } - if (m_configuration.custom_http_headers) { - for (auto& header : *m_configuration.custom_http_headers) { - [urlRequest addValue:[NSString stringWithCString:header.second.c_str() encoding:NSUTF8StringEncoding] - forHTTPHeaderField:[NSString stringWithCString:header.first.c_str() encoding:NSUTF8StringEncoding]]; - } - } - if (request.method != http_method::get && !request.body.empty()) { - [urlRequest setHTTPBody:[[NSString stringWithCString:request.body.c_str() encoding:NSUTF8StringEncoding] - dataUsingEncoding:NSUTF8StringEncoding]]; - } - - NSURLSession *session; - if (m_configuration.proxy_config) { - NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSString *proxyHost = @(m_configuration.proxy_config->address.c_str()); - NSInteger proxyPort = m_configuration.proxy_config->port; - sessionConfiguration.connectionProxyDictionary = @{ - @"HTTPSEnable": @YES, - @"HTTPSProxy": @(proxyPort), - @"HTTPSPort": proxyHost, - }; - sessionConfiguration.requestCachePolicy = NSURLRequestCachePolicy::NSURLRequestReloadIgnoringLocalCacheData; - urlRequest.cachePolicy = NSURLRequestCachePolicy::NSURLRequestReloadIgnoringLocalCacheData; - [[NSURLCache sharedURLCache] removeAllCachedResponses]; - - session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; - if (m_configuration.proxy_config->username_password) { - auto userpass = util::format("%1:%2", m_configuration.proxy_config->username_password->first, - m_configuration.proxy_config->username_password->second); - std::string encoded_userpass; - encoded_userpass.resize(realm::util::base64_encoded_size(userpass.length())); - realm::util::base64_encode(userpass, encoded_userpass); - [urlRequest addValue:@(util::format("Basic %1", encoded_userpass).c_str()) forHTTPHeaderField:@"'Proxy-Authorization'"]; - } - - } else { - session = [NSURLSession sharedSession]; - } - - NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest - completionHandler:[request = std::move(request), - completion = completion_block](NSData *data, NSURLResponse *response, NSError *error) { - auto httpResponse = (NSHTTPURLResponse *)response; - std::string body; - if (data) { - body = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] cStringUsingEncoding:NSUTF8StringEncoding]; - } - int status_code; - if (error) { - status_code = 500; - } else { - status_code = static_cast(httpResponse.statusCode); - } - - completion({ - .http_status_code=status_code, - .body=body - }); - }]; - [dataTask resume]; - } -} diff --git a/src/cpprealm/internal/curl/network_transport.cpp b/src/cpprealm/internal/curl/network_transport.cpp deleted file mode 100644 index c668018f4..000000000 --- a/src/cpprealm/internal/curl/network_transport.cpp +++ /dev/null @@ -1,209 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////// - -#include -#include - -#include - -#include -#include - -namespace realm::networking { - - namespace { - - class CurlGlobalGuard { - public: - CurlGlobalGuard() - { - std::lock_guard lk(m_mutex); - if (++m_users == 1) { - curl_global_init(CURL_GLOBAL_ALL); - } - } - - ~CurlGlobalGuard() - { - std::lock_guard lk(m_mutex); - if (--m_users == 0) { - curl_global_cleanup(); - } - } - - CurlGlobalGuard(const CurlGlobalGuard&) = delete; - CurlGlobalGuard(CurlGlobalGuard&&) = delete; - CurlGlobalGuard& operator=(const CurlGlobalGuard&) = delete; - CurlGlobalGuard& operator=(CurlGlobalGuard&&) = delete; - - private: - static std::mutex m_mutex; - static int m_users; - }; - - std::mutex CurlGlobalGuard::m_mutex = {}; - int CurlGlobalGuard::m_users = 0; - - static size_t curl_write_cb(char* ptr, size_t size, size_t nmemb, std::string* response) - { - size_t realsize = size * nmemb; - response->append(ptr, realsize); - return realsize; - } - - static size_t curl_header_cb(char* buffer, size_t size, size_t nitems, std::map* response_headers) - { - std::string combined(buffer, size * nitems); - if (auto pos = combined.find(':'); pos != std::string::npos) { - std::string key = combined.substr(0, pos); - std::string value = combined.substr(pos + 1); - while (value.size() > 0 && value[0] == ' ') { - value = value.substr(1); - } - while (value.size() > 0 && (value[value.size() - 1] == '\r' || value[value.size() - 1] == '\n')) { - value = value.substr(0, value.size() - 1); - } - response_headers->insert({key, value}); - } - else { - if (combined.size() > 5 && combined.substr(0, 5) != "HTTP/") { // ignore for now HTTP/1.1 ... - std::cerr << "test transport skipping header: " << combined << std::endl; - } - } - return nitems * size; - } - } // namespace - - using SSLVerifyCallback = std::function; - - CURLcode ssl_ctx_callback(CURL *curl, void */*sslctx*/, SSLVerifyCallback *parm) { - auto verify_callback = (SSLVerifyCallback)(*parm); - - char *url; - curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url); - std::string server_address(url); - - long port; - curl_easy_getinfo(curl, CURLINFO_PRIMARY_PORT, &port); - - const char *pem_data = "-----BEGIN CERTIFICATE-----\n...certificate data...\n-----END CERTIFICATE-----\n"; - size_t pem_size = std::strlen(pem_data); - - int preverify_ok = 1; - int depth = 0; - - bool result = verify_callback(server_address, port, pem_data, pem_size, preverify_ok, depth); - return result ? CURLE_OK : CURLE_SSL_CERTPROBLEM; - } - - void default_http_transport::send_request_to_server(const ::realm::networking::request& request, - std::function&& completion_block) { - CurlGlobalGuard curl_global_guard; - auto curl = curl_easy_init(); - if (!curl) { - completion_block(::realm::networking::response{500, -1, {}, "", std::nullopt}); - return; - } - - struct curl_slist* list = nullptr; - - std::string response; - ::realm::networking::http_headers response_headers; - - curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str()); - - - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, m_configuration.client_validate_ssl); - - if (m_configuration.ssl_verify_callback) { - curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_callback); - curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA, &m_configuration.ssl_verify_callback); - } - - if (m_configuration.ssl_trust_certificate_path) { - curl_easy_setopt(curl, CURLOPT_CAINFO, m_configuration.ssl_trust_certificate_path->c_str()); - } - - if (m_configuration.proxy_config) { - curl_easy_setopt(curl, CURLOPT_PROXY, util::format("%1:%2", m_configuration.proxy_config->address, m_configuration.proxy_config->port).c_str()); - curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L); - if (m_configuration.proxy_config->username_password) { - curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, util::format("%1:%2", m_configuration.proxy_config->username_password->first, m_configuration.proxy_config->username_password->second).c_str()); - } - } - - /* Now specify the POST data */ - if (request.method == ::realm::networking::http_method::post) { - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); - } - else if (request.method == ::realm::networking::http_method::put) { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); - } - else if (request.method == ::realm::networking::http_method::patch) { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); - } - else if (request.method == ::realm::networking::http_method::del) { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); - } - else if (request.method == ::realm::networking::http_method::patch) { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str()); - } - - curl_easy_setopt(curl, CURLOPT_TIMEOUT, request.timeout_ms); - - for (auto header : request.headers) { - auto header_str = util::format("%1: %2", header.first, header.second); - list = curl_slist_append(list, header_str.c_str()); - } - if (m_configuration.custom_http_headers) { - for (auto header : *m_configuration.custom_http_headers) { - auto header_str = util::format("%1: %2", header.first, header.second); - list = curl_slist_append(list, header_str.c_str()); - } - } - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); - curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_cb); - curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response_headers); - - auto response_code = curl_easy_perform(curl); - if (response_code != CURLE_OK) { - fprintf(stderr, "curl_easy_perform() failed when sending request to '%s' with body '%s': %s\n", - request.url.c_str(), request.body.c_str(), curl_easy_strerror(response_code)); - } - long http_code = 0; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); - curl_easy_cleanup(curl); - curl_slist_free_all(list); - completion_block({ - static_cast(http_code), - 0, // binding_response_code - std::move(response_headers), - std::move(response), - std::nullopt - }); - } -} // namespace realm::networking diff --git a/src/cpprealm/internal/network/network_transport.cpp b/src/cpprealm/internal/networking/network_transport.cpp similarity index 97% rename from src/cpprealm/internal/network/network_transport.cpp rename to src/cpprealm/internal/networking/network_transport.cpp index 6054a70d3..543d05a26 100644 --- a/src/cpprealm/internal/network/network_transport.cpp +++ b/src/cpprealm/internal/networking/network_transport.cpp @@ -17,17 +17,17 @@ //////////////////////////////////////////////////////////////////////////// #if __has_include() -#include +#include "realm/util/config.h" #endif -#include -#include - -#include -#include -#include -#include -#include -#include +#include "cpprealm/app.hpp" +#include "cpprealm/networking/http.hpp" + +#include "realm/object-store/sync/generic_network_transport.hpp" +#include "realm/sync/network/http.hpp" +#include "realm/sync/network/network.hpp" +#include "realm/sync/network/network_ssl.hpp" +#include "realm/util/base64.hpp" +#include "realm/util/uri.hpp" #include diff --git a/tools/cmake/Config.cmake.in b/tools/cmake/Config.cmake.in index 91f24b089..bbb11e2ec 100644 --- a/tools/cmake/Config.cmake.in +++ b/tools/cmake/Config.cmake.in @@ -11,7 +11,6 @@ if(NOT APPLE AND NOT EMSCRIPTEN AND NOT WINDOWS_STORE AND NOT ANDROID) set(REALM_HAVE_UV 1) find_package(libuv REQUIRED) find_dependency(libuv) - find_package(CURL) endif() find_package(Realm CONFIG REQUIRED)