Skip to content
This repository has been archived by the owner on Dec 20, 2019. It is now read-only.

Commit

Permalink
Enhancing robustness. Enhancing error handling. Adding RPCJs settings.
Browse files Browse the repository at this point in the history
  • Loading branch information
dajuric committed Nov 9, 2017
1 parent 441e3e0 commit 1f7f16f
Show file tree
Hide file tree
Showing 17 changed files with 279 additions and 62 deletions.
3 changes: 2 additions & 1 deletion Samples/ClientJs/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,12 @@ static void Main(string[] args)

//start server and bind its local and remote API
var cts = new CancellationTokenSource();
Server.ListenAsync("http://localhost:8001/", cts.Token, (c, ws) => c.Bind<LocalAPI, IRemoteAPI>(new LocalAPI())).Wait(0);
var s = Server.ListenAsync("http://localhost:8001/", cts.Token, (c, ws) => c.Bind<LocalAPI, IRemoteAPI>(new LocalAPI()));

Console.Write("Running: '{0}'. Press [Enter] to exit.", nameof(TestClientJs));
Console.ReadLine();
cts.Cancel();
s.Wait();
}
}
}
2 changes: 2 additions & 0 deletions Samples/ClientJs/ServerClientJs.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<ApplicationIcon />
<OutputType>Exe</OutputType>
<StartupObject />

<DocumentationFile>bin\$(TargetFramework)\ServerClientJs.xml</DocumentationFile>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions Samples/MultiService/MultiService.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<ApplicationIcon />
<OutputType>Exe</OutputType>
<StartupObject />

<DocumentationFile>bin\$(TargetFramework)\MultiService.xml</DocumentationFile>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions Samples/Serialization/Serialization.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<ApplicationIcon />
<OutputType>Exe</OutputType>
<StartupObject />

<DocumentationFile>bin\$(TargetFramework)\Serialization.xml</DocumentationFile>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
Expand Down
3 changes: 3 additions & 0 deletions Source/WebSocketRPC.AspCore/WebSocketRPC.AspCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
<Version>1.0.0</Version>
<PackageOutputPath>../../Deploy/Nuget/bin/</PackageOutputPath>
<RootNamespace>WebSocketRPC</RootNamespace>

<IncludeSource>True</IncludeSource>
<IncludeSymbols>True</IncludeSymbols>
</PropertyGroup>

</Project>
44 changes: 32 additions & 12 deletions Source/WebSocketRPC.Base/ClientServer/Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public class Connection
static string messageToBig = "The message exceeds the maximum allowed message size: {0} bytes.";

WebSocket socket;
TaskQueue sendTaskQueue;

/// <summary>
/// Creates new connection.
Expand All @@ -51,6 +52,7 @@ public class Connection
internal protected Connection(WebSocket socket, IReadOnlyDictionary<string, string> cookies)
{
this.socket = socket;
this.sendTaskQueue = new TaskQueue();
this.Cookies = cookies;
}

Expand Down Expand Up @@ -92,7 +94,8 @@ public async Task<bool> SendAsync(ArraySegment<byte> data)
return false;
}

await socket.SendAsync(data, WebSocketMessageType.Binary, true, CancellationToken.None);
Debug.WriteLine("Sending binary data.");
await sendTaskQueue.Enqueue(() => sendAsync(data, WebSocketMessageType.Binary));
return true;
}

Expand All @@ -116,10 +119,23 @@ public async Task<bool> SendAsync(string data, Encoding e)
}

Debug.WriteLine("Sending: " + data);
await socket.SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None);
await sendTaskQueue.Enqueue(() => sendAsync(segment, WebSocketMessageType.Text));
return true;
}

async Task sendAsync(ArraySegment<byte> data, WebSocketMessageType msgType)
{
try
{
await socket.SendAsync(data, msgType, true, CancellationToken.None);
}
catch(Exception ex)
{
if (socket.State != WebSocketState.Open)
await CloseAsync(WebSocketCloseStatus.InternalServerError, ex.Message);
}
}

/// <summary>
/// Closes the connection.
/// </summary>
Expand All @@ -128,12 +144,18 @@ public async Task<bool> SendAsync(string data, Encoding e)
/// <returns>Task.</returns>
public async Task CloseAsync(WebSocketCloseStatus closeStatus = WebSocketCloseStatus.NormalClosure, string statusDescription = "")
{
if (socket.State != WebSocketState.Open)
return;

await socket.CloseOutputAsync(closeStatus, statusDescription, CancellationToken.None);
OnClose?.Invoke();
clearEvents();
try
{
if (socket.State == WebSocketState.Open || socket.State == WebSocketState.CloseReceived)
await socket.CloseOutputAsync(closeStatus, statusDescription, CancellationToken.None);
}
catch
{ } //do not propagate the exception
finally
{
OnClose?.Invoke();
clearEvents();
}
}

/// <summary>
Expand All @@ -151,7 +173,7 @@ internal static async Task ListenReceiveAsync(Connection connection, Cancellatio
{
connection.OnOpen?.Invoke();
byte[] receiveBuffer = new byte[RPCSettings.MaxMessageSize];

while (webSocket.State == WebSocketState.Open)
{
WebSocketReceiveResult receiveResult = null;
Expand Down Expand Up @@ -186,9 +208,8 @@ internal static async Task ListenReceiveAsync(Connection connection, Cancellatio
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
connection.OnError?.Invoke(ex);
connection.OnClose?.Invoke();
await connection.CloseAsync(WebSocketCloseStatus.InternalServerError, ex.Message);
//socket will be aborted -> no need to close manually
}
}
Expand All @@ -205,7 +226,6 @@ internal void InvokeErrorAsync(Exception ex)

private void clearEvents()
{
OnOpen = null;
OnClose = null;
OnError = null;
OnReceive = null;
Expand Down
8 changes: 1 addition & 7 deletions Source/WebSocketRPC.Base/ConnectionBinders/Base/Binder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,7 @@ abstract class Binder : IBinder
protected Binder(Connection connection)
{
Connection = connection;

Connection.OnOpen += () =>
{
Debug.WriteLine("Open");

RPC.AllBinders.Add(this);
};
RPC.AllBinders.Add(this);

Connection.OnClose += () =>
{
Expand Down
10 changes: 8 additions & 2 deletions Source/WebSocketRPC.Base/Invokers/Messages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ struct Request

public static Request FromJson(string json)
{
var root = JObject.Parse(json);
JObject root = null;
try { root = JObject.Parse(json); }
catch { return default(Request); }

var r = new Request
{
FunctionName = root[nameof(FunctionName)]?.Value<string>(),
Expand Down Expand Up @@ -65,7 +68,10 @@ struct Response

public static Response FromJson(string json)
{
var root = JObject.Parse(json);
JObject root = null;
try { root = JObject.Parse(json); }
catch { return default(Response); }

var r = new Response
{
FunctionName = root[nameof(FunctionName)]?.Value<string>(),
Expand Down
36 changes: 36 additions & 0 deletions Source/WebSocketRPC.Base/Utils/TaskQueue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Threading;
using System.Threading.Tasks;

namespace WebSocketRPC
{
//taken from: https://stackoverflow.com/questions/25691679/best-way-in-net-to-manage-queue-of-tasks-on-a-separate-single-thread and modified
class TaskQueue
{
private SemaphoreSlim semaphore;
public TaskQueue()
{
semaphore = new SemaphoreSlim(1);
}

public async Task Enqueue(Func<Task> func)
{
await semaphore.WaitAsync();
try
{
await func();
}
finally
{
semaphore.Release();
}
}

~TaskQueue()
{
//dispose pattern is not needed because' AvailableWaitHandle' is not used
//ref: https://stackoverflow.com/questions/32033416/do-i-need-to-dispose-a-semaphoreslim
semaphore.Dispose();
}
}
}
1 change: 1 addition & 0 deletions Source/WebSocketRPC.Base/WebSocketRPC.Base.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@
<Compile Include="$(MSBuildThisFileDirectory)RPC.cs" />
<Compile Include="$(MSBuildThisFileDirectory)RPCSettings.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utils\CookieUtils.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utils\TaskQueue.cs" />
</ItemGroup>
</Project>
16 changes: 15 additions & 1 deletion Source/WebSocketRPC.JS/Components/JsCallerGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ public static (string, MethodInfo[]) GetMethods<T>(params Expression<Action<T>>[
return (objType.Name, methodList);
}

public static string GenerateRequireJsHeader(string className)
{
var t = new string[] {
$"define(() => {className}); //'require.js' support"
};

var sb = new StringBuilder();
sb.Append(String.Join(Environment.NewLine, t));
sb.Append(Environment.NewLine);
sb.Append(Environment.NewLine);

return sb.ToString();
}

public static string GenerateHeader(string className)
{
var t = new string[] {
Expand Down Expand Up @@ -107,7 +121,7 @@ public static string GenerateMethod(string methodName, string[] argNames)
$"\t this.{jsMName} = function({argList}) {{",
$"\t return callRPC(\"{methodName}\", Object.values(this.{jsMName}.arguments));",
$"\t }};",
$""
$"{Environment.NewLine}"
};

var sb = new StringBuilder();
Expand Down
2 changes: 1 addition & 1 deletion Source/WebSocketRPC.JS/Components/JsDocGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ public static string GetMethodDoc(XmlNodeList mmebers, string methodName,
IList<string> pNames, IList<Type> pTypes, Type returnType, string linePrefix = "\t")
{
var mElem = getMethod(mmebers, methodName);
if (mElem == null) return String.Empty;

var s = getSummary(mElem);
var p = getParams(mElem);
var r = getReturn(mElem);

var jsDoc = new StringBuilder();
jsDoc.AppendLine();
jsDoc.AppendLine(String.Format("{0}/**", linePrefix));
{
jsDoc.AppendLine(String.Format("{0} * @description - {1}", linePrefix, s));
Expand Down
29 changes: 17 additions & 12 deletions Source/WebSocketRPC.JS/RPCJs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
using System;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

Expand All @@ -41,13 +40,16 @@ public static class RPCJs
/// Generates Javascript code from the provided class or interface type.
/// </summary>
/// <typeparam name="T">Class or interface type.</typeparam>
/// <param name="omittedMethods">The methods of the class / interface that should be omitted when creating the Javascript code.</param>
/// <param name="settings">RPC-Js settings used for Javascript code generation.</param>
/// <returns>Javascript API.</returns>
public static string GenerateCaller<T>(params Expression<Action<T>>[] omittedMethods)
public static string GenerateCaller<T>(RPCJsSettings<T> settings = null)
{
var (tName, mInfos) = JsCallerGenerator.GetMethods<T>(omittedMethods);
settings = settings ?? new RPCJsSettings<T>();
var (tName, mInfos) = JsCallerGenerator.GetMethods(settings.OmittedMethods);
tName = settings.NameOverwrite ?? tName;

var sb = new StringBuilder();
if(settings.WithRequireSupport) sb.Append(JsCallerGenerator.GenerateRequireJsHeader(tName));
sb.Append(JsCallerGenerator.GenerateHeader(tName));

foreach (var m in mInfos)
Expand All @@ -67,15 +69,18 @@ public static string GenerateCaller<T>(params Expression<Action<T>>[] omittedMet
/// </summary>
/// <typeparam name="T">Class or interface type.</typeparam>
/// <param name="xmlDocPath">Xml assembly definition file.</param>
/// <param name="omittedMethods">The methods of the class / interface that should be omitted when creating the Javascript code.</param>
/// <param name="settings">RPC-Js settings used for Javascript code generation.</param>
/// <returns>Javascript API.</returns>
public static string GenerateCallerWithDoc<T>(string xmlDocPath, params Expression<Action<T>>[] omittedMethods)
public static string GenerateCallerWithDoc<T>(string xmlDocPath, RPCJsSettings<T> settings = null)
{
var (tName, mInfos) = JsCallerGenerator.GetMethods(omittedMethods);
settings = settings ?? new RPCJsSettings<T>();
var (tName, mInfos) = JsCallerGenerator.GetMethods(settings.OmittedMethods);
tName = settings.NameOverwrite ?? tName;

var xmlMemberNodes = JsDocGenerator.GetMemberNodes(xmlDocPath);
var sb = new StringBuilder();

var sb = new StringBuilder();
if (settings.WithRequireSupport) sb.Append(JsCallerGenerator.GenerateRequireJsHeader(tName));
sb.Append(JsDocGenerator.GetClassDoc(xmlMemberNodes, tName));
sb.Append(JsCallerGenerator.GenerateHeader(tName));

Expand All @@ -98,19 +103,19 @@ public static string GenerateCallerWithDoc<T>(string xmlDocPath, params Expressi
/// <para>The xml assembly definition is taken form the executing assembly if available.</para>
/// </summary>
/// <typeparam name="T">Class or interface type.</typeparam>
/// <param name="omittedMethods">The methods of the class / interface that should be omitted when creating the Javascript code.</param>
/// <param name="settings">RPC-Js settings used for Javascript code generation.</param>
/// <returns>Javascript API.</returns>
public static string GenerateCallerWithDoc<T>(params Expression<Action<T>>[] omittedMethods)
public static string GenerateCallerWithDoc<T>(RPCJsSettings<T> settings = null)
{
var assembly = Assembly.GetEntryAssembly();
var fInfo = new FileInfo(assembly.Location);

var xmlDocPath = Path.ChangeExtension(assembly.Location, ".xml");

if (!File.Exists(xmlDocPath))
return GenerateCaller(omittedMethods);
return GenerateCaller(settings);
else
return GenerateCallerWithDoc(xmlDocPath, omittedMethods);
return GenerateCallerWithDoc(xmlDocPath, settings);
}
}
}
Loading

0 comments on commit 1f7f16f

Please sign in to comment.