mirror of
https://github.com/sbrl/Nibriboard.git
synced 2018-01-10 21:33:49 +00:00
It sure does feel strangely both good and scary to be completely refactoring so many core classes.
This commit is contained in:
parent
afb6eb8dca
commit
be258f63dc
8 changed files with 246 additions and 370 deletions
|
@ -1,33 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
using SBRL.GlidingSquirrel.Http;
|
|
||||||
using SBRL.GlidingSquirrel.Websocket;
|
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Nibriboard.Client
|
|
||||||
{
|
|
||||||
public class HttpClientSettingsHandler : WebsocketServer
|
|
||||||
{
|
|
||||||
private ClientSettings settings;
|
|
||||||
|
|
||||||
public HttpClientSettingsHandler(ClientSettings inSettings)
|
|
||||||
{
|
|
||||||
settings = inSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleRequest(string uri, HttpRequest request, HttpResponse response, HttpContext context) {
|
|
||||||
StreamWriter responseData = new StreamWriter(response.Content) { AutoFlush = true };
|
|
||||||
|
|
||||||
string settingsJson = JsonConvert.SerializeObject(settings);
|
|
||||||
response.ContentLength = settingsJson.Length;
|
|
||||||
response.Headers.Add("content-type", "application/json");
|
|
||||||
|
|
||||||
responseData.Write(settingsJson);
|
|
||||||
|
|
||||||
Log.WriteLine("[Http/ClientSettings] Sent settings");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,89 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using MimeSharp;
|
|
||||||
|
|
||||||
using SBRL.GlidingSquirrel.Websocket;
|
|
||||||
|
|
||||||
using SBRL.Utilities;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace Nibriboard.Client
|
|
||||||
{
|
|
||||||
public class HttpEmbeddedFileHandler : WebsocketsServer
|
|
||||||
{
|
|
||||||
private string filePrefix;
|
|
||||||
|
|
||||||
private Mime mimeTypeFinder = new Mime();
|
|
||||||
private Dictionary<string, string> mimeTypeOverrides = new Dictionary<string, string>() {
|
|
||||||
["application/xhtml+xml"] = "text/html",
|
|
||||||
["application/tei+xml"] = "image/x-icon"
|
|
||||||
};
|
|
||||||
|
|
||||||
private List<string> embeddedFiles = new List<string>(EmbeddedFiles.ResourceList);
|
|
||||||
|
|
||||||
public HttpEmbeddedFileHandler(string inFilePrefix)
|
|
||||||
{
|
|
||||||
filePrefix = inFilePrefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleRequest(string uri, HttpRequest request, HttpResponse response, HttpContext context) {
|
|
||||||
StreamWriter responseData = new StreamWriter(response.Content) { AutoFlush = true };
|
|
||||||
if (request.Method != HttpMethod.Get) {
|
|
||||||
response.ResponseCode = HttpResponseCode.MethodNotAllowed;
|
|
||||||
response.ContentType = "text/plain";
|
|
||||||
responseData.WriteLine("Error: That method isn't supported yet.");
|
|
||||||
logRequest(request, response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
string expandedFilePath = getEmbeddedFileReference(request.URI);
|
|
||||||
if (!embeddedFiles.Contains(expandedFilePath)) {
|
|
||||||
expandedFilePath += "index.html";
|
|
||||||
}
|
|
||||||
if (!embeddedFiles.Contains(expandedFilePath)) {
|
|
||||||
response.ResponseCode = HttpResponseCode.NotFound;
|
|
||||||
response.ContentType = "text/plain";
|
|
||||||
responseData.WriteLine("Can't find {0}.", expandedFilePath);
|
|
||||||
logRequest(request, response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
response.ContentType = getMimeType(expandedFilePath);
|
|
||||||
response.Headers.Add("content-type", response.ContentType);
|
|
||||||
|
|
||||||
byte[] embeddedFile = EmbeddedFiles.ReadAllBytes(expandedFilePath);
|
|
||||||
response.ContentLength = embeddedFile.Length;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
response.Content.Write(embeddedFile, 0, embeddedFile.Length);
|
|
||||||
}
|
|
||||||
catch(Exception error)
|
|
||||||
{
|
|
||||||
Log.WriteLine($"[Nibriboard/EmbeddedFileHandler] Error: {error.Message} Details:");
|
|
||||||
Log.WriteLine(error.ToString());
|
|
||||||
}
|
|
||||||
logRequest(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected string getEmbeddedFileReference(string uri) {
|
|
||||||
return filePrefix + "." + uri.TrimStart("/".ToCharArray()).Replace('/', '.');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected string getMimeType(string uri) {
|
|
||||||
string mimeType = mimeTypeFinder.Lookup(uri);
|
|
||||||
foreach (KeyValuePair<string, string> mimeMapping in mimeTypeOverrides) {
|
|
||||||
if (mimeType == mimeMapping.Key)
|
|
||||||
mimeType = mimeMapping.Value;
|
|
||||||
}
|
|
||||||
return mimeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void logRequest(HttpRequest request, HttpResponse response) {
|
|
||||||
Log.WriteLine("[Http/FileHandler] {0} {1} {2} {3}", response.ResponseCode.ResponseCode(), response.ContentType, request.Method.ToString().ToUpper(), request.URI);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,12 +5,13 @@ using System.Text;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
using IotWeb.Common.Http;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using SBRL.Utilities;
|
using SBRL.Utilities;
|
||||||
using Nibriboard.Client.Messages;
|
using Nibriboard.Client.Messages;
|
||||||
using Nibriboard.RippleSpace;
|
using Nibriboard.RippleSpace;
|
||||||
|
|
||||||
|
using SBRL.GlidingSquirrel.Websocket;
|
||||||
|
|
||||||
namespace Nibriboard.Client
|
namespace Nibriboard.Client
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -38,7 +39,7 @@ namespace Nibriboard.Client
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The nibri client manager
|
/// The nibri client manager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly NibriClientManager manager;
|
private readonly NibriboardApp manager;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The plane that this client is currently on.
|
/// The plane that this client is currently on.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -48,7 +49,7 @@ namespace Nibriboard.Client
|
||||||
/// The underlying websocket connection to the client.
|
/// The underlying websocket connection to the client.
|
||||||
/// Please try not to call the send method on here - use the NibriClient Send() method instead.
|
/// Please try not to call the send method on here - use the NibriClient Send() method instead.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly WebSocket client;
|
private readonly WebsocketClient connection;
|
||||||
|
|
||||||
private static readonly Dictionary<string, Type> messageEventTypes = new Dictionary<string, Type>() {
|
private static readonly Dictionary<string, Type> messageEventTypes = new Dictionary<string, Type>() {
|
||||||
["HandshakeRequest"] = typeof(HandshakeRequestMessage),
|
["HandshakeRequest"] = typeof(HandshakeRequestMessage),
|
||||||
|
@ -64,7 +65,11 @@ namespace Nibriboard.Client
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this nibri client is still connected.
|
/// Whether this nibri client is still connected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Connected = true;
|
public bool Connected {
|
||||||
|
get {
|
||||||
|
return connection.IsClosing;
|
||||||
|
}
|
||||||
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fires when this nibri client disconnects.
|
/// Fires when this nibri client disconnects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -117,41 +122,26 @@ namespace Nibriboard.Client
|
||||||
|
|
||||||
#region Core Setup & Message Routing Logic
|
#region Core Setup & Message Routing Logic
|
||||||
|
|
||||||
public NibriClient(NibriClientManager inManager, WebSocket inClient)
|
public NibriClient(NibriboardApp inManager, WebsocketClient inClient)
|
||||||
{
|
{
|
||||||
Log.WriteLine("[Nibriboard/WebSocket] New NibriClient connected with id #{0}.", Id);
|
Log.WriteLine("[Nibriboard/WebSocket] New NibriClient connected with id #{0}.", Id);
|
||||||
|
|
||||||
manager = inManager;
|
manager = inManager;
|
||||||
client = inClient;
|
connection = inClient;
|
||||||
|
|
||||||
|
// Attach a few events
|
||||||
|
connection.OnDisconnection += handleDisconnection;
|
||||||
|
connection.OnTextMessage += handleMessage;
|
||||||
|
|
||||||
client.DataReceived += async (WebSocket clientSocket, string frame) => {
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await handleMessage(frame);
|
|
||||||
}
|
|
||||||
catch (Exception error)
|
|
||||||
{
|
|
||||||
await Console.Error.WriteLineAsync(error.ToString());
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Task.Run(async () => await onMessage(frame)).Wait();
|
private async Task handleMessage(object sender, TextMessageEventArgs eventArgs)
|
||||||
};
|
|
||||||
// Store whether this NibriClient is still connected or not
|
|
||||||
client.ConnectionClosed += (WebSocket socket) => {
|
|
||||||
Connected = false;
|
|
||||||
Disconnected(this);
|
|
||||||
Log.WriteLine("[NibriClient] Client #{0} disconnected.", Id);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task handleMessage(string frame)
|
|
||||||
{
|
{
|
||||||
// Update the last time we received a message from the client
|
// Update the last time we received a message from the client
|
||||||
LastMessageTime = DateTime.Now;
|
LastMessageTime = DateTime.Now;
|
||||||
|
|
||||||
// Extract the event name from the message that the client sent.
|
// Extract the event name from the message that the client sent.
|
||||||
string eventName = JsonUtilities.DeserializeProperty<string>(frame, "Event");
|
string eventName = JsonUtilities.DeserializeProperty<string>(eventArgs.Payload, "Event");
|
||||||
|
|
||||||
if(eventName == null) {
|
if(eventName == null) {
|
||||||
Log.WriteLine("[NibriClient#{0}] Received message that didn't have an event.", Id);
|
Log.WriteLine("[NibriClient#{0}] Received message that didn't have an event.", Id);
|
||||||
|
@ -170,7 +160,7 @@ namespace Nibriboard.Client
|
||||||
Type jsonNet = typeof(JsonConvert);
|
Type jsonNet = typeof(JsonConvert);
|
||||||
MethodInfo deserialiserInfo = jsonNet.GetMethods().First(method => method.Name == "DeserializeObject" && method.IsGenericMethod);
|
MethodInfo deserialiserInfo = jsonNet.GetMethods().First(method => method.Name == "DeserializeObject" && method.IsGenericMethod);
|
||||||
MethodInfo genericInfo = deserialiserInfo.MakeGenericMethod(messageType);
|
MethodInfo genericInfo = deserialiserInfo.MakeGenericMethod(messageType);
|
||||||
var decodedMessage = genericInfo.Invoke(null, new object[] { frame });
|
var decodedMessage = genericInfo.Invoke(null, new object[] { eventArgs.Payload });
|
||||||
|
|
||||||
string handlerMethodName = "handle" + decodedMessage.GetType().Name;
|
string handlerMethodName = "handle" + decodedMessage.GetType().Name;
|
||||||
Type clientType = this.GetType();
|
Type clientType = this.GetType();
|
||||||
|
@ -180,11 +170,19 @@ namespace Nibriboard.Client
|
||||||
catch(Exception error)
|
catch(Exception error)
|
||||||
{
|
{
|
||||||
Log.WriteLine("[NibriClient#{0}] Error decoding and / or handling message.", Id);
|
Log.WriteLine("[NibriClient#{0}] Error decoding and / or handling message.", Id);
|
||||||
Log.WriteLine("[NibriClient#{0}] Raw frame content: {1}", Id, frame);
|
Log.WriteLine("[NibriClient#{0}] Raw frame content: {1}", Id, eventArgs.Payload);
|
||||||
Log.WriteLine("[NibriClient#{0}] Exception details: {1}", Id, error);
|
Log.WriteLine("[NibriClient#{0}] Exception details: {1}", Id, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Task handleDisconnection(object sender, ClientDisconnectedEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
Disconnected?.Invoke(this);
|
||||||
|
Log.WriteLine("[NibriClient] Client #{0} disconnected.", Id);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
@ -222,7 +220,7 @@ namespace Nibriboard.Client
|
||||||
|
|
||||||
Log.WriteLine("[NibriClient/#{0}] Sending message with length {1}.", Id, message.Length);
|
Log.WriteLine("[NibriClient/#{0}] Sending message with length {1}.", Id, message.Length);
|
||||||
|
|
||||||
client.Send(message);
|
connection.Send(message);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,7 +237,7 @@ namespace Nibriboard.Client
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Closes the connection to the client gracefully.
|
/// Closes the connection to the client gracefully.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void CloseConnection(Message lastMessage)
|
public async Task CloseConnection(Message lastMessage)
|
||||||
{
|
{
|
||||||
if (!Connected)
|
if (!Connected)
|
||||||
return;
|
return;
|
||||||
|
@ -247,7 +245,7 @@ namespace Nibriboard.Client
|
||||||
// Tell the client that we're shutting down
|
// Tell the client that we're shutting down
|
||||||
Send(lastMessage);
|
Send(lastMessage);
|
||||||
|
|
||||||
client.Close();
|
await connection.Close(WebsocketCloseReason.Normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -514,7 +512,7 @@ namespace Nibriboard.Client
|
||||||
protected ClientStatesMessage GenerateClientStateUpdate()
|
protected ClientStatesMessage GenerateClientStateUpdate()
|
||||||
{
|
{
|
||||||
ClientStatesMessage result = new ClientStatesMessage();
|
ClientStatesMessage result = new ClientStatesMessage();
|
||||||
foreach (NibriClient otherClient in manager.Clients)
|
foreach (NibriClient otherClient in manager.NibriClients)
|
||||||
{
|
{
|
||||||
// Don't include ourselves in the update message!
|
// Don't include ourselves in the update message!
|
||||||
if (otherClient == this)
|
if (otherClient == this)
|
||||||
|
|
|
@ -1,177 +0,0 @@
|
||||||
using System;
|
|
||||||
using IotWeb.Common.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using Nibriboard.Client.Messages;
|
|
||||||
using System.Threading;
|
|
||||||
using Nibriboard.RippleSpace;
|
|
||||||
|
|
||||||
namespace Nibriboard.Client
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Manages a group of <see cref="Nibriboard.Client.NibriClient"/>s.
|
|
||||||
/// </summary>
|
|
||||||
public class NibriClientManager : IWebSocketRequestHandler
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The ripple space manager that this client manager is connected to.
|
|
||||||
/// </summary>
|
|
||||||
public RippleSpaceManager SpaceManager { get; private set; }
|
|
||||||
|
|
||||||
private ClientSettings clientSettings;
|
|
||||||
public List<NibriClient> Clients = new List<NibriClient>();
|
|
||||||
|
|
||||||
public LineIncubator LineIncubator = new LineIncubator();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The cancellation token that's used by the main server to tell us when we should shut down.
|
|
||||||
/// </summary>
|
|
||||||
protected CancellationToken canceller;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The interval at which heatbeats should be sent to the client.
|
|
||||||
/// </summary>
|
|
||||||
public readonly int HeatbeatInterval = 5000;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The number of clients currently connected to this Nibriboard.
|
|
||||||
/// </summary>
|
|
||||||
public int ClientCount {
|
|
||||||
get {
|
|
||||||
return Clients.Count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public NibriClientManager(ClientSettings inClientSettings, RippleSpaceManager inSpaceManager, CancellationToken inCancellationToken)
|
|
||||||
{
|
|
||||||
clientSettings = inClientSettings;
|
|
||||||
canceller = inCancellationToken;
|
|
||||||
|
|
||||||
SpaceManager = inSpaceManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether we will accept a given new WebSocket connection or not.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uri">The uri the user connected to.</param>
|
|
||||||
/// <param name="protocol">The protocol the user is connecting with.</param>
|
|
||||||
/// <returns>Whether we want to accept the WebSocket connection attempt or not.</returns>
|
|
||||||
public bool WillAcceptRequest(string uri, string protocol)
|
|
||||||
{
|
|
||||||
//Log.WriteLine("[Nibriboard/Websocket] Accepting new {0} connection.", protocol);
|
|
||||||
return clientSettings.WebsocketProtocol == protocol;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Handles WebSocket clients when they first connect, wrapping them in
|
|
||||||
/// a <see cref="Nibriboard.Client.NibriClient" /> instance and adding them to
|
|
||||||
/// the client list.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="newSocket">New socket.</param>
|
|
||||||
public void Connected(WebSocket newSocket)
|
|
||||||
{
|
|
||||||
NibriClient client = new NibriClient(this, newSocket);
|
|
||||||
client.Disconnected += handleDisconnection; // Clean up when the client disconnects
|
|
||||||
|
|
||||||
Clients.Add(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends a message to all the connected clients, except the one who's sending it.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sendingClient">The client sending the message.</param>
|
|
||||||
/// <param name="message">The message that is to bee sent.</param>
|
|
||||||
public void Broadcast(NibriClient sendingClient, Message message)
|
|
||||||
{
|
|
||||||
foreach(NibriClient client in Clients)
|
|
||||||
{
|
|
||||||
// Don't send the message to the sender
|
|
||||||
if (client == sendingClient)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
client.Send(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Sends a message to everyone on the same plane as the sender, except the sender themselves.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sendingClient">The sending client.</param>
|
|
||||||
/// <param name="message">The message to send.</param>
|
|
||||||
public void BroadcastPlane(NibriClient sendingClient, Message message)
|
|
||||||
{
|
|
||||||
foreach(NibriClient client in Clients)
|
|
||||||
{
|
|
||||||
// Don't send the message to the sender
|
|
||||||
if(client == sendingClient)
|
|
||||||
continue;
|
|
||||||
// Only send the message to others on the same plane
|
|
||||||
if(client.CurrentPlane != sendingClient.CurrentPlane)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
client.Send(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends a message to everyone on a specified plane.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="plane">The plane to send the message to.</param>
|
|
||||||
/// <param name="message">The message to send.</param>
|
|
||||||
public void ReflectPlane(Plane plane, Message message)
|
|
||||||
{
|
|
||||||
foreach(NibriClient client in Clients)
|
|
||||||
{
|
|
||||||
if(client.CurrentPlane != plane)
|
|
||||||
continue;
|
|
||||||
client.Send(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Periodically tidies up the client list, disconnecting old clients.
|
|
||||||
/// </summary>
|
|
||||||
private async Task ClientMaintenanceMonkey()
|
|
||||||
{
|
|
||||||
while (true) {
|
|
||||||
// Exit if we've been asked to shut down
|
|
||||||
if (canceller.IsCancellationRequested) {
|
|
||||||
close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disconnect unresponsive clients.
|
|
||||||
foreach (NibriClient client in Clients) {
|
|
||||||
// If we haven't heard from this client in a little while, send a heartbeat message
|
|
||||||
if(client.MillisecondsSinceLastMessage > HeatbeatInterval)
|
|
||||||
client.SendHeartbeat();
|
|
||||||
|
|
||||||
// If the client hasn't sent us a message in a while (even though we sent
|
|
||||||
// them a heartbeat to check on them on the last loop), disconnect them
|
|
||||||
if (client.MillisecondsSinceLastMessage > HeatbeatInterval * 2)
|
|
||||||
client.CloseConnection(new IdleDisconnectMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.Delay(HeatbeatInterval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cleans up this NibriClient manager ready for shutdown.
|
|
||||||
/// </summary>
|
|
||||||
private void close()
|
|
||||||
{
|
|
||||||
// Close the connection to all the remaining nibri clients, telling them that the server is about to shut down
|
|
||||||
foreach (NibriClient client in Clients)
|
|
||||||
client.CloseConnection(new ShutdownMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clean up after a client disconnects from the server.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="disconnectedClient">The client that has disconnected.</param>
|
|
||||||
private void handleDisconnection(NibriClient disconnectedClient)
|
|
||||||
{
|
|
||||||
Clients.Remove(disconnectedClient);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -69,11 +69,8 @@
|
||||||
<Compile Include="Utilities\EmbeddedFiles.cs" />
|
<Compile Include="Utilities\EmbeddedFiles.cs" />
|
||||||
<Compile Include="Env.cs" />
|
<Compile Include="Env.cs" />
|
||||||
<Compile Include="RippleSpace\RippleSpaceManager.cs" />
|
<Compile Include="RippleSpace\RippleSpaceManager.cs" />
|
||||||
<Compile Include="Client\HttpEmbeddedFileHandler.cs" />
|
|
||||||
<Compile Include="Client\NibriClient.cs" />
|
<Compile Include="Client\NibriClient.cs" />
|
||||||
<Compile Include="Client\NibriClientManager.cs" />
|
|
||||||
<Compile Include="Client\ClientSettings.cs" />
|
<Compile Include="Client\ClientSettings.cs" />
|
||||||
<Compile Include="Client\HttpClientSettingsHandler.cs" />
|
|
||||||
<Compile Include="Utilities\PointExtensions.cs" />
|
<Compile Include="Utilities\PointExtensions.cs" />
|
||||||
<Compile Include="Utilities\JsonUtilities.cs" />
|
<Compile Include="Utilities\JsonUtilities.cs" />
|
||||||
<Compile Include="Client\Messages\Message.cs" />
|
<Compile Include="Client\Messages\Message.cs" />
|
||||||
|
@ -109,6 +106,7 @@
|
||||||
<Compile Include="RippleSpace\PlaneInfo.cs" />
|
<Compile Include="RippleSpace\PlaneInfo.cs" />
|
||||||
<Compile Include="Utilities\BinaryIO.cs" />
|
<Compile Include="Utilities\BinaryIO.cs" />
|
||||||
<Compile Include="Client\Messages\ViewportUpdateMessage.cs" />
|
<Compile Include="Client\Messages\ViewportUpdateMessage.cs" />
|
||||||
|
<Compile Include="NibriboardApp.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="ClientFiles\index.html" />
|
<EmbeddedResource Include="ClientFiles\index.html" />
|
||||||
|
|
202
Nibriboard/NibriboardApp.cs
Normal file
202
Nibriboard/NibriboardApp.cs
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Nibriboard.Client;
|
||||||
|
using Nibriboard.Client.Messages;
|
||||||
|
using Nibriboard.RippleSpace;
|
||||||
|
using SBRL.GlidingSquirrel.Http;
|
||||||
|
using SBRL.GlidingSquirrel.Websocket;
|
||||||
|
using SBRL.Utilities;
|
||||||
|
|
||||||
|
namespace Nibriboard
|
||||||
|
{
|
||||||
|
public class NibriboardAppStartInfo
|
||||||
|
{
|
||||||
|
public string FilePrefix { get; set; }
|
||||||
|
|
||||||
|
public ClientSettings ClientSettings { get; set; }
|
||||||
|
public RippleSpaceManager SpaceManager { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NibriboardApp : WebsocketServer
|
||||||
|
{
|
||||||
|
private string filePrefix;
|
||||||
|
private List<string> embeddedFiles = new List<string>(EmbeddedFiles.ResourceList);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ripple space manager that this client manager is connected to.
|
||||||
|
/// </summary>
|
||||||
|
public RippleSpaceManager SpaceManager { get; private set; }
|
||||||
|
|
||||||
|
public LineIncubator LineIncubator = new LineIncubator();
|
||||||
|
|
||||||
|
private ClientSettings clientSettings;
|
||||||
|
|
||||||
|
public List<NibriClient> NibriClients = new List<NibriClient>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of clients currently connected to this Nibriboard.
|
||||||
|
/// </summary>
|
||||||
|
public int ClientCount {
|
||||||
|
get {
|
||||||
|
return Clients.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public NibriboardApp(NibriboardAppStartInfo startInfo, IPAddress inBindAddress, int inPort) : base(inBindAddress, inPort)
|
||||||
|
{
|
||||||
|
clientSettings = startInfo.ClientSettings;
|
||||||
|
SpaceManager = startInfo.SpaceManager;
|
||||||
|
|
||||||
|
filePrefix = startInfo.FilePrefix;
|
||||||
|
MimeTypeOverrides.Add(".ico", "image/x-icon");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override Task HandleClientConnected(object sender, ClientConnectedEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
NibriClient client = new NibriClient(this, eventArgs.ConnectingClient);
|
||||||
|
|
||||||
|
client.Disconnected += (NibriClient disconnectedClient) => NibriClients.Remove(disconnectedClient);
|
||||||
|
NibriClients.Add(client);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task HandleClientDisconnected(object sender, ClientDisconnectedEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleHttpRequest(HttpRequest request, HttpResponse response)
|
||||||
|
{
|
||||||
|
if(request.Method != HttpMethod.GET)
|
||||||
|
{
|
||||||
|
response.ResponseCode = HttpResponseCode.MethodNotAllowed;
|
||||||
|
response.ContentType = "text/plain";
|
||||||
|
await response.SetBody("Error: That method isn't supported yet.");
|
||||||
|
logRequest(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(request.Url == "/Settings.json")
|
||||||
|
{
|
||||||
|
|
||||||
|
string settingsJson = JsonConvert.SerializeObject(clientSettings);
|
||||||
|
response.ContentLength = settingsJson.Length;
|
||||||
|
response.ContentType = "application/json";
|
||||||
|
await response.SetBody(settingsJson);
|
||||||
|
|
||||||
|
Log.WriteLine("[Http/ClientSettings] Sent settings to {0}", request.ClientAddress);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string expandedFilePath = getEmbeddedFileReference(request.Url);
|
||||||
|
if(!embeddedFiles.Contains(expandedFilePath))
|
||||||
|
{
|
||||||
|
expandedFilePath += "index.html";
|
||||||
|
}
|
||||||
|
if(!embeddedFiles.Contains(expandedFilePath))
|
||||||
|
{
|
||||||
|
response.ResponseCode = HttpResponseCode.NotFound;
|
||||||
|
response.ContentType = "text/plain";
|
||||||
|
await response.SetBody($"Can't find expandedFilePath.");
|
||||||
|
logRequest(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.ContentType = LookupMimeType(expandedFilePath);
|
||||||
|
|
||||||
|
string embeddedFile = EmbeddedFiles.ReadAllText(expandedFilePath);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await response.SetBody(embeddedFile);
|
||||||
|
}
|
||||||
|
catch(Exception error)
|
||||||
|
{
|
||||||
|
Log.WriteLine($"[Nibriboard/EmbeddedFileHandler] Error: {error.Message} Details:");
|
||||||
|
Log.WriteLine(error.ToString());
|
||||||
|
}
|
||||||
|
logRequest(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Interface Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a message to all the connected clients, except the one who's sending it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sendingClient">The client sending the message.</param>
|
||||||
|
/// <param name="message">The message that is to bee sent.</param>
|
||||||
|
public void Broadcast(NibriClient sendingClient, Message message)
|
||||||
|
{
|
||||||
|
foreach(NibriClient client in NibriClients)
|
||||||
|
{
|
||||||
|
// Don't send the message to the sender
|
||||||
|
if(client == sendingClient)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
client.Send(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a message to everyone on the same plane as the sender, except the sender themselves.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sendingClient">The sending client.</param>
|
||||||
|
/// <param name="message">The message to send.</param>
|
||||||
|
public void BroadcastPlane(NibriClient sendingClient, Message message)
|
||||||
|
{
|
||||||
|
foreach(NibriClient client in NibriClients)
|
||||||
|
{
|
||||||
|
// Don't send the message to the sender
|
||||||
|
if(client == sendingClient)
|
||||||
|
continue;
|
||||||
|
// Only send the message to others on the same plane
|
||||||
|
if(client.CurrentPlane != sendingClient.CurrentPlane)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
client.Send(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a message to everyone on a specified plane.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="plane">The plane to send the message to.</param>
|
||||||
|
/// <param name="message">The message to send.</param>
|
||||||
|
public void ReflectPlane(Plane plane, Message message)
|
||||||
|
{
|
||||||
|
foreach(NibriClient client in NibriClients)
|
||||||
|
{
|
||||||
|
if(client.CurrentPlane != plane)
|
||||||
|
continue;
|
||||||
|
client.Send(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Utility Methods
|
||||||
|
|
||||||
|
protected string getEmbeddedFileReference(string uri)
|
||||||
|
{
|
||||||
|
return filePrefix + "." + uri.TrimStart("/".ToCharArray()).Replace('/', '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logRequest(HttpRequest request, HttpResponse response)
|
||||||
|
{
|
||||||
|
Log.WriteLine(
|
||||||
|
"[Http/FileHandler] {0} {1} {2} {3}",
|
||||||
|
response.ResponseCode,
|
||||||
|
response.ContentType,
|
||||||
|
request.Method,
|
||||||
|
request.Url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,15 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
|
|
||||||
using Nibriboard.RippleSpace;
|
|
||||||
using Nibriboard.Client;
|
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
|
using SBRL.GlidingSquirrel.Websocket;
|
||||||
|
|
||||||
|
using Nibriboard.RippleSpace;
|
||||||
|
using Nibriboard.Client;
|
||||||
|
|
||||||
namespace Nibriboard
|
namespace Nibriboard
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -18,14 +19,11 @@ namespace Nibriboard
|
||||||
public class NibriboardServer
|
public class NibriboardServer
|
||||||
{
|
{
|
||||||
private TcpListener commandServer;
|
private TcpListener commandServer;
|
||||||
private WebsocketsServer httpServer;
|
private NibriboardApp appServer;
|
||||||
|
|
||||||
private ClientSettings clientSettings;
|
private ClientSettings clientSettings;
|
||||||
private RippleSpaceManager planeManager;
|
private RippleSpaceManager planeManager;
|
||||||
|
|
||||||
private readonly CancellationTokenSource clientManagerCanceller = new CancellationTokenSource();
|
|
||||||
private NibriClientManager clientManager;
|
|
||||||
|
|
||||||
public readonly int CommandPort = 31587;
|
public readonly int CommandPort = 31587;
|
||||||
public readonly int Port = 31586;
|
public readonly int Port = 31586;
|
||||||
|
|
||||||
|
@ -49,37 +47,16 @@ namespace Nibriboard
|
||||||
};
|
};
|
||||||
|
|
||||||
// HTTP Server setup
|
// HTTP Server setup
|
||||||
httpServer = new HttpServer(Port);
|
appServer = new NibriboardApp(new NibriboardAppStartInfo() {
|
||||||
httpServer.AddHttpRequestHandler(
|
FilePrefix = "Nibriboard.ClientFiles",
|
||||||
"/",
|
ClientSettings = clientSettings,
|
||||||
new HttpEmbeddedFileHandler("Nibriboard.ClientFiles")
|
SpaceManager = planeManager
|
||||||
/*new HttpResourceHandler(
|
}, IPAddress.IPv6Any, Port);
|
||||||
Assembly.GetExecutingAssembly(),
|
|
||||||
"ClientFiles",
|
|
||||||
"index.html"
|
|
||||||
)*/
|
|
||||||
);
|
|
||||||
httpServer.AddHttpRequestHandler(
|
|
||||||
"/Settings.json",
|
|
||||||
new HttpClientSettingsHandler(clientSettings)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Websocket setup
|
|
||||||
clientManager = new NibriClientManager(
|
|
||||||
clientSettings,
|
|
||||||
planeManager,
|
|
||||||
clientManagerCanceller.Token
|
|
||||||
);
|
|
||||||
httpServer.AddWebSocketRequestHandler(
|
|
||||||
clientSettings.WebSocketPath,
|
|
||||||
|
|
||||||
clientManager
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Start()
|
public async Task Start()
|
||||||
{
|
{
|
||||||
httpServer.Start();
|
await appServer.Start();
|
||||||
Log.WriteLine("[NibriboardServer] Started on port {0}", Port);
|
Log.WriteLine("[NibriboardServer] Started on port {0}", Port);
|
||||||
|
|
||||||
await planeManager.StartMaintenanceMonkey();
|
await planeManager.StartMaintenanceMonkey();
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit e219ad112d256e00563eca76f13691ebe486e3bd
|
Subproject commit e59dd72dd475b685ede1a05258ea0369bdcbc8c1
|
Loading…
Reference in a new issue