mirror of
https://github.com/scratchfoundation/scratch-link.git
synced 2025-08-28 22:39:42 -04:00
implement Session request handling
This commit is contained in:
parent
ac6c51474e
commit
c85b67b0e5
5 changed files with 253 additions and 46 deletions
103
scratch-link/JsonRpc/JsonRpc2Error.cs
Normal file
103
scratch-link/JsonRpc/JsonRpc2Error.cs
Normal file
|
@ -0,0 +1,103 @@
|
|||
// <copyright file="JsonRpc2Error.cs" company="Scratch Foundation">
|
||||
// Copyright (c) Scratch Foundation. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace ScratchLink.JsonRpc;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Data class representing a JSON-RPC 2.0 Error object.
|
||||
/// </summary>
|
||||
internal class JsonRpc2Error
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the numeric error code for this error.
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a string providing a short description of the error.
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional value containing additional information about the error.
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public object Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates an Error object representing a Parse Error.
|
||||
/// </summary>
|
||||
/// <param name="data">An optional value containing additional information about the error.</param>
|
||||
/// <returns>A new Error object.</returns>
|
||||
public static JsonRpc2Error ParseError(object data = null)
|
||||
{
|
||||
return new JsonRpc2Error { Code = -32700, Message = "Parse Error", Data = data };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an Error object representing an Invalid Request error.
|
||||
/// </summary>
|
||||
/// <param name="data">An optional value containing additional information about the error.</param>
|
||||
/// <returns>A new Error object.</returns>
|
||||
public static JsonRpc2Error InvalidRequest(object data = null)
|
||||
{
|
||||
return new JsonRpc2Error { Code = -32600, Message = "Invalid Request", Data = data };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an Error object representing a Method Not Found error.
|
||||
/// </summary>
|
||||
/// <param name="data">An optional value containing additional information about the error.</param>
|
||||
/// <returns>A new Error object.</returns>
|
||||
public static JsonRpc2Error MethodNotFound(object data = null)
|
||||
{
|
||||
return new JsonRpc2Error { Code = -32601, Message = "Method Not Found", Data = data };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an Error object representing an Invalid Params error.
|
||||
/// </summary>
|
||||
/// <param name="data">An optional value containing additional information about the error.</param>
|
||||
/// <returns>A new Error object.</returns>
|
||||
public static JsonRpc2Error InvalidParams(object data = null)
|
||||
{
|
||||
return new JsonRpc2Error { Code = -32602, Message = "Invalid Params", Data = data };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an Error object representing an Internal Error.
|
||||
/// </summary>
|
||||
/// <param name="data">An optional value containing additional information about the error.</param>
|
||||
/// <returns>A new Error object.</returns>
|
||||
public static JsonRpc2Error InternalError(object data = null)
|
||||
{
|
||||
return new JsonRpc2Error { Code = -32603, Message = "Internal Error", Data = data };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an Error object representing a Server Error.
|
||||
/// </summary>
|
||||
/// <param name="code">A numeric code for this error. Should be between -32000 and -32099, inclusive.</param>
|
||||
/// <param name="data">An optional value containing additional information about the error.</param>
|
||||
/// <returns>A new Error object.</returns>
|
||||
public static JsonRpc2Error ServerError(int code, object data = null)
|
||||
{
|
||||
return new JsonRpc2Error { Code = code, Message = "Server Error", Data = data };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an Error object representing an Application Error.
|
||||
/// </summary>
|
||||
/// <param name="data">An optional value containing additional information about the error.</param>
|
||||
/// <returns>A new Error object.</returns>
|
||||
public static JsonRpc2Error ApplicationError(object data = null)
|
||||
{
|
||||
return new JsonRpc2Error { Code = -32500, Message = "Application Error", Data = data };
|
||||
}
|
||||
}
|
27
scratch-link/JsonRpc/JsonRpc2Exception.cs
Normal file
27
scratch-link/JsonRpc/JsonRpc2Exception.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
// <copyright file="JsonRpc2Exception.cs" company="Scratch Foundation">
|
||||
// Copyright (c) Scratch Foundation. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace ScratchLink.JsonRpc;
|
||||
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Exception class to hold a JSON-RPC 2.0 error.
|
||||
/// </summary>
|
||||
internal class JsonRpc2Exception : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonRpc2Exception"/> class to report a <see cref="JsonRpc2Error"/>.
|
||||
/// </summary>
|
||||
/// <param name="error">The JSON-RPC error object to report.</param>
|
||||
public JsonRpc2Exception(JsonRpc2Error error)
|
||||
{
|
||||
this.Error = error;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="JsonRpc2Error"/> object associated with the thrown error.
|
||||
/// </summary>
|
||||
public JsonRpc2Error Error { get; init; }
|
||||
}
|
|
@ -1,28 +1,24 @@
|
|||
// <copyright file="Request.cs" company="Scratch Foundation">
|
||||
// <copyright file="JsonRpc2Request.cs" company="Scratch Foundation">
|
||||
// Copyright (c) Scratch Foundation. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace ScratchLink.JsonRpc;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// Data class representing a JSON-RPC 2.0 Request object.
|
||||
/// If the "id" property is null, this is a Notification object.
|
||||
/// </summary>
|
||||
internal class Request
|
||||
internal class JsonRpc2Request
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the JSON RPC version string (always "2.0").
|
||||
/// </summary>
|
||||
[JsonPropertyName("jsonrpc")]
|
||||
public string JsonRPC { get; private set; }
|
||||
[JsonPropertyOrder(-100)]
|
||||
public string JsonRPC { get; } = "2.0";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the method being called.
|
||||
|
@ -35,7 +31,7 @@ internal class Request
|
|||
/// </summary>
|
||||
[JsonPropertyName("params")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public JsonElement? Params { get; set; }
|
||||
public object Params { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the request ID. May be a string, integer, or absent.
|
||||
|
@ -43,5 +39,5 @@ internal class Request
|
|||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public JsonElement? Id { get; set; }
|
||||
public object Id { get; set; }
|
||||
}
|
44
scratch-link/JsonRpc/JsonRpc2Response.cs
Normal file
44
scratch-link/JsonRpc/JsonRpc2Response.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
// <copyright file="JsonRpc2Response.cs" company="Scratch Foundation">
|
||||
// Copyright (c) Scratch Foundation. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace ScratchLink.JsonRpc;
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Data class representing a JSON-RPC 2.0 Response object.
|
||||
/// Either "result" or "error" should be filled, not both.
|
||||
/// </summary>
|
||||
internal class JsonRpc2Response
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the JSON RPC version string (always "2.0").
|
||||
/// </summary>
|
||||
[JsonPropertyName("jsonrpc")]
|
||||
[JsonPropertyOrder(-100)]
|
||||
public string JsonRPC { get; } = "2.0";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the successful result of the corresponding Request.
|
||||
/// This is REQUIRED on success and MUST NOT exist if there was an error.
|
||||
/// </summary>
|
||||
[JsonPropertyName("result")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public object Result { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an object describing an error triggered by the corresponding Request.
|
||||
/// This is REQUIRED on error and MUST NOT exist if there was no error.
|
||||
/// </summary>
|
||||
[JsonPropertyName("error")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public JsonRpc2Error Error { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the response ID, which must match the ID of the corresponding Request. May be a string or integer.
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public JsonElement Id { get; set; }
|
||||
}
|
|
@ -4,14 +4,14 @@
|
|||
|
||||
namespace ScratchLink;
|
||||
|
||||
using ScratchLink.JsonRpc;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
using JsonRpcMethodHandler = Func<
|
||||
string, // method name
|
||||
System.Text.Json.JsonElement?, // method params / args
|
||||
Task<System.Text.Json.Nodes.JsonValue> // return value
|
||||
object, // params / args
|
||||
Task<object> // return value - must be JSON-serializable
|
||||
>;
|
||||
|
||||
/// <summary>
|
||||
|
@ -19,6 +19,12 @@ using JsonRpcMethodHandler = Func<
|
|||
/// </summary>
|
||||
internal class Session
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the Scratch Link network protocol version. Note that this is not the application version.
|
||||
/// Keep this in sync with the version number in `NetworkProtocol.md`.
|
||||
/// </summary>
|
||||
protected const string NetworkProtocolVersion = "1.2";
|
||||
|
||||
/// <summary>
|
||||
/// Stores the mapping from method names to handlers.
|
||||
/// </summary>
|
||||
|
@ -73,9 +79,63 @@ internal class Session
|
|||
this.cancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
protected Task<JsonValue> HandleGetVersion(string methodName, JsonElement? args)
|
||||
/// <summary>
|
||||
/// Handle a "getVersion" request.
|
||||
/// </summary>
|
||||
/// <param name="methodName">The name of the method called (expected: "getVersion").</param>
|
||||
/// <param name="args">Any arguments passed to the method by the caller (expected: none).</param>
|
||||
/// <returns>A string representing the protocol version.</returns>
|
||||
protected Task<object> HandleGetVersion(string methodName, object args)
|
||||
{
|
||||
return Task.FromResult(JsonValue.Create(0));
|
||||
return Task.FromResult<object>(new Dictionary<string, string>
|
||||
{
|
||||
{ "protocol", NetworkProtocolVersion },
|
||||
});
|
||||
}
|
||||
|
||||
private Task<object> HandleUnrecognizedMethod(string methodName, object args)
|
||||
{
|
||||
throw new JsonRpc2Exception(JsonRpc2Error.MethodNotFound(methodName));
|
||||
}
|
||||
|
||||
private async Task HandleRequest(JsonRpc.JsonRpc2Request request, CancellationToken cancellationToken)
|
||||
{
|
||||
var handler = this.Handlers.GetValueOrDefault(request.Method, this.HandleUnrecognizedMethod);
|
||||
|
||||
object result = null;
|
||||
JsonRpc2Error error = null;
|
||||
|
||||
try
|
||||
{
|
||||
result = await handler(request.Method, request.Params);
|
||||
}
|
||||
catch (JsonRpc2Exception e)
|
||||
{
|
||||
error = e.Error;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
error = JsonRpc2Error.ApplicationError($"Unhandled error encountered during call: {e}");
|
||||
}
|
||||
|
||||
if (request.Id is JsonElement requestId)
|
||||
{
|
||||
await this.SendResponse(requestId, result, error, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendResponse(JsonElement id, object result, JsonRpc2Error error, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = new JsonRpc2Response
|
||||
{
|
||||
Id = id,
|
||||
Result = (error == null) ? result : null,
|
||||
Error = error,
|
||||
};
|
||||
var responseBytes = JsonSerializer.SerializeToUtf8Bytes(response);
|
||||
|
||||
var webSocket = this.context.WebSocket;
|
||||
await webSocket.SendAsync(responseBytes, WebSocketMessageType.Text, true, cancellationToken);
|
||||
}
|
||||
|
||||
private async void CommLoop()
|
||||
|
@ -89,16 +149,15 @@ internal class Session
|
|||
{
|
||||
messageBuffer.SetLength(0);
|
||||
var result = await webSocket.ReceiveMessageToStream(messageBuffer, MessageSizeLimit, cancellationToken);
|
||||
if (result.CloseStatus.HasValue)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
messageBuffer.Position = 0;
|
||||
var request = JsonSerializer.Deserialize<JsonRpc.Request>(messageBuffer);
|
||||
if (request != null)
|
||||
if (messageBuffer.Length > 0)
|
||||
{
|
||||
await this.HandleRequest(request, cancellationToken);
|
||||
messageBuffer.Position = 0;
|
||||
var request = JsonSerializer.Deserialize<JsonRpc.JsonRpc2Request>(messageBuffer);
|
||||
if (request != null)
|
||||
{
|
||||
await this.HandleRequest(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,26 +171,4 @@ internal class Session
|
|||
webSocket.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleRequest(JsonRpc.Request request, CancellationToken cancellationToken)
|
||||
{
|
||||
var handler = this.Handlers[request.Method];
|
||||
/*
|
||||
try
|
||||
{
|
||||
var = (handler != null)
|
||||
if (handler != null)
|
||||
{
|
||||
var result =
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
var result = handler != null ?
|
||||
await handler(request.Method, request.Params) :
|
||||
await HandleUnrecognizedMethod(request.Method, request.Params);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue