Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 19 additions & 16 deletions .github/workflows/build_publish_master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,29 @@ on:
jobs:
build:

strategy:
matrix:
os: ['ubuntu-latest']
dotnet-version: ['3.1.201']
project : ['Json-Rpc']

runs-on: ${{ matrix.os }}
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
- uses: actions/checkout@v7
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: ${{matrix.dotnet-version}}
# global.json pins the SDK; the 8.0 runtime is needed for the net8.0 test target.
global-json-file: global.json
dotnet-version: |
8.0.x
10.0.x
- name: Install dependencies
run: dotnet restore
run: dotnet restore AustinHarris.JsonRpc.sln
# Building the solution packs every package project (GeneratePackageOnBuild); `dotnet pack` on the
# solution would trip NU5026 with GeneratePackageOnBuild, so the packages come from the build.
- name: Build
run: dotnet build ${{matrix.project}} --configuration Release
run: dotnet build AustinHarris.JsonRpc.sln --configuration Release --no-restore
- name: Test
run: dotnet test AustinHarris.JsonRpcTestN
# Publish
run: dotnet test AustinHarris.JsonRpcTestN --configuration Release --no-build
# Publish all four packages: the core and the three companions (Json.NET, System.Text.Json, ASP.NET Core).
- 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
run: |
for project in Json-Rpc AustinHarris.JsonRpc.Newtonsoft AustinHarris.JsonRpc.SystemTextJson AustinHarris.JsonRpc.AspNetCore; do
dotnet nuget push "$project/bin/Release/"*.nupkg --skip-duplicate --source "https://api.nuget.org/v3/index.json" --api-key ${{ secrets.NugetKey }} # API key for the NuGet feed
done
37 changes: 23 additions & 14 deletions .github/workflows/build_pull_request.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Pull Reqest
name: Pull Request
on:
pull_request:
paths-ignore:
Expand All @@ -11,25 +11,34 @@ jobs:
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
- uses: actions/checkout@v7
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: ${{matrix.dotnet-version}}
# global.json pins the SDK; the 8.0 runtime is needed for the net8.0 test target.
global-json-file: global.json
dotnet-version: |
8.0.x
10.0.x
- name: Install dependencies
run: dotnet restore
run: dotnet restore AustinHarris.JsonRpc.sln
# Building the solution packs every package project (GeneratePackageOnBuild); `dotnet pack` on the
# solution would trip NU5026 with GeneratePackageOnBuild, so the packages come from the build.
- name: Build
run: dotnet build ${{matrix.project}} --configuration Release --version-suffix ci-${{ github.run_id }}-${{ github.run_number }}
run: dotnet build AustinHarris.JsonRpc.sln --configuration Release --no-restore --version-suffix ci-${{ github.run_id }}-${{ github.run_number }}
- name: Test
run: dotnet test AustinHarris.JsonRpcTestN
# Publish
run: dotnet test AustinHarris.JsonRpcTestN --configuration Release --no-build
# Publish all four pre-release packages: the core and the three companions (Json.NET, System.Text.Json, ASP.NET Core).
# Non-fatal: the build and tests are the pull-request verdict; a rejected key (403) or a fork's missing secret
# shows as a warning here and fails loudly in the master workflow instead.
- 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

if: ${{ matrix.os == 'ubuntu-latest' }}
continue-on-error: true
run: |
for project in Json-Rpc AustinHarris.JsonRpc.Newtonsoft AustinHarris.JsonRpc.SystemTextJson AustinHarris.JsonRpc.AspNetCore; do
dotnet nuget push "$project/bin/Release/"*.nupkg --skip-duplicate --source "https://api.nuget.org/v3/index.json" --api-key ${{ secrets.NugetKey }} # API key for the NuGet feed
done
24 changes: 24 additions & 0 deletions .github/workflows/charts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Benchmark charts
# The committed charts, the explorer page and the README figures must agree with benchmarks/charts/benchmarks.json.
on:
pull_request:
paths:
- "benchmarks/charts/**"
- "README.md"
- "samples/WasmHost/README.md"
push:
branches: [ master ]
paths:
- "benchmarks/charts/**"
- "README.md"
- "samples/WasmHost/README.md"

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Renderer tests
run: python3 -m unittest benchmarks/charts/test_render.py
- name: Committed outputs and README figures match the data
run: python3 benchmarks/charts/render.py --check
40 changes: 40 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Benchmark explorer (GitHub Pages)
on:
push:
branches: [ master ]
paths:
- "benchmarks/charts/**"
- ".github/workflows/pages.yml"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: true

jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v7
# The committed outputs must match the data: a stale chart or explorer fails the deploy.
- name: Check the committed charts against the data
run: python3 benchmarks/charts/render.py --check
- name: Stage the site
run: |
mkdir -p site
cp benchmarks/charts/explorer.html site/index.html
cp benchmarks/charts/*.svg benchmarks/charts/*.json site/
- uses: actions/configure-pages@v6
- uses: actions/upload-pages-artifact@v5
with:
path: site
- id: deployment
uses: actions/deploy-pages@v5
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,9 @@ Backup*/
UpgradeLog*.XML
.nuget/NuGet.exe
.vs/

# JetBrains Rider / IntelliJ
.idea/

# Python renderer caches
__pycache__/
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Company>Austin Harris</Company>
<Authors>Austin Harris</Authors>
<Product>Json-Rpc.Net ASP.NET Core host</Product>
<Description>ASP.NET Core / Kestrel hosting for JSON-RPC.Net: MapJsonRpc endpoint (PipeReader in, BodyWriter out, no strings), a raw Kestrel ConnectionHandler for JSON-RPC over TCP, and DI registration of services.</Description>
<VersionPrefix>2.0.0</VersionPrefix>
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
<Copyright>Austin Harris</Copyright>
<PackageProjectUrl>https://github.com/Astn/JSON-RPC.NET</PackageProjectUrl>
<RepositoryUrl>https://github.com/Astn/JSON-RPC.NET</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>json-rpc;jsonrpc;json;rpc;server;aspnetcore;kestrel;pipelines</PackageTags>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Nullable>disable</Nullable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<RootNamespace>AustinHarris.JsonRpc.AspNetCore</RootNamespace>
</PropertyGroup>

<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="\" />
</ItemGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Json-Rpc\AustinHarris.JsonRpc.csproj" />
</ItemGroup>

</Project>
78 changes: 78 additions & 0 deletions AustinHarris.JsonRpc.AspNetCore/JsonRpcConnectionHandler.Async.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.Threading.Tasks;
using AustinHarris.JsonRpc.Serialization;
using Microsoft.AspNetCore.Connections;

namespace AustinHarris.JsonRpc.AspNetCore
{
public partial class JsonRpcConnectionHandler
{
private async Task RunAsynchronousMethodsAsync(ConnectionContext connection)
{
var input = connection.Transport.Input;
var output = connection.Transport.Output;
var token = connection.ConnectionClosed;
string session = _options.SessionId ?? Handler.DefaultSessionId();
// Keep result commits separate from transport flushes: a method can finish on another
// thread while an earlier reply is waiting for transport backpressure.
using var reply = new PooledByteBufferWriter();
try
{
while (true)
{
var result = await input.ReadAsync(token).ConfigureAwait(false);
var buffer = result.Buffer;
bool wrote = false;
try
{
while (JsonFramer.TryReadDocument(ref buffer, out var document))
{
token.ThrowIfCancellationRequested();
if (document.Length <= 1 && document.First.Span[0] != (byte)'{' && document.First.Span[0] != (byte)'[') continue;
if (document.Length > _options.MaxRequestBytes)
{
connection.Abort(new ConnectionAbortedException("JSON-RPC document exceeds MaxRequestBytes."));
return;
}
reply.Clear();
var pending = JsonRpcProcessor.ProcessAsync(session, document, reply, connection, _options.Serializer, token);
if (!pending.IsCompleted && wrote)
{
bool closed = false;
try
{
var flush = await output.FlushAsync(token).ConfigureAwait(false);
closed = flush.IsCompleted || flush.IsCanceled;
wrote = false;
}
finally
{
// Even a failed flush cannot release the input or reply while invocation runs.
await pending.ConfigureAwait(false);
}
if (closed) return;
}
else await pending.ConfigureAwait(false);
token.ThrowIfCancellationRequested();
if (reply.WrittenCount != 0) { reply.CopyTo(output); wrote = true; }
}
if (wrote)
{
var flush = await output.FlushAsync(token).ConfigureAwait(false);
if (flush.IsCompleted || flush.IsCanceled) return;
}
if (result.IsCompleted || result.IsCanceled) return;
if (buffer.Length > _options.MaxRequestBytes)
{
connection.Abort(new ConnectionAbortedException("JSON-RPC document exceeds MaxRequestBytes."));
return;
}
}
finally { input.AdvanceTo(buffer.Start, buffer.End); }
}
}
catch (OperationCanceledException) when (token.IsCancellationRequested) { }
catch (ConnectionAbortedException) { }
}
}
}
79 changes: 79 additions & 0 deletions AustinHarris.JsonRpc.AspNetCore/JsonRpcConnectionHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System;
using System.Buffers;
using System.Threading.Tasks;
using AustinHarris.JsonRpc.Serialization;
using Microsoft.AspNetCore.Connections;
using Microsoft.Extensions.Options;

namespace AustinHarris.JsonRpc.AspNetCore
{
/// <summary>
/// JSON-RPC over a raw Kestrel connection (TCP, Unix socket, named pipe): clients send JSON documents back to
/// back (optionally whitespace / newline separated) and receive responses in order. Wire it up with
/// <c>kestrel.ListenLocalhost(port, l => l.UseConnectionHandler&lt;JsonRpcConnectionHandler&gt;())</c>.
/// The connection's <see cref="ConnectionContext"/> is the RPC context for every call.
/// </summary>
public partial class JsonRpcConnectionHandler : ConnectionHandler
{
private readonly JsonRpcOptions _options;

public JsonRpcConnectionHandler(IOptions<JsonRpcOptions> options)
{
_options = options?.Value ?? new JsonRpcOptions();
}

/// <summary>Processes a connection using the hosting mode selected in options.</summary>
public override Task OnConnectedAsync(ConnectionContext connection)
{
return _options.EnableAsyncMethods ? RunAsynchronousMethodsAsync(connection) : RunSynchronousMethodsAsync(connection);
}

private async Task RunSynchronousMethodsAsync(ConnectionContext connection)
{
var input = connection.Transport.Input;
var output = connection.Transport.Output;
string session = _options.SessionId ?? Handler.DefaultSessionId();

while (true)
{
var result = await input.ReadAsync(connection.ConnectionClosed).ConfigureAwait(false);
var buffer = result.Buffer;
bool wrote = false;

while (JsonFramer.TryReadDocument(ref buffer, out var document))
{
// the framer hands back a one-byte slice for anything that is not '{' or '[': drop it
if (document.Length > 1 || document.First.Span[0] == (byte)'{' || document.First.Span[0] == (byte)'[')
{
if (document.Length > _options.MaxRequestBytes)
{
connection.Abort(new ConnectionAbortedException("JSON-RPC document exceeds MaxRequestBytes."));
return;
}
JsonRpcProcessor.Process(session, in document, output, connection, _options.Serializer);
wrote = true;
}
}

if (wrote)
{
var flush = await output.FlushAsync(connection.ConnectionClosed).ConfigureAwait(false);
if (flush.IsCompleted || flush.IsCanceled) break;
}

if (result.IsCompleted || result.IsCanceled)
{
input.AdvanceTo(buffer.Start, buffer.End);
break;
}
if (buffer.Length > _options.MaxRequestBytes)
{
connection.Abort(new ConnectionAbortedException("JSON-RPC document exceeds MaxRequestBytes."));
return;
}
// consumed up to the last complete document, examined everything
input.AdvanceTo(buffer.Start, buffer.End);
}
}
}
}
Loading
Loading