/// <summary> /// Start the engine and use given router to handle request. /// </summary> /// <param name="router">The router which will handle the request.</param> public override void Start(IJsonRpcRouter router) { _router = router ?? throw new ArgumentNullException(nameof(router)); _stopped = false; _listener = new HttpListener(); _listener.Prefixes.Add(_prefix); _listener.UnsafeConnectionNtlmAuthentication = true; _listener.IgnoreWriteExceptions = true; _listener.Start(); Task.Factory.StartNew(async() => { while (!_stopped) { try { var context = await _listener.GetContextAsync().ConfigureAwait(false); if (context.Request.IsWebSocketRequest) { var requestPath = string.Empty; if (context.Request.Url != null) { requestPath = context.Request.Url.AbsolutePath; } var websocketContext = await context.AcceptWebSocketAsync("JsonRpcLite").ConfigureAwait(false); ProcessWebSocketAsync(requestPath, _router, websocketContext.WebSocket); } else { context.Response.StatusCode = 400; context.Response.Close(); } } catch (Exception ex) { Logger.WriteError($"GetContext error:{ex.Format()}"); } } }, TaskCreationOptions.LongRunning); Logger.WriteInfo("JsonRpc websocket server engine started."); }
/// <summary> /// Start the engine and use given router to handle request. /// </summary> /// <param name="router">The router which will handle the request.</param> public abstract void Start(IJsonRpcRouter router);
/// <summary> /// Handle connected request and return result. /// </summary> /// <param name="context">The http context to handle.</param> /// <param name="router">The router to dispatch the request data.</param> /// <param name="cancellationToken">The cancellation token which can cancel this method.</param> /// <returns>Void</returns> protected async Task HandleContextAsync(IJsonRpcHttpContext context, IJsonRpcRouter router, CancellationToken cancellationToken = default) { var httpMethod = context.GetRequestHttpMethod(); var requestPath = context.GetRequestPath(); Logger.WriteVerbose($"Handle request [{httpMethod}]: {requestPath}"); try { var serviceName = GetRpcServiceName(requestPath); if (string.IsNullOrEmpty(serviceName)) { Logger.WriteWarning($"Service for request: {requestPath} not found."); throw new HttpException((int)HttpStatusCode.ServiceUnavailable, "Service does not exist."); } if (httpMethod == "get") { var smdRequest = false; var smdIndex = serviceName.LastIndexOf(".smd", StringComparison.InvariantCultureIgnoreCase); if (smdIndex != -1) { serviceName = serviceName.Substring(0, smdIndex); smdRequest = true; } if (!router.ServiceExists(serviceName)) { Logger.WriteWarning($"Service for request: {requestPath} not found."); throw new HttpException((int)HttpStatusCode.ServiceUnavailable, $"Service [{serviceName}] does not exist."); } if (SmdEnabled && smdRequest) { try { var smdData = await router.GetServiceSmdData(serviceName); await WriteSmdDataAsync(context, smdData, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { throw new HttpException((int)HttpStatusCode.InternalServerError, ex.Message); } } else { throw new HttpException((int)HttpStatusCode.NotFound, $"Resource for {requestPath} does not exist."); } } else if (httpMethod == "post") { if (!router.ServiceExists(serviceName)) { Logger.WriteWarning($"Service for request: {requestPath} not found."); throw new ServerErrorException("Service does not exist.", $"Service [{serviceName}] does not exist."); } try { await DispatchAsync(context, router, serviceName, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { if (ex is RpcException) { throw; } throw new ServerErrorException("Internal server error.", ex.Message); } } else { throw new HttpException((int)HttpStatusCode.MethodNotAllowed, $"Invalid http-method:{httpMethod}"); } } catch (Exception ex) { Logger.WriteError($"Handle request {requestPath} error: {ex.Message}"); if (ex is HttpException httpException) { await WriteHttpExceptionAsync(context, httpException, cancellationToken).ConfigureAwait(false); } else { var response = new JsonRpcResponse(); if (ex is RpcException rpcException) { response.WriteResult(rpcException); } else { var serverError = new InternalErrorException($"Handle request {requestPath} error: {ex.Message}"); response.WriteResult(serverError); } await WriteRpcResponsesAsync(context, new[] { response }, cancellationToken).ConfigureAwait(false); } } finally { context.Close(); } }
/// <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."); } }
/// <summary> /// Process 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> private async void ProcessWebSocketAsync(string requestPath, IJsonRpcRouter router, WebSocket socket) { await HandleWebSocketAsync(requestPath, router, socket).ConfigureAwait(false); }