Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for HTTP tunnelling #106

Merged
merged 6 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,23 @@ X.Y.Z Release notes (YYYY-MM-DD)
Subsequently, if you can make a frozen Realm / Object live again by calling `thaw()`.
It is not recommended to have too many long-lived frozen Realm's / Objects in your application as it may balloon memory consumption.
* Add ability to sort `experimental::results` / `managed<std::vector<T>>`.
* Add support for HTTP tunneling. Usage:
```cpp
realm::proxy_config proxy_config;
proxy_config.type = realm::proxy_config_type::HTTP;
proxy_config.port = 8080;
proxy_config.address = "127.0.0.1";
proxy_config.username_password = {"username", "password"};

realm::App::configuration app_config;
app_config.proxy_configuration = proxy_config;
auto app = realm::App(app_config);

auto user = app.get_current_user();
auto sync_config = user->flexible_sync_configuration();
sync_config.set_proxy_config(proxy_config);
auto synced_realm = experimental::db(sync_config);
```

### Breaking Changes
* None
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,4 @@ endif()

if (NOT REALM_CPP_NO_TESTS)
add_subdirectory(tests)
endif()
endif()
3 changes: 2 additions & 1 deletion src/cpprealm/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,8 @@ namespace realm {

auto app_config = app::App::Config();
app_config.app_id = config.app_id;
app_config.transport = std::make_shared<internal::DefaultTransport>(config.custom_http_headers);
app_config.transport = std::make_shared<internal::DefaultTransport>(config.custom_http_headers,
config.proxy_configuration);
app_config.base_url = config.base_url;
auto device_info = app::App::Config::DeviceInfo();

Expand Down
2 changes: 2 additions & 0 deletions src/cpprealm/app.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include <utility>

namespace realm {
using proxy_config = sync_config::proxy_config;
using sync_session = internal::bridge::sync_session;
class SyncUser;

Expand Down Expand Up @@ -211,6 +212,7 @@ class App {
std::optional<std::string> path;
std::optional<std::map<std::string, std::string>> custom_http_headers;
std::optional<std::array<char, 64>> metadata_encryption_key;
std::optional<sync_config::proxy_config> proxy_configuration;
};

[[deprecated("Use App(const configuration&) instead.")]]
Expand Down
1 change: 1 addition & 0 deletions src/cpprealm/db.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace realm {
using sync_config = internal::bridge::realm::sync_config;
using db_config = internal::bridge::realm::config;
using sync_session = internal::bridge::sync_session;
using status = internal::bridge::status;

template <typename ...Ts>
struct db {
Expand Down
32 changes: 31 additions & 1 deletion src/cpprealm/internal/apple/network_transport.mm
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@

#include <Foundation/NSData.h>
#include <Foundation/NSURL.h>
#include <Foundation/NSURLCache.h>
#include <Foundation/NSURLResponse.h>
#include <Foundation/NSURLSession.h>

#include <realm/util/base64.hpp>

namespace realm::internal {
void DefaultTransport::send_request_to_server(const app::Request& request,
util::UniqueFunction<void(const app::Response&)>&& completion_block) {
Expand Down Expand Up @@ -63,7 +66,34 @@
[urlRequest setHTTPBody:[[NSString stringWithCString:request.body.c_str() encoding:NSUTF8StringEncoding]
dataUsingEncoding:NSUTF8StringEncoding]];
}
NSURLSession *session = [NSURLSession sharedSession];

NSURLSession *session;
if (m_proxy_config) {
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSString *proxyHost = @(m_proxy_config->address.c_str());
NSInteger proxyPort = m_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_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;
encoded_userpass.resize(realm::util::base64_encoded_size(userpass.length()));
realm::util::base64_encode(userpass.data(), userpass.size(), encoded_userpass.data(), encoded_userpass.size());
[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_ptr = completion_block.release()](NSData *data, NSURLResponse *response, NSError *error) {
Expand Down
12 changes: 12 additions & 0 deletions src/cpprealm/internal/bridge/realm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,18 @@ namespace realm::internal::bridge {
}
}

void realm::config::set_proxy_config(const sync_config::proxy_config& config) {
if (get_config()->sync_config) {
SyncConfig::ProxyConfig core_config;
core_config.address = config.address;
core_config.port = config.port;
core_config.type = SyncConfig::ProxyConfig::Type::HTTP;
get_config()->sync_config->proxy_config = std::move(core_config);
} else {
throw std::logic_error("Proxy configuration can only be set on a config for a synced Realm.");
}
}

void realm::config::set_schema_version(uint64_t version) {
get_config()->schema_version = version;
}
Expand Down
39 changes: 24 additions & 15 deletions src/cpprealm/internal/bridge/realm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,29 @@ namespace realm::internal::bridge {
RecoverOrDiscard,
};

struct sync_config;
struct sync_config {

struct proxy_config {
using port_type = std::uint_fast16_t;
std::string address;
port_type port;
// For basic authorization.
std::optional<std::pair<std::string, std::string>> username_password;
};

struct flx_sync_enabled {};
sync_config() {}
sync_config(const std::shared_ptr<SyncUser> &user);
sync_config(const std::shared_ptr<SyncConfig> &);//NOLINT(google-explicit-constructor)
operator std::shared_ptr<SyncConfig>() const; //NOLINT(google-explicit-constructor)
void set_client_resync_mode(client_resync_mode &&);
void set_stop_policy(sync_session_stop_policy &&);
void set_error_handler(std::function<void(const sync_session &, const sync_error &)> &&fn);

private:
std::shared_ptr<SyncConfig> m_config;
};

struct config {
// How to handle update_schema() being called on a file which has
// already been initialized with a different schema
Expand Down Expand Up @@ -151,6 +173,7 @@ namespace realm::internal::bridge {
void set_scheduler(const std::shared_ptr<struct scheduler>&);
void set_sync_config(const std::optional<struct sync_config>&);
void set_custom_http_headers(const std::map<std::string, std::string>& headers);
void set_proxy_config(const sync_config::proxy_config&);
void set_schema_version(uint64_t version);
void set_encryption_key(const std::array<char, 64>&);
std::optional<schema> get_schema();
Expand All @@ -164,20 +187,6 @@ namespace realm::internal::bridge {
#endif
};

struct sync_config {
struct flx_sync_enabled {};
sync_config() {}
sync_config(const std::shared_ptr<SyncUser> &user);
sync_config(const std::shared_ptr<SyncConfig> &);//NOLINT(google-explicit-constructor)
operator std::shared_ptr<SyncConfig>() const; //NOLINT(google-explicit-constructor)
void set_client_resync_mode(client_resync_mode &&);
void set_stop_policy(sync_session_stop_policy &&);
void set_error_handler(std::function<void(const sync_session &, const sync_error &)> &&fn);

private:
std::shared_ptr<SyncConfig> m_config;
};

realm();
realm(const config&); //NOLINT(google-explicit-constructor)
realm(std::shared_ptr<Realm>); //NOLINT(google-explicit-constructor)
Expand Down
14 changes: 11 additions & 3 deletions src/cpprealm/internal/curl/network_transport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ namespace realm::internal {
return nitems * size;
}

static app::Response do_http_request(const app::Request& request)
static app::Response do_http_request(const app::Request& request,
const std::optional<bridge::realm::sync_config::proxy_config>& proxy_config = std::nullopt)
{
CurlGlobalGuard curl_global_guard;
auto curl = curl_easy_init();
Expand All @@ -104,6 +105,13 @@ namespace realm::internal {
data. */
curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str());

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());
}

/* Now specify the POST data */
if (request.method == app::HttpMethod::post) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.c_str());
Expand Down Expand Up @@ -161,10 +169,10 @@ namespace realm::internal {
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));
completion_block(do_http_request(req_copy, m_proxy_config));
return;
}
completion_block(do_http_request(request));
completion_block(do_http_request(request, m_proxy_config));
}


Expand Down
9 changes: 8 additions & 1 deletion src/cpprealm/internal/generic_network_transport.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,25 @@
#define realm_cpp_generic_network_transport

#include <realm/object-store/sync/generic_network_transport.hpp>
#include <cpprealm/internal/bridge/realm.hpp>

#include <map>
#include <optional>

namespace realm::internal {

class DefaultTransport : public app::GenericNetworkTransport {
public:
DefaultTransport(const std::optional<std::map<std::string, std::string>>& custom_http_headers = std::nullopt) : m_custom_http_headers(custom_http_headers) {}
DefaultTransport(const std::optional<std::map<std::string, std::string>>& custom_http_headers = std::nullopt,
const std::optional<bridge::realm::sync_config::proxy_config>& proxy_config = std::nullopt)
: m_custom_http_headers(custom_http_headers),
m_proxy_config(proxy_config) {}

void send_request_to_server(const app::Request& request,
util::UniqueFunction<void(const app::Response&)>&& completion);
private:
std::optional<std::map<std::string, std::string>> m_custom_http_headers;
std::optional<bridge::realm::sync_config::proxy_config> m_proxy_config;
};

} // namespace realm
Expand Down
89 changes: 74 additions & 15 deletions src/cpprealm/internal/network/network_transport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
#include <realm/sync/network/http.hpp>
#include <realm/sync/network/network.hpp>
#include <realm/sync/noinst/client_impl_base.hpp>
#include <realm/util/base64.hpp>
#include <realm/util/uri.hpp>

#include <regex>


namespace realm::internal {
struct DefaultSocket : realm::sync::network::Socket {
Expand Down Expand Up @@ -80,31 +85,81 @@ namespace realm::internal {

void DefaultTransport::send_request_to_server(const app::Request& request,
util::UniqueFunction<void(const app::Response&)>&& completion_block) {
// Get the host name.
size_t found = request.url.find_first_of(":");
std::string domain = request.url.substr(found+3);
size_t found1 = domain.find_first_of(":");
size_t found2 = domain.find_first_of("/");
const std::string host = domain.substr(found1 + 1, found2 - found1 - 1);
std::string host = realm::util::Uri(request.url).get_auth();

realm::sync::network::Service service;
realm::sync::network::Endpoint ep;
using namespace realm::sync::network::ssl;
Context m_ssl_context;
DefaultSocket socket{service};

try {
realm::sync::network::Endpoint ep;
auto resolver = realm::sync::network::Resolver{service};
auto resolved = resolver.resolve(sync::network::Resolver::Query(host, "443"));
ep = *resolved.begin();
if (m_proxy_config) {
std::string proxy_address = realm::util::Uri(m_proxy_config->address).get_auth();
if (proxy_address.empty()) {
std::error_code e;
auto address = realm::sync::network::make_address(m_proxy_config->address, e);
if (e.value() > 0) {
app::Response response;
response.custom_status_code = e.value();
response.body = e.message();
completion_block(std::move(response));
return;
}
ep = realm::sync::network::Endpoint(address, m_proxy_config->port);
} else {
auto resolved = resolver.resolve(sync::network::Resolver::Query(proxy_address, m_proxy_config->port));
ep = *resolved.begin();
}
} else {
auto resolved = resolver.resolve(sync::network::Resolver::Query(host, "443"));
ep = *resolved.begin();
}
socket.connect(ep);
} catch (...) {
app::Response response;
response.http_status_code = 500;
response.custom_status_code = util::error::operation_aborted;
completion_block(std::move(response));
return;
}

using namespace realm::sync::network::ssl;
Context m_ssl_context;
DefaultSocket socket{service};
socket.connect(ep);
auto logger = util::Logger::get_default_logger();

if (m_proxy_config) {
realm::sync::HTTPRequest req;
req.method = realm::sync::HTTPMethod::Connect;
req.headers.emplace("Host", util::format("%1:%2", host, "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;
encoded_userpass.resize(realm::util::base64_encoded_size(userpass.length()));
realm::util::base64_encode(userpass.data(), userpass.size(), encoded_userpass.data(), encoded_userpass.size());
req.headers.emplace("Proxy-Authorization", util::format("Basic %1", encoded_userpass));
}
realm::sync::HTTPClient<DefaultSocket> m_proxy_client = realm::sync::HTTPClient<DefaultSocket>(socket, logger);
auto handler = [&](realm::sync::HTTPResponse response, std::error_code ec) {
if (ec && ec != util::error::operation_aborted) {
app::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;
res.http_status_code = static_cast<uint16_t>(response.status);
completion_block(std::move(res));
return;
}
service.stop();
};

m_proxy_client.async_request(req, std::move(handler)); // Throws

service.run_until_stopped();
service.reset();
}

#if REALM_INCLUDE_CERTS
m_ssl_context.use_included_certificate_roots();
Expand All @@ -114,7 +169,6 @@ namespace realm::internal {
socket.ssl_stream->set_host_name(host); // Throws

socket.ssl_stream->set_verify_mode(VerifyMode::peer);
auto logger = util::Logger::get_default_logger();
socket.ssl_stream->set_logger(logger.get());

realm::sync::HTTPHeaders headers;
Expand Down Expand Up @@ -182,6 +236,11 @@ namespace realm::internal {
res.custom_status_code = 0;
cb(res);
});
} else {
app::Response response;
response.custom_status_code = util::error::operation_aborted;
completion_block(std::move(response));
return;
}
};
socket.async_handshake(std::move(handler)); // Throws
Expand Down
Loading