-
Notifications
You must be signed in to change notification settings - Fork 16
RPC Server: Advanced Usages
If you have read RPC Server: Getting Started, here you can learn some more advanced usages on server-side RPC handling.
Like "middlewares" in ASP.NET Core, in JsonRpc.Standard, it's possible to intercept the incoming JSON-RPC Request messages and customize the way they are processed. In JsonRpc.Standard, the middlewares are added by calling JsonRpcServiceHostBuilder.Intercept
. For example, if you want to log the timing of each requests and responses, you may insert a middleware before the actual method dispatching and invocation step. Here is how it's done in UnitTestProject1.Utility
public static IJsonRpcServiceHost CreateJsonRpcServiceHost(UnitTestBase owner)
{
var builder = new JsonRpcServiceHostBuilder();
builder.Register(typeof(Utility).GetTypeInfo().Assembly);
builder.ContractResolver = DefaultContractResolver;
var globalSw = Stopwatch.StartNew();
var session = new SessionFeature();
if (owner.Output != null)
{
builder.Intercept(async (context, next) =>
{
var sw = Stopwatch.StartNew();
owner.Output.WriteLine("{0}> {1}", globalSw.Elapsed, context.Request);
try
{
context.Features.Set(session);
await next();
owner.Output.WriteLine("{0}< {1}", globalSw.Elapsed, context.Response);
}
finally
{
owner.Output.WriteLine("Server: Ellapsed time: {0}", sw.Elapsed);
}
});
}
builder.LoggerFactory = owner.LoggerFactory;
return builder.Build();
}
in this xUnit
-based unit test project, I used ITestOutputHelper
provided by xUnit
to print the timings, which will give me the following output
00:00:00.3802118> {"id":"32106157#1","method":"one","jsonrpc":"2.0"}
00:00:00.8513458< {"id":"32106157#1","result":1,"jsonrpc":"2.0"}
Server: Ellapsed time: 00:00:00.4970798
00:00:00.9267219> {"id":"32106157#2","method":"one","params":{"negative":false},"jsonrpc":"2.0"}
00:00:00.9487014< {"id":"32106157#2","result":1,"jsonrpc":"2.0"}
Server: Ellapsed time: 00:00:00.0223802
00:00:00.9496177> {"id":"32106157#3","method":"one","params":{"negative":true},"jsonrpc":"2.0"}
00:00:00.9500884< {"id":"32106157#3","result":-1,"jsonrpc":"2.0"}
Server: Ellapsed time: 00:00:00.0020213
00:00:00.9521625> {"id":"32106157#4","method":"two","jsonrpc":"2.0"}
00:00:00.9572604< {"id":"32106157#4","result":2,"jsonrpc":"2.0"}
Server: Ellapsed time: 00:00:00.0055367
00:00:00.9582994> {"id":"32106157#5","method":"two","params":{"negative":false},"jsonrpc":"2.0"}
00:00:00.9586298< {"id":"32106157#5","result":2,"jsonrpc":"2.0"}
Server: Ellapsed time: 00:00:00.0005468
00:00:00.9590794> {"id":"32106157#6","method":"two","params":{"negative":true},"jsonrpc":"2.0"}
00:00:00.9593365< {"id":"32106157#6","result":-2,"jsonrpc":"2.0"}
Server: Ellapsed time: 00:00:00.0004628
For another middleware example of converting OperationCanceledException
emitted in JSON-RPC method handler into LSP-compliant RequestCancelled
error response, you may take a look at the implementation of UseCancellationHandling
of LanguageServer.VsCode/LanguageServerExtensions.cs in LanguageServer.NET.
Again, uhmm, I stole the notion of "feature" from ASP.NET Core. Since JsonRpc.Standard v0.3, you can add or consume the "features" via RequestContext.Features
. For example, considering that all the JsonRpcService
instances are transcient and used in a "throwaway" manner, if you want to implement some session-like behavior, to pass the state between multiple method-calls, you can use custom feature interfaces to achieve this.
First of all, you can add features to the feature collection of the RequestContext
in your middleware, so that you may use it in the method handler of JsonRpcService
or in the inner middleware. Again, in CreateJsonRpcServiceHost
utility function in the unit test project, I attached a SessionFeature
instance to each of the incoming requests by calling context.Features.Set(session)
. Noting the session
I passed into the feature collection is virtually the same object instance, each of the JSON-RPC call shares the same state, which allows the JSON-RPC server to remember whether the last Delay
call is successful, for unit-testing purpose.
Besides, most of the JsonRpcServerHandler
can attach a collection of default features by setting JsonRpcServerHandler.DefaultFeatures
collection. You can also attach your session instance from here.
What's more, some JsonRpcServerHandler
may attach some other features. For example, StreamRpcServerHandler
will automatically attach IRequestCancellationFeature
, if server-side cancellation is enabled, to each of the incoming requests. This will make it possible for the client to request the server to cancel another proceeding method handler. See InitializaionService.CancelRequest
of DemoLanguageServer/Services/InitializaionService.cs in LanguageServer.NET for how this cancellation is initiated in the service method handler.
Another example is that AspNetCoreRpcServerHandler
will automatically attach IAspNetCoreFeature
to every incoming JSON-RPC request over HTTP. This allows service method handler to access the HttpContext
of each request. This also allows the dependency injection of JsonRpcService
instances based on HttpContext.RequestServices
DI container.