SEBWIN-405: Changed implementation for sending of log events and implemented server quit event.

This commit is contained in:
Damian Büchel 2020-08-01 17:55:18 +02:00
parent 6997d3a5f5
commit 09fbc6579a
6 changed files with 104 additions and 28 deletions

View file

@ -206,7 +206,7 @@ namespace SafeExamBrowser.Client
if (Server != null) if (Server != null)
{ {
// TODO Server.TerminationRequested += Server_TerminationRequested;
} }
} }
@ -240,7 +240,7 @@ namespace SafeExamBrowser.Client
if (Server != null) if (Server != null)
{ {
// TODO Server.TerminationRequested -= Server_TerminationRequested;
} }
foreach (var activator in context.Activators.OfType<ITerminationActivator>()) foreach (var activator in context.Activators.OfType<ITerminationActivator>())
@ -551,6 +551,12 @@ namespace SafeExamBrowser.Client
shutdown.Invoke(); shutdown.Invoke();
} }
private void Server_TerminationRequested()
{
logger.Info("Attempting to shutdown as requested by the server...");
TryRequestShutdown();
}
private void Shell_QuitButtonClicked(System.ComponentModel.CancelEventArgs args) private void Shell_QuitButtonClicked(System.ComponentModel.CancelEventArgs args)
{ {
PauseActivators(); PauseActivators();

View file

@ -77,6 +77,7 @@ namespace SafeExamBrowser.Runtime.Operations
if (status == LoadStatus.Success) if (status == LoadStatus.Success)
{ {
// TODO: Why aren't the server settings and SEB mode correctly set in the exam configuration?
var serverSettings = Context.Next.Settings.Server; var serverSettings = Context.Next.Settings.Server;
Context.Next.AppConfig.ServerApi = info.Api; Context.Next.AppConfig.ServerApi = info.Api;

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Server.Contracts.Events
{
/// <summary>
/// Event handler used to indicate that a termination instruction has been detected.
/// </summary>
public delegate void TerminationRequestedEventHandler();
}

View file

@ -9,6 +9,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using SafeExamBrowser.Server.Contracts.Data; using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.Server.Contracts.Events;
using SafeExamBrowser.Settings.Server; using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server.Contracts namespace SafeExamBrowser.Server.Contracts
@ -18,6 +19,11 @@ namespace SafeExamBrowser.Server.Contracts
/// </summary> /// </summary>
public interface IServerProxy public interface IServerProxy
{ {
/// <summary>
/// Event fired when the server detects an instruction to terminate SEB.
/// </summary>
event TerminationRequestedEventHandler TerminationRequested;
/// <summary> /// <summary>
/// Attempts to initialize a connection with the server. /// Attempts to initialize a connection with the server.
/// </summary> /// </summary>

View file

@ -56,6 +56,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="Data\ConnectionInfo.cs" /> <Compile Include="Data\ConnectionInfo.cs" />
<Compile Include="Data\Exam.cs" /> <Compile Include="Data\Exam.cs" />
<Compile Include="Events\TerminationRequestedEventHandler.cs" />
<Compile Include="IServerProxy.cs" /> <Compile Include="IServerProxy.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Data\ServerResponse.cs" /> <Compile Include="Data\ServerResponse.cs" />
@ -66,5 +67,6 @@
<Name>SafeExamBrowser.Settings</Name> <Name>SafeExamBrowser.Settings</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View file

@ -14,6 +14,7 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Timers; using System.Timers;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -22,9 +23,11 @@ using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Server.Contracts; using SafeExamBrowser.Server.Contracts;
using SafeExamBrowser.Server.Contracts.Data; using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.Server.Contracts.Events;
using SafeExamBrowser.Server.Data; using SafeExamBrowser.Server.Data;
using SafeExamBrowser.Settings.Logging; using SafeExamBrowser.Settings.Logging;
using SafeExamBrowser.Settings.Server; using SafeExamBrowser.Settings.Server;
using Timer = System.Timers.Timer;
namespace SafeExamBrowser.Server namespace SafeExamBrowser.Server
{ {
@ -32,6 +35,7 @@ namespace SafeExamBrowser.Server
{ {
private ApiVersion1 api; private ApiVersion1 api;
private AppConfig appConfig; private AppConfig appConfig;
private CancellationTokenSource cancellationTokenSource;
private string connectionToken; private string connectionToken;
private string examId; private string examId;
private HttpClient httpClient; private HttpClient httpClient;
@ -40,12 +44,16 @@ namespace SafeExamBrowser.Server
private string oauth2Token; private string oauth2Token;
private int pingNumber; private int pingNumber;
private ServerSettings settings; private ServerSettings settings;
private Task task;
private Timer timer; private Timer timer;
public event TerminationRequestedEventHandler TerminationRequested;
public ServerProxy(AppConfig appConfig, ILogger logger) public ServerProxy(AppConfig appConfig, ILogger logger)
{ {
this.api = new ApiVersion1(); this.api = new ApiVersion1();
this.appConfig = appConfig; this.appConfig = appConfig;
this.cancellationTokenSource = new CancellationTokenSource();
this.httpClient = new HttpClient(); this.httpClient = new HttpClient();
this.logger = logger; this.logger = logger;
this.logContent = new ConcurrentQueue<ILogContent>(); this.logContent = new ConcurrentQueue<ILogContent>();
@ -244,6 +252,9 @@ namespace SafeExamBrowser.Server
logger.Subscribe(this); logger.Subscribe(this);
task = new Task(SendLog, cancellationTokenSource.Token);
task.Start();
timer.AutoReset = false; timer.AutoReset = false;
timer.Elapsed += Timer_Elapsed; timer.Elapsed += Timer_Elapsed;
timer.Interval = 1000; timer.Interval = 1000;
@ -253,25 +264,60 @@ namespace SafeExamBrowser.Server
public void StopConnectivity() public void StopConnectivity()
{ {
logger.Unsubscribe(this); logger.Unsubscribe(this);
cancellationTokenSource.Cancel();
task.Wait();
timer.Stop(); timer.Stop();
timer.Elapsed -= Timer_Elapsed; timer.Elapsed -= Timer_Elapsed;
} }
private void Timer_Elapsed(object sender, ElapsedEventArgs args) private void SendLog()
{ {
var authorization = ("Authorization", $"Bearer {oauth2Token}"); var authorization = ("Authorization", $"Bearer {oauth2Token}");
var contentType = "application/json;charset=UTF-8";
var token = ("SEBConnectionToken", connectionToken); var token = ("SEBConnectionToken", connectionToken);
logger.Info("Starting to send log items...");
while (!cancellationTokenSource.IsCancellationRequested)
{
try
{
if (logContent.TryDequeue(out var c) && c is ILogMessage message)
{
var json = new JObject
{
["type"] = ToLogType(message.Severity),
["timestamp"] = message.DateTime.Ticks,
["text"] = message.Message
};
var content = json.ToString();
// TODO: Why can't we send multiple log messages in one request?
var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, content, contentType, authorization, token);
}
}
catch (Exception e)
{
logger.Error("Failed to send log!", e);
}
}
logger.Info("Stopped sending log items.");
}
private void Timer_Elapsed(object sender, ElapsedEventArgs args)
{
try try
{ {
var authorization = ("Authorization", $"Bearer {oauth2Token}");
var content = $"timestamp={DateTime.Now.Ticks}&ping-number={++pingNumber}"; var content = $"timestamp={DateTime.Now.Ticks}&ping-number={++pingNumber}";
var contentType = "application/x-www-form-urlencoded"; var contentType = "application/x-www-form-urlencoded";
var token = ("SEBConnectionToken", connectionToken);
var success = TryExecute(HttpMethod.Post, api.PingEndpoint, out var response, content, contentType, authorization, token); var success = TryExecute(HttpMethod.Post, api.PingEndpoint, out var response, content, contentType, authorization, token);
if (success) if (success && TryParseInstruction(response.Content, out var instruction) && instruction == "SEB_QUIT")
{ {
// TODO: Fire event if instruction is sent via response! Task.Run(() => TerminationRequested?.Invoke());
} }
else else
{ {
@ -283,28 +329,6 @@ namespace SafeExamBrowser.Server
logger.Error("Failed to send ping!", e); logger.Error("Failed to send ping!", e);
} }
try
{
if (logContent.TryDequeue(out var c) && c is ILogMessage message)
{
var json = new JObject
{
["type"] = ToLogType(message.Severity),
["timestamp"] = message.DateTime.Ticks,
["text"] = message.Message
};
var content = json.ToString();
var contentType = "application/json;charset=UTF-8";
// TODO: Why can't we send multiple log messages in one request?
var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, content, contentType, authorization, token);
}
}
catch (Exception e)
{
logger.Error("Failed to send log!", e);
}
timer.Start(); timer.Start();
} }
@ -413,6 +437,24 @@ namespace SafeExamBrowser.Server
return exams.Any(); return exams.Any();
} }
private bool TryParseInstruction(HttpContent content, out string instruction)
{
instruction = default(string);
try
{
var json = JsonConvert.DeserializeObject(Extract(content)) as JObject;
instruction = json["instruction"].Value<string>();
}
catch (Exception e)
{
logger.Error("Failed to parse instruction!", e);
}
return instruction != default(string);
}
private bool TryParseOauth2Token(HttpContent content) private bool TryParseOauth2Token(HttpContent content)
{ {
try try
@ -461,7 +503,11 @@ namespace SafeExamBrowser.Server
try try
{ {
response = httpClient.SendAsync(request).GetAwaiter().GetResult(); response = httpClient.SendAsync(request).GetAwaiter().GetResult();
logger.Debug($"Completed request: {request.Method} '{request.RequestUri}' -> {ToString(response)}");
if (request.RequestUri.AbsolutePath != api.LogEndpoint && request.RequestUri.AbsolutePath != api.PingEndpoint)
{
logger.Debug($"Completed request: {request.Method} '{request.RequestUri}' -> {ToString(response)}");
}
} }
catch (TaskCanceledException) catch (TaskCanceledException)
{ {