Skip to content
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
2 changes: 1 addition & 1 deletion GitVersion.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# current development version which is first version of next release branched away from master
next-version: 1.0.0
next-version: 1.1.0

# configuration for all branches
assembly-versioning-scheme: MajorMinorPatchTag
Expand Down
51 changes: 49 additions & 2 deletions src/NewRemoting/Client.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Security;
Expand All @@ -10,6 +11,7 @@
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Castle.Core.Internal;
using Castle.DynamicProxy;
Expand Down Expand Up @@ -54,7 +56,21 @@ public sealed class Client : IDisposable
/// <param name="port">Network port</param>
/// <param name="authenticationInformation"> credentials for authentication</param>
/// <param name="settings">Advanced connection settings</param>
public Client(string server, int port, AuthenticationInformation authenticationInformation, ConnectionSettings settings)
public Client(string server, int port, AuthenticationInformation authenticationInformation,
ConnectionSettings settings)
: this(server, port, authenticationInformation, settings, new List<JsonConverter>())
{
}

/// <summary>
/// Creates a remoting client for the given server and opens the network connection
/// </summary>
/// <param name="server">Server name or IP</param>
/// <param name="port">Network port</param>
/// <param name="authenticationInformation"> credentials for authentication</param>
/// <param name="settings">Advanced connection settings</param>
/// <param name="extraConverters">A list of separate type-to-json converters. The use of <see cref="IManualSerialization"/> is preferred, though</param>
public Client(string server, int port, AuthenticationInformation authenticationInformation, ConnectionSettings settings, IList<JsonConverter> extraConverters)
{
Settings = settings ?? throw new ArgumentNullException(nameof(settings));
_clientAuthentication = authenticationInformation;
Expand Down Expand Up @@ -83,7 +99,7 @@ public Client(string server, int port, AuthenticationInformation authenticationI
_builder = new DefaultProxyBuilder();
_proxy = new ProxyGenerator(_builder);
_instanceManager = new InstanceManager(_proxy, instanceLogger);
_formatterFactory = new FormatterFactory(_instanceManager);
_formatterFactory = new FormatterFactory(_instanceManager, extraConverters);

_messageHandler = new MessageHandler(_instanceManager, _formatterFactory, Logger);

Expand Down Expand Up @@ -116,6 +132,15 @@ public Client(string server, int port, AuthenticationInformation authenticationI

// This is used as return channel
_server = new Server(s, _messageHandler, _interceptor);
_server.AddExternalSurrogates(extraConverters);

var remotingServerService = RequestRemoteInstance<IRemoteServerService>();
if (remotingServerService == null)
{
throw new RemotingException("Could not connect to remote loader interface, although server is up");
}

Logger.LogInformation("Got interface to {0}", remotingServerService.GetType().Name);
}

public ConnectionSettings Settings { get; }
Expand Down Expand Up @@ -628,5 +653,27 @@ public Task ForceGcAsync()
{
return Task.Factory.StartNew(ForceGc);
}

/// <summary>
/// Pushes the extra surrogate declarations to the server.
/// Cannot be done directly in the constructor, as it may first be necessary to copy the code to the remote side
/// </summary>
/// <exception cref="NotImplementedException"></exception>
public void PublishExtraSurrogates()
{
var remoteServer = RequestRemoteInstance<IRemoteServerService>();
var extraSurrogates = _formatterFactory.GetExternalSurrogates();
if (extraSurrogates.Any())
{
List<Type> typeList = new List<Type>();
foreach (var instance in extraSurrogates)
{
typeList.Add(instance.GetType());
}

remoteServer.RegisterConverters(typeList);
Logger.LogInformation("Extra surrogates registered");
}
}
}
}
47 changes: 46 additions & 1 deletion src/NewRemoting/FormatterFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,55 @@ namespace NewRemoting
/// </summary>
public class FormatterFactory
{
private readonly IList<JsonConverter> _externalSurrogateList;
private readonly IInstanceManager _instanceManager;
private readonly ConcurrentDictionary<string, JsonSerializerOptions> _cusBinaryFormatters;

public FormatterFactory(IInstanceManager instanceManager)
public FormatterFactory(IInstanceManager instanceManager, IList<JsonConverter> externalSurrogateList = null)
{
_instanceManager = instanceManager;
_externalSurrogateList = externalSurrogateList ?? new List<JsonConverter>();
_cusBinaryFormatters = new ConcurrentDictionary<string, JsonSerializerOptions>();
}

/// <summary>
/// Adds an external surrogate to this formatter.
/// </summary>
/// <param name="surrogate">The surrogate to add</param>
/// <param name="clearCache">True to clear the cache of JsonSerializerOptions</param>
/// <returns>True on success, false if there's already a surrogate with the same type(!) registered</returns>
public bool AddExternalSurrogate(JsonConverter surrogate, bool clearCache = true)
{
if (_externalSurrogateList.Any(x => x.GetType() == surrogate.GetType()))
{
return false;
}

_externalSurrogateList.Add(surrogate);

if (clearCache)
{
_cusBinaryFormatters.Clear();
}

return true;
}

public void AddExternalSurrogates(IEnumerable<JsonConverter> surrogates)
{
foreach (var s in surrogates)
{
AddExternalSurrogate(s, false);
}

_cusBinaryFormatters.Clear();
}

public IList<JsonConverter> GetExternalSurrogates()
{
return _externalSurrogateList;
}

public JsonSerializerOptions CreateOrGetFormatter(string otherSideProcessId)
{
if (_cusBinaryFormatters.TryGetValue(otherSideProcessId, out var formatter))
Expand All @@ -57,6 +97,11 @@ public JsonSerializerOptions CreateOrGetFormatter(string otherSideProcessId)
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
};

foreach (var converter in _externalSurrogateList)
{
options.Converters.Add(converter);
}

_cusBinaryFormatters.TryAdd(otherSideProcessId, options);
return options;
}
Expand Down
183 changes: 98 additions & 85 deletions src/NewRemoting/IRemoteLoaderClient.cs
Original file line number Diff line number Diff line change
@@ -1,87 +1,100 @@
using System;
using System.Threading;
using Microsoft.Extensions.Logging;

namespace NewRemoting
{
/// <summary>
/// Executes code on a remote machine
/// </summary>
public interface IRemoteLoaderClient : IDisposable
{
/// <summary>
/// Get the internal <see cref="Client"/> instance, to perform advanced service queries.
/// </summary>
Client RemoteClient
{
get;
}

/// <summary>
/// Creates an object in a host process on a remote machine
/// </summary>
/// <typeparam name="T">Type of object to create</typeparam>
/// <param name="parameters">Constructor arguments</param>
T CreateObject<T>(object[] parameters)
where T : MarshalByRefObject;

/// <summary>
/// Creates an object in a host process on a remote machine
/// </summary>
/// <typeparam name="T">Type of object to create</typeparam>
T CreateObject<T>()
where T : MarshalByRefObject;

/// <summary>
/// Connects the remote loader to the remote system;
/// </summary>
/// <param name="cancellationToken">Abort token for connection attempt</param>
/// <param name="clientConnectionLogger">A logger for logging all communication activities. Use for debugging only, as it has a
/// performance penalty</param>
/// <exception cref="RemotingException">Thrown if connection to remote loader fails</exception>
void Connect(CancellationToken cancellationToken, ILogger clientConnectionLogger = null);
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading;
using Microsoft.Extensions.Logging;

/// <summary>
/// Connects the remote loader to the remote system; Fails if an instance already exists.
/// The instance existence check is done on the remotingServer application name.
/// </summary>
/// <param name="checkExistingInstance">If true a check of existing process with same name is done on the remote host</param>
/// <param name="cancellationToken">Abort token for connection attempt</param>
/// <param name="clientConnectionLogger">A logger for logging all communication activities. Use for debugging only, as it has a
/// performance penalty</param>
/// <returns>Return true if a new instance of the remoting server is launched, false if an instance already exists</returns>
/// <exception cref="RemotingException">Thrown if connection to remote loader fails. Or if the remote command to get the running process fails</exception>
/// <exception cref="OperationCanceledException">Thrown if timeout occurs or cancellation</exception>
bool Connect(bool checkExistingInstance, CancellationToken cancellationToken, ILogger clientConnectionLogger = null);
namespace NewRemoting
{
/// <summary>
/// Executes code on a remote machine
/// </summary>
public interface IRemoteLoaderClient : IDisposable
{
/// <summary>
/// Get the internal <see cref="Client"/> instance, to perform advanced service queries.
/// </summary>
Client RemoteClient
{
get;
}

/// <summary>
/// Creates an object in a host process on a remote machine.
/// Provides mocking ability.
/// </summary>
/// <typeparam name="TCreate">Concrete type of object to create</typeparam>
/// <typeparam name="TReturn">Return type, may be an interface</typeparam>
/// <param name="parameters">Constructor arguments</param>
TReturn CreateObject<TCreate, TReturn>(object[] parameters)
where TCreate : MarshalByRefObject, TReturn
where TReturn : class;

/// <summary>
/// Creates an object in a host process on a remote machine.
/// Provides mocking ability.
/// </summary>
/// <typeparam name="TCreate">Concrete type of object to create</typeparam>
/// <typeparam name="TReturn">Return type, may be an interface</typeparam>
TReturn CreateObject<TCreate, TReturn>()
where TCreate : MarshalByRefObject, TReturn
where TReturn : class;

/// <summary>
/// Retrieve a registered instance of the given type
/// </summary>
/// <typeparam name="T">Type to query</typeparam>
/// <returns>A reference to an instance of the type from the remote service registry, or null if no
/// such instance exists</returns>
T RequestRemoteInstance<T>()
where T : class;
}
}
/// <summary>
/// Creates an object in a host process on a remote machine
/// </summary>
/// <typeparam name="T">Type of object to create</typeparam>
/// <param name="parameters">Constructor arguments</param>
T CreateObject<T>(object[] parameters)
where T : MarshalByRefObject;

/// <summary>
/// Creates an object in a host process on a remote machine
/// </summary>
/// <typeparam name="T">Type of object to create</typeparam>
T CreateObject<T>()
where T : MarshalByRefObject;

/// <summary>
/// Connects the remote loader to the remote system;
/// </summary>
/// <param name="cancellationToken">Abort token for connection attempt</param>
/// <param name="clientConnectionLogger">A logger for logging all communication activities. Use for debugging only, as it has a
/// performance penalty</param>
/// <exception cref="RemotingException">Thrown if connection to remote loader fails</exception>
void Connect(CancellationToken cancellationToken, ILogger clientConnectionLogger);

/// <summary>
/// Connects the remote loader to the remote system;
/// </summary>
/// <param name="cancellationToken">Abort token for connection attempt</param>
/// <param name="extraSurrogates">A list of extra converters</param>
/// <param name="clientConnectionLogger">A logger for logging all communication activities. Use for debugging only, as it has a
/// performance penalty</param>
/// <exception cref="RemotingException">Thrown if connection to remote loader fails</exception>
void Connect(CancellationToken cancellationToken, IList<JsonConverter> extraSurrogates, ILogger clientConnectionLogger);

/// <summary>
/// Connects the remote loader to the remote system; Fails if an instance already exists.
/// The instance existence check is done on the remotingServer application name.
/// </summary>
/// <param name="checkExistingInstance">If true a check of existing process with same name is done on the remote host</param>
/// <param name="cancellationToken">Abort token for connection attempt</param>
/// <param name="extraSurrogates">A set of extra converters</param>
/// <param name="clientConnectionLogger">A logger for logging all communication activities. Use for debugging only, as it has a
/// performance penalty</param>
/// <returns>Return true if a new instance of the remoting server is launched, false if an instance already exists</returns>
/// <exception cref="RemotingException">Thrown if connection to remote loader fails. Or if the remote command to get the running process fails</exception>
/// <exception cref="OperationCanceledException">Thrown if timeout occurs or cancellation</exception>
bool Connect(bool checkExistingInstance, CancellationToken cancellationToken, IList<JsonConverter> extraSurrogates, ILogger clientConnectionLogger);

/// <summary>
/// Creates an object in a host process on a remote machine.
/// Provides mocking ability.
/// </summary>
/// <typeparam name="TCreate">Concrete type of object to create</typeparam>
/// <typeparam name="TReturn">Return type, may be an interface</typeparam>
/// <param name="parameters">Constructor arguments</param>
TReturn CreateObject<TCreate, TReturn>(object[] parameters)
where TCreate : MarshalByRefObject, TReturn
where TReturn : class;

/// <summary>
/// Creates an object in a host process on a remote machine.
/// Provides mocking ability.
/// </summary>
/// <typeparam name="TCreate">Concrete type of object to create</typeparam>
/// <typeparam name="TReturn">Return type, may be an interface</typeparam>
TReturn CreateObject<TCreate, TReturn>()
where TCreate : MarshalByRefObject, TReturn
where TReturn : class;

/// <summary>
/// Retrieve a registered instance of the given type
/// </summary>
/// <typeparam name="T">Type to query</typeparam>
/// <returns>A reference to an instance of the type from the remote service registry, or null if no
/// such instance exists</returns>
T RequestRemoteInstance<T>()
where T : class;
}
}
5 changes: 5 additions & 0 deletions src/NewRemoting/IRemoteServerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

namespace NewRemoting
{
/// <summary>
/// This interface offers some services the server is always providing for it's own use.
/// </summary>
public interface IRemoteServerService
{
/// <summary>
Expand Down Expand Up @@ -93,5 +96,7 @@ public interface IRemoteServerService
/// Performs a server side GC
/// </summary>
void PerformGc();

void RegisterConverters(IList<Type> converters);
}
}
Loading