/// <summary> /// Invoke remote method and get the result. /// </summary> /// <typeparam name="T">The return type.</typeparam> /// <param name="serviceName">The name of the service</param> /// <param name="methodName">The method name to call.</param> /// <param name="args">The parameters of the method.</param> /// <param name="cancellationToken">The cancellation token which can cancel this method.</param> /// <returns>The result value.</returns> public async Task <T> InvokeAsync <T>(string serviceName, string methodName, object[] args, CancellationToken cancellationToken = default) { var id = Interlocked.Increment(ref _requestId); var request = new JsonRpcRequest(id, methodName, new JsonRpcRequestParameter(RequestParameterType.Object, args)); var requestData = await JsonRpcCodec.EncodeRequestsAsync(new[] { request }, cancellationToken).ConfigureAwait(false); var responseData = await ProcessAsync(serviceName, requestData, cancellationToken).ConfigureAwait(false); var responses = await JsonRpcCodec.DecodeResponsesAsync(responseData, cancellationToken).ConfigureAwait(false); if (responses.Length > 0) { var response = responses[0]; var responseId = Convert.ToInt32(response.Id); if (responseId != id) { throw new InvalidOperationException("Response id is not matched."); } if (response.Result is RpcException exception) { throw exception; } var resultString = (string)response.Result; using var utf8StringData = Utf8StringData.Get(resultString); return(await JsonSerializer.DeserializeAsync <T>(utf8StringData.Stream, JsonRpcConvertSettings.SerializerOptions, cancellationToken).ConfigureAwait(false)); } throw new InvalidOperationException("Fail to get invoke result from server."); }
/// <summary> /// Process a byte[] request which contains the json data. /// </summary> /// <param name="serviceName">The name of the service.</param> /// <param name="requestData">The request data</param> /// <param name="cancellationToken">The cancellation token which can cancel this method.</param> /// <returns>The response data.</returns> public async Task <byte[]> ProcessAsync(string serviceName, byte[] requestData, CancellationToken cancellationToken) { if (_router == null) { throw new NullReferenceException("The router is null"); } if (Logger.DebugMode) { var requestString = Encoding.UTF8.GetString(requestData); Logger.WriteDebug($"Receive request data:{requestString}"); } await using var requestStream = new MemoryStream(requestData); var requests = await JsonRpcCodec.DecodeRequestsAsync(requestStream, cancellationToken).ConfigureAwait(false); var responses = await _router.DispatchRequestsAsync(serviceName, requests, cancellationToken).ConfigureAwait(false); var responseData = await JsonRpcCodec.EncodeResponsesAsync(responses, cancellationToken).ConfigureAwait(false); if (Logger.DebugMode) { var responseString = Encoding.UTF8.GetString(responseData); Logger.WriteDebug($"Response data sent:{responseString}"); } return(responseData); }
/// <summary> /// Invoke remote method without result. /// </summary> /// <param name="serviceName">The name of the service</param> /// <param name="methodName">The method name to call.</param> /// <param name="cancellationToken">The cancellation token which can cancel this method.</param> /// <param name="args">The parameters of the method.</param> /// <returns>Void</returns> public async Task VoidInvokeAsync(string serviceName, string methodName, CancellationToken cancellationToken, params object[] args) { var id = Interlocked.Increment(ref _requestId); var request = new JsonRpcRequest(id, methodName, new JsonRpcRequestParameter(RequestParameterType.Object, args)); var requestData = await JsonRpcCodec.EncodeRequestsAsync(new[] { request }, cancellationToken).ConfigureAwait(false); var responseData = await ProcessAsync(serviceName, requestData, cancellationToken).ConfigureAwait(false); var responses = await JsonRpcCodec.DecodeResponsesAsync(responseData, cancellationToken).ConfigureAwait(false); if (responses.Length > 0) { var response = responses[0]; var responseId = Convert.ToInt32(response.Id); if (responseId != id) { throw new InvalidOperationException("Response id is not matched."); } if (response.Result is RpcException exception) { throw exception; } var resultString = (string)response.Result; if (resultString != "null") { throw new InvalidOperationException("The result from server is not [null]"); } } else { throw new InvalidOperationException("Fail to get invoke result from server."); } }
/// <summary> /// Dispatch request to specified service. /// </summary> /// <param name="context">The HttpListenerContext</param> /// <param name="router">The router to handle the rpc request</param> /// <param name="serviceName">The name of the service</param> /// <param name="cancellationToken">The cancellation token which can cancel this method</param> /// <returns>Void</returns> private async Task DispatchAsync(IJsonRpcHttpContext context, IJsonRpcRouter router, string serviceName, CancellationToken cancellationToken = default) { var dataLength = (int)context.GetRequestContentLength(); var requestData = ArrayPool <byte> .Shared.Rent(dataLength); JsonRpcRequest[] requests; try { var inputStream = context.GetInputStream(); await ReadRequestDataAsync(inputStream, requestData, dataLength, cancellationToken).ConfigureAwait(false); if (Logger.DebugMode) { var requestString = Encoding.UTF8.GetString(requestData); Logger.WriteDebug($"Receive request data:{requestString}"); } requests = await JsonRpcCodec.DecodeRequestsAsync(requestData, cancellationToken, dataLength).ConfigureAwait(false); } finally { ArrayPool <byte> .Shared.Return(requestData); } var responses = await router.DispatchRequestsAsync(serviceName, requests, cancellationToken).ConfigureAwait(false); await WriteRpcResponsesAsync(context, responses, cancellationToken).ConfigureAwait(false); }
/// <summary> /// Dispatch request to different services. /// </summary> /// <param name="service">The service to handle the context</param> /// <param name="context">The http context</param> /// <returns>Void</returns> private async Task DispatchCall(JsonRpcService service, WebSocketContext context) { var webSocket = context.WebSocket; while (true) { if (webSocket.CloseStatus != null) { throw new WebSocketException((int)webSocket.CloseStatus, webSocket.CloseStatusDescription); } JsonRpcRequest[] requests; await using (var requestStream = await GetRequestStreamAsync(webSocket).ConfigureAwait(false)) { var requestData = ArrayPool <byte> .Shared.Rent((int)requestStream.Length); try { await ReadRequestDataAsync(requestStream, requestData).ConfigureAwait(false); if (Logger.DebugMode) { var requestString = Encoding.UTF8.GetString(requestData); Logger.WriteDebug($"Receive request data:{requestString}"); } requests = await JsonRpcCodec.DecodeRequestsAsync(requestData).ConfigureAwait(false); } finally { ArrayPool <byte> .Shared.Return(requestData); } } if (requests.Length == 1) { var request = requests[0]; var response = await GetResponseAsync(service, request).ConfigureAwait(false); await WriteResponse(context, response).ConfigureAwait(false); } else { //batch call. var responseList = new List <JsonRpcResponse>(); foreach (var request in requests) { var response = await GetResponseAsync(service, request).ConfigureAwait(false); if (response != null) { responseList.Add(response); } } if (responseList.Count > 0) { await WriteResponses(context, responseList.ToArray()).ConfigureAwait(false); } } } }
/// <summary> /// Write rpc responses back to the client. /// </summary> /// <param name="context">The http context</param> /// <param name="responses">The responses to write back.</param> /// <param name="cancellationToken">The cancel token which will cancel this method.</param> /// <returns>Void</returns> private async Task WriteRpcResponsesAsync(IJsonRpcHttpContext context, JsonRpcResponse[] responses, CancellationToken cancellationToken = default) { try { var resultData = await JsonRpcCodec.EncodeResponsesAsync(responses, cancellationToken).ConfigureAwait(false); await WriteRpcResultAsync(context, resultData, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { Logger.WriteWarning($"Write rpc response back to client error:{ex}"); } }
/// <summary> /// Handle request request and get the response /// </summary> /// <param name="service">The service which will handle the request.</param> /// <param name="request">The request to handle.</param> /// <param name="cancellationToken">The cancellation token which can cancel this method.</param> /// <returns>The response for the request.</returns> private async Task <JsonRpcResponse> GetResponseAsync(IJsonRpcCallService service, JsonRpcRequest request, CancellationToken cancellationToken) { JsonRpcResponse response = null; try { var rpcCall = service[request.Method]; if (rpcCall == null) { throw new MethodNotFoundException($"Method: {request.Method} not found."); } object[] arguments; if (request.Params.Type == RequestParameterType.RawString) { var paramString = (string)request.Params.Value; arguments = await JsonRpcCodec.DecodeArgumentsAsync(paramString, rpcCall.Parameters, cancellationToken).ConfigureAwait(false); } else { if (request.Params.Value is Array array) { arguments = array.Cast <object>().ToArray(); } else { arguments = new[] { request.Params.Value }; } } //From here we got the response id. response = new JsonRpcResponse(request.Id); if (arguments.Length == rpcCall.Parameters.Count) { try { var result = await rpcCall.Call(arguments).ConfigureAwait(false); if (request.IsNotification) { return(null); } response.WriteResult(result); } catch (Exception ex) { var argumentString = new StringBuilder(); argumentString.Append(Environment.NewLine); var index = 0; foreach (var argument in arguments) { argumentString.AppendLine($"[{index}] {argument}"); } argumentString.Append(Environment.NewLine); response.WriteResult(new InternalErrorException($"Call method {rpcCall.Name} with args:{argumentString} error :{ex.Format()}")); } } else { throw new InvalidParamsException("Argument count is not matched"); } } catch (Exception ex) { response ??= new JsonRpcResponse(); if (ex is RpcException rpcException) { response.WriteResult(rpcException); } else { var serverError = new InternalErrorException($"Handle request {request} error: {ex.Format()}"); response.WriteResult(serverError); } } return(response); }
/// <summary> /// Handle request request and get the response /// </summary> /// <param name="service">The service which will handle the request.</param> /// <param name="request">The request to handle.</param> /// <returns>The response for the request.</returns> protected async Task <JsonRpcResponse> GetResponseAsync(JsonRpcService service, JsonRpcRequest request) { JsonRpcResponse response = null; try { var rpcCall = service.GetRpcCall(request.Method); if (rpcCall == null) { throw new MethodNotFoundException($"Method: {request.Method} not found."); } var arguments = await JsonRpcCodec.DecodeArgumentsAsync(request.Params, rpcCall.Parameters).ConfigureAwait(false); //From here we got the response id. response = new JsonRpcResponse(request.Id); //The parser will add context into the args, so the final count is parameter count + 1. if (arguments.Length == rpcCall.Parameters.Count) { try { var result = await rpcCall.Call(arguments).ConfigureAwait(false); if (request.IsNotification) { return(null); } response.WriteResult(result); } catch (Exception ex) { var argumentString = new StringBuilder(); argumentString.Append(Environment.NewLine); var index = 0; foreach (var argument in arguments) { argumentString.AppendLine($"[{index}] {argument}"); } argumentString.Append(Environment.NewLine); Logger.WriteError($"Call method {rpcCall.Name} with args:{argumentString} error :{ex.Format()}"); response.WriteResult(new InternalErrorException()); } } else { throw new InvalidParamsException("Argument count is not matched"); } } catch (Exception ex) { response ??= new JsonRpcResponse(); if (ex is RpcException rpcException) { Logger.WriteError($"Handle request {request} error: {rpcException.Format()}"); response.WriteResult(rpcException); } else { Logger.WriteError($"Handle request {request} error: {ex.Format()}"); var serverError = new InternalErrorException(); response.WriteResult(serverError); } } return(response); }
/// <summary> /// Write a group of JsonRpcHttpResponse to remote side. /// </summary> /// <param name="context">The context of the http.</param> /// <param name="responses">A group of JsonRpcHttpResponses to write.</param> /// <returns>Void</returns> public async Task WriteResponses(object context, JsonRpcResponse[] responses) { var resultData = await JsonRpcCodec.EncodeResponsesAsync(responses).ConfigureAwait(false); await WriteResultAsync(context, resultData).ConfigureAwait(false); }
/// <summary> /// Dispatch request to different services. /// </summary> /// <param name="service">The service to handle the context</param> /// <param name="context">The http context</param> /// <returns>Void</returns> private async Task DispatchCall(JsonRpcService service, HttpListenerContext context) { var httpMethod = context.Request.HttpMethod.ToLower(); if (httpMethod != "post") { throw new ServerErrorException("Invalid http-method.", $"Invalid http-method:{httpMethod}"); } Logger.WriteVerbose($"Handle request [{httpMethod}]: {context.Request.Url}"); var requestData = ArrayPool <byte> .Shared.Rent((int)context.Request.ContentLength64); JsonRpcRequest[] requests; try { await ReadRequestDataAsync(context.Request.InputStream, requestData).ConfigureAwait(false); context.Request.InputStream.Close(); if (Logger.DebugMode) { var requestString = Encoding.UTF8.GetString(requestData); Logger.WriteDebug($"Receive request data:{requestString}"); } requests = await JsonRpcCodec.DecodeRequestsAsync(requestData).ConfigureAwait(false); } finally { ArrayPool <byte> .Shared.Return(requestData); } if (requests.Length == 1) { var request = requests[0]; var response = await GetResponseAsync(service, request).ConfigureAwait(false); await WriteResponse(context, response).ConfigureAwait(false); } else { //batch call. var responseList = new List <JsonRpcResponse>(); foreach (var request in requests) { var response = await GetResponseAsync(service, request).ConfigureAwait(false); if (response != null) { responseList.Add(response); } } if (responseList.Count > 0) { await WriteResponses(context, responseList.ToArray()).ConfigureAwait(false); } else { await WriteResultAsync(context).ConfigureAwait(false); } } }
/// <summary> /// Handle data from websocket and return result data to remote client. /// </summary> /// <param name="requestPath">The request path from the http request.</param> /// <param name="router">The router to handle the request data.</param> /// <param name="socket">The connected websocket.</param> /// <param name="cancellationToken">The cancellation token which can cancel this method</param> protected async Task HandleWebSocketAsync(string requestPath, IJsonRpcRouter router, WebSocket socket, CancellationToken cancellationToken = default) { try { var serviceName = GetRpcServiceName(requestPath); if (string.IsNullOrEmpty(serviceName) || !router.ServiceExists(serviceName)) { Logger.WriteWarning($"Service {serviceName} does not exist."); throw new InvalidOperationException($"Service [{serviceName}] does not exist."); } byte[] receiveBuffer = null; MemoryStream inputStream = null; // While the WebSocket connection remains open run a simple loop that receives data and sends it back. while (socket.State == WebSocketState.Open) { cancellationToken.ThrowIfCancellationRequested(); receiveBuffer ??= ArrayPool <byte> .Shared.Rent(1024); inputStream ??= new MemoryStream(); var receiveResult = await socket.ReceiveAsync(receiveBuffer, cancellationToken).ConfigureAwait(false); if (receiveResult.MessageType == WebSocketMessageType.Close) { await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", cancellationToken).ConfigureAwait(false); } else if (receiveResult.MessageType == WebSocketMessageType.Text) { await socket.CloseAsync(WebSocketCloseStatus.InvalidMessageType, "Cannot accept text frame", cancellationToken).ConfigureAwait(false); } else { await inputStream.WriteAsync(receiveBuffer, 0, receiveResult.Count, cancellationToken).ConfigureAwait(false); //handle stream if (receiveResult.EndOfMessage) { //Release resources. var requestData = inputStream.ToArray(); await inputStream.DisposeAsync().ConfigureAwait(false); inputStream = null; ArrayPool <byte> .Shared.Return(receiveBuffer); receiveBuffer = null; if (Logger.DebugMode) { var requestString = Encoding.UTF8.GetString(requestData); Logger.WriteDebug($"Receive request data: {requestString}"); } var requests = await JsonRpcCodec.DecodeRequestsAsync(requestData, cancellationToken).ConfigureAwait(false); var responses = await router.DispatchRequestsAsync(serviceName, requests, cancellationToken).ConfigureAwait(false); var responseData = await JsonRpcCodec.EncodeResponsesAsync(responses, cancellationToken).ConfigureAwait(false); await socket.SendAsync(responseData, WebSocketMessageType.Binary, true, cancellationToken).ConfigureAwait(false); if (Logger.DebugMode) { var resultString = Encoding.UTF8.GetString(responseData); Logger.WriteDebug($"Response data sent:{resultString}"); } } } } } catch (Exception ex) { Logger.WriteError($"Handle request {requestPath} error: {ex.Message}"); } finally { socket.Dispose(); Logger.WriteVerbose("Remote websocket closed."); } }