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

Draft networking #7715

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Expand Up @@ -62,6 +62,14 @@ allows submitting to the app store with Xcode 12.
* Using the dynamic subscript API on unmanaged objects before first opening a
Realm or if `objectTypes` was set when opening a Realm would throw an
exception ([#7786](https://github.com/realm/realm-swift/issues/7786)).
* None.
* Add ability to use Swift Query syntax in `@ObservedResults`, which allows you
to filter results using the `where` parameter.
* Add ability to use `MutableSet` with `StateRealmObject` in SwiftUI.

### Fixed
* Adding a Realm Object to a `ObservedResults` or a collections using `StateRealmObject` that is managed by the same Realm
would throw if the Object was frozen and not thawed before hand.

<!-- ### Breaking Changes - ONLY INCLUDE FOR NEW MAJOR version -->

Expand All @@ -71,6 +79,9 @@ allows submitting to the app store with Xcode 12.
* Carthage release for Swift is built with Xcode 13.4.
* CocoaPods: 1.10 or later.
* Xcode: 13.1-13.4.
* Carthage release for Swift is built with Xcode 13.3.
* CocoaPods: 1.10 or later.
* Xcode: 12.4-13.3.

### Internal
* Upgraded realm-core from ? to ?
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Expand Up @@ -110,7 +110,7 @@ let package = Package(
targets: ["Realm", "RealmSwift"]),
],
dependencies: [
.package(name: "RealmDatabase", url: "https://github.com/realm/realm-core", .exact(Version(coreVersionStr)!))
.package(name: "RealmDatabase", path: "../realm-core-networking")
],
targets: [
.target(
Expand Down
3 changes: 2 additions & 1 deletion Realm/RLMApp.mm
Expand Up @@ -56,6 +56,7 @@ void send_request_to_server(app::Request&& request,
rlmRequest.method = static_cast<RLMHTTPMethod>(request.method);
rlmRequest.timeout = request.timeout_ms / 1000.0;

__block auto blockCompletionBlock = std::move(completion);
// Send the request through to the Cocoa level transport
auto completion_ptr = completion.release();
[m_transport sendRequestToServer:rlmRequest completion:^(RLMResponse *response) {
Expand All @@ -67,7 +68,7 @@ void send_request_to_server(app::Request&& request,

// Convert the RLMResponse to an app:Response and pass downstream to
// the object store
completion(app::Response{
blockCompletionBlock(app::Response{
.http_status_code = static_cast<int>(response.httpStatusCode),
.custom_status_code = static_cast<int>(response.customStatusCode),
.headers = bridgingHeaders,
Expand Down
163 changes: 163 additions & 0 deletions Realm/RLMSyncConfiguration.mm
Expand Up @@ -35,9 +35,159 @@
#import <realm/object-store/sync/sync_session.hpp>
#import <realm/sync/config.hpp>
#import <realm/sync/protocol.hpp>
#import <realm/util/ez_websocket.hpp>

using namespace realm;

API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0))
@interface RLMCocoaSocketDelegate : NSObject<NSURLSessionWebSocketDelegate>
@property realm::util::websocket::EZObserver *observer;
@property util::Logger *logger;
@end

@implementation RLMCocoaSocketDelegate

- (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didOpenWithProtocol:(NSString *)protocol {
REALM_ASSERT([protocol length] > 0);
_logger->info(">>> CocoaSocket didOpenWithProtocol");
_observer->websocket_handshake_completion_handler([protocol cStringUsingEncoding:NSUTF8StringEncoding]);
}

- (void)URLSession:(NSURLSession *)session webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didCloseWithCode:(NSURLSessionWebSocketCloseCode)closeCode reason:(NSData *)reason {
_logger->info(">>> CocoaSocket didCloseWithCode '%1'", closeCode);
_observer->websocket_close_message_received(std::error_code(static_cast<int>(closeCode), std::generic_category()),
RLMStringDataWithNSString([[NSString alloc] initWithData: reason encoding:NSUTF8StringEncoding]));
}

@end

namespace realm::util::websocket {
class API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) CocoaSocket: public EZSocket {

public:
CocoaSocket(EZConfig config, EZObserver* observer, EZEndpoint&& endpoint):
m_config(config)
, delegate([RLMCocoaSocketDelegate new])
{
delegate.logger = &m_config.logger;
setup(observer, std::move(endpoint));
}

RLMCocoaSocketDelegate *delegate;
NSURLSessionWebSocketTask *task;
NSURLSession *session;

~CocoaSocket()
{
destroySocket();
}

util::Logger& logger() const
{
return m_config.logger;
}

util::UniqueFunction<void()> m_write_completion_handler;

void async_write_binary(const char *data, size_t size, util::UniqueFunction<void ()> &&handler) override
{
logger().info(">>> CocoaSocket async_write_binary");
m_write_completion_handler = std::move(handler);

auto message = [[NSURLSessionWebSocketMessage alloc] initWithString:@(data)];
[task sendMessage:message
completionHandler:^(NSError * _Nullable error) {
if (error) {
logger().error("Failed to send message: %1", error.localizedDescription.UTF8String); // Throws
delegate.observer->websocket_read_or_write_error_handler(std::error_code(static_cast<int>(error.code), std::generic_category())); // Throws
destroySocket(error);
return;
}
m_write_completion_handler();
m_write_completion_handler = nullptr;
}];
}

private:

void destroySocket(NSError *error = nil) {
if (error) {
[session invalidateAndCancel];
} else {
[session finishTasksAndInvalidate];
}
}

NSURL *buildUrl(EZEndpoint endpoint) {
NSMutableArray<NSString *> *strs = [NSMutableArray<NSString *> new];
endpoint.is_ssl ? [strs addObject: @"wss://"] : [strs addObject: @"ws://"];
endpoint.proxy ? [strs addObject: @(endpoint.proxy->address.data())] : [strs addObject: @(endpoint.address.data())];
endpoint.proxy ? [strs addObject: [NSString stringWithFormat:@":%u", endpoint.proxy->port]] : [strs addObject: [NSString stringWithFormat:@":%u", endpoint.port]];
[strs addObject:@(endpoint.path.data())];
logger().info("Connecting to '%1'", [[strs componentsJoinedByString:@""] cStringUsingEncoding:NSUTF8StringEncoding]);
return [[NSURL alloc] initWithString:[strs componentsJoinedByString:@""]];
}

void setup(EZObserver* observer, EZEndpoint&& endpoint)
{
logger().info(">>> CocoaSocket setup");
delegate.observer = observer;

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];

auto sessionHeaders = [NSMutableDictionary new];
for (const auto &x: endpoint.headers) {
sessionHeaders[@(x.first.c_str())] = @(x.second.c_str());
}
configuration.HTTPAdditionalHeaders = sessionHeaders;

auto protocols = [[NSString stringWithUTF8String:endpoint.protocols.c_str()] componentsSeparatedByString:@","];

NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:delegate delegateQueue:nil];

task = [session webSocketTaskWithURL:buildUrl(endpoint) protocols:protocols];
[task receiveMessageWithCompletionHandler:^(NSURLSessionWebSocketMessage * _Nullable message, NSError * _Nullable error) {
if (error) {
logger().error("Failed to connect to endpoint '%1:%2'", endpoint.address, endpoint.proxy->port); // Throws
observer->websocket_read_or_write_error_handler(std::error_code(static_cast<int>(error.code), std::generic_category())); // Throws
destroySocket(error);
return;
}
switch (message.type) {
case NSURLSessionWebSocketMessageTypeData:
observer->websocket_binary_message_received([[[NSString alloc] initWithData:message.data encoding:NSUTF8StringEncoding] UTF8String],
[message.data length]);
break;
case NSURLSessionWebSocketMessageTypeString:
observer->websocket_binary_message_received([message.string UTF8String],
[message.data length]);
break;
}
}];
[task resume];
}

EZConfig m_config;
};

class API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) CocoaSocketFactory: public EZSocketFactory {
public:
CocoaSocketFactory(EZConfig config)
: EZSocketFactory(config)
, m_config(config)
{
}

std::unique_ptr<EZSocket> connect(EZObserver* observer, EZEndpoint&& endpoint) override
{
return std::unique_ptr<EZSocket>(new CocoaSocket(std::move(m_config), observer, std::move(endpoint)));
}

private:
EZConfig m_config;
};
}

namespace {
using ProtocolError = realm::sync::ProtocolError;

Expand Down Expand Up @@ -381,10 +531,23 @@ - (instancetype)initWithUser:(RLMUser *)user
}
}

_config->socket_factory = defaultSocketFactory();
self.customFileURL = customFileURL;
return self;
}
return nil;
}

@end

using namespace realm::util::websocket;

std::function<std::unique_ptr<EZSocketFactory>(EZConfig&&)> defaultSocketFactory() {
return [](EZConfig&& config) mutable {
if (@available(macOS 10.15, *)) {
return std::unique_ptr<EZSocketFactory>(new CocoaSocketFactory(std::move(config)));
} else {
return std::unique_ptr<EZSocketFactory>(new EZSocketFactory(std::move(config)));
}
};
}
4 changes: 4 additions & 0 deletions Realm/RLMSyncConfiguration_Private.hpp
Expand Up @@ -20,6 +20,7 @@

#import <functional>
#import <memory>
#import <realm/util/ez_websocket.hpp>

namespace realm {
class SyncSession;
Expand All @@ -28,6 +29,9 @@ struct SyncError;
using SyncSessionErrorHandler = void(std::shared_ptr<SyncSession>, SyncError);
}

using namespace realm::util::websocket;
extern std::function<std::unique_ptr<realm::util::websocket::EZSocketFactory>(realm::util::websocket::EZConfig&&)> defaultSocketFactory();

NS_ASSUME_NONNULL_BEGIN

@interface RLMSyncConfiguration ()
Expand Down
2 changes: 2 additions & 0 deletions Realm/RLMSyncManager.mm
Expand Up @@ -23,6 +23,7 @@
#import "RLMUser_Private.hpp"
#import "RLMSyncUtil_Private.hpp"
#import "RLMUtil.hpp"
#import "RLMSyncConfiguration_Private.hpp"

#import <realm/sync/config.hpp>
#import <realm/object-store/sync/sync_manager.hpp>
Expand Down Expand Up @@ -131,6 +132,7 @@ + (SyncClientConfig)configurationWithRootDirectory:(NSURL *)rootDirectory appId:
config.user_agent_application_info = RLMStringDataWithNSString(appId);
}

config.socket_factory = defaultSocketFactory();
return config;
}

Expand Down