public async Task <IActionResult> Pull() { // Identify calling client. If now allowed, return RPC failure. string?clientID = auth.GetClientID(Request); if (clientID == null) { return(Unauthorized()); } // Read request body (if any). We do not use a [FromBody] parameter, because // we want to explicitly use our JsonLib for deserializing (and not overwrite the // user's selected default ASP.NET Core JSON serializer) RpcCommandResult?lastResult = null; using (var reader = new StreamReader(Request.Body)) { var body = await reader.ReadToEndAsync(); if (body.Length > 0) { lastResult = JsonLib.FromJson <RpcCommandResult>(body); } } // Report result and query next method return(Ok(await RpcServerEngine.Instance.OnClientPull(clientID, lastResult))); }
/// <summary> /// Call this method after enqueuing the command to wait for the result of its execution. /// The returned task finishes when the call was either successfully executed and /// acknowledged, or failed (e.g. because of a timeout). /// The result is stored in the given command itself. If successful, the return value /// is also returned, otherwise an <see cref="RpcException"/> is thrown. /// </summary> public async Task <T> WaitForResult <T>() { try { // Wait for result until timeout await Task.WhenAny(runningTask.Task, Task.Delay(TimeoutMs ?? defaultTimeoutMs)); // Timeout? if (false == IsFinished()) { throw new RpcException(new RpcFailure(RpcFailureType.Timeout, "Timeout")); } // Failed? Then throw RPC exception var result = GetResult(); if (result.Failure is RpcFailure failure) { throw new RpcException(failure); } // Return JSON-encoded result (or null for void return type) if (result.ResultJson is string json) { return(JsonLib.FromJson <T>(json)); } else { return(default !);
public async Task <IActionResult> Push() { // Identify calling client. If now allowed, return RPC failure. string?clientID = auth.GetClientID(Request); if (clientID == null) { return(Unauthorized()); } // Read request body (if any). We do not use a [FromBody] parameter, because // we want to explicitly use our JsonLib for deserializing (and not overwrite the // user's selected default ASP.NET Core JSON serializer) try { using (var reader = new StreamReader(Request.Body)) { var body = await reader.ReadToEndAsync(); if (body.Length > 0) { // Run command and return the result var command = JsonLib.FromJson <RpcCommand>(body); return(Ok(await RpcServerEngine.Instance.OnClientPush(clientID, command, runner))); } } } catch { // Can happen when the client cancelled the request } // Command missing return(BadRequest()); }
/// <summary> /// Polls the next command to execute locally from the server. The result of the /// last executed command must be given, if there is one. The returned Task may block /// some time, because the server uses the long polling technique to reduce network traffic. /// If the server can not be reached, an exception is thrown (because the last result /// has to be transmitted again). /// </summary> private async Task <RpcCommand?> PullFromServer(RpcCommandResult?lastResult) { // Long polling. The server returns null after the long polling time. var bodyJson = lastResult != null?JsonLib.ToJson(lastResult) : null; var httpResponse = await httpPull.PostAsync(clientConfig.ServerUrl + "/pull", bodyJson != null?new StringContent(bodyJson, Encoding.UTF8, "application/json") : null); if (httpResponse.IsSuccessStatusCode) { // Last result was received. The server responded with the next command or null, // if there is currently none. if (httpResponse.Content.Headers.ContentLength > 0) { return(JsonLib.FromJson <RpcCommand>(await httpResponse.Content.ReadAsStringAsync())); } else { return(null); } } else { // Remote exception. throw new Exception("Server responded with status code " + httpResponse.StatusCode); } }
public void DequeueCommand(string clientID, ulong commandID) { lock (syncLock) { if (GetLatestFile(clientID) is FileInfo file) { var command = JsonLib.FromJson <RpcCommand>(File.ReadAllText(file.FullName)); if (command.ID == commandID) { file.Delete(); } } } }
public RpcCommand?PeekCommand(string clientID) { lock (syncLock) { if (GetLatestFile(clientID) is FileInfo file) { return(JsonLib.FromJson <RpcCommand>(File.ReadAllText(file.FullName))); } else { return(null); } } }
/// <summary> /// Sends the given command to the server now, reads the result or catches the /// exception, and sets the command's state accordingly. /// </summary> private async Task ExecuteOnServerNow(RpcCommand command) { RpcCommandResult result; var bodyJson = JsonLib.ToJson(command); try { var httpResponse = await httpPush.PostAsync(clientConfig.ServerUrl + "/push", new StringContent(bodyJson, Encoding.UTF8, "application/json")); if (httpResponse.IsSuccessStatusCode) { // Response (either success or remote failure) received. result = JsonLib.FromJson <RpcCommandResult>(await httpResponse.Content.ReadAsStringAsync()); } else { // The server did not respond with 200 (which it should do even in case of // a remote exception). So there is a communication error. result = RpcCommandResult.FromFailure(command.ID, new RpcFailure(RpcFailureType.RpcError, "Remote side problem with RPC call. HTTP status code " + (int)httpResponse.StatusCode)); } } catch { // Could not reach server. result = RpcCommandResult.FromFailure(command.ID, new RpcFailure(RpcFailureType.Timeout, "Could not reach the server")); } // When a result was received (i.e. when there was no network problem), the command is finished if (false == (result.Failure?.IsNetworkProblem == true) && command.ID == serverCache.CurrentCommand?.ID) { serverCache.FinishCurrentCommand(); } // Finish command command.Finish(result); }
/// <summary> /// Gets the decoded message parameter with the given index. /// </summary> public T GetParam <T>(int index) => JsonLib.FromJson <T>(MethodParameters[index]);