2022-04-11 15:38:02 -07:00
|
|
|
|
// <copyright file="WebSocketListener.cs" company="Scratch Foundation">
|
|
|
|
|
// Copyright (c) Scratch Foundation. All rights reserved.
|
|
|
|
|
// </copyright>
|
|
|
|
|
|
|
|
|
|
namespace ScratchLink;
|
|
|
|
|
|
2022-07-07 17:40:49 -07:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2022-04-11 15:38:02 -07:00
|
|
|
|
using System.Net;
|
|
|
|
|
using System.Net.WebSockets;
|
2022-07-07 17:40:49 -07:00
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2022-04-11 15:38:02 -07:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Listen for WebSocket connections and direct them to service handlers.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal class WebSocketListener
|
|
|
|
|
{
|
2022-04-12 12:48:59 -07:00
|
|
|
|
private readonly HttpListener listener = new ();
|
2022-04-11 15:38:02 -07:00
|
|
|
|
|
2022-04-12 12:48:59 -07:00
|
|
|
|
private readonly CancellationTokenSource cts = new ();
|
2022-04-11 15:38:02 -07:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets a value indicating whether WebSocketListener is supported in the current environment.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static bool IsSupported => HttpListener.IsSupported;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2022-04-12 14:22:05 -07:00
|
|
|
|
/// Gets or sets the action which will be called when the listener receives a WebSocket connection.
|
2022-04-11 15:38:02 -07:00
|
|
|
|
/// </summary>
|
2022-04-12 14:22:05 -07:00
|
|
|
|
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; }
|
2022-04-11 15:38:02 -07:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Start listening for connections. If already listening, stop and restart with the new prefix list.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="prefixes">
|
|
|
|
|
/// The list of HTTP(S) URL prefixes to listen on.
|
|
|
|
|
/// Use "http" instead of "ws" or "https" instead of "wss".
|
|
|
|
|
/// <example><code>
|
|
|
|
|
/// http://locahost:1234/
|
|
|
|
|
/// https://127.0.0.1/
|
|
|
|
|
/// </code></example>
|
|
|
|
|
/// </param>
|
2022-04-12 14:22:05 -07:00
|
|
|
|
public void Start(IEnumerable<string> prefixes)
|
2022-04-11 15:38:02 -07:00
|
|
|
|
{
|
|
|
|
|
if (this.listener.IsListening)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var prefix in prefixes)
|
|
|
|
|
{
|
|
|
|
|
this.listener.Prefixes.Add(prefix);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.listener.Start();
|
|
|
|
|
Task.Run(async () =>
|
|
|
|
|
{
|
|
|
|
|
CancellationToken token = this.cts.Token;
|
|
|
|
|
while (!token.IsCancellationRequested)
|
|
|
|
|
{
|
|
|
|
|
var context = await this.listener.GetContextAsync();
|
|
|
|
|
if (context.Request.IsWebSocketRequest)
|
|
|
|
|
{
|
2022-04-12 14:22:05 -07:00
|
|
|
|
var webSocketContext = await context.AcceptWebSocketAsync(null);
|
|
|
|
|
this.OnWebSocketConnection(webSocketContext);
|
2022-04-11 15:38:02 -07:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2022-04-12 14:22:05 -07:00
|
|
|
|
this.OnOtherConnection(context);
|
2022-04-11 15:38:02 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-12 14:22:05 -07:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Stop listening for connections and terminate processing of all ongoing requests.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void Stop()
|
2022-04-11 15:38:02 -07:00
|
|
|
|
{
|
2022-04-12 14:22:05 -07:00
|
|
|
|
this.cts.Cancel();
|
|
|
|
|
this.listener.Stop();
|
2022-04-11 15:38:02 -07:00
|
|
|
|
}
|
|
|
|
|
}
|