VERY rough draft for SessionManager

This commit is contained in:
Christopher Willis-Ford 2022-04-12 14:22:05 -07:00
parent 4441a72551
commit 75f7caace5
10 changed files with 179 additions and 37 deletions

View file

@ -10,10 +10,10 @@ namespace ScratchLink;
public static class MauiProgram
{
/// <summary>
/// Build and return a MauiApp instance to host our app.
/// Create and return a MauiAppBuilder which will build an instance to host our app.
/// </summary>
/// <returns>A new instance of <see cref="MauiApp"/> configured for our app.</returns>
public static MauiApp CreateMauiApp()
public static MauiAppBuilder CreateMauiAppBuilder()
{
var builder = MauiApp.CreateBuilder();
builder
@ -23,6 +23,6 @@ public static class MauiProgram
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
return builder.Build();
return builder;
}
}

View file

@ -5,6 +5,7 @@
namespace ScratchLink;
using Foundation;
using ScratchLink.Platforms.MacCatalyst;
/// <summary>
/// The AppDelegate connects UIApplication to MauiApp on MacCatalyst.
@ -17,5 +18,10 @@ public class AppDelegate : MauiUIApplicationDelegate
/// MacCatalyst-specific configuration can go here.
/// </summary>
/// <returns>A new instance of <see cref="MauiApp"/> configured for our app.</returns>
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
protected override MauiApp CreateMauiApp()
{
var builder = MauiProgram.CreateMauiAppBuilder();
builder.Services.Add(new ServiceDescriptor(typeof(SessionManager), typeof(MacSessionManager), ServiceLifetime.Singleton));
return builder.Build();
}
}

View file

@ -0,0 +1,19 @@
// <copyright file="MacSessionManager.cs" company="Scratch Foundation">
// Copyright (c) Scratch Foundation. All rights reserved.
// </copyright>
namespace ScratchLink.Platforms.MacCatalyst;
using System.Net.WebSockets;
/// <summary>
/// Implements the Mac-specific functionality of the SessionManager.
/// </summary>
internal class MacSessionManager : SessionManager
{
/// <inheritdoc/>
protected override Session MakeNewSession(WebSocketContext webSocketContext)
{
return new Session(webSocketContext);
}
}

View file

@ -6,6 +6,8 @@
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace ScratchLink.WinUI;
using ScratchLink.Platforms.Windows;
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
@ -25,5 +27,10 @@ public partial class App : MauiWinUIApplication
/// MacCatalyst-specific configuration can go here.
/// </summary>
/// <returns>A new instance of <see cref="MauiApp"/> configured for our app.</returns>
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
protected override MauiApp CreateMauiApp()
{
var builder = MauiProgram.CreateMauiAppBuilder();
builder.Services.Add(new ServiceDescriptor(typeof(SessionManager), typeof(WindowsSessionManager), ServiceLifetime.Singleton));
return builder.Build();
}
}

View file

@ -0,0 +1,19 @@
// <copyright file="WindowsSessionManager.cs" company="Scratch Foundation">
// Copyright (c) Scratch Foundation. All rights reserved.
// </copyright>
namespace ScratchLink.Platforms.Windows;
using System.Net.WebSockets;
/// <summary>
/// Implements the Windows-specific functionality of the SessionManager.
/// </summary>
internal class WindowsSessionManager : SessionManager
{
/// <inheritdoc/>
protected override Session MakeNewSession(WebSocketContext webSocketContext)
{
return new Session(webSocketContext);
}
}

View file

@ -16,6 +16,7 @@ public partial class ScratchLinkApp : Application
{
private const int WebSocketPort = 20111;
private readonly SessionManager sessionManager;
private readonly WebSocketListener webSocketListener;
/// <summary>
@ -38,22 +39,27 @@ public partial class ScratchLinkApp : Application
return;
}
this.sessionManager = IPlatformApplication.Current.Services.GetService<SessionManager>();
this.webSocketListener = new ()
{
Handlers =
OnWebSocketConnection = (webSocketContext) =>
{
{ ServicePath.BLE, this.HandleSessionDebug },
{ ServicePath.BT, this.HandleSessionDebug },
this.sessionManager.ClientDidConnect(webSocketContext);
},
OnOtherConnection = (context) =>
{
throw new NotImplementedException();
},
};
this.webSocketListener.Listen(new[]
this.webSocketListener.Start(new[]
{
string.Format("http://127.0.0.1:{0}/", WebSocketPort),
string.Format("http://localhost:{0}/", WebSocketPort),
});
}
private void HandleSessionDebug(HttpListenerWebSocketContext context)
private void HandleSessionDebug(WebSocketContext context)
{
var origin = context.Headers.Get("origin");
var socket = context.WebSocket;
@ -63,10 +69,4 @@ public partial class ScratchLinkApp : Application
});
socket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
}
private static class ServicePath
{
public const string BLE = "/scratch/ble";
public const string BT = "/scratch/bt";
}
}

23
scratch-link/Session.cs Normal file
View file

@ -0,0 +1,23 @@
// <copyright file="Session.cs" company="Scratch Foundation">
// Copyright (c) Scratch Foundation. All rights reserved.
// </copyright>
namespace ScratchLink;
using System.Net.WebSockets;
internal class Session
{
private WebSocketContext context;
public Session(WebSocketContext context)
{
this.context = context;
var webSocket = context.WebSocket;
Task.Run(async () =>
{
await webSocket.SendString("hello", true, CancellationToken.None);
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
});
}
}

View file

@ -0,0 +1,39 @@
// <copyright file="SessionManager.cs" company="Scratch Foundation">
// Copyright (c) Scratch Foundation. All rights reserved.
// </copyright>
namespace ScratchLink;
using System.Net.WebSockets;
/// <summary>
/// This class connects a WebSocket to the appropriate session type and tracks the collection of active sessions.
/// </summary>
internal abstract class SessionManager
{
/// <summary>
/// Activated when the number of active sessions changes.
/// </summary>
public event EventHandler ActiveSessionCountChanged;
/// <summary>
/// Gets the count of active connected WebSocket sessions.
/// </summary>
public int ActiveSessionCount { get; private set; } = 0;
/// <summary>
/// Call this with a new connection context to ask the SessionManager to build and manage a session for it.
/// </summary>
/// <param name="webSocketContext">The WebSocket context which the SessionManager should adopt and connect to a session.</param>
public void ClientDidConnect(WebSocketContext webSocketContext)
{
var session = this.MakeNewSession(webSocketContext);
}
/// <summary>
/// Create a new Session object to handle a new WebSocket connection.
/// </summary>
/// <param name="webSocketContext">Create a Session to handle this connection.</param>
/// <returns>A new Session object connected to the provided context.</returns>
protected abstract Session MakeNewSession(WebSocketContext webSocketContext);
}

View file

@ -0,0 +1,33 @@
// <copyright file="WebSocketExtensions.cs" company="Scratch Foundation">
// Copyright (c) Scratch Foundation. All rights reserved.
// </copyright>
namespace ScratchLink;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading.Tasks;
/// <summary>
/// This class contains custom extensions to System.Net.WebSockets.WebSocket.
/// </summary>
internal static class WebSocketExtensions
{
/// <summary>
/// Sends a string over the WebSocket connection asynchronously.
/// </summary>
/// <param name="ws"></param>
/// <param name="message"></param>
/// <param name="endOfMessage"></param>
/// <param name="cancellationToken"></param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
public static Task SendString(this WebSocket ws, string message, bool endOfMessage, CancellationToken cancellationToken)
{
var messageBytes = Encoding.UTF8.GetBytes(message);
var buffer = new ArraySegment<byte>(messageBytes);
return ws.SendAsync(buffer, WebSocketMessageType.Text, endOfMessage, cancellationToken);
}
}

View file

@ -22,9 +22,14 @@ internal class WebSocketListener
public static bool IsSupported => HttpListener.IsSupported;
/// <summary>
/// Gets the mapping of path to handler.
/// Gets or sets the action which will be called when the listener receives a WebSocket connection.
/// </summary>
public Dictionary<string, Action<HttpListenerWebSocketContext>> Handlers { get; } = new ();
public Action<WebSocketContext> OnWebSocketConnection { get; set; }
/// <summary>
/// Gets or sets the action which will be called when the listener receives a non-WebSocket connection.
/// </summary>
public Action<HttpListenerContext> OnOtherConnection { get; set; }
/// <summary>
/// Start listening for connections. If already listening, stop and restart with the new prefix list.
@ -37,7 +42,7 @@ internal class WebSocketListener
/// https://127.0.0.1/
/// </code></example>
/// </param>
public void Listen(IEnumerable<string> prefixes)
public void Start(IEnumerable<string> prefixes)
{
if (this.listener.IsListening)
{
@ -58,32 +63,23 @@ internal class WebSocketListener
var context = await this.listener.GetContextAsync();
if (context.Request.IsWebSocketRequest)
{
this.HandleWebSocketRequest(context);
var webSocketContext = await context.AcceptWebSocketAsync(null);
this.OnWebSocketConnection(webSocketContext);
}
else
{
this.HandleOtherRequest(context);
this.OnOtherConnection(context);
}
}
});
}
private async void HandleWebSocketRequest(HttpListenerContext context)
/// <summary>
/// Stop listening for connections and terminate processing of all ongoing requests.
/// </summary>
public void Stop()
{
var webSocket = await context.AcceptWebSocketAsync(null);
var handler = this.Handlers[context.Request.Url.AbsolutePath];
if (handler != null)
{
handler(webSocket);
}
else
{
throw new NotImplementedException();
}
}
private void HandleOtherRequest(HttpListenerContext context)
{
throw new NotImplementedException();
this.cts.Cancel();
this.listener.Stop();
}
}