mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-07-02 10:38:16 +00:00
fix: improve port forwarding logic and error handling in RoutingFeedback session
This commit is contained in:
parent
00c7128778
commit
a18f7800d1
3 changed files with 70 additions and 90 deletions
|
|
@ -58,8 +58,12 @@ public class DebugSessionRequestHandler : WebApiBaseRequestHandler
|
|||
Debug.WebsocketSink.StartServerAndSetPort(port);
|
||||
Debug.SetWebSocketMinimumDebugLevel(Serilog.Events.LogEventLevel.Verbose);
|
||||
}
|
||||
else
|
||||
{
|
||||
port = Debug.WebsocketSink.Port;
|
||||
}
|
||||
|
||||
// Attempt to get the CS LAN IP and forward the port
|
||||
// Always ensure port forwarding is active — it may have been removed by timeout
|
||||
try
|
||||
{
|
||||
var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(
|
||||
|
|
@ -188,6 +192,7 @@ public class DebugSessionRequestHandler : WebApiBaseRequestHandler
|
|||
if (Debug.WebsocketSink.HasActiveConnections)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Debug websocket has active connections; keeping port forward");
|
||||
StartPortForwardTimeout(port, csIp);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ using Serilog.Events;
|
|||
namespace PepperDash.Essentials.Core.Web.RequestHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handles HTTP requests to start and retrieve the routing feedback WebSocket session URL.
|
||||
/// GET returns the current URL (starting the server if necessary).
|
||||
/// POST stops the server.
|
||||
/// Handles HTTP requests to start and stop the routing feedback WebSocket session.
|
||||
/// GET starts the server and returns connection URLs. POST stops the session.
|
||||
/// Automatically configures port forwarding for Crestron processors with a CS LAN adapter.
|
||||
/// </summary>
|
||||
public class RoutingFeedbackSessionRequestHandler : WebApiBaseRequestHandler
|
||||
{
|
||||
|
|
@ -20,11 +20,6 @@ public class RoutingFeedbackSessionRequestHandler : WebApiBaseRequestHandler
|
|||
private CTimer _portForwardTimeoutTimer;
|
||||
private readonly object _timerLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the singleton RoutingFeedbackWebsocket instance.
|
||||
/// </summary>
|
||||
public static RoutingFeedbackWebsocket Instance => _instance;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
|
|
@ -34,7 +29,7 @@ public class RoutingFeedbackSessionRequestHandler : WebApiBaseRequestHandler
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles GET requests: starts the routing feedback WebSocket if not running and returns the URL.
|
||||
/// Starts the routing feedback WebSocket server and returns the connection URL.
|
||||
/// </summary>
|
||||
protected override void HandleGet(HttpCwsContext context)
|
||||
{
|
||||
|
|
@ -44,7 +39,6 @@ public class RoutingFeedbackSessionRequestHandler : WebApiBaseRequestHandler
|
|||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0);
|
||||
|
||||
var port = 0;
|
||||
string csIp = null;
|
||||
|
||||
if (!_instance.IsRunning)
|
||||
{
|
||||
|
|
@ -52,8 +46,13 @@ public class RoutingFeedbackSessionRequestHandler : WebApiBaseRequestHandler
|
|||
port = new Random().Next(65335, 65434);
|
||||
_instance.StartServerAndSetPort(port);
|
||||
}
|
||||
else
|
||||
{
|
||||
port = _instance.Port;
|
||||
}
|
||||
|
||||
// Attempt to get the CS LAN IP and forward the port
|
||||
// Always ensure port forwarding is active — it may have been removed by timeout
|
||||
string csIp = null;
|
||||
try
|
||||
{
|
||||
var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(
|
||||
|
|
@ -91,10 +90,8 @@ public class RoutingFeedbackSessionRequestHandler : WebApiBaseRequestHandler
|
|||
{
|
||||
context.Response.StatusCode = 500;
|
||||
context.Response.StatusDescription = "Internal Server Error";
|
||||
context.Response.ContentType = "application/json";
|
||||
context.Response.ContentEncoding = Encoding.UTF8;
|
||||
context.Response.Write(
|
||||
JsonConvert.SerializeObject(new { error = "Failed to start routing feedback WebSocket server." }),
|
||||
JsonConvert.SerializeObject(new { error = "Failed to start routing feedback WebSocket server. Check logs for details." }),
|
||||
false);
|
||||
context.Response.End();
|
||||
return;
|
||||
|
|
@ -109,20 +106,21 @@ public class RoutingFeedbackSessionRequestHandler : WebApiBaseRequestHandler
|
|||
};
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Routing Feedback Session URL: {0}", url);
|
||||
Debug.LogMessage(LogEventLevel.Information, "Fallback Routing Feedback Session URL: {0}", data.fallbackUrl);
|
||||
if (data.fallbackUrl != null)
|
||||
Debug.LogMessage(LogEventLevel.Information, "Routing Feedback Fallback URL: {0}", data.fallbackUrl);
|
||||
|
||||
var json = JsonConvert.SerializeObject(data);
|
||||
var res = JsonConvert.SerializeObject(data);
|
||||
|
||||
context.Response.StatusCode = 200;
|
||||
context.Response.StatusDescription = "OK";
|
||||
context.Response.ContentType = "application/json";
|
||||
context.Response.ContentEncoding = Encoding.UTF8;
|
||||
context.Response.Write(json, false);
|
||||
context.Response.StatusCode = 200;
|
||||
context.Response.StatusDescription = "OK";
|
||||
context.Response.Write(res, false);
|
||||
context.Response.End();
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError(ex, "Error handling routing feedback session GET: {message}", ex.Message);
|
||||
Debug.LogMessage(LogEventLevel.Error, "Error handling routing feedback session request: {0}", e);
|
||||
context.Response.StatusCode = 500;
|
||||
context.Response.StatusDescription = "Internal Server Error";
|
||||
context.Response.End();
|
||||
|
|
@ -130,58 +128,51 @@ public class RoutingFeedbackSessionRequestHandler : WebApiBaseRequestHandler
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles POST requests: stops the routing feedback WebSocket server.
|
||||
/// Stops the routing feedback WebSocket session and removes port forwarding.
|
||||
/// </summary>
|
||||
protected override void HandlePost(HttpCwsContext context)
|
||||
{
|
||||
CancelPortForwardTimeout();
|
||||
|
||||
var port = _instance.Port;
|
||||
|
||||
_instance.StopServer();
|
||||
|
||||
// Remove port forwarding if CS LAN exists
|
||||
try
|
||||
{
|
||||
CancelPortForwardTimeout();
|
||||
var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(
|
||||
EthernetAdapterType.EthernetCSAdapter);
|
||||
var csIp = CrestronEthernetHelper.GetEthernetParameter(
|
||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId);
|
||||
|
||||
var port = _instance.Port;
|
||||
_instance.StopServer();
|
||||
var result = CrestronEthernetHelper.RemovePortForwarding(
|
||||
(ushort)port, (ushort)port, csIp,
|
||||
CrestronEthernetHelper.ePortMapTransport.TCP);
|
||||
|
||||
// Remove port forwarding if CS LAN exists
|
||||
try
|
||||
if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr)
|
||||
{
|
||||
var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(
|
||||
EthernetAdapterType.EthernetCSAdapter);
|
||||
var csIp = CrestronEthernetHelper.GetEthernetParameter(
|
||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId);
|
||||
|
||||
var result = CrestronEthernetHelper.RemovePortForwarding(
|
||||
(ushort)port, (ushort)port, csIp,
|
||||
CrestronEthernetHelper.ePortMapTransport.TCP);
|
||||
|
||||
if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding for port {0}: {1}", port, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Port forwarding for port {0} removed", port);
|
||||
}
|
||||
Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding for routing port {0}: {1}", port, result);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
else
|
||||
{
|
||||
// No CS LAN adapter
|
||||
Debug.LogMessage(LogEventLevel.Information, "Port forwarding for routing port {0} removed", port);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding: {0}", ex.Message);
|
||||
}
|
||||
|
||||
context.Response.StatusCode = 200;
|
||||
context.Response.StatusDescription = "OK";
|
||||
context.Response.End();
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// No CS LAN adapter
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError(ex, "Error handling routing feedback session POST: {message}", ex.Message);
|
||||
context.Response.StatusCode = 500;
|
||||
context.Response.StatusDescription = "Internal Server Error";
|
||||
context.Response.End();
|
||||
Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding for routing: {0}", ex.Message);
|
||||
}
|
||||
|
||||
context.Response.StatusCode = 200;
|
||||
context.Response.StatusDescription = "OK";
|
||||
context.Response.End();
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Routing Feedback WebSocket Session Stopped");
|
||||
}
|
||||
|
||||
private void StartPortForwardTimeout(int port, string csIp)
|
||||
|
|
@ -194,6 +185,7 @@ public class RoutingFeedbackSessionRequestHandler : WebApiBaseRequestHandler
|
|||
if (_instance.HasActiveConnections)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Routing feedback websocket has active connections; keeping port forward");
|
||||
StartPortForwardTimeout(port, csIp);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -211,7 +203,7 @@ public class RoutingFeedbackSessionRequestHandler : WebApiBaseRequestHandler
|
|||
}
|
||||
else
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Port forwarding for port {0} removed due to timeout", port);
|
||||
Debug.LogMessage(LogEventLevel.Information, "Port forwarding for routing port {0} removed due to timeout", port);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Timers;
|
||||
using Crestron.SimplSharp;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -12,7 +11,6 @@ using Newtonsoft.Json.Serialization;
|
|||
using PepperDash.Core;
|
||||
using Serilog.Events;
|
||||
using WebSocketSharp;
|
||||
using WebSocketSharp.Net;
|
||||
using WebSocketSharp.Server;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Web;
|
||||
|
|
@ -114,11 +112,11 @@ public class RoutingFeedbackWebsocket : IKeyed
|
|||
_httpsServer.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12;
|
||||
_httpsServer.SslConfiguration.ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
|
||||
|
||||
_httpsServer.AddWebSocketService<RoutingFeedbackClient>(_path, () => new RoutingFeedbackClient(this));
|
||||
_httpsServer.OnGet += HandleHttpGet;
|
||||
_httpsServer.AddWebSocketService<RoutingFeedbackClient>(_path);
|
||||
_httpsServer.Log.Level = LogLevel.Warn;
|
||||
_httpsServer.Start();
|
||||
|
||||
RoutingFeedbackClient.Owner = this;
|
||||
SubscribeToRoutingEvents();
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Routing Feedback WebSocket ready at {url}", this, Url);
|
||||
|
|
@ -340,23 +338,6 @@ public class RoutingFeedbackWebsocket : IKeyed
|
|||
service.Sessions.Broadcast(message);
|
||||
}
|
||||
|
||||
private void HandleHttpGet(object sender, HttpRequestEventArgs e)
|
||||
{
|
||||
var res = e.Response;
|
||||
res.ContentType = "text/html";
|
||||
res.ContentEncoding = Encoding.UTF8;
|
||||
res.StatusCode = 200;
|
||||
|
||||
const string html = @"<!DOCTYPE html>
|
||||
<html><head><title>Essentials Routing Feedback</title></head>
|
||||
<body style=""font-family:sans-serif;padding:2rem;text-align:center"">
|
||||
<h2>Certificate Accepted</h2>
|
||||
<p>You may close this tab and return to the configuration app.</p>
|
||||
</body></html>";
|
||||
|
||||
res.WriteContent(Encoding.UTF8.GetBytes(html));
|
||||
}
|
||||
|
||||
private static X509Certificate2 LoadCert(string certPath, string certPassword)
|
||||
{
|
||||
return new X509Certificate2(certPath, certPassword, X509KeyStorageFlags.EphemeralKeySet);
|
||||
|
|
@ -408,32 +389,34 @@ public class RoutingFeedbackWebsocket : IKeyed
|
|||
/// </summary>
|
||||
public class RoutingFeedbackClient : WebSocketBehavior
|
||||
{
|
||||
private readonly RoutingFeedbackWebsocket _owner;
|
||||
/// <summary>
|
||||
/// Static reference to the owning <see cref="RoutingFeedbackWebsocket"/> instance.
|
||||
/// Set before the server starts accepting connections.
|
||||
/// </summary>
|
||||
internal static RoutingFeedbackWebsocket Owner { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RoutingFeedbackClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="owner">The owning <see cref="RoutingFeedbackWebsocket"/> instance.</param>
|
||||
public RoutingFeedbackClient(RoutingFeedbackWebsocket owner)
|
||||
public RoutingFeedbackClient()
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnOpen()
|
||||
{
|
||||
base.OnOpen();
|
||||
Debug.LogMessage(LogEventLevel.Information, "Routing feedback client connected from: {url}", _owner, Context.WebSocket.Url);
|
||||
Debug.LogMessage(LogEventLevel.Information, "Routing feedback client connected from: {url}", Owner, Context.WebSocket.Url);
|
||||
|
||||
// Send full state snapshot to the newly connected client
|
||||
try
|
||||
{
|
||||
var snapshot = _owner.GetSnapshotMessage();
|
||||
var snapshot = Owner.GetSnapshotMessage();
|
||||
Send(snapshot);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError(ex, "Error sending routing snapshot to client: {message}", _owner, ex.Message);
|
||||
Debug.LogError(ex, "Error sending routing snapshot to client: {message}", Owner, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -441,13 +424,13 @@ public class RoutingFeedbackClient : WebSocketBehavior
|
|||
protected override void OnClose(CloseEventArgs e)
|
||||
{
|
||||
base.OnClose(e);
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Routing feedback client disconnected: {code} {reason}", _owner, e.Code, e.Reason);
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Routing feedback client disconnected: {code} {reason}", Owner, e.Code, e.Reason);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnError(WebSocketSharp.ErrorEventArgs e)
|
||||
{
|
||||
base.OnError(e);
|
||||
Debug.LogError(e.Exception, "Routing feedback client error: {message}", _owner, e.Message);
|
||||
Debug.LogError(e.Exception, "Routing feedback client error: {message}", Owner, e.Message);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue