From 590f487011dd199e9ddd679acdc6839fa9d0fb23 Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Fri, 2 Feb 2024 18:27:59 -0600 Subject: [PATCH] Rewrite HttpServer #786 * doesn't crash the QF application on errors * now has sane HTML * same no-frills design * reverted the access/name change to Session.Logon/Logout from #826; that change was a blunder (by me) --- Examples/Executor/Program.cs | 1 - QuickFIXn/HttpServer.cs | 860 ++++++++++++++--------------------- QuickFIXn/Session.cs | 9 +- RELEASE_NOTES.md | 2 +- 4 files changed, 351 insertions(+), 521 deletions(-) diff --git a/Examples/Executor/Program.cs b/Examples/Executor/Program.cs index a53555cca..1c38f7caf 100644 --- a/Examples/Executor/Program.cs +++ b/Examples/Executor/Program.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Runtime.ConstrainedExecution; using System.Text; -using Acceptor; using QuickFix; namespace Executor diff --git a/QuickFIXn/HttpServer.cs b/QuickFIXn/HttpServer.cs index edf88f947..0dc3ce3ea 100644 --- a/QuickFIXn/HttpServer.cs +++ b/QuickFIXn/HttpServer.cs @@ -1,572 +1,402 @@ #nullable enable using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; using System.Text; using System.Threading; -using System.Web; -using QuickFix; - -namespace Acceptor -{ - public class HttpServer : IDisposable - { - private readonly HttpListener _httpListener; - private readonly Thread _connectionThread; - private bool _running, _disposed; - private readonly SessionSettings _sessionSettings; - - public HttpServer(string prefix, SessionSettings settings) - { - if (!HttpListener.IsSupported) - { - // Requires at least a Windows XP with Service Pack 2 - throw new NotSupportedException( - "The Http Server cannot run on this operating system."); - } - _httpListener = new HttpListener(); - _httpListener.Prefixes.Add(prefix); - _sessionSettings = settings; - _connectionThread = new Thread(ConnectionThreadStart); - } +namespace QuickFix; + +public class HttpServer : IDisposable { + private readonly HttpListener _httpListener; + private readonly Thread _connectionThread; + private bool _running, _disposed; + private readonly SessionSettings _sessionSettings; + + private readonly IReadOnlyList _sessions; + + private static class ToggleParam { + public const string Enabled = "enabled"; + public const string SendRedundantResendRequests = "sendRedundantResendRequests"; + public const string CheckCompId = "checkCompId"; + public const string CheckLatency = "checkLatency"; + public const string ResetOnLogon = "resetOnLogon"; + public const string ResetOnLogout = "resetOnLogout"; + public const string ResetOnDisconnect = "resetOnDisconnect"; + public const string RefreshOnLogon = "refreshOnLogon"; + public const string MsInTimestamp = "msInTimestamp"; + public const string PersistMessages = "persistMessages"; + } - public void Start() - { - if (!_httpListener.IsListening) - { - _httpListener.Start(); - _running = true; - // Use a thread to listen to the Http requests - _connectionThread.Start(); - } + private static class AdjustParam { + public const string MaxLatency = "maxLatency"; + public const string LogonTimeout = "logonTimeout"; + public const string LogoutTimeout = "logoutTimeout"; + + public const string Minus10 = "minus10"; + public const string Minus1 = "minus1"; + public const string Plus1 = "plus1"; + public const string Plus10 = "plus10"; + + public static int ToInt(string adjustmentSymbol) { + return adjustmentSymbol switch + { + Minus10 => -10, + Minus1 => -1, + Plus1 => 1, + Plus10 => 10, + _ => throw new ApplicationException($"I don't recognize adjustment symbol {adjustmentSymbol}") + }; } + } - public void Stop() - { - if (_httpListener.IsListening) - { - _running = false; - _httpListener.Stop(); - } + public HttpServer(string prefix, SessionSettings sessionSettings) { + if (!HttpListener.IsSupported) { + // Requires at least a Windows XP with Service Pack 2 + throw new NotSupportedException( + "The Http Server cannot run on this operating system."); } - private void ConnectionThreadStart() - { - try - { - while (_running) - { - // Grab the context and pass it to the processor methods to handle it - HttpListenerContext context = _httpListener.GetContext(); - HttpListenerRequest request = context.Request; - HttpListenerResponse response = context.Response; - - StringBuilder pageBuilder = new StringBuilder("
QuickFIX Engine Web Interface

QuickFIX Engine Web Interface

"); - pageBuilder.Append($"
[HOME] [RELOAD]

"); - - var responseString = request.Url?.AbsolutePath switch - { - "/" => ProcessRoot(request, pageBuilder), - "/session" => SessionDetails(request, pageBuilder), - "/resetSession" => ResetSession(request, pageBuilder), - "/resetSessions" => ResetSessions(request, pageBuilder), - "/refreshSession" => RefreshSession(request, pageBuilder), - "/refreshSessions" => RefreshSessions(request, pageBuilder), - "/enableSessions" => EnableSessions(request, pageBuilder), - "/disableSessions" => DisableSessions(request, pageBuilder), - _ => ProcessRoot(request, pageBuilder) - }; - - byte[] buffer = Encoding.UTF8.GetBytes(responseString); - response.ContentLength64 = buffer.Length; - Stream output = response.OutputStream; - output.Write(buffer, 0, buffer.Length); - output.Close(); - } - } - catch (HttpListenerException) - { - Console.WriteLine("HTTP server was shut down."); - } + _httpListener = new HttpListener(); + _httpListener.Prefixes.Add(prefix); + _sessionSettings = sessionSettings; + + List sessionsList = new(); + foreach (SessionID sessionId in sessionSettings.GetSessions().OrderBy(x => x.ToString())) { + Session session = Session.LookupSession(sessionId)!; // cast away the nullable status + sessionsList.Add(session); } - private string DisableSessions(HttpListenerRequest request, StringBuilder pageBuilder) - { - bool confirm = false; + _sessions = sessionsList.AsReadOnly(); - if (request.QueryString["confirm"] is not null) - { - if (Convert.ToInt16(request.QueryString["confirm"]) != 0) - { - confirm = true; - foreach (SessionID sessionId in _sessionSettings.GetSessions()) - { - Session.LookupSession(sessionId)?.Logout(); - } - } - } + _connectionThread = new Thread(ConnectionThreadStart); + } - if (confirm) - { - pageBuilder = new StringBuilder(); - pageBuilder.Append("
QuickFIX Engine Web Interface

QuickFIX Engine Web Interface

"); - pageBuilder.Append("

Sessions have been disabled

"); - } - else - { - pageBuilder.Append("

Are you sure you want to disable all sessions ?

"); - pageBuilder.Append($"
[YES, disable sessions] [NO, do not disable sessions]
"); - } - return pageBuilder.ToString(); + public void Start() { + if (!_httpListener.IsListening) { + _httpListener.Start(); + _running = true; + // Use a thread to listen to the Http requests + _connectionThread.Start(); } + } - private string EnableSessions(HttpListenerRequest request, StringBuilder pageBuilder) - { - bool confirm = false; + public void Stop() { + if (_httpListener.IsListening) { + _running = false; + _httpListener.Stop(); + } + } - if (request.QueryString["confirm"] is not null) - { - if (Convert.ToInt16(request.QueryString["confirm"]) != 0) - { - confirm = true; - foreach (SessionID sessionId in _sessionSettings.GetSessions()) - { - Session.LookupSession(sessionId)?.SetLogonState(); + private void ConnectionThreadStart() { + try { + while (_running) { + HttpListenerContext context = _httpListener.GetContext(); + HttpListenerRequest request = context.Request; + HttpListenerResponse response = context.Response; + + StringBuilder sb = new StringBuilder(); + sb.AppendLine(""); + sb.AppendLine(" "); + sb.AppendLine(" QuickFIX/n Engine Simple Web Interface"); + sb.AppendLine(); + sb.AppendLine(" "); + sb.AppendLine(); + sb.AppendLine(" "); + sb.AppendLine("

QuickFIX/n Engine Simple Web Interface

"); + sb.AppendLine("
"); + sb.AppendLine(); + + try { + switch (request.Url?.AbsolutePath) { + case "/": + RenderRoot(sb); + break; + case "/session": + RenderSession(request, sb); + break; + case "/resetAll": + foreach (Session sess in _sessions) + sess.Reset("Reset all sessions from web interface"); + response.Redirect("/"); + response.Close(); + continue; + case "/refreshAll": + foreach (Session sess in _sessions) + sess.Refresh(); + response.Redirect("/"); + response.Close(); + continue; + case "/enableAll": + foreach (Session sess in _sessions) + sess.Logon(); + response.Redirect("/"); + response.Close(); + continue; + case "/disableAll": + foreach (Session sess in _sessions) + sess.Logout(); + response.Redirect("/"); + response.Close(); + continue; + case "/resetSession": + ResetSessionAndRedirect(request, response); + continue; + case "/refreshSession": + RefreshSessionAndRedirect(request, response); + continue; + + default: + throw new ApplicationException("unrecognized url"); } + } catch (Exception ex) { + sb.AppendLine("

An error occurred:

"); + sb.AppendLine($"

{ex.Message}

"); + sb.AppendLine("
Full exception info:
"); + sb.AppendLine($"
{ex}
"); } - } - if (confirm) - { - pageBuilder = new StringBuilder(); - pageBuilder.Append("
QuickFIX Engine Web Interface

QuickFIX Engine Web Interface

"); - pageBuilder.Append("

Sessions have been enabled

"); - } - else - { - pageBuilder.Append("

Are you sure you want to enable all sessions ?

"); - pageBuilder.Append($"
[YES, enable sessions] [NO, do not enable sessions]
"); - } - return pageBuilder.ToString(); - } - - private string RefreshSession(HttpListenerRequest request, StringBuilder pageBuilder) - { - SessionID sessionId = new SessionID( - request.QueryString["beginstring"] ?? "", - request.QueryString["sendercompid"] ?? "", - request.QueryString["targetcompid"] ?? ""); - Session? sessionDetails = Session.LookupSession(sessionId); - if (sessionDetails == null) throw new Exception("Session not found"); - bool confirm = false; - string urlOriginalString = request.Url!.OriginalString; + sb.AppendLine(" "); + sb.AppendLine(""); + sb.AppendLine(""); - string url = "/session?" + GetParameterList(urlOriginalString); - - if (request.QueryString["confirm"] is not null) - { - if (Convert.ToInt16(request.QueryString["confirm"]) != 0) - { - confirm = true; - sessionDetails.Refresh(); - url = RemoveQueryStringByKey(urlOriginalString, "confirm"); - } - } - - if (confirm) - { - pageBuilder = new StringBuilder(); - pageBuilder.Append($"
QuickFIX Engine Web Interface

QuickFIX Engine Web Interface

"); - pageBuilder.Append($"
[HOME] [RELOAD]

"); - pageBuilder.Append($"

{sessionId} has been refreshed

"); - } - else - { - pageBuilder.Append($"

Are you sure you want to refresh session {sessionId}?

"); - pageBuilder.Append($"
[YES, refresh session] [NO, do not refresh session]
"); + byte[] buffer = Encoding.UTF8.GetBytes(sb.ToString()); + response.ContentLength64 = buffer.Length; + Stream output = response.OutputStream; + output.Write(buffer, 0, buffer.Length); + output.Close(); } - return pageBuilder.ToString(); + } catch (HttpListenerException) { + Console.WriteLine("HTTP server was shut down."); } + } - private string RefreshSessions(HttpListenerRequest request, StringBuilder pageBuilder) - { - bool confirm = false; - - if (request.QueryString["confirm"] is not null) - { - if (Convert.ToInt16(request.QueryString["confirm"]) != 0) - { - confirm = true; - foreach (SessionID sessionId in _sessionSettings.GetSessions()) - { - Session.LookupSession(sessionId)?.Refresh(); - } - } - } - - if (confirm) - { - pageBuilder = new StringBuilder(); - pageBuilder.Append("
QuickFIX Engine Web Interface

QuickFIX Engine Web Interface

"); - pageBuilder.Append("

Sessions have been refreshed

"); - } - else - { - pageBuilder.Append("

Are you sure you want to refresh all sessions ?

"); - pageBuilder.Append($"
[YES, refresh sessions] [NO, do not refresh sessions]
"); - } - return pageBuilder.ToString(); + private void RenderRoot(StringBuilder sb) { + sb.AppendLine($"
{_sessionSettings.GetSessions().Count} Sessions managed by QuickFIX/n
"); + sb.AppendLine("
"); + sb.AppendLine(); + + sb.AppendLine("
"); + sb.AppendLine(" RESET ALL"); + sb.AppendLine(" REFRESH ALL"); + sb.AppendLine(" ENABLE ALL"); + sb.AppendLine(" DISABLE ALL"); + sb.AppendLine("
"); + sb.AppendLine("
"); + + sb.AppendLine(" "); + + sb.AppendLine(" "); + var headers = new[] { "Session", "Type", "Enabled", "Session Time", "Logged On", "Next Incoming", "Next Outgoing" }; + foreach (string str in headers) { + sb.AppendLine($" "); } - private string ResetSessions(HttpListenerRequest request, StringBuilder pageBuilder) - { - bool confirm = false; + sb.AppendLine(" "); - if (request.QueryString["confirm"] is not null) - { - if (Convert.ToInt16(request.QueryString["confirm"]) != 0) - { - confirm = true; - foreach (SessionID sessionId in _sessionSettings.GetSessions()) - { - Session.LookupSession(sessionId)?.Reset("Reset from WebInterface"); - } - } - } + for (int idx = 0; idx < _sessions.Count; idx++) { + Session session = _sessions[idx]; - if (confirm) - { - pageBuilder = new StringBuilder(); - pageBuilder.Append("
QuickFIX Engine Web Interface

QuickFIX Engine Web Interface

"); - pageBuilder.Append("

Sessions have been reset

"); - } - else - { - pageBuilder.Append("

Are you sure you want to reset all sessions ?

"); - pageBuilder.Append($"
[YES, reset sessions] [NO, do not reset sessions]
"); - } - return pageBuilder.ToString(); + sb.AppendLine(" "); + sb.AppendLine($" "); } - private string ResetSession(HttpListenerRequest request, StringBuilder pageBuilder) - { - SessionID sessionId = new SessionID( - request.QueryString["beginstring"] ?? "", - request.QueryString["sendercompid"] ?? "", - request.QueryString["targetcompid"] ?? ""); - Session? sessionDetails = Session.LookupSession(sessionId); - if (sessionDetails == null) throw new Exception("Session not found"); - - bool confirm = false; - string urlOriginalString = request.Url!.OriginalString; - - string url = "/session?" + GetParameterList(urlOriginalString); + sb.Append("
{str}
{session.SessionID}"); + sb.AppendLine($" {(session.IsInitiator ? "initiator" : "acceptor")}"); + sb.AppendLine($" {(session.IsEnabled ? "yes" : "no")}"); + sb.AppendLine($" {(session.IsSessionTime ? "yes" : "no")}"); + sb.AppendLine($" {(session.IsLoggedOn ? "yes" : "no")}"); + sb.AppendLine($" {session.NextTargetMsgSeqNum.ToString()}"); + sb.AppendLine($" {session.NextSenderMsgSeqNum.ToString()}"); + sb.AppendLine("
"); + } - if (request.QueryString["confirm"] is not null) - { - if (Convert.ToInt16(request.QueryString["confirm"])!=0) - { - confirm = true; - sessionDetails.Reset("Reset from WebInterface"); - url = RemoveQueryStringByKey(urlOriginalString, "confirm"); - } - } + private void RenderSession(HttpListenerRequest request, StringBuilder sb) { + (int sessionIdx, Session session) = GetSessionFromRequest(request); - if (confirm) - { - pageBuilder = new StringBuilder(); - pageBuilder.Append($"
QuickFIX Engine Web Interface

QuickFIX Engine Web Interface

"); - pageBuilder.Append($"
[HOME] [RELOAD]

"); - pageBuilder.Append($"

{sessionId} has been reset

"); - } + if (bool.TryParse(request.QueryString[ToggleParam.Enabled], out var toEnabled)) { + if (toEnabled) + session.Logon(); else - { - pageBuilder.Append($"

Are you sure you want to reset session {sessionId}?

"); - pageBuilder.Append($"
[YES, reset session] [NO, do not reset session]
"); - } - return pageBuilder.ToString(); + session.Logout(); } - - private string ProcessRoot(HttpListenerRequest request, StringBuilder pageBuilder) - { - //Session count - pageBuilder.Append($"
{_sessionSettings.GetSessions().Count} Sessions managed by QuickFIX

"); - - //session management links - pageBuilder.Append($"
RESET REFRESH ENABLE DISABLE

"); - - //Start the table generation - pageBuilder.Append(""); - - pageBuilder.Append(""); - pageBuilder.Append(AddCell("Session", true)); - pageBuilder.Append(AddCell("Type", true)); - pageBuilder.Append(AddCell("Enabled", true)); - pageBuilder.Append(AddCell("Session Time", true)); - pageBuilder.Append(AddCell("Logged On", true)); - pageBuilder.Append(AddCell("Next Incoming", true)); - pageBuilder.Append(AddCell("Next Outgoing", true)); - pageBuilder.Append(""); - - foreach (SessionID sessionId in _sessionSettings.GetSessions()) - { - Session sessionDetails = Session.LookupSession(sessionId)!; - pageBuilder.Append(""); - pageBuilder.Append(AddCell(string.Format( - "{0}:{1}->{2} {3}", - sessionId.BeginString, sessionId.SenderCompID, sessionId.TargetCompID, sessionId))); - pageBuilder.Append(AddCell(sessionDetails.IsInitiator ? "initiator" : "acceptor")); - pageBuilder.Append(AddCell(sessionDetails.IsEnabled ? "yes" : "no")); - pageBuilder.Append(AddCell(sessionDetails.IsSessionTime ? "yes" : "no")); - pageBuilder.Append(AddCell(sessionDetails.IsLoggedOn ? "yes" : "no")); - pageBuilder.Append(AddCell(sessionDetails.NextTargetMsgSeqNum.ToString())); - pageBuilder.Append(AddCell(sessionDetails.NextSenderMsgSeqNum.ToString())); - pageBuilder.Append(""); - } - - pageBuilder.Append("
"); - return pageBuilder.ToString(); + if (bool.TryParse(request.QueryString[ToggleParam.SendRedundantResendRequests], out var toSendRrr)) + session.SendRedundantResendRequests = toSendRrr; + if (bool.TryParse(request.QueryString[ToggleParam.CheckCompId], out var toCheckCompId)) + session.CheckCompID = toCheckCompId; + if (bool.TryParse(request.QueryString[ToggleParam.CheckLatency], out var toCheckLatency)) + session.CheckLatency = toCheckLatency; + + if (request.QueryString[AdjustParam.MaxLatency] is not null) { + string adjustmentSymbol = request.QueryString[AdjustParam.MaxLatency]!; + int newVal = session.MaxLatency + AdjustParam.ToInt(adjustmentSymbol); + session.MaxLatency = newVal < 0 ? 0 : newVal; } - - private string SessionDetails(HttpListenerRequest request, StringBuilder pageBuilder) - { - SessionID sessionId = new SessionID( - request.QueryString["beginstring"] ?? "", - request.QueryString["sendercompid"] ?? "", - request.QueryString["targetcompid"] ?? ""); - Session? sessionDetails = Session.LookupSession(sessionId); - if (sessionDetails == null) throw new Exception("Session not found"); - - string url = request.Url?.OriginalString ?? "(null)"; - string urlOriginalString = url; - - if (request.QueryString["enabled"] is not null) - { - if (!Convert.ToBoolean(request.QueryString["enabled"])) - sessionDetails.Logout(); - else - sessionDetails.SetLogonState(); - - url = RemoveQueryStringByKey(urlOriginalString, "Enabled"); - } - - if (request.QueryString["next incoming"] is not null) - { - SeqNumType val = Convert.ToUInt64(request.QueryString["next incoming"]); - sessionDetails.NextTargetMsgSeqNum = (val == 0 || val == SeqNumType.MaxValue) ? 1 : val; - url = RemoveQueryStringByKey(urlOriginalString, "next incoming"); - } - - if (request.QueryString["Next Outgoing"] is not null) - { - SeqNumType val = Convert.ToUInt64(request.QueryString["Next Outgoing"]); - sessionDetails.NextSenderMsgSeqNum = (val == 0 || val == SeqNumType.MaxValue) ? 1 : val; - url = RemoveQueryStringByKey(urlOriginalString, "Next Outgoing"); - } - - if (request.QueryString["SendRedundantResendRequests"] is not null) - { - sessionDetails.SendRedundantResendRequests = Convert.ToBoolean(request.QueryString["SendRedundantResendRequests"]); - url = RemoveQueryStringByKey(urlOriginalString, "SendRedundantResendRequests"); - } - - if (request.QueryString["CheckCompId"] is not null) - { - sessionDetails.CheckCompID = Convert.ToBoolean(request.QueryString["CheckCompId"]); - url = RemoveQueryStringByKey(urlOriginalString, "CheckCompId"); - } - - if (request.QueryString["CheckLatency"] is not null) - { - sessionDetails.CheckLatency = Convert.ToBoolean(request.QueryString["CheckLatency"]); - url = RemoveQueryStringByKey(urlOriginalString, "CheckLatency"); - } - - if (request.QueryString["MaxLatency"] is not null) - { - int val = Convert.ToInt16(request.QueryString["MaxLatency"]); - sessionDetails.MaxLatency = val <= 0 ? 1 : val; - url = RemoveQueryStringByKey(urlOriginalString, "MaxLatency"); - } - - if (request.QueryString["LogonTimeout"] is not null) - { - int val = Convert.ToInt16(request.QueryString["LogonTimeout"]); - sessionDetails.LogonTimeout = val <= 0 ? 1 : val; - url = RemoveQueryStringByKey(urlOriginalString, "LogonTimeout"); - } - - if (request.QueryString["LogoutTimeout"] is not null) - { - int val = Convert.ToInt16(request.QueryString["LogoutTimeout"]); - sessionDetails.LogoutTimeout = val <= 0 ? 1 : val; - url = RemoveQueryStringByKey(urlOriginalString, "LogoutTimeout"); - } - - if (request.QueryString["ResetOnLogon"] is not null) - { - sessionDetails.ResetOnLogon = Convert.ToBoolean(request.QueryString["ResetOnLogon"]); - url = RemoveQueryStringByKey(urlOriginalString, "ResetOnLogon"); - } - - if (request.QueryString["ResetOnLogout"] is not null) - { - sessionDetails.ResetOnLogout = Convert.ToBoolean(request.QueryString["ResetOnLogout"]); - url = RemoveQueryStringByKey(urlOriginalString, "ResetOnLogout"); - } - - if (request.QueryString["ResetOnDisconnect"] is not null) - { - sessionDetails.ResetOnDisconnect = Convert.ToBoolean(request.QueryString["ResetOnDisconnect"]); - url = RemoveQueryStringByKey(urlOriginalString, "ResetOnDisconnect"); - } - - if (request.QueryString["RefreshOnLogon"] is not null) - { - sessionDetails.RefreshOnLogon = Convert.ToBoolean(request.QueryString["RefreshOnLogon"]); - url = RemoveQueryStringByKey(urlOriginalString, "RefreshOnLogon"); - } - - if (request.QueryString["MillisecondsInTimestamp"] is not null) - { - sessionDetails.MillisecondsInTimeStamp = Convert.ToBoolean(request.QueryString["MillisecondsInTimestamp"]); - url = RemoveQueryStringByKey(urlOriginalString, "MillisecondsInTimestamp"); - } - - if (request.QueryString["PersistMessages"] is not null) - { - sessionDetails.PersistMessages = Convert.ToBoolean(request.QueryString["PersistMessages"]); - url = RemoveQueryStringByKey(urlOriginalString, "PersistMessages"); - } - - - pageBuilder.Append($"
{sessionId}

"); - pageBuilder.Append($"
[RESET] [REFRESH]

"); - - pageBuilder.Append(""); - - pageBuilder.Append(AddRow("Enabled", sessionDetails.IsEnabled, url)); - pageBuilder.Append(AddRow("ConnectionType", sessionDetails.IsInitiator?"initiator": "acceptor")); - pageBuilder.Append(AddRow("SessionTime", sessionDetails.IsSessionTime)); - pageBuilder.Append(AddRow("LoggedOn", sessionDetails.IsLoggedOn)); - pageBuilder.Append(AddRow("Next Incoming", sessionDetails.NextTargetMsgSeqNum, url)); - pageBuilder.Append(AddRow("Next Outgoing", sessionDetails.NextSenderMsgSeqNum, url)); - pageBuilder.Append(AddRow("SendRedundantResendRequests", sessionDetails.SendRedundantResendRequests, url)); - pageBuilder.Append(AddRow("CheckCompId", sessionDetails.CheckCompID, url)); - pageBuilder.Append(AddRow("CheckLatency", sessionDetails.CheckLatency, url)); - pageBuilder.Append(AddRow("MaxLatency", sessionDetails.MaxLatency, url)); - pageBuilder.Append(AddRow("LogonTimeout", sessionDetails.LogonTimeout, url)); - pageBuilder.Append(AddRow("LogoutTimeout", sessionDetails.LogoutTimeout, url)); - pageBuilder.Append(AddRow("ResetOnLogon", sessionDetails.ResetOnLogon, url)); - pageBuilder.Append(AddRow("ResetOnLogout", sessionDetails.ResetOnLogout, url)); - pageBuilder.Append(AddRow("ResetOnDisconnect", sessionDetails.ResetOnDisconnect, url)); - pageBuilder.Append(AddRow("RefreshOnLogon", sessionDetails.RefreshOnLogon, url)); - pageBuilder.Append(AddRow("MillisecondsInTimestamp", sessionDetails.MillisecondsInTimeStamp, url)); - pageBuilder.Append(AddRow("PersistMessages", sessionDetails.PersistMessages, url)); - - pageBuilder.Append("
"); - return pageBuilder.ToString(); + if (request.QueryString[AdjustParam.LogonTimeout] is not null) { + string adjustmentSymbol = request.QueryString[AdjustParam.LogonTimeout]!; + int newVal = session.LogonTimeout + AdjustParam.ToInt(adjustmentSymbol); + session.LogonTimeout = newVal < 0 ? 0 : newVal; + } + if (request.QueryString[AdjustParam.LogoutTimeout] is not null) { + var adjustmentSymbol = request.QueryString[AdjustParam.LogoutTimeout]!; + int newVal = session.LogoutTimeout + AdjustParam.ToInt(adjustmentSymbol); + session.LogoutTimeout = newVal < 0 ? 0 : newVal; } - private static string RemoveQueryStringByKey(string url, string key) - { - var uri = new Uri(url); + if (bool.TryParse(request.QueryString[ToggleParam.ResetOnLogon], out var toResetOnLogon)) + session.ResetOnLogon = toResetOnLogon; + if (bool.TryParse(request.QueryString[ToggleParam.ResetOnLogout], out var toResetOnLogout)) + session.ResetOnLogout = toResetOnLogout; + if (bool.TryParse(request.QueryString[ToggleParam.ResetOnDisconnect], out var toResetOnDisconnect)) + session.ResetOnDisconnect = toResetOnDisconnect; + if (bool.TryParse(request.QueryString[ToggleParam.RefreshOnLogon], out var toRefreshOnLogon)) + session.RefreshOnLogon = toRefreshOnLogon; + if (bool.TryParse(request.QueryString[ToggleParam.MsInTimestamp], out var toMsInTimestamp)) + session.MillisecondsInTimeStamp = toMsInTimestamp; + if (bool.TryParse(request.QueryString[ToggleParam.PersistMessages], out var toPersistMessages)) + session.PersistMessages = toPersistMessages; + + sb.AppendLine($"
{session.SessionID}
"); + sb.AppendLine("
"); + + sb.AppendLine("
"); + sb.AppendLine($" RESET this session"); + sb.AppendLine($" REFRESH this session"); + sb.AppendLine("
"); + sb.AppendLine("
"); + + sb.AppendLine(" "); + sb.AppendLine(" "); + sb.AppendLine(" "); + sb.AppendLine(" "); + sb.AppendLine(" "); + sb.AppendLine(" "); + RenderDetailToggleRow(sb, sessionIdx, "Enabled", session.IsEnabled, ToggleParam.Enabled); + RenderDetailPlainRow(sb, "ConnectionType", session.IsInitiator ? "initiator" : "acceptor"); + RenderDetailPlainRow(sb, "IsSessionTime", session.IsSessionTime ? "yes" : "no"); + RenderDetailPlainRow(sb, "LoggedOn", session.IsLoggedOn ? "yes" : "no"); + RenderDetailPlainRow(sb, "NextIncoming", session.NextTargetMsgSeqNum.ToString()); + RenderDetailPlainRow(sb, "NextIncoming", session.NextSenderMsgSeqNum.ToString()); + RenderDetailToggleRow(sb, sessionIdx, "SendRedundantResendRequests", session.SendRedundantResendRequests, ToggleParam.SendRedundantResendRequests); + RenderDetailToggleRow(sb, sessionIdx, "CheckCompId", session.CheckCompID, ToggleParam.CheckCompId); + RenderDetailToggleRow(sb, sessionIdx, "CheckLatency", session.CheckLatency, ToggleParam.CheckLatency); + RenderDetailAdjustableIntRow(sb, sessionIdx, "MaxLatency (seconds)", session.MaxLatency, AdjustParam.MaxLatency); + RenderDetailAdjustableIntRow(sb, sessionIdx, "LogonTimeout (seconds)", session.LogonTimeout, AdjustParam.LogonTimeout); + RenderDetailAdjustableIntRow(sb, sessionIdx, "LogoutTimeout (seconds)", session.LogoutTimeout, AdjustParam.LogoutTimeout); + RenderDetailToggleRow(sb, sessionIdx, "ResetOnLogon", session.ResetOnLogon, ToggleParam.ResetOnLogon); + RenderDetailToggleRow(sb, sessionIdx, "ResetOnLogout", session.ResetOnLogout, ToggleParam.ResetOnLogout); + RenderDetailToggleRow(sb, sessionIdx, "ResetOnDisconnect", session.ResetOnDisconnect, ToggleParam.ResetOnDisconnect); + RenderDetailToggleRow(sb, sessionIdx, "RefreshOnLogon", session.RefreshOnLogon, ToggleParam.RefreshOnLogon); + RenderDetailToggleRow(sb, sessionIdx, "MillisecondsInTimestamp", session.MillisecondsInTimeStamp, ToggleParam.MsInTimestamp); + RenderDetailToggleRow(sb, sessionIdx, "PersistMessages", session.PersistMessages, ToggleParam.PersistMessages); + sb.AppendLine("
AttributeCurrent ValueAction
"); + } - // this gets all the query string key value pairs as a collection - var newQueryString = HttpUtility.ParseQueryString(uri.Query); + private (int, Session) GetSessionFromRequest(HttpListenerRequest request) { + if (!int.TryParse(request.QueryString["idx"], out var sessionIdx)) + throw new ApplicationException("Missing or erroneous `idx` (session index) parameter"); - // this removes the key if exists - newQueryString.Remove(key); + if (sessionIdx >= _sessions.Count) + throw new ApplicationException("Invalid session index (too high)"); - // this gets the page path from root without QueryString - string pagePathWithoutQueryString = uri.GetLeftPart(UriPartial.Path); + return (sessionIdx, _sessions[sessionIdx]); + } - return newQueryString.Count > 0 - ? $"{pagePathWithoutQueryString}?{newQueryString}" - : pagePathWithoutQueryString; - } + private static void RenderDetailToggleRow(StringBuilder sb, int sessionIdx, string label, bool val, string toggleParam) { + sb.AppendLine(" "); + sb.AppendLine($" {label}"); + sb.AppendLine($" {(val ? "yes" : "no")}"); + sb.AppendLine($" toggle"); + sb.AppendLine(" "); + } - /// - /// Returns the http parameter string from a url - /// - /// e.g. - /// e.g. (or empty string if is null) - private static string GetParameterList(string? url) { - return string.IsNullOrWhiteSpace(url) ? "" : $"{HttpUtility.ParseQueryString(new Uri(url).Query)}"; - } + private static void RenderDetailPlainRow(StringBuilder sb, string label, string val) { + sb.AppendLine(" "); + sb.AppendLine($" {label}"); + sb.AppendLine($" {val}"); + sb.AppendLine(" "); + sb.AppendLine(" "); + } - private static string AddRow(string colName, bool val, string url="") - { - string valueAsStr = val ? "yes" : "no"; - string innerHtml = url.Length > 0 - ? $"toggle" - : ""; - return AddRow(colName, valueAsStr, innerHtml); - } + private static void RenderDetailAdjustableIntRow(StringBuilder sb, int sessionIdx, string label, int val, string adjustParam) { + sb.AppendLine(" "); + sb.AppendLine($" {label}"); + sb.AppendLine($" {val}"); + sb.AppendLine(" "); + sb.AppendLine($" <<"); + sb.AppendLine($" <"); + sb.AppendLine($" >"); + sb.AppendLine($" >>"); + sb.AppendLine(" "); + sb.AppendLine(" "); + } - private static string AddRow(string colName, int val, string url = "") - { - string innerHtml = $" << " + - $" < " + - " | " + - $" > " + - $" >> "; - return AddRow(colName, val.ToString(), innerHtml); - } + private void ResetSessionAndRedirect(HttpListenerRequest request, HttpListenerResponse response) { + (int sessionIdx, Session session) = GetSessionFromRequest(request); + session.Reset("Reset single session from web interface"); + response.Redirect($"/session?idx={sessionIdx}"); + response.Close(); + } - private static string AddRow(string colName, SeqNumType val, string url = "") - { - string innerHtml = $" << " + - $" < " + - " | " + - $" > " + - $" >> "; - return AddRow(colName, val.ToString(), innerHtml); - } + private void RefreshSessionAndRedirect(HttpListenerRequest request, HttpListenerResponse response) { + (int sessionIdx, Session session) = GetSessionFromRequest(request); + session.Refresh(); + response.Redirect($"/session?idx={sessionIdx}"); + response.Close(); + } - private static string AddRow(string colName, string val, string innerHtml = "") - { - StringBuilder row = new StringBuilder(); - row.Append(""); - row.Append(AddCell(colName)); - row.Append(AddCell(val)); - row.Append(AddCell(innerHtml)); - row.Append(""); - return row.ToString(); - } + ~HttpServer() => Dispose(false); - private static string AddCell(string cellContent, bool header = false) - { - string entryType = header ? "th" : "td"; - return "<"+entryType+" align=\"left\">" + cellContent + ""; - } + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } - ~HttpServer() => Dispose(false); - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + protected virtual void Dispose(bool disposing) { + if (_disposed) + return; + + if (disposing) { + if (_running) + Stop(); - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - if (disposing) - { - if (_running) - { - Stop(); - } #pragma warning disable SYSLIB0006 - _connectionThread.Abort(); + _connectionThread.Abort(); #pragma warning restore SYSLIB0006 - } - _disposed = true; } + + _disposed = true; } } diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs index 3e8815653..a4251cf85 100755 --- a/QuickFIXn/Session.cs +++ b/QuickFIXn/Session.cs @@ -370,18 +370,19 @@ public bool Send(string message) } /// - /// Sets some internal state variables. + /// Sets some internal state variables to enable the session. /// - internal void SetLogonState() + public void Logon() { _state.IsEnabled = true; _state.LogoutReason = ""; } /// - /// Sets some internal state variables. + /// Sets some internal state variables to disable the session. + /// Users will be disconnected on next cycle. /// - internal void Logout(string reason = "") + public void Logout(string reason = "") { _state.IsEnabled = false; _state.LogoutReason = reason; diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 162ade271..6cbc6dac4 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -39,7 +39,6 @@ What's New * ClientHandlerThread is now internal. Unlikely anyone will notice. * SocketReader: delete public ctor. Probably no one is using it. * Session: rename (capitalize) TargetDefaultApplVerId property - * Session: Logon() and Logout() made internal and renamed. No one should be using these. * SessionState: ctor now requires a MessageStore. (Existing callers used an object initializer anyway) * Many protected functions were altered, but probably no user code is touching them * #827 - cleanup/nullable-ize StreamFactory, SessionID, Settings, SessionSettings, SessionSchedule (gbirchmeier) @@ -58,6 +57,7 @@ What's New * #785 - use correct SocketError "Shutdown" code when socket is deliberately shutdown (oclancy) * #711 - fix explicit 0.0.0.0 address binding (bohdanstefaniuk) * #823 - get rid of IOIQty enums in FIX5 DDs, allow free string (gbirchmeier) +* #786 - rewrite HttpServer: better HTML, no crash on errors (gbirchmeier) ### v1.11.2: * same as v1.11.1, but I fixed the readme in the pushed nuget packages