diff --git a/.github/workflows/build_publish_master.yml b/.github/workflows/build_publish_master.yml new file mode 100644 index 0000000..1a7fe10 --- /dev/null +++ b/.github/workflows/build_publish_master.yml @@ -0,0 +1,33 @@ +name: Build Master +on: + push: + paths-ignore: + - "**/*.md" + branches: [ master ] + +jobs: + build: + + strategy: + matrix: + os: ['ubuntu-latest'] + dotnet-version: ['3.1.201'] + project : ['Json-Rpc'] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + - name: Setup .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: ${{matrix.dotnet-version}} + - name: Install dependencies + run: dotnet restore + - name: Build + run: dotnet build ${{matrix.project}} --configuration Release + - name: Test + run: dotnet test AustinHarris.JsonRpcTestN + # Publish + - name: publish nuget version change + run: dotnet nuget push Json-Rpc/bin/Release/*.nupkg --skip-duplicate --source "https://www.nuget.org" --api-key ${{secrets.NugetKey}} # API key for the NuGet feed diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml new file mode 100644 index 0000000..f3e06e9 --- /dev/null +++ b/.github/workflows/build_pull_request.yml @@ -0,0 +1,35 @@ +name: Pull Reqest +on: + pull_request: + paths-ignore: + - "**/*.md" + branches: [ master ] + +jobs: + build: + + strategy: + matrix: + os: ['windows-latest','ubuntu-latest'] + dotnet-version: ['3.1.201'] + project : ['Json-Rpc'] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + - name: Setup .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: ${{matrix.dotnet-version}} + - name: Install dependencies + run: dotnet restore + - name: Build + run: dotnet build ${{matrix.project}} --configuration Release --version-suffix ci-${{ github.run_id }}-${{ github.run_number }} + - name: Test + run: dotnet test AustinHarris.JsonRpcTestN + # Publish + - name: publish nuget version change + if: ${{matrix.os == 'ubuntu-latest'}} + run: dotnet nuget push Json-Rpc/bin/Release/*.nupkg --skip-duplicate --source "https://www.nuget.org" --api-key ${{secrets.NugetKey}} # API key for the NuGet feed + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 11908c9..6ea46be 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,4 @@ _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML .nuget/NuGet.exe +.vs/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f6ab2fa..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: csharp -solution: AustinHarris.JsonRpc.sln -mono: - - latest - - 3.12.0 - - 3.10.0 - - 3.8.0 -install: - - nuget restore AustinHarris.JsonRpc.sln - - nuget install NUnit.Runners -Version 2.6.4 -OutputDirectory testrunner -script: - - xbuild /p:Configuration=Release AustinHarris.JsonRpc.sln - - mono ./testrunner/NUnit.Runners.2.6.4/tools/nunit-console.exe ./AustinHarris.JsonRpcTestN/bin/Release/AustinHarris.JsonRpcTestN.dll \ No newline at end of file diff --git a/AustinHarris.JsonRpc.AspNet/AustinHarris.JsonRpc.AspNet.csproj b/AustinHarris.JsonRpc.AspNet/AustinHarris.JsonRpc.AspNet.csproj index bf84656..3470e2c 100644 --- a/AustinHarris.JsonRpc.AspNet/AustinHarris.JsonRpc.AspNet.csproj +++ b/AustinHarris.JsonRpc.AspNet/AustinHarris.JsonRpc.AspNet.csproj @@ -36,8 +36,8 @@ 4 - - ..\packages\Newtonsoft.Json.8.0.2\lib\net40\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.12.0.3\lib\net40\Newtonsoft.Json.dll True @@ -47,6 +47,7 @@ + diff --git a/AustinHarris.JsonRpc.AspNet/JsonRpcHandler.cs b/AustinHarris.JsonRpc.AspNet/JsonRpcHandler.cs index 7f2cf04..6a38c39 100644 --- a/AustinHarris.JsonRpc.AspNet/JsonRpcHandler.cs +++ b/AustinHarris.JsonRpc.AspNet/JsonRpcHandler.cs @@ -1,135 +1,16 @@ -using System; -using System.IO; -using System.IO.Compression; -using System.Text; -using System.Web; +using AustinHarris.JsonRpc.AspNet; namespace AustinHarris.JsonRpc.Handlers.AspNet { - public class JsonRpcHandler : IHttpAsyncHandler + /// + /// Used default SessionId + /// For routing use JsonRpcHandlerBase + /// + public class JsonRpcHandler : JsonRpcHandlerBase { - #region Fields - /// - /// UTF8 Encoding without BOM. - /// - static Encoding UTF8Encoding = new UTF8Encoding(false); - #endregion - - #region IHttpHandler Members - - public bool IsReusable - { - get { return true; } - } - - public void ProcessRequest(HttpContext context) - { - // not used - } - - #endregion - - #region IHttpAsyncHandler Members - - /// - /// Initiates an asynchronous call to the HTTP handler. - /// - /// An object that provides references to intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests. - /// The to call when the asynchronous method call is complete. If is null, the delegate is not called. - /// Any extra data needed to process the request. - /// - /// An that contains information about the status of the process. - /// - public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData) - { - var async = new JsonRpcStateAsync(cb, context); - async.JsonRpc = GetJsonRpcString(context.Request); - JsonRpcProcessor.Process(async,context.Request); - return async; - } - - private static string GetJsonRpcString(System.Web.HttpRequest request) + protected override string GetSessionId() { - string json = string.Empty; - if (request.RequestType == "GET") - { - json = request.Params["jsonrpc"] ?? string.Empty; - } - else if (request.RequestType == "POST") - { - if (request.ContentType == "application/x-www-form-urlencoded") - { - json = request.Params["jsonrpc"] ?? string.Empty; - } - else - { - json = new StreamReader(request.InputStream).ReadToEnd(); - } - } - return json; + return Handler.DefaultSessionId(); } - - /// - /// Provides an asynchronous process End method when the process ends. - /// - /// An that contains information about the status of the process. - public void EndProcessRequest(IAsyncResult result) - { - var state = result as JsonRpcStateAsync; - if (state != null) - { - var r = state.Result; - var callback = ((HttpContext)state.AsyncState).Request.Params["callback"]; - if (!string.IsNullOrWhiteSpace(callback)) - { - r = string.Format("{0}({1})", callback, r); - } - - // try to compress the response data. - // fix me: compression filters in IHttpModule always failed for IHttpAsyncHandler - CompressResponseIfPossible(((HttpContext)state.AsyncState).Request, ((HttpContext)state.AsyncState).Response, r, UTF8Encoding); - } - } - - #endregion - - #region Utility methods - - /// - /// Transfer the result data compressed when the client accepts gzip. - /// - /// A HttpRequest object that represents the HTTP request. - /// A HttpResponse object that represents the HTTP response to be sent to the client. - /// The string data to be sent to the client. - /// The Encoding to be used to encode as the result. - static void CompressResponseIfPossible(HttpRequest request, HttpResponse response, String result, Encoding encoding) - { - string AcceptEncoding = request.Headers["Accept-Encoding"]; - if (AcceptEncoding != null && AcceptEncoding.Contains("gzip")) - { - //response.Headers.Remove("Content-Encoding"); - response.AddHeader("Content-Encoding", "gzip"); - - using (var gstream = new GZipStream(response.OutputStream, CompressionMode.Compress)) - using (var writer = new StreamWriter(gstream, encoding)) - { - writer.Write(result); - writer.Flush(); - } - } - else - { - using (StreamWriter writer = new StreamWriter(response.OutputStream, encoding)) - { - writer.Write(result); - writer.Flush(); - } - } - - response.End(); - } - - - #endregion } -} +} \ No newline at end of file diff --git a/AustinHarris.JsonRpc.AspNet/JsonRpcHandlerBase.cs b/AustinHarris.JsonRpc.AspNet/JsonRpcHandlerBase.cs new file mode 100644 index 0000000..8d61777 --- /dev/null +++ b/AustinHarris.JsonRpc.AspNet/JsonRpcHandlerBase.cs @@ -0,0 +1,138 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Text; +using System.Web; + +namespace AustinHarris.JsonRpc.AspNet +{ + public abstract class JsonRpcHandlerBase : IHttpAsyncHandler + { + #region Fields + /// + /// UTF8 Encoding without BOM. + /// + private static readonly Encoding Utf8Encoding = new UTF8Encoding(false); + + protected abstract string GetSessionId(); + #endregion + + #region IHttpHandler Members + + public bool IsReusable + { + get { return true; } + } + + public void ProcessRequest(HttpContext context) + { + // not used + } + + #endregion + + #region IHttpAsyncHandler Members + + /// + /// Initiates an asynchronous call to the HTTP handler. + /// + /// An object that provides references to intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests. + /// The to call when the asynchronous method call is complete. If is null, the delegate is not called. + /// Any extra data needed to process the request. + /// + /// An that contains information about the status of the process. + /// + public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData) + { + var async = new JsonRpcStateAsync(cb, context); + async.JsonRpc = GetJsonRpcString(context.Request); + JsonRpcProcessor.Process(GetSessionId(),async, context.Request); + return async; + } + + private static string GetJsonRpcString(System.Web.HttpRequest request) + { + string json = string.Empty; + if (request.RequestType == "GET") + { + json = request.Params["jsonrpc"] ?? string.Empty; + } + else if (request.RequestType == "POST") + { + if (request.ContentType == "application/x-www-form-urlencoded") + { + json = request.Params["jsonrpc"] ?? string.Empty; + } + else + { + json = new StreamReader(request.InputStream).ReadToEnd(); + } + } + return json; + } + + /// + /// Provides an asynchronous process End method when the process ends. + /// + /// An that contains information about the status of the process. + public void EndProcessRequest(IAsyncResult result) + { + var state = result as JsonRpcStateAsync; + if (state == null) return; + + var stateResult = state.Result; + var callback = ((HttpContext)state.AsyncState).Request.Params["callback"]; + if (!string.IsNullOrWhiteSpace(callback)) + { + stateResult = string.Format("{0}({1})", callback, stateResult); + } + + // try to compress the response data. + // fix me: compression filters in IHttpModule always failed for IHttpAsyncHandler + CompressResponseIfPossible(((HttpContext)state.AsyncState).Request, ((HttpContext)state.AsyncState).Response, stateResult, Utf8Encoding); + } + + #endregion + + #region Utility methods + + /// + /// Transfer the result data compressed when the client accepts gzip. + /// + /// A HttpRequest object that represents the HTTP request. + /// A HttpResponse object that represents the HTTP response to be sent to the client. + /// The string data to be sent to the client. + /// The Encoding to be used to encode as the result. + static void CompressResponseIfPossible(HttpRequest request, HttpResponse response, String result, Encoding encoding) + { + response.ContentType = "application/json-rpc"; + string acceptEncoding = request.Headers["Accept-Encoding"]; + if (acceptEncoding != null && acceptEncoding.Contains("gzip")) + { + //response.Headers.Remove("Content-Encoding"); + response.AddHeader("Content-Encoding", "gzip"); + + using (var gstream = new GZipStream(response.OutputStream, CompressionMode.Compress)) + using (var writer = new StreamWriter(gstream, encoding)) + { + writer.Write(result); + writer.Flush(); + } + } + else + { + using (StreamWriter writer = new StreamWriter(response.OutputStream, encoding)) + { + writer.Write(result); + writer.Flush(); + } + } + + response.End(); + } + + + #endregion + + } +} diff --git a/AustinHarris.JsonRpc.AspNet/packages.config b/AustinHarris.JsonRpc.AspNet/packages.config index 28e5e44..0fa4e01 100644 --- a/AustinHarris.JsonRpc.AspNet/packages.config +++ b/AustinHarris.JsonRpc.AspNet/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/AustinHarris.JsonRpc.sln b/AustinHarris.JsonRpc.sln index 3fa6f16..6cf1c28 100644 --- a/AustinHarris.JsonRpc.sln +++ b/AustinHarris.JsonRpc.sln @@ -1,19 +1,12 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -VisualStudioVersion = 12.0.30723.0 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BBFBBA8A-2F75-422C-ACCD-D05A6EF7244C}" - ProjectSection(SolutionItems) = preProject - AustinHarris.JsonRpc.vsmdi = AustinHarris.JsonRpc.vsmdi - Local.testsettings = Local.testsettings - TraceAndTestImpact.testsettings = TraceAndTestImpact.testsettings - EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AustinHarris.JsonRpc", "Json-Rpc\AustinHarris.JsonRpc.csproj", "{24FC1A2A-0BC3-43A7-9BFE-B628C2C4A307}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AustinHarris.JsonRpc.AspNet", "AustinHarris.JsonRpc.AspNet\AustinHarris.JsonRpc.AspNet.csproj", "{FFFDEBBC-93F5-4A22-9EC5-D86A4A792DBB}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AustinHarris.JsonRpcTestN", "AustinHarris.JsonRpcTestN\AustinHarris.JsonRpcTestN.csproj", "{8569B076-5A8B-4D6A-B75D-EF75A390AA5F}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestServer_Console", "TestServer_Console\TestServer_Console.csproj", "{31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}" @@ -44,22 +37,6 @@ Global {24FC1A2A-0BC3-43A7-9BFE-B628C2C4A307}.Release|Mixed Platforms.Build.0 = Release|x86 {24FC1A2A-0BC3-43A7-9BFE-B628C2C4A307}.Release|x86.ActiveCfg = Release|x86 {24FC1A2A-0BC3-43A7-9BFE-B628C2C4A307}.Release|x86.Build.0 = Release|x86 - {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Debug|ARM.ActiveCfg = Debug|Any CPU - {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Debug|ARM.Build.0 = Debug|Any CPU - {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Debug|x86.ActiveCfg = Debug|x86 - {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Debug|x86.Build.0 = Debug|x86 - {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Release|Any CPU.Build.0 = Release|Any CPU - {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Release|ARM.ActiveCfg = Release|Any CPU - {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Release|ARM.Build.0 = Release|Any CPU - {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Release|x86.ActiveCfg = Release|x86 - {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Release|x86.Build.0 = Release|x86 {8569B076-5A8B-4D6A-B75D-EF75A390AA5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8569B076-5A8B-4D6A-B75D-EF75A390AA5F}.Debug|Any CPU.Build.0 = Debug|Any CPU {8569B076-5A8B-4D6A-B75D-EF75A390AA5F}.Debug|ARM.ActiveCfg = Debug|Any CPU @@ -76,20 +53,22 @@ Global {8569B076-5A8B-4D6A-B75D-EF75A390AA5F}.Release|Mixed Platforms.Build.0 = Release|Any CPU {8569B076-5A8B-4D6A-B75D-EF75A390AA5F}.Release|x86.ActiveCfg = Release|Any CPU {8569B076-5A8B-4D6A-B75D-EF75A390AA5F}.Release|x86.Build.0 = Release|Any CPU - {FFFDEBBC-93F5-4A22-9EC5-D86A4A792DBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FFFDEBBC-93F5-4A22-9EC5-D86A4A792DBB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FFFDEBBC-93F5-4A22-9EC5-D86A4A792DBB}.Debug|ARM.ActiveCfg = Debug|Any CPU - {FFFDEBBC-93F5-4A22-9EC5-D86A4A792DBB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {FFFDEBBC-93F5-4A22-9EC5-D86A4A792DBB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {FFFDEBBC-93F5-4A22-9EC5-D86A4A792DBB}.Debug|x86.ActiveCfg = Debug|Any CPU - {FFFDEBBC-93F5-4A22-9EC5-D86A4A792DBB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FFFDEBBC-93F5-4A22-9EC5-D86A4A792DBB}.Release|Any CPU.Build.0 = Release|Any CPU - {FFFDEBBC-93F5-4A22-9EC5-D86A4A792DBB}.Release|ARM.ActiveCfg = Release|Any CPU - {FFFDEBBC-93F5-4A22-9EC5-D86A4A792DBB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {FFFDEBBC-93F5-4A22-9EC5-D86A4A792DBB}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {FFFDEBBC-93F5-4A22-9EC5-D86A4A792DBB}.Release|x86.ActiveCfg = Release|Any CPU - EndGlobalSection - GlobalSection(NestedProjects) = preSolution + {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Debug|ARM.ActiveCfg = Debug|Any CPU + {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Debug|ARM.Build.0 = Debug|Any CPU + {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Debug|x86.ActiveCfg = Debug|x86 + {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Debug|x86.Build.0 = Debug|x86 + {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Release|Any CPU.Build.0 = Release|Any CPU + {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Release|ARM.ActiveCfg = Release|Any CPU + {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Release|ARM.Build.0 = Release|Any CPU + {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Release|x86.ActiveCfg = Release|x86 + {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AustinHarris.JsonRpcTestN/AustinHarris.JsonRpcTestN.csproj b/AustinHarris.JsonRpcTestN/AustinHarris.JsonRpcTestN.csproj index 0c4bd29..c5db038 100644 --- a/AustinHarris.JsonRpcTestN/AustinHarris.JsonRpcTestN.csproj +++ b/AustinHarris.JsonRpcTestN/AustinHarris.JsonRpcTestN.csproj @@ -1,69 +1,21 @@ - - + + - Debug - AnyCPU - {8569B076-5A8B-4D6A-B75D-EF75A390AA5F} - Library - AustinHarris.JsonRpcTestN - AustinHarris.JsonRpcTestN - v4.5 - ..\ - true + Austin Harris + netcoreapp3.0;netcoreapp3.1 - - true - full - false - bin\Debug - DEBUG; - prompt - 4 - false - - - full - true - bin\Release - prompt - 4 - false - - - - ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll - True - - - ..\packages\NUnit.2.6.4\lib\nunit.framework.dll - True - - - - - - - - - - - Designer - - + + - - {24FC1A2A-0BC3-43A7-9BFE-B628C2C4A307} - AustinHarris.JsonRpc - + + + + + + - + - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - + \ No newline at end of file diff --git a/AustinHarris.JsonRpcTestN/Test.cs b/AustinHarris.JsonRpcTestN/Test.cs index 2a53969..ed46543 100644 --- a/AustinHarris.JsonRpcTestN/Test.cs +++ b/AustinHarris.JsonRpcTestN/Test.cs @@ -47,7 +47,7 @@ public void TestCanCreateMultipleServicesOfSameTypeInTheirOwnSessions() for (int i = 0; i < 100; i++) { - ServiceBinder.bindService(i.ToString(), () => Poco.WithOffset(i)); + ServiceBinder.BindService(i.ToString(), Poco.WithOffset(i)); } for (int i = 0; i < 100; i++) @@ -64,14 +64,11 @@ public void TestCanCreateMultipleServicesOfSameTypeInTheirOwnSessions() public void TestCanCreateAndRemoveSession() { var h = JsonRpc.Handler.GetSessionHandler("this one"); - - h.Register("workie", new Func(x => "workie ... " + x)); - var metadata = new System.Collections.Generic.List> { Tuple.Create ("sooper", typeof(string)), Tuple.Create ("returns", typeof(string)) }.ToDictionary(x => x.Item1, x => x.Item2); - h.MetaData.AddService("workie", metadata, new System.Collections.Generic.Dictionary()); + h.RegisterFuction("workie", metadata, new System.Collections.Generic.Dictionary(),new Func(x => "workie ... " + x)); string request = @"{method:'workie',params:{'sooper':'good'},id:1}"; string expectedResult = "{\"jsonrpc\":\"2.0\",\"result\":\"workie ... good\",\"id\":1}"; @@ -82,14 +79,14 @@ public void TestCanCreateAndRemoveSession() var actual1 = JObject.Parse(result.Result); var expected1 = JObject.Parse(expectedResult); - Assert.AreEqual(expected1, actual1); + Assert.IsTrue(JToken.DeepEquals(expected1, actual1)); h.Destroy(); var result2 = JsonRpcProcessor.Process("this one", request); result2.Wait(); - Assert.AreEqual(JObject.Parse(expectedResultAfterDestroy), JObject.Parse(result2.Result)); + Assert.IsTrue(JToken.DeepEquals(JObject.Parse(expectedResultAfterDestroy), JObject.Parse(result2.Result))); } [Test()] @@ -116,40 +113,17 @@ public void NullableDateTimeToNullableDateTime() Assert.AreEqual(expectedDate, acutalDate); } - [Test()] - public void NullableFloatToNullableFloat() + [TestCase(@"{method:'NullableFloatToNullableFloat',params:[1.2345],id:1}", ExpectedResult = "{\"jsonrpc\":\"2.0\",\"result\":1.2345,\"id\":1}")] + [TestCase(@"{method:'NullableFloatToNullableFloat',params:[3.14159],id:1}", ExpectedResult = "{\"jsonrpc\":\"2.0\",\"result\":3.14159,\"id\":1}")] + [TestCase(@"{method:'NullableFloatToNullableFloat',params:[null],id:1}", ExpectedResult = "{\"jsonrpc\":\"2.0\",\"result\":null,\"id\":1}")] + public string NullableFloatToNullableFloat(string request) { - string request = @"{method:'NullableFloatToNullableFloat',params:[1.2345],id:1}"; - string expectedResult = "{\"jsonrpc\":\"2.0\",\"result\":1.2345,\"id\":1}"; var result = JsonRpcProcessor.Process(request); result.Wait(); - Assert.AreEqual(result.Result, expectedResult); - Assert.AreEqual(expectedResult, result.Result); + return result.Result; } - - [Test()] - public void NullableFloatToNullableFloat3() - { - string request = @"{method:'NullableFloatToNullableFloat',params:[3.14159],id:1}"; - string expectedResult = "{\"jsonrpc\":\"2.0\",\"result\":3.14159,\"id\":1}"; - var result = JsonRpcProcessor.Process(request); - result.Wait(); - Assert.AreEqual(result.Result, expectedResult); - Assert.AreEqual(expectedResult, result.Result); - } - - - [Test()] - public void NullableFloatToNullableFloat2() - { - string request = @"{method:'NullableFloatToNullableFloat',params:[null],id:1}"; - string expectedResult = "{\"jsonrpc\":\"2.0\",\"id\":1}"; - var result = JsonRpcProcessor.Process(request); - result.Wait(); - Assert.AreEqual(result.Result, expectedResult); - Assert.AreEqual(expectedResult, result.Result); - } - + + [Test()] public void DecimalToNullableDecimal() { @@ -199,7 +173,7 @@ public void StringToRefException() string expectedResult = "{\"jsonrpc\":\"2.0\",\"error\":{\"message\":\"refException worked\",\"code\":-1,\"data\":null},\"id\":1}"; var result = JsonRpcProcessor.Process(request); result.Wait(); - Assert.AreEqual(JObject.Parse(expectedResult), JObject.Parse(result.Result)); + Assert.IsTrue(JToken.DeepEquals(JObject.Parse(expectedResult), JObject.Parse(result.Result))); } [Test()] @@ -1405,6 +1379,18 @@ public void TestOptionalParametersBoolsAndStrings() Assert.IsFalse(result.Result.Contains("error")); Assert.AreEqual(expectedResult, result.Result); } + + [TestCase("{method:\"TestDifferentOptionalParameters\",params:{location:\"loc1\", uid:\"abc123\", wavelengths: [0.0], traces: [0.0]},id:1}", ExpectedResult = "{\"jsonrpc\":\"2.0\",\"result\":\"this is the requested measurement\",\"id\":1}")] + [TestCase("{method:\"TestDifferentOptionalParameters\",params:{uid:\"abc123\", wavelengths: [0.0], traces: [0.0]},id:1}", ExpectedResult = "{\"jsonrpc\":\"2.0\",\"result\":\"this is the requested measurement\",\"id\":1}")] + [TestCase("{method:\"TestDifferentOptionalParameters\",params:{location:\"loc1\", uid:\"abc123\", traces: [0.0]},id:1}", ExpectedResult = "{\"jsonrpc\":\"2.0\",\"result\":\"this is the requested measurement\",\"id\":1}")] + [TestCase("{method:\"TestDifferentOptionalParameters\",params:{location:\"loc1\", uid:\"abc123\", wavelengths: [0.0]},id:1}", ExpectedResult = "{\"jsonrpc\":\"2.0\",\"result\":\"this is the requested measurement\",\"id\":1}")] + [TestCase("{method:\"TestDifferentOptionalParameters\",params:{uid:\"abc123\", wavelengths: [0.0]},id:1}", ExpectedResult = "{\"jsonrpc\":\"2.0\",\"result\":\"this is the requested measurement\",\"id\":1}")] + public string TestDifferentOptionalParametersNamedWorking(string request) + { + var result = JsonRpcProcessor.Process(request); + result.Wait(); + return result.Result; + } [Test()] public void TestBatchResultWrongRequests() @@ -1440,6 +1426,17 @@ public void TestEmptyBatchResult() Assert.IsTrue(string.IsNullOrEmpty(result.Result)); } + + [Test()] + public void TestNotificationVoidResult() + { + var secondRequest = @"{""jsonrpc"":""2.0"",""method"":""Notify"",""params"":[""Hello World!""], ""id"":73}"; + var result = JsonRpcProcessor.Process(secondRequest); + result.Wait(); + Console.WriteLine(result.Result); + Assert.IsTrue(result.Result.Contains("result"), "Json Rpc 2.0 Spec - 'result' - This member is REQUIRED on success. A function that returns void should have the result property included even though the value may be null."); + } + [Test()] public void TestLeftOutParams() { @@ -1582,13 +1579,11 @@ public void TestPreProcessOnSession() PreProcessHandlerLocal preHandler = new PreProcessHandlerLocal(); h.SetPreProcessHandler(new PreProcessHandler(preHandler.PreProcess)); - h.Register("workie", new Func(x => "workie ... " + x)); - var metadata = new System.Collections.Generic.List> { Tuple.Create ("sooper", typeof(string)), Tuple.Create ("returns", typeof(string)) }.ToDictionary(x => x.Item1, x => x.Item2); - h.MetaData.AddService("workie", metadata, new System.Collections.Generic.Dictionary()); + h.RegisterFuction("workie", metadata, new System.Collections.Generic.Dictionary(),new Func(x => "workie ... " + x)); string request = @"{method:'workie',params:{'sooper':'good'},id:1}"; string expectedResult = "{\"jsonrpc\":\"2.0\",\"result\":\"workie ... good\",\"id\":1}"; @@ -1598,7 +1593,7 @@ public void TestPreProcessOnSession() var actual1 = JObject.Parse(result.Result); var expected1 = JObject.Parse(expectedResult); - Assert.AreEqual(expected1, actual1); + Assert.IsTrue(JToken.DeepEquals(expected1, actual1)); Assert.AreEqual(1, preHandler.run); h.Destroy(); @@ -1607,7 +1602,7 @@ public void TestPreProcessOnSession() result2.Wait(); Assert.AreEqual(1, preHandler.run); - Assert.AreEqual(JObject.Parse(expectedResultAfterDestroy), JObject.Parse(result2.Result)); + Assert.IsTrue(JToken.DeepEquals(JObject.Parse(expectedResultAfterDestroy), JObject.Parse(result2.Result))); } class PostProcessHandlerLocal @@ -1824,13 +1819,11 @@ public void TestPostProcessOnSession() PostProcessHandlerLocal postHandler = new PostProcessHandlerLocal(false); h.SetPostProcessHandler(new PostProcessHandler(postHandler.PostProcess)); - h.Register("workie", new Func(x => "workie ... " + x)); - var metadata = new System.Collections.Generic.List> { Tuple.Create ("sooper", typeof(string)), Tuple.Create ("returns", typeof(string)) }.ToDictionary(x => x.Item1, x => x.Item2); - h.MetaData.AddService("workie", metadata, new System.Collections.Generic.Dictionary()); + h.RegisterFuction("workie", metadata, new System.Collections.Generic.Dictionary(), new Func(x => "workie ... " + x)); string request = @"{method:'workie',params:{'sooper':'good'},id:1}"; string expectedResult = "{\"jsonrpc\":\"2.0\",\"result\":\"workie ... good\",\"id\":1}"; @@ -1840,7 +1833,7 @@ public void TestPostProcessOnSession() var actual1 = JObject.Parse(result.Result); var expected1 = JObject.Parse(expectedResult); - Assert.AreEqual(expected1, actual1); + Assert.IsTrue(JToken.DeepEquals(expected1, actual1)); Assert.AreEqual(1, postHandler.run); h.Destroy(); @@ -1849,7 +1842,7 @@ public void TestPostProcessOnSession() result2.Wait(); Assert.AreEqual(1, postHandler.run); - Assert.AreEqual(JObject.Parse(expectedResultAfterDestroy), JObject.Parse(result2.Result)); + Assert.IsTrue(JToken.DeepEquals(JObject.Parse(expectedResultAfterDestroy), JObject.Parse(result2.Result))); } [Test()] @@ -1862,6 +1855,42 @@ public void TestExtraParameters() Assert.IsTrue(result.Result.Contains("\"code\":-32602")); } + [Test()] + public void TestExtraPositionalParameters() + { + string request = @"{method:'ReturnsDateTime',params:[1,2,'mytext'],id:1}"; + var result = JsonRpcProcessor.Process(request); + result.Wait(); + Assert.IsTrue(result.Result.Contains("error")); + Assert.IsTrue(result.Result.Contains("\"code\":-32602")); + } + + [Test()] + public void TestCustomParameterName() + { + Func request = (string paramName) => String.Format("{{method:'TestCustomParameterName',params:{{ {0}:'some string'}},id:1}}", paramName); + string expectedResult = "{\"jsonrpc\":\"2.0\",\"result\":true,\"id\":1}"; + // Check custom param name specified in attribute works + var result = JsonRpcProcessor.Process(request("myCustomParameter")); + result.Wait(); + Assert.AreEqual(JObject.Parse(expectedResult), JObject.Parse(result.Result)); + // Check method can't be used with its actual parameter name + result = JsonRpcProcessor.Process(request("arg")); + result.Wait(); + StringAssert.Contains("-32602", result.Result); // check for 'invalid params' error code + } + + [Test()] + public void TestCustomParameterWithNoSpecificName() + { + Func request = (string paramName) => String.Format("{{method:'TestCustomParameterWithNoSpecificName',params:{{ {0}:'some string'}},id:1}}", paramName); + string expectedResult = "{\"jsonrpc\":\"2.0\",\"result\":true,\"id\":1}"; + // Check method can be used with its parameter name + var result = JsonRpcProcessor.Process(request("arg")); + result.Wait(); + Assert.AreEqual(JObject.Parse(expectedResult), JObject.Parse(result.Result)); + } + [Test] public void TestNestedReturnType() { @@ -1872,6 +1901,26 @@ public void TestNestedReturnType() Assert.AreEqual(expected, result.Result); } + [Test()] + public void TestWrongParamType() + { + string request = @"{method:'TestOptionalParamdouble',params:{input:'mytext'},id:1}"; + var result = JsonRpcProcessor.Process(request); + result.Wait(); + Assert.IsTrue(result.Result.Contains("error")); + Assert.IsTrue(result.Result.Contains("\"code\":-32603")); + } + + [Test()] + public void TestWrongIdType() + { + string request = @"{method:'TestOptionalParamdouble',params:{input:5},id:{what:4,that:3}}"; + var result = JsonRpcProcessor.Process(request); + result.Wait(); + Assert.IsTrue(result.Result.Contains("error")); + Assert.IsTrue(result.Result.Contains("\"code\":-32600")); + } + private static void AssertJsonAreEqual(string expectedJson, string actualJson) { Newtonsoft.Json.Linq.JObject expected = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(expectedJson); @@ -1899,6 +1948,8 @@ private static void AssertJsonAreEqual(JToken expectedJson, JToken actualJson, s private static void AssertJsonAreEqual(JObject expectedJson, JObject actualJson, string path) { + Console.WriteLine("expected: {0}", expectedJson); + Console.WriteLine("actual : {0}", actualJson); Assert.AreEqual(expectedJson.Count, actualJson.Count, "Count of json object at " + path); for (var expectedElementsEnumerator = expectedJson.GetEnumerator(); expectedElementsEnumerator.MoveNext(); ) { diff --git a/AustinHarris.JsonRpcTestN/packages.config b/AustinHarris.JsonRpcTestN/packages.config deleted file mode 100644 index d274cb4..0000000 --- a/AustinHarris.JsonRpcTestN/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/AustinHarris.JsonRpcTestN/service.cs b/AustinHarris.JsonRpcTestN/service.cs index 5da391f..b34d957 100644 --- a/AustinHarris.JsonRpcTestN/service.cs +++ b/AustinHarris.JsonRpcTestN/service.cs @@ -79,6 +79,19 @@ private string devideByZero(string s) return s + j / i; } + + [JsonRpcMethod] + private bool TestCustomParameterName([JsonRpcParam("myCustomParameter")] string arg) + { + return true; + } + + [JsonRpcMethod] + private bool TestCustomParameterWithNoSpecificName([JsonRpcParam] string arg) + { + return true; + } + [JsonRpcMethod] private string StringToRefException(string s, ref JsonRpcException refException) { @@ -376,6 +389,12 @@ public TreeNode TestNestedReturnType() } }; } + + [JsonRpcMethod] + private string TestDifferentOptionalParameters(string uid, string location = null, List traces = null, List wavelengths = null) + { + return "this is the requested measurement"; + } } } diff --git a/Json-Rpc/Attributes.cs b/Json-Rpc/Attributes.cs index dd78282..1592511 100644 --- a/Json-Rpc/Attributes.cs +++ b/Json-Rpc/Attributes.cs @@ -24,4 +24,27 @@ public string JsonMethodName get { return jsonMethodName; } } } + + /// + /// Used to assign JsonRpc parameter name to method argument. + /// + [AttributeUsage(AttributeTargets.Parameter, Inherited = false, AllowMultiple = false)] + public sealed class JsonRpcParamAttribute : Attribute + { + readonly string jsonParamName; + + /// + /// Used to assign JsonRpc parameter name to method argument. + /// + /// Lets you specify the parameter name as it will be referred to by JsonRpc. + public JsonRpcParamAttribute(string jsonParamName = "") + { + this.jsonParamName = jsonParamName; + } + + public string JsonParamName + { + get { return jsonParamName; } + } + } } diff --git a/Json-Rpc/AustinHarris.JsonRpc.csproj b/Json-Rpc/AustinHarris.JsonRpc.csproj index 9d67f72..f6a680d 100644 --- a/Json-Rpc/AustinHarris.JsonRpc.csproj +++ b/Json-Rpc/AustinHarris.JsonRpc.csproj @@ -1,98 +1,27 @@ - - - - Debug - x86 - {24FC1A2A-0BC3-43A7-9BFE-B628C2C4A307} - Library - Properties - AustinHarris.JsonRpc - AustinHarris.JsonRpc - - - 512 - SAK - SAK - SAK - SAK - ..\..\CoiniumServ\build\ - true - v4.0 - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - AnyCPU - bin\Debug\ - TRACE;DEBUG - 4 - false - - - AnyCPU - bin\Release\ - 4 - false - - - - - - - - - - - - - - - - - - - - - ..\packages\Newtonsoft.Json.8.0.2\lib\net40\Newtonsoft.Json.dll - True - - - - - - + + - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + Austin Harris + Json-Rpc.Net Core + Core functionality for JsonRpc.Net + 1.2.3 + $(VersionSuffix) + Austin Harris + https://github.com/Astn/JSON-RPC.NET + https://raw.githubusercontent.com/Astn/JSON-RPC.NET/master/LICENSE + + Improves support for optional parameters - @HoMS1987 https://github.com/HoMS1987 + Fixes protocol validation of the ID property - @pedrolcl https://github.com/pedrolcl + DotNet Core support - @astn https://github.com/astn + + netstandard2.0;netstandard2.1;netcoreapp3.1 + true + - - - - - - + + + + + + \ No newline at end of file diff --git a/Json-Rpc/AustinHarris.JsonRpc.nuspec b/Json-Rpc/AustinHarris.JsonRpc.nuspec index 5a3c555..ad36f62 100644 --- a/Json-Rpc/AustinHarris.JsonRpc.nuspec +++ b/Json-Rpc/AustinHarris.JsonRpc.nuspec @@ -12,12 +12,12 @@ false The fastest .Net JSON RPC Server JSON-RPC.Net is a high performance Json-Rpc 2.0 server, leveraging the popular JSON.NET library. Easily create a JSON RPC server for your Angular javascript apps, also supports sockets and pipes, oh my! - Performance improvements -Astn, Bug Fixes: Optional Parameter Ordering -artnim, Batches with superflous commans -artnim, ParamCount Exception -artnim. + Optional JsonSerializer Settings - @hovi. ProcessSync is now public - @astn en-US fast json rpc server socket javascript json-rpc.net json-rpc jsonrpc json.net web services webapi service angular server angularjs - + diff --git a/Json-Rpc/Handler.cs b/Json-Rpc/Handler.cs index 9922f95..095bacb 100644 --- a/Json-Rpc/Handler.cs +++ b/Json-Rpc/Handler.cs @@ -14,10 +14,15 @@ public class Handler { #region Members - - //private static Handler current; - private static ConcurrentDictionary _sessionHandlers; - private static string _defaultSessionId; + private const string Name_of_JSONRPCEXCEPTION = "JsonRpcException&"; + private static int _sessionHandlerMasterVersion = 1; + [ThreadStatic] + private static Dictionary _sessionHandlersLocal; + [ThreadStatic] + private static int _sessionHandlerLocalVersion = 0; + private static ConcurrentDictionary _sessionHandlersMaster; + + private static volatile string _defaultSessionId; #endregion #region Constructors @@ -26,15 +31,14 @@ static Handler() { //current = new Handler(Guid.NewGuid().ToString()); _defaultSessionId = Guid.NewGuid().ToString(); - _sessionHandlers = new ConcurrentDictionary(); - _sessionHandlers[_defaultSessionId] = new Handler(_defaultSessionId); + _sessionHandlersMaster = new ConcurrentDictionary(); + _sessionHandlersMaster[_defaultSessionId] = new Handler(_defaultSessionId); } private Handler(string sessionId) { SessionId = sessionId; this.MetaData = new SMD(); - this.Handlers = new Dictionary(); } #endregion @@ -54,7 +58,17 @@ private Handler(string sessionId) /// public static Handler GetSessionHandler(string sessionId) { - return _sessionHandlers.GetOrAdd(sessionId, new Handler(sessionId)); + if (_sessionHandlerMasterVersion != _sessionHandlerLocalVersion) + { + _sessionHandlersLocal = new Dictionary(_sessionHandlersMaster); + _sessionHandlerLocalVersion = _sessionHandlerMasterVersion; + } + if (_sessionHandlersLocal.ContainsKey(sessionId)) + { + return _sessionHandlersLocal[sessionId]; + } + Interlocked.Increment(ref _sessionHandlerMasterVersion); + return _sessionHandlersMaster.GetOrAdd(sessionId, new Handler(sessionId)); } /// @@ -73,8 +87,8 @@ public static Handler GetSessionHandler() public static void DestroySession(string sessionId) { Handler h; - _sessionHandlers.TryRemove(sessionId, out h); - h.Handlers.Clear(); + _sessionHandlersMaster.TryRemove(sessionId, out h); + Interlocked.Increment(ref _sessionHandlerMasterVersion); h.MetaData.Services.Clear(); } /// @@ -95,9 +109,6 @@ public void Destroy() /// public string SessionId { get; private set; } - private static ConcurrentDictionary RpcContexts = new ConcurrentDictionary(); - private static ConcurrentDictionary RpcExceptions = new ConcurrentDictionary(); - /// /// Provides access to a context specific to each JsonRpc method invocation. /// Warning: Must be called from within the execution context of the jsonRpc Method to return the context @@ -105,44 +116,31 @@ public void Destroy() /// public static object RpcContext() { - if (Task.CurrentId == null) - return null; - - if (RpcContexts.ContainsKey(Task.CurrentId.Value) == false) - return null; - - return RpcContexts[Task.CurrentId.Value]; + return __currentRpcContext; } + [ThreadStatic] + static JsonRpcException __currentRpcException; /// /// Allows you to set the exception used in in the JsonRpc response. - /// Warning: Must be called from within the execution context of the jsonRpc method. + /// Warning: Must be called from the same thread as the jsonRpc method. /// /// public static void RpcSetException(JsonRpcException exception) { - if (Task.CurrentId != null) - RpcExceptions[Task.CurrentId.Value] = exception; - else - throw new InvalidOperationException("This method is only valid when used within the context of a method marked as a JsonRpcMethod, and that method must of been invoked by the JsonRpc Handler."); + __currentRpcException = exception; } - - private void RemoveRpcException() + public static JsonRpcException RpcGetAndRemoveRpcException() { - if (Task.CurrentId != null) - { - var id = Task.CurrentId.Value; - RpcExceptions[id] = null; - JsonRpcException va; - RpcExceptions.TryRemove(id, out va); - } + var ex = __currentRpcException; + __currentRpcException = null ; + return ex; } private AustinHarris.JsonRpc.PreProcessHandler externalPreProcessingHandler; private AustinHarris.JsonRpc.PostProcessHandler externalPostProcessingHandler; private Func externalErrorHandler; private Func parseErrorHandler; - private Dictionary Handlers { get; set; } #endregion /// @@ -150,32 +148,44 @@ private void RemoveRpcException() /// public SMD MetaData { get; set; } - private const string THREAD_CALLBACK_SLOT_NAME ="Callback"; - #region Public Methods /// - /// Registers a jsonRpc method name (key) to be mapped to a specific function + /// Allows you to register all the functions on a Pojo Type that have been attributed as [JsonRpcMethod] to the specified sessionId /// - /// The Method as it will be called from JsonRpc - /// The method that will be invoked - /// - public bool Register(string key, Delegate handle) + /// The session to register against + /// The instance containing JsonRpcMethods to register + public static void RegisterInstance(string sessionID, object instance) { - var result = false; + ServiceBinder.BindService(sessionID, instance); + } - if (!this.Handlers.ContainsKey(key)) - { - this.Handlers.Add(key, handle); - } + /// + /// Allows you to register any function, lambda, etc even when not attributed with JsonRpcMethod. + /// Requires you to specify all types and defaults + /// + /// The method name that will map to the registered function + /// The parameter names and types that will be positionally bound to the function + /// Optional default values for parameters + /// A reference to the Function + public void RegisterFuction(string methodName, Dictionary parameterNameTypeMapping, Dictionary parameterNameDefaultValueMapping, Delegate implementation) + { + MetaData.AddService(methodName, parameterNameTypeMapping, parameterNameDefaultValueMapping, implementation); + } + + public void UnRegisterFunction(string methodName) + { + MetaData.Services.Remove(methodName); + } - return result; + public void SetPreProcessHandler(AustinHarris.JsonRpc.PreProcessHandler handler) + { + externalPreProcessingHandler = handler; } - public void UnRegister(string key) + public void SetPostProcessHandler(AustinHarris.JsonRpc.PostProcessHandler handler) { - this.Handlers.Remove(key); - MetaData.Services.Remove(key); + externalPostProcessingHandler = handler; } /// @@ -184,7 +194,7 @@ public void UnRegister(string key) /// JsonRpc Request to be processed /// Optional context that will be available from within the jsonRpcMethod. /// - public JsonResponse Handle(JsonRequest Rpc, Object RpcContext = null, Action callback = null) + public JsonResponse Handle(JsonRequest Rpc, Object RpcContext = null) { AddRpcContext(RpcContext); @@ -198,15 +208,15 @@ public JsonResponse Handle(JsonRequest Rpc, Object RpcContext = null, Action x != null); - var getCount = Rpc.Params as ICollection; + var loopCt = 0; - + var getCount = Rpc.Params as ICollection; if (getCount != null) { loopCt = getCount.Count; } var paramCount = loopCt; - if (paramCount == metaDataParamCount - 1 && metadata.parameters[metaDataParamCount - 1].ObjectType.Name.Contains(typeof(JsonRpcException).Name)) + if (paramCount == metaDataParamCount - 1 && metadata.parameters[metaDataParamCount - 1].ObjectType.Name.Equals(Name_of_JSONRPCEXCEPTION)) { paramCount++; expectsRefException = true; } parameters = new object[paramCount]; - if (isJArray) + if (Rpc.Params is Newtonsoft.Json.Linq.JArray) { var jarr = ((Newtonsoft.Json.Linq.JArray)Rpc.Params); - //var loopCt = jarr.Count; - //var pCount = loopCt; - //if (pCount == metaDataParamCount - 1 && metadata.parameters[metaDataParamCount].GetType() == typeof(JsonRpcException)) - // pCount++; - //parameters = new object[pCount]; - for (int i = 0; i < loopCt; i++) + for (int i = 0; i < loopCt && i < metadata.parameters.Length; i++) { parameters[i] = CleanUpParameter(jarr[i], metadata.parameters[i]); } } - else if (isJObject) + else if (Rpc.Params is Newtonsoft.Json.Linq.JObject) { - var jo = Rpc.Params as Newtonsoft.Json.Linq.JObject; - //var loopCt = jo.Count; - //var pCount = loopCt; - //if (pCount == metaDataParamCount - 1 && metadata.parameters[metaDataParamCount].GetType() == typeof(JsonRpcException)) - // pCount++; - //parameters = new object[pCount]; - var asDict = jo as IDictionary; + var asDict = Rpc.Params as IDictionary; for (int i = 0; i < loopCt && i < metadata.parameters.Length; i++) { - if (asDict.ContainsKey(metadata.parameters[i].Name) == false) + if (asDict.ContainsKey(metadata.parameters[i].Name) == true) + { + parameters[i] = CleanUpParameter(asDict[metadata.parameters[i].Name], metadata.parameters[i]); + continue; + } + else { + var foundDefault = metadata.defaultValues + .FirstOrDefault(defaul => defaul.Name == metadata.parameters[i].Name); + if (foundDefault != null) + { + parameters[i] = foundDefault.Value; + continue; + } + JsonResponse response = new JsonResponse() { Error = ProcessException(Rpc, @@ -275,9 +285,8 @@ public JsonResponse Handle(JsonRequest Rpc, Object RpcContext = null, Action - /// Method returns the actual callback set to this thread in Handle() method. - /// If callback is not set, then empty callback is returned. - /// - /// - internal Action GetAsyncCallback() - { - object o = Thread.GetData(Thread.GetNamedDataSlot(THREAD_CALLBACK_SLOT_NAME)); - Action callback; - if(o is Action) - { - callback = o as Action; - } - else - { - callback = delegate(JsonResponse a) { }; - } - return callback; - } - + [ThreadStatic] + static object __currentRpcContext; private void AddRpcContext(object RpcContext) { - if (Task.CurrentId != null) - RpcContexts[Task.CurrentId.Value] = RpcContext; + __currentRpcContext = RpcContext; } private void RemoveRpcContext() { - if (Task.CurrentId != null) - { - var id = Task.CurrentId.Value; - RpcContexts[id] = null; - object va; - RpcContexts.TryRemove(id, out va); - } + __currentRpcContext = null; } private JsonRpcException ProcessException(JsonRequest req, JsonRpcException ex) @@ -446,47 +427,46 @@ internal void SetParseErrorHandler(Func callback, JsonRequest request, JsonResponse response, object context) + private JsonResponse PostProcess(JsonRequest request, JsonResponse response, object context) { if (externalPostProcessingHandler != null) { @@ -534,22 +512,9 @@ private JsonResponse PostProcess(Action callback, JsonRequest requ response = new JsonResponse() { Error = ProcessException(request, new JsonRpcException(-32603, "Internal Error", ex)) }; } } - - if (callback != null) - callback.Invoke(response); - return response; } - public void SetPreProcessHandler(AustinHarris.JsonRpc.PreProcessHandler handler) - { - externalPreProcessingHandler = handler; - } - - public void SetPostProcessHandler(AustinHarris.JsonRpc.PostProcessHandler handler) - { - externalPostProcessingHandler = handler; - } } } diff --git a/Json-Rpc/JsonRequest.cs b/Json-Rpc/JsonRequest.cs index 033ea1f..e68c1ff 100644 --- a/Json-Rpc/JsonRequest.cs +++ b/Json-Rpc/JsonRequest.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Newtonsoft.Json; +using Newtonsoft.Json; namespace AustinHarris.JsonRpc { @@ -16,6 +12,19 @@ public JsonRequest() { } + public JsonRequest(string method, object pars, object id) + { + Method = method; + Params = pars; + Id = id; + } + + [JsonProperty("jsonrpc")] + public string JsonRpc + { + get { return "2.0"; } + } + [JsonProperty("method")] public string Method { get; set; } diff --git a/Json-Rpc/JsonResponse.cs b/Json-Rpc/JsonResponse.cs index d16a64b..941de82 100644 --- a/Json-Rpc/JsonResponse.cs +++ b/Json-Rpc/JsonResponse.cs @@ -13,7 +13,7 @@ namespace AustinHarris.JsonRpc public class JsonResponse { [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "jsonrpc")] - public string JsonRpc { get { return "2.0"; } } + public string JsonRpc { get; set; } = "2.0"; [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "result")] public object Result { get; set; } @@ -32,7 +32,7 @@ public class JsonResponse public class JsonResponse { [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "jsonrpc")] - public string JsonRpc { get { return "2.0"; } } + public string JsonRpc { get; set; } = "2.0"; [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "result")] public T Result { get; set; } diff --git a/Json-Rpc/JsonRpcProcessor.cs b/Json-Rpc/JsonRpcProcessor.cs index 31bc872..5020e88 100644 --- a/Json-Rpc/JsonRpcProcessor.cs +++ b/Json-Rpc/JsonRpcProcessor.cs @@ -13,227 +13,168 @@ namespace AustinHarris.JsonRpc { public static class JsonRpcProcessor { - public static void AsyncProcess(string jsonRpc, Action callback, object context = null) + public static void Process(JsonRpcStateAsync async, object context = null, + JsonSerializerSettings settings = null) { - Task.Factory.StartNew(() => AsyncProcessInternal(Handler.DefaultSessionId(), jsonRpc, context, callback)); + Process(Handler.DefaultSessionId(), async, context, settings); } - public static void AsyncProcess(string sessionId,string jsonRpc, Action callback, object context = null) + public static void Process(string sessionId, JsonRpcStateAsync async, object context = null, + JsonSerializerSettings settings = null) { - Task.Factory.StartNew(() => AsyncProcessInternal(sessionId, jsonRpc, context, callback)); + Process(sessionId, async.JsonRpc, context, settings) + .ContinueWith(t => + { + async.Result = t.Result; + async.SetCompleted(); + }); } - /// - /// The callback will be returned to the user, who needs to invoke it in concrete - /// service implementation. Call should be made directly from same thread as - /// service method is executed. - /// - /// Handler session id - /// - public static Action GetAsyncProcessCallback(string sessionId = "") + public static Task Process(string jsonRpc, object context = null, + JsonSerializerSettings settings = null) { - Handler handler; - if ("" == sessionId) - { - handler = Handler.GetSessionHandler(Handler.DefaultSessionId()); - } - else - { - handler = Handler.GetSessionHandler(sessionId); - } + return Process(Handler.DefaultSessionId(), jsonRpc, context, settings); + } - return handler.GetAsyncCallback(); + public static Task Process(string sessionId, string jsonRpc, object context = null, + JsonSerializerSettings settings = null) + { + return Task.Factory.StartNew((_) => + { + var tuple = (Tuple)_; + return ProcessSync(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4); + }, new Tuple(sessionId, jsonRpc, context, settings)); } - private static void AsyncProcessInternal(string sessionId, string jsonRpc, object jsonRpcContext, Action callback) + public static string ProcessSync(string sessionId, string jsonRpc, object jsonRpcContext, + JsonSerializerSettings settings = null) { - Handler handler = Handler.GetSessionHandler(sessionId); + var handler = Handler.GetSessionHandler(sessionId); + + JsonRequest[] batch = null; try { - Tuple[] batch = null; if (isSingleRpc(jsonRpc)) { - batch = new[] { Tuple.Create(JsonConvert.DeserializeObject(jsonRpc)) }; + var foo = JsonConvert.DeserializeObject(jsonRpc, settings); + batch = new[] { foo }; } else { - batch = JsonConvert.DeserializeObject(jsonRpc) - .Select(request => new Tuple(request)) - .ToArray(); - } - - if (batch.Length == 0) - { - callback.Invoke(Newtonsoft.Json.JsonConvert.SerializeObject(new JsonResponse - { - Error = handler.ProcessParseException(jsonRpc, - new JsonRpcException(3200, "Invalid Request", "Batch of calls was empty.")) - })); - } - - foreach (var tuple in batch) - { - JsonRequest jsonRequest = tuple.Item1; - JsonResponse jsonResponse = new JsonResponse(); - - if (jsonRequest == null) - { - jsonResponse.Error = handler.ProcessParseException(jsonRpc, - new JsonRpcException(-32700, "Parse error", - "Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.")); - } - else - { - jsonResponse.Id = jsonRequest.Id; - - if (jsonRequest.Method == null) - { - jsonResponse.Error = handler.ProcessParseException(jsonRpc, - new JsonRpcException(-32600, "Invalid Request", "Missing property 'method'")); - } - else - { - handler.Handle(jsonRequest, jsonRpcContext, - delegate(JsonResponse a) - { - a.Id = jsonRequest.Id; - if (a.Id != null || a.Error != null) - { - callback.Invoke(JsonConvert.SerializeObject(a)); - } - } - ); - } - } + batch = JsonConvert.DeserializeObject(jsonRpc, settings); } } catch (Exception ex) { - callback.Invoke(Newtonsoft.Json.JsonConvert.SerializeObject(new JsonResponse + return Newtonsoft.Json.JsonConvert.SerializeObject(new JsonResponse { Error = handler.ProcessParseException(jsonRpc, new JsonRpcException(-32700, "Parse error", ex)) - })); + }, settings); } - } - public static void Process(JsonRpcStateAsync async, object context = null) - { - Task.Factory.StartNew((_async) => + if (batch.Length == 0) { - var tuple = (Tuple)_async; - ProcessJsonRpcState(tuple.Item1, tuple.Item2); - }, new Tuple(async, context)); - - } + return Newtonsoft.Json.JsonConvert.SerializeObject(new JsonResponse + { + Error = handler.ProcessParseException(jsonRpc, + new JsonRpcException(3200, "Invalid Request", "Batch of calls was empty.")) + }, settings); + } - public static void Process(string sessionId, JsonRpcStateAsync async, object context = null) - { - var t = Task.Factory.StartNew((_async) => + var singleBatch = batch.Length == 1; + StringBuilder sbResult = null; + for (var i = 0; i < batch.Length; i++) { - var i = (Tuple)_async; - ProcessJsonRpcState(i.Item1, i.Item2, i.Item3); - }, new Tuple(sessionId, async, context)); + var jsonRequest = batch[i]; + var jsonResponse = new JsonResponse(); - } - internal static void ProcessJsonRpcState(JsonRpcStateAsync async, object jsonRpcContext = null) - { - ProcessJsonRpcState(Handler.DefaultSessionId(), async, jsonRpcContext); - } - internal static void ProcessJsonRpcState(string sessionId, JsonRpcStateAsync async, object jsonRpcContext = null) - { - async.Result = ProcessInternal(sessionId, async.JsonRpc, jsonRpcContext); - async.SetCompleted(); - } - - public static Task Process(string jsonRpc, object context = null) - { - return Process(Handler.DefaultSessionId(), jsonRpc, context); - } - public static Task Process(string sessionId, string jsonRpc, object context = null) - { - return Task.Factory.StartNew((_) => - { - var tuple = (Tuple)_; - return ProcessInternal(tuple.Item1, tuple.Item2, tuple.Item3); - }, new Tuple(sessionId, jsonRpc, context)); - } - - private static string ProcessInternal(string sessionId, string jsonRpc, object jsonRpcContext) - { - var handler = Handler.GetSessionHandler(sessionId); - - try - { - Tuple[] batch = null; - if (isSingleRpc(jsonRpc)) + if (jsonRequest == null) { - batch = new[] { Tuple.Create(JsonConvert.DeserializeObject(jsonRpc), new JsonResponse()) }; + jsonResponse.Error = handler.ProcessParseException(jsonRpc, + new JsonRpcException(-32700, "Parse error", + "Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.")); } - else + else if (jsonRequest.Method == null) { - batch = JsonConvert.DeserializeObject(jsonRpc) - .Select(request => new Tuple(request, new JsonResponse())) - .ToArray(); + jsonResponse.Error = handler.ProcessParseException(jsonRpc, + new JsonRpcException(-32600, "Invalid Request", "Missing property 'method'")); } - - if (batch.Length == 0) + else if (!isSimpleValueType(jsonRequest.Id)) { - return Newtonsoft.Json.JsonConvert.SerializeObject(new JsonResponse - { - Error = handler.ProcessParseException(jsonRpc, - new JsonRpcException(3200, "Invalid Request", "Batch of calls was empty.")) - }); + jsonResponse.Error = handler.ProcessParseException(jsonRpc, + new JsonRpcException(-32600, "Invalid Request", "Id property must be either null or string or integer.")); } - - foreach (var tuple in batch) + else { - var jsonRequest = tuple.Item1; - var jsonResponse = tuple.Item2; + jsonResponse.Id = jsonRequest.Id; + + var data = handler.Handle(jsonRequest, jsonRpcContext); - if (jsonRequest == null) + if (data == null) continue; + + jsonResponse.JsonRpc = data.JsonRpc; + jsonResponse.Error = data.Error; + jsonResponse.Result = data.Result; + + } + if (jsonResponse.Result == null && jsonResponse.Error == null) + { + // Per json rpc 2.0 spec + // result : This member is REQUIRED on success. + // This member MUST NOT exist if there was an error invoking the method. + // Either the result member or error member MUST be included, but both members MUST NOT be included. + jsonResponse.Result = new Newtonsoft.Json.Linq.JValue((Object)null); + } + // special case optimization for single Item batch + if (singleBatch && (jsonResponse.Id != null || jsonResponse.Error != null)) + { + StringWriter sw = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(sw); + writer.WriteStartObject(); + if (!string.IsNullOrEmpty(jsonResponse.JsonRpc)) + { + writer.WritePropertyName("jsonrpc"); writer.WriteValue(jsonResponse.JsonRpc); + } + if (jsonResponse.Error != null) { - jsonResponse.Error = handler.ProcessParseException(jsonRpc, - new JsonRpcException(-32700, "Parse error", - "Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.")); + writer.WritePropertyName("error"); writer.WriteRawValue(JsonConvert.SerializeObject(jsonResponse.Error, settings)); } else { - jsonResponse.Id = jsonRequest.Id; - - if (jsonRequest.Method == null) - { - jsonResponse.Error = handler.ProcessParseException(jsonRpc, - new JsonRpcException(-32600, "Invalid Request", "Missing property 'method'")); - } - else - { - var data = handler.Handle(jsonRequest, jsonRpcContext); - - if (data == null) continue; - - jsonResponse.Error = data.Error; - jsonResponse.Result = data.Result; - } + writer.WritePropertyName("result"); writer.WriteRawValue(JsonConvert.SerializeObject(jsonResponse.Result, settings)); } - } + writer.WritePropertyName("id"); writer.WriteValue(jsonResponse.Id); + writer.WriteEndObject(); + return sw.ToString(); - var responses = new string[batch.Count(x => x.Item2.Id != null || x.Item2.Error != null)]; - var idx = 0; - foreach (var resp in batch.Where(x => x.Item2.Id != null || x.Item2.Error != null)) + //return JsonConvert.SerializeObject(jsonResponse); + } + else if (jsonResponse.Id == null && jsonResponse.Error == null) { - responses[idx++] = JsonConvert.SerializeObject(resp.Item2); + // do nothing + sbResult = new StringBuilder(0); } - - return responses.Length == 0 ? string.Empty : responses.Length == 1 ? responses[0] : string.Format("[{0}]", string.Join(",", responses)); - } - catch (Exception ex) - { - return Newtonsoft.Json.JsonConvert.SerializeObject(new JsonResponse + else { - Error = handler.ProcessParseException(jsonRpc, new JsonRpcException(-32700, "Parse error", ex)) - }); + // write out the response + if (i == 0) + { + sbResult = new StringBuilder("["); + } + + sbResult.Append(JsonConvert.SerializeObject(jsonResponse, settings)); + if (i < batch.Length - 1) + { + sbResult.Append(','); + } + else if (i == batch.Length - 1) + { + sbResult.Append(']'); + } + } } + return sbResult.ToString(); } private static bool isSingleRpc(string json) @@ -245,5 +186,15 @@ private static bool isSingleRpc(string json) } return true; } + + private static bool isSimpleValueType(object property) + { + if (property == null) + return true; + return property.GetType() == typeof(System.String) || + property.GetType() == typeof(System.Int64) || + property.GetType() == typeof(System.Int32) || + property.GetType() == typeof(System.Int16); + } } } diff --git a/Json-Rpc/JsonRpcService.cs b/Json-Rpc/JsonRpcService.cs index 46bfb63..d8ec961 100644 --- a/Json-Rpc/JsonRpcService.cs +++ b/Json-Rpc/JsonRpcService.cs @@ -1,21 +1,22 @@ namespace AustinHarris.JsonRpc { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using AustinHarris.JsonRpc; - + /// + /// For routing use SessionId + /// public abstract class JsonRpcService { protected JsonRpcService() { - ServiceBinder.bindService(Handler.DefaultSessionId(), () => this); + ServiceBinder.BindService(Handler.DefaultSessionId(), this); } + /// + /// Routing by SessionId + /// + /// protected JsonRpcService(string sessionID) { - ServiceBinder.bindService(sessionID, () => this); + ServiceBinder.BindService(sessionID, this); } } -} +} \ No newline at end of file diff --git a/Json-Rpc/Properties/AssemblyInfo.cs b/Json-Rpc/Properties/AssemblyInfo.cs deleted file mode 100644 index 802d0fd..0000000 --- a/Json-Rpc/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Json-Rpc.Net Core")] -[assembly: AssemblyDescription("Core functionality for JsonRpc.Net")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Austin Harris")] -[assembly: AssemblyProduct("Json-Rpc.Net Core")] -[assembly: AssemblyCopyright("Austin Harris")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("2f8036b2-223d-4b90-b6a9-fadddeb3ac0d")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -[assembly: AssemblyVersion("1.0.5.0")] -[assembly: AssemblyFileVersion("1.0.5.0")] diff --git a/Json-Rpc/SMDService.cs b/Json-Rpc/SMDService.cs index 2c3fa8b..fe84bb5 100644 --- a/Json-Rpc/SMDService.cs +++ b/Json-Rpc/SMDService.cs @@ -34,9 +34,9 @@ public SMD () TypeHashes = new List(); } - public void AddService(string method, Dictionary parameters, Dictionary defaultValues) + internal void AddService(string method, Dictionary parameters, Dictionary defaultValues, Delegate dele) { - var newService = new SMDService(transport,"JSON-RPC-2.0",parameters, defaultValues); + var newService = new SMDService(transport,"JSON-RPC-2.0",parameters, defaultValues, dele); Services.Add(method,newService); } @@ -64,6 +64,7 @@ public static bool ContainsType(JObject jo) public class SMDService { + public Delegate dele; /// /// Defines a service method http://dojotoolkit.org/reference-guide/1.8/dojox/rpc/smd.html /// @@ -71,9 +72,10 @@ public class SMDService /// URL, PATH, JSON, JSON-RPC-1.0, JSON-RPC-1.1, JSON-RPC-2.0 /// /// - public SMDService(string transport, string envelope, Dictionary parameters, Dictionary defaultValues ) + public SMDService(string transport, string envelope, Dictionary parameters, Dictionary defaultValues, Delegate dele) { // TODO: Complete member initialization + this.dele = dele; this.transport = transport; this.envelope = envelope; this.parameters = new SMDAdditionalParameters[parameters.Count-1]; // last param is return type similar to Func<,> diff --git a/Json-Rpc/ServiceBinder.cs b/Json-Rpc/ServiceBinder.cs index d6b8451..20423a6 100644 --- a/Json-Rpc/ServiceBinder.cs +++ b/Json-Rpc/ServiceBinder.cs @@ -8,11 +8,18 @@ public static class ServiceBinder { - public static void bindService(string sessionID, Func serviceFactory) + public static void BindService() where T : new() + { + BindService(Handler.DefaultSessionId()); + } + public static void BindService(string sessionID) where T : new() + { + BindService(sessionID, new T()); + } + + public static void BindService(string sessionID, Object instance) { - var instance = serviceFactory(); var item = instance.GetType(); // var item = typeof(T); - var regMethod = typeof(Handler).GetMethod("Register"); var methods = item.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(m => m.GetCustomAttributes(typeof(JsonRpcMethodAttribute), false).Length > 0); foreach (var meth in methods) @@ -25,12 +32,26 @@ public static void bindService(string sessionID, Func serviceFactory) List parameterTypeArray = new List(); for (int i = 0; i < paramzs.Length; i++) { + string paramName; + var paramAttrs = paramzs[i].GetCustomAttributes(typeof(JsonRpcParamAttribute), false); + if (paramAttrs.Length > 0) + { + paramName = ((JsonRpcParamAttribute)paramAttrs[0]).JsonParamName; + if (string.IsNullOrEmpty(paramName)) + { + paramName = paramzs[i].Name; + } + } + else + { + paramName = paramzs[i].Name; + } // reflection attribute information for optional parameters //http://stackoverflow.com/questions/2421994/invoking-methods-with-optional-parameters-through-reflection - paras.Add(paramzs[i].Name, paramzs[i].ParameterType); + paras.Add(paramName, paramzs[i].ParameterType); if (paramzs[i].IsOptional) // if the parameter is an optional, add the default value to our default values dictionary. - defaultValues.Add(paramzs[i].Name, paramzs[i].DefaultValue); + defaultValues.Add(paramName, paramzs[i].DefaultValue); } var resType = meth.ReturnType; @@ -42,18 +63,9 @@ public static void bindService(string sessionID, Func serviceFactory) var methodName = handlerAttribute.JsonMethodName == string.Empty ? meth.Name : handlerAttribute.JsonMethodName; var newDel = Delegate.CreateDelegate(System.Linq.Expressions.Expression.GetDelegateType(paras.Values.ToArray()), instance /*Need to add support for other methods outside of this instance*/, meth); var handlerSession = Handler.GetSessionHandler(sessionID); - regMethod.Invoke(handlerSession, new object[] { methodName, newDel }); - handlerSession.MetaData.AddService(methodName, paras, defaultValues); + handlerSession.MetaData.AddService(methodName, paras, defaultValues, newDel); } } } - public static void bindService(string sessionID) where T : new() - { - bindService(sessionID, () => new T()); - } - public static void bindService() where T : new() - { - bindService(Handler.DefaultSessionId()); - } } } \ No newline at end of file diff --git a/Json-Rpc/packages.config b/Json-Rpc/packages.config deleted file mode 100644 index 79f852f..0000000 --- a/Json-Rpc/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/README.md b/README.md index 5ba55be..7847e23 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,37 @@ + ![Screenshot](http://i.imgur.com/rxHaXLb.png) json-rpc.net ============ -.Net [![astn-jsonrpc MyGet Build Status](https://www.myget.org/BuildSource/Badge/astn-jsonrpc?identifier=e8ccb637-ccd4-4940-b62c-bc1283cd9ddc)](https://www.myget.org/feed/Activity/astn-jsonrpc) Mono [![Build Status](https://travis-ci.org/Astn/JSON-RPC.NET.svg?branch=master)](https://travis-ci.org/Astn/JSON-RPC.NET) +![Build Master](https://github.com/Astn/JSON-RPC.NET/workflows/Build%20Master/badge.svg) ![NuGet Badge](https://buildstats.info/nuget/AustinHarris.JsonRpc) JSON-RPC.Net is a high performance Json-Rpc 2.0 server, leveraging the popular JSON.NET library. Host in ASP.NET, also supports sockets and pipes, oh my! +## Performance + +These are results from running the TestServer_Console project. + +##### Xeon E-2176M @ 2.70GHz 64.0 GB (Date: Thu Apr 30 17:34:22 2020 -0600) + +``` +Starting benchmark +processed 50 rpc in 137ms for 364.96 rpc/sec +processed 100 rpc in 0ms for ∞ rpc/sec +processed 300 rpc in 1ms for 300,000.00 rpc/sec +processed 1,200 rpc in 7ms for 171,428.57 rpc/sec +processed 6,000 rpc in 26ms for 230,769.23 rpc/sec +processed 36,000 rpc in 166ms for 216,867.47 rpc/sec +processed 252,000 rpc in 1,121ms for 224,799.29 rpc/sec +Finished benchmark... +``` + +## Do you like this? + +[![https://www.buymeacoffee.com/Ekati](https://cdn.buymeacoffee.com/buttons/default-blue.png)](https://www.buymeacoffee.com/Ekati) + + ##### Requirements -* dotnet 4.0 or mono +* dotnet-standard (dotnet core | mono | .net framework) ##### License JSON-RPC.net is licensed under The MIT License (MIT), check the [LICENSE](https://github.com/CoiniumServ/JSON-RPC.NET/blob/master/LICENSE) file for details. @@ -28,9 +52,10 @@ To install JSON-RPC.NET AspNet, run the following command in the Package Manager PM> Install-Package AustinHarris.JsonRpc.AspNet ``` -##### Performance -Under ideal conditions > 120k rpc/sec (cpu i7-2600,console server, no IO bottleneck) + + + ##### Getting Started & Documentation diff --git a/TestServer_Console/Program.cs b/TestServer_Console/Program.cs index 34f76ae..df3152f 100644 --- a/TestServer_Console/Program.cs +++ b/TestServer_Console/Program.cs @@ -5,6 +5,7 @@ using AustinHarris.JsonRpc; using System.Threading; using System.Diagnostics; +using System.Threading.Tasks; namespace TestServer_Console { @@ -16,36 +17,29 @@ class Program static void Main(string[] args) { - string input = ""; - do + PrintOptions(); + for (string line = Console.ReadLine(); line != "q"; line = Console.ReadLine()) { - input = PrintOptions(); - if (string.IsNullOrWhiteSpace(input)) + if (string.IsNullOrWhiteSpace(line)) Benchmark(); - else if (input.StartsWith("C", StringComparison.CurrentCultureIgnoreCase)) + else if (line.StartsWith("c", StringComparison.CurrentCultureIgnoreCase)) ConsoleInput(); - else - PrintOptions(); - } while (input != "x"); + PrintOptions(); + } } - private static string PrintOptions() + private static void PrintOptions() { Console.WriteLine("Hit Enter to run benchmark"); Console.WriteLine("'c' to start reading console input"); - Console.WriteLine("'x' to exit"); - return Console.ReadLine(); + Console.WriteLine("'q' to quit"); } private static void ConsoleInput() { - var rpcResultHandler = new AsyncCallback(_ => Console.WriteLine(((JsonRpcStateAsync)_).Result)); - for (string line = Console.ReadLine(); !string.IsNullOrEmpty(line); line = Console.ReadLine()) { - var async = new JsonRpcStateAsync(rpcResultHandler, null); - async.JsonRpc = line; - JsonRpcProcessor.Process(async); + JsonRpcProcessor.Process(line).ContinueWith(response => Console.WriteLine( response.Result )); } } @@ -60,41 +54,21 @@ private static void Benchmark() { cnt *= iteration; ctr = 0; + Task[] tasks = new Task[cnt]; var sw = Stopwatch.StartNew(); - AutoResetEvent are = new AutoResetEvent(false); - var rpcResultHandler = new AsyncCallback(_ => - { - if(Interlocked.Increment(ref ctr) == cnt) - { - sw.Stop(); - Console.WriteLine("processed {0} rpc in {1}ms for {2} rpc/sec",cnt,sw.ElapsedMilliseconds, (double)cnt * 1000d / sw.ElapsedMilliseconds); - are.Set(); - } - }); + var sessionid = Handler.DefaultSessionId(); for (int i = 0; i < cnt; i+=5) { - var async = new JsonRpcStateAsync(rpcResultHandler, null); - async.JsonRpc = "{'method':'add','params':[1,2],'id':1}"; - JsonRpcProcessor.Process(async); - - async = new JsonRpcStateAsync(rpcResultHandler, null); - async.JsonRpc = "{'method':'addInt','params':[1,7],'id':2}"; - JsonRpcProcessor.Process(async); - - async = new JsonRpcStateAsync(rpcResultHandler, null); - async.JsonRpc = "{'method':'NullableFloatToNullableFloat','params':[1.23],'id':3}"; - JsonRpcProcessor.Process(async); - - async = new JsonRpcStateAsync(rpcResultHandler, null); - async.JsonRpc = "{'method':'Test2','params':[3.456],'id':4}"; - JsonRpcProcessor.Process(async); - - async = new JsonRpcStateAsync(rpcResultHandler, null); - async.JsonRpc = "{'method':'StringMe','params':['Foo'],'id':5}"; - JsonRpcProcessor.Process(async); + tasks[i] = JsonRpcProcessor.Process(sessionid, "{'method':'add','params':[1,2],'id':1}"); + tasks[i+1] = JsonRpcProcessor.Process(sessionid, "{'method':'addInt','params':[1,7],'id':2}"); + tasks[i+2] = JsonRpcProcessor.Process(sessionid, "{'method':'NullableFloatToNullableFloat','params':[1.23],'id':3}"); + tasks[i+3] = JsonRpcProcessor.Process(sessionid, "{'method':'Test2','params':[3.456],'id':4}"); + tasks[i+4] = JsonRpcProcessor.Process(sessionid, "{'method':'StringMe','params':['Foo'],'id':5}"); } - are.WaitOne(); + Task.WaitAll(tasks); + sw.Stop(); + Console.WriteLine("processed {0:N0} rpc in \t {1:N0}ms for \t {2:N} rpc/sec", cnt, sw.ElapsedMilliseconds, (double)cnt * 1000d / sw.ElapsedMilliseconds); } diff --git a/TestServer_Console/Properties/AssemblyInfo.cs b/TestServer_Console/Properties/AssemblyInfo.cs deleted file mode 100644 index fdd1edf..0000000 --- a/TestServer_Console/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TestServer_Console")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("TestServer_Console")] -[assembly: AssemblyCopyright("Copyright © 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("5e7696bc-52cc-48f8-a575-40b17165ee20")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TestServer_Console/TestServer_Console.csproj b/TestServer_Console/TestServer_Console.csproj index 5e276d2..ba5f427 100644 --- a/TestServer_Console/TestServer_Console.csproj +++ b/TestServer_Console/TestServer_Console.csproj @@ -1,94 +1,24 @@ - - + + - Debug - x86 - {31AE59FC-B6F6-4AC7-A7B9-1E07630AE42B} + Austin Harris Exe - Properties - TestServer_Console - TestServer_Console - v4.0 - Client - 512 - SAK - SAK - SAK - SAK - ..\ - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - x86 - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - AnyCPU - bin\Debug\ - 4 - false - - - AnyCPU - bin\Release\ - 4 - false + netcoreapp3.1 + + - - ..\packages\Newtonsoft.Json.8.0.2\lib\net40\Newtonsoft.Json.dll - True - - - - - - - - - - ..\packages\Newtonsoft.Json.7.0.1\lib\net40\Newtonsoft.Json.dll - + + + - - - + + + - - {24FC1A2A-0BC3-43A7-9BFE-B628C2C4A307} - AustinHarris.JsonRpc - + - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + \ No newline at end of file diff --git a/TestServer_Console/packages.config b/TestServer_Console/packages.config deleted file mode 100644 index bfe088a..0000000 --- a/TestServer_Console/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/TestServer_Console/service.cs b/TestServer_Console/service.cs index 5802453..6fc2602 100644 --- a/TestServer_Console/service.cs +++ b/TestServer_Console/service.cs @@ -37,5 +37,95 @@ public string StringMe(string x) { return x; } + + [JsonRpcMethod] + private double add_1(double l, double r) + { + return l + r; + } + + [JsonRpcMethod] + private int addInt_1(int l, int r) + { + return l + r; + } + + [JsonRpcMethod] + public float? NullableFloatToNullableFloat_1(float? a) + { + return a; + } + + [JsonRpcMethod] + public decimal? Test2_1(decimal x) + { + return x; + } + + [JsonRpcMethod] + public string StringMe_1(string x) + { + return x; + } + + [JsonRpcMethod] + private double add_2(double l, double r) + { + return l + r; + } + + [JsonRpcMethod] + private int addInt_2(int l, int r) + { + return l + r; + } + + [JsonRpcMethod] + public float? NullableFloatToNullableFloat_2(float? a) + { + return a; + } + + [JsonRpcMethod] + public decimal? Test2_2(decimal x) + { + return x; + } + + [JsonRpcMethod] + public string StringMe_2(string x) + { + return x; + } + + [JsonRpcMethod] + private double add_3(double l, double r) + { + return l + r; + } + + [JsonRpcMethod] + private int addInt_3(int l, int r) + { + return l + r; + } + + [JsonRpcMethod] + public float? NullableFloatToNullableFloat_3(float? a) + { + return a; + } + + [JsonRpcMethod] + public decimal? Test2_3(decimal x) + { + return x; + } + + [JsonRpcMethod] + public string StringMe_3(string x) + { + return x; + } } }