diff --git a/CoreRemoting.Tests/RpcTests.cs b/CoreRemoting.Tests/RpcTests.cs index 2e44ba7..96ecd1f 100644 --- a/CoreRemoting.Tests/RpcTests.cs +++ b/CoreRemoting.Tests/RpcTests.cs @@ -565,14 +565,17 @@ void AfterCall(object sender, ServerRpcContext ctx) client.Connect(); + var dbError = "23503: delete from table 'clients' violates " + + "foreign key constraint 'order_client_fk' on table 'orders'"; + // simulate a database error on the server-side var proxy = client.CreateProxy(); - var ex = Assert.Throws(() => - proxy.Error("23503: delete from table 'clients' violates foreign key constraint 'order_client_fk' on table 'orders'")) - .GetInnermostException(); + var ex = Assert.Throws(() => proxy.Error(dbError)); Assert.NotNull(ex); Assert.Equal("Deleting clients is not allowed.", ex.Message); + Assert.NotNull(ex.InnerException); + Assert.Equal(dbError, ex.InnerException.Message); } finally { diff --git a/CoreRemoting/ClientRpcContext.cs b/CoreRemoting/ClientRpcContext.cs index b70eea6..82d661a 100644 --- a/CoreRemoting/ClientRpcContext.cs +++ b/CoreRemoting/ClientRpcContext.cs @@ -36,7 +36,7 @@ internal ClientRpcContext() /// /// Gets or sets an exception that describes an error that occurred on server side RPC invocation. /// - public RemoteInvocationException RemoteException { get; set; } + public Exception RemoteException { get; set; } /// /// Gets a wait handle that is set, when the response of this RPC call is received from server. diff --git a/CoreRemoting/RemotingClient.cs b/CoreRemoting/RemotingClient.cs index 0b786f8..d996ccd 100644 --- a/CoreRemoting/RemotingClient.cs +++ b/CoreRemoting/RemotingClient.cs @@ -45,20 +45,20 @@ public sealed class RemotingClient : IRemotingClient private byte[] _serverPublicKeyBlob; // ReSharper disable once InconsistentNaming - private static readonly ConcurrentDictionary _clientInstances = + private static readonly ConcurrentDictionary _clientInstances = new ConcurrentDictionary(); - + private static WeakReference _defaultRemotingClientRef; /// /// Event: Fires after client was disconnected. /// public event Action AfterDisconnect; - + #endregion - + #region Construction - + private RemotingClient() { MethodCallMessageBuilder = new MethodCallMessageBuilder(); @@ -71,7 +71,7 @@ private RemotingClient() _authenticationCompletedWaitHandle = new ManualResetEventSlim(initialState: false); _goodbyeCompletedWaitHandle = new ManualResetEventSlim(initialState: false); } - + /// /// Creates a new instance of the RemotingClient class. /// @@ -80,15 +80,15 @@ public RemotingClient(ClientConfig config) : this() { if (config == null) throw new ArgumentException("No config provided and no default configuration found."); - + Serializer = config.Serializer ?? new BsonSerializerAdapter(); MessageEncryption = config.MessageEncryption; - + _config = config; - + if (MessageEncryption) _keyPair = new RsaKeyPair(config.KeySize); - + _channel = config.Channel ?? new TcpClientChannel(); _channel.Init(this); @@ -111,10 +111,10 @@ public RemotingClient(ClientConfig config) : this() oldClient?.Dispose(); return this; }); - - if (!config.IsDefault) + + if (!config.IsDefault) return; - + RemotingClient.DefaultRemotingClient ??= this; } @@ -129,9 +129,9 @@ private void OnDisconnected() activeCalls = _activeCalls; _activeCalls = null; } - + _goodbyeCompletedWaitHandle.Set(); - + foreach (var activeCall in activeCalls) { activeCall.Value.Error = true; @@ -141,14 +141,14 @@ private void OnDisconnected() } #endregion - + #region Properties /// /// Gets the proxy generator instance. /// private static readonly ProxyGenerator ProxyGenerator = new ProxyGenerator(); - + /// /// Gets a utility object for building remoting messages. /// @@ -158,7 +158,7 @@ private void OnDisconnected() /// Gets a utility object to provide encryption of remoting messages. /// private IMessageEncryptionManager MessageEncryptionManager { get; } - + /// /// Gets the configured serializer. /// @@ -168,12 +168,12 @@ private void OnDisconnected() /// Gets the local client delegate registry. /// internal ClientDelegateRegistry ClientDelegateRegistry => _delegateRegistry; - + /// /// Gets or sets the invocation timeout in milliseconds. /// public int? InvocationTimeout { get; set; } - + /// /// Gets or sets whether messages should be encrypted or not. /// @@ -208,17 +208,17 @@ public bool HasSession } } - + /// /// Gets the authenticated identity. May be null if authentication failed or if authentication is not configured. /// - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] + [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] public RemotingIdentity Identity { get; private set; } - + #endregion - + #region Connection management - + /// /// Connects this CoreRemoting client instance to the configured CoreRemoting server. /// @@ -232,7 +232,7 @@ public void Connect() _goodbyeCompletedWaitHandle.Reset(); lock(_syncObject) _activeCalls = new Dictionary(); - + _channel.Connect(); if (_channel.RawMessageTransport.LastException != null) @@ -247,7 +247,7 @@ public void Connect() throw new NetworkException("Handshake with server failed."); else Authenticate(); - + StartKeepSessionAliveTimer(); } @@ -314,7 +314,7 @@ public void Disconnect(bool quiet = false) _handshakeCompletedWaitHandle?.Reset(); _authenticationCompletedWaitHandle?.Reset(); Identity = null; - + AfterDisconnect?.Invoke(); } @@ -325,7 +325,7 @@ private void StartKeepSessionAliveTimer() { if (_config.KeepSessionAliveInterval <= 0) return; - + _keepSessionAliveTimer = new Timer(Convert.ToDouble(_config.KeepSessionAliveInterval * 1000)); @@ -334,7 +334,7 @@ private void StartKeepSessionAliveTimer() } /// - /// Event procedure: Called when the keep session alive timer elapses. + /// Event procedure: Called when the keep session alive timer elapses. /// /// Event sender /// Event arguments @@ -342,10 +342,10 @@ private void KeepSessionAliveTimerOnElapsed(object sender, ElapsedEventArgs e) { if (_keepSessionAliveTimer == null) return; - + if (!_keepSessionAliveTimer.Enabled) return; - + if (_rawMessageTransport == null) return; @@ -354,7 +354,7 @@ private void KeepSessionAliveTimerOnElapsed(object sender, ElapsedEventArgs e) OnDisconnected(); return; } - + // Send empty message to keep session alive _rawMessageTransport.SendMessage(new byte[0]); } @@ -375,7 +375,7 @@ private byte[] SharedSecret() } #endregion - + #region Authentication /// @@ -386,7 +386,7 @@ private void Authenticate() { if (_config.Credentials == null || (_config.Credentials!=null && _config.Credentials.Length == 0)) return; - + if (_authenticationCompletedWaitHandle.IsSet) return; @@ -409,14 +409,14 @@ private void Authenticate() byte[] rawData = Serializer.Serialize(wireMessage); _rawMessageTransport.LastException = null; - + _rawMessageTransport.SendMessage(rawData); - + if (_rawMessageTransport.LastException != null) throw _rawMessageTransport.LastException; _authenticationCompletedWaitHandle.Wait(_config.AuthenticationTimeout * 1000); - + if (!_authenticationCompletedWaitHandle.IsSet) throw new SecurityException("Authentication timeout."); @@ -425,9 +425,9 @@ private void Authenticate() } #endregion - + #region Handling received messages - + /// /// Called when a message is received from server. /// @@ -490,12 +490,12 @@ private void ProcessCompleteHandshakeMessage(WireMessage message) { if (MessageEncryption) { - var signedMessageData = + var signedMessageData = Serializer.Deserialize(message.Data); - - var encryptedSecret = + + var encryptedSecret = Serializer.Deserialize(signedMessageData.MessageRawData); - + _serverPublicKeyBlob = encryptedSecret.SendersPublicKeyBlob; if (!RsaSignature.VerifySignature( @@ -532,7 +532,7 @@ private void ProcessCompleteHandshakeMessage(WireMessage message) private void ProcessAuthenticationResponseMessage(WireMessage message) { byte[] sharedSecret = SharedSecret(); - + var authResponseMessage = Serializer .Deserialize( @@ -546,7 +546,7 @@ private void ProcessAuthenticationResponseMessage(WireMessage message) _isAuthenticated = authResponseMessage.IsAuthenticated; Identity = _isAuthenticated ? authResponseMessage.AuthenticatedIdentity : null; - + _authenticationCompletedWaitHandle.Set(); } @@ -557,7 +557,7 @@ private void ProcessAuthenticationResponseMessage(WireMessage message) private void ProcessRemoteDelegateInvocationMessage(WireMessage message) { byte[] sharedSecret = SharedSecret(); - + var delegateInvocationMessage = Serializer .Deserialize( @@ -567,14 +567,14 @@ private void ProcessRemoteDelegateInvocationMessage(WireMessage message) sharedSecret: sharedSecret, sendersPublicKeyBlob: _serverPublicKeyBlob, sendersPublicKeySize: _keyPair?.KeySize ?? 0)); - + var localDelegate = _delegateRegistry.GetDelegateByHandlerKey(delegateInvocationMessage.HandlerKey); // Invoke local delegate with arguments from remote caller localDelegate.DynamicInvoke(delegateInvocationMessage.DelegateArguments); } - + /// /// Processes a RPC result message from server. /// @@ -584,13 +584,13 @@ private void ProcessRpcResultMessage(WireMessage message) { byte[] sharedSecret = SharedSecret(); - Guid unqiueCallKey = + Guid unqiueCallKey = message.UniqueCallKey == null - ? Guid.Empty + ? Guid.Empty : new Guid(message.UniqueCallKey); ClientRpcContext clientRpcContext; - + lock (_syncObject) { if (_activeCalls == null) @@ -611,7 +611,7 @@ private void ProcessRpcResultMessage(WireMessage message) try { var remoteException = - Serializer.Deserialize( + Serializer.Deserialize( MessageEncryptionManager.GetDecryptedMessageData( message: message, serializer: Serializer, @@ -641,7 +641,7 @@ private void ProcessRpcResultMessage(WireMessage message) sharedSecret: sharedSecret, sendersPublicKeyBlob: _serverPublicKeyBlob, sendersPublicKeySize: _keyPair?.KeySize ?? 0); - + var resultMessage = Serializer .Deserialize(rawMessage); @@ -651,18 +651,21 @@ private void ProcessRpcResultMessage(WireMessage message) catch (Exception e) { clientRpcContext.Error = true; - clientRpcContext.RemoteException = new RemoteInvocationException( - message: e.Message, - innerEx: e.GetType().IsSerializable ? e : null); + + clientRpcContext.RemoteException = + new RemoteInvocationException( + message: e.Message, + innerEx: e.ToSerializable()); } } + clientRpcContext.WaitHandle.Set(); } - + #endregion - + #region RPC - + /// /// Calls a method on a remote service. /// @@ -675,15 +678,15 @@ internal Task InvokeRemoteMethod(MethodCallMessage methodCallM Task.Run(() => { byte[] sharedSecret = SharedSecret(); - + lock (_syncObject) { if (_activeCalls == null) throw new RemoteInvocationException("ServerDisconnected"); } - + var clientRpcContext = new ClientRpcContext(); - + lock (_syncObject) { if (_activeCalls.ContainsKey(clientRpcContext.UniqueCallKey)) @@ -718,7 +721,7 @@ internal Task InvokeRemoteMethod(MethodCallMessage methodCallM throw _rawMessageTransport.LastException; } - if (oneWay || clientRpcContext.ResultMessage != null) + if (oneWay || clientRpcContext.ResultMessage != null) return clientRpcContext; // TODO: replace with a Task to avoid freezing the current thread? @@ -729,14 +732,14 @@ internal Task InvokeRemoteMethod(MethodCallMessage methodCallM return clientRpcContext; }); - + return sendTask; } - + #endregion - + #region Proxy management - + /// /// Creates a proxy object to provide access to a remote service. /// @@ -762,7 +765,7 @@ public object CreateProxy(Type serviceInterfaceType, string serviceName = "") { var serviceProxyType = typeof(ServiceProxy<>).MakeGenericType(serviceInterfaceType); var serviceProxy = Activator.CreateInstance(serviceProxyType, this, serviceName); - + return ProxyGenerator.CreateInterfaceProxyWithoutTarget( interfaceToProxy: serviceInterfaceType, interceptor: (IInterceptor)serviceProxy); @@ -787,16 +790,16 @@ public void ShutdownProxy(object serviceProxy) { if (!ProxyUtil.IsProxy(serviceProxy)) return; - + var proxyType = serviceProxy.GetType(); - - var hiddenInterceptorsField = - proxyType.GetField("__interceptors", + + var hiddenInterceptorsField = + proxyType.GetField("__interceptors", BindingFlags.Instance | BindingFlags.NonPublic); if (hiddenInterceptorsField == null) return; - + var interceptors = hiddenInterceptorsField.GetValue(serviceProxy) as IInterceptor[]; var coreRemotingInterceptor = @@ -808,9 +811,9 @@ where interceptor is IServiceProxy } #endregion - + #region IDisposable implementation - + /// /// Frees managed resources. /// @@ -820,9 +823,9 @@ public void Dispose() RemotingClient.DefaultRemotingClient = null; _clientInstances.TryRemove(_config.UniqueClientInstanceName, out _); - + Disconnect(); - + _cancellationTokenSource.Cancel(); _cancellationTokenSource.Dispose(); _delegateRegistry.Clear(); @@ -862,9 +865,9 @@ public void Dispose() _keyPair?.Dispose(); } - + #endregion - + #region Managing client instances /// @@ -900,13 +903,13 @@ public static IRemotingClient DefaultRemotingClient } internal set { - _defaultRemotingClientRef = - value == null - ? null + _defaultRemotingClientRef = + value == null + ? null : new WeakReference(value); } } - + #endregion } }