diff --git a/src/wan24-AutoDiscover Shared/wan24-AutoDiscover Shared.csproj b/src/wan24-AutoDiscover Shared/wan24-AutoDiscover Shared.csproj
index 14243c6..fab8a58 100644
--- a/src/wan24-AutoDiscover Shared/wan24-AutoDiscover Shared.csproj
+++ b/src/wan24-AutoDiscover Shared/wan24-AutoDiscover Shared.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/src/wan24-AutoDiscover/Controllers/DiscoveryController.cs b/src/wan24-AutoDiscover/Controllers/DiscoveryController.cs
index 4f24b8c..8187373 100644
--- a/src/wan24-AutoDiscover/Controllers/DiscoveryController.cs
+++ b/src/wan24-AutoDiscover/Controllers/DiscoveryController.cs
@@ -16,7 +16,7 @@ namespace wan24.AutoDiscover.Controllers
///
/// Responses
[ApiController, Route("autodiscover")]
- public sealed class DiscoveryController(XmlResponseInstances responses) : ControllerBase()
+ public sealed class DiscoveryController(InstancePool responses) : ControllerBase()
{
///
/// Max. request length in bytes
@@ -40,22 +40,26 @@ public sealed class DiscoveryController(XmlResponseInstances responses) : Contro
private const string EMAIL_NODE_NAME = "EMailAddress";
///
- /// Missing email address message
+ /// Invalid request XML message bytes
///
- private static readonly byte[] MissingEmailMessage = "Missing email address in request".GetBytes();
+ private static readonly byte[] InvalidRequestMessage = "Invalid Request XML".GetBytes();
///
- /// Invalid email address message
+ /// Missing email address message bytes
///
- private static readonly byte[] InvalidEmailMessage = "Invalid email address in request".GetBytes();
+ private static readonly byte[] MissingEmailMessage = "Missing Email Address".GetBytes();
///
- /// Unknown domain name message
+ /// Invalid email address message bytes
///
- private static readonly byte[] UnknownDomainMessage = "Unknown domain name".GetBytes();
+ private static readonly byte[] InvalidEmailMessage = "Invalid Email Address".GetBytes();
+ ///
+ /// Unknown domain name message bytes
+ ///
+ private static readonly byte[] UnknownDomainMessage = "Unknown Domain Name".GetBytes();
///
/// Responses
///
- private readonly XmlResponseInstances Responses = responses;
+ private readonly InstancePool Responses = responses;
///
/// Autodiscover (POX request body required)
@@ -72,17 +76,28 @@ public async Task AutoDiscoverAsync()
{
await HttpContext.Request.Body.CopyToAsync(ms, HttpContext.RequestAborted).DynamicContext();
ms.Position = 0;
- using XmlReader xmlRequest = XmlReader.Create(ms);
- while (xmlRequest.Read())
+ try
{
- if (xmlRequest.Name != EMAIL_NODE_NAME) continue;
- emailAddress = xmlRequest.ReadElementContentAsString();
- break;
+ using XmlReader requestXml = XmlReader.Create(ms);
+ while (requestXml.Read())
+ {
+ if (!requestXml.Name.Equals(EMAIL_NODE_NAME, StringComparison.OrdinalIgnoreCase))
+ continue;
+ emailAddress = requestXml.ReadElementContentAsString();
+ break;
+ }
+ }
+ catch(XmlException ex)
+ {
+ if (Logging.Debug)
+ Logging.WriteDebug($"Parsing POX request from {HttpContext.Connection.RemoteIpAddress}:{HttpContext.Connection.RemotePort} failed: {ex}");
+ RespondBadRequest(InvalidRequestMessage);
+ return;
}
}
if(emailAddress is null)
{
- await BadRequestAsync(MissingEmailMessage).DynamicContext();
+ RespondBadRequest(MissingEmailMessage);
return;
}
if (Logging.Trace)
@@ -90,41 +105,45 @@ public async Task AutoDiscoverAsync()
string[] emailParts = emailAddress.Split('@', 2);// @ splitted email alias and domain name
if (emailParts.Length != 2 || !MailAddress.TryCreate(emailAddress, out _))
{
- await BadRequestAsync(InvalidEmailMessage).DynamicContext();
+ RespondBadRequest(InvalidEmailMessage);
return;
}
// Generate the response
- using XmlResponse xml = await Responses.GetOneAsync(HttpContext.RequestAborted).DynamicContext();// Response XML
if (DomainConfig.GetConfig(HttpContext.Request.Host.Host, emailParts) is not DomainConfig config)
{
- await BadRequestAsync(UnknownDomainMessage).DynamicContext();
+ RespondBadRequest(UnknownDomainMessage);
return;
}
if (Logging.Trace)
Logging.WriteTrace($"Creating POX response for \"{emailAddress}\" request from {HttpContext.Connection.RemoteIpAddress}:{HttpContext.Connection.RemotePort}");
HttpContext.Response.StatusCode = OK_STATUS_CODE;
HttpContext.Response.ContentType = XML_MIME_TYPE;
- await HttpContext.Response.StartAsync(HttpContext.RequestAborted).DynamicContext();
- Task sendXmlOutput = xml.XmlOutput.CopyToAsync(HttpContext.Response.Body, HttpContext.RequestAborted);
- config.CreateXml(xml.XML, emailParts);
- xml.FinalizeXmlOutput();
- await sendXmlOutput.DynamicContext();
- await HttpContext.Response.CompleteAsync().DynamicContext();
- if (Logging.Trace)
- Logging.WriteTrace($"POX response for \"{emailAddress}\" request from {HttpContext.Connection.RemoteIpAddress}:{HttpContext.Connection.RemotePort} sent");
+ using XmlResponse responseXml = await Responses.GetOneAsync(HttpContext.RequestAborted).DynamicContext();// Response XML
+ Task sendXmlOutput = responseXml.XmlOutput.CopyToAsync(HttpContext.Response.Body, HttpContext.RequestAborted);
+ try
+ {
+ config.CreateXml(responseXml.XML, emailParts);
+ responseXml.FinalizeXmlOutput();
+ }
+ finally
+ {
+ await sendXmlOutput.DynamicContext();
+ if (Logging.Trace)
+ Logging.WriteTrace($"POX response for \"{emailAddress}\" request from {HttpContext.Connection.RemoteIpAddress}:{HttpContext.Connection.RemotePort} sent");
+ }
}
///
/// Respond with a bad request message
///
/// Message
- private async Task BadRequestAsync(ReadOnlyMemory message)
+ private void RespondBadRequest(byte[] message)
{
if (Logging.Trace)
Logging.WriteTrace($"Invalid POX request from {HttpContext.Connection.RemoteIpAddress}:{HttpContext.Connection.RemotePort}: \"{message.ToUtf8String()}\"");
HttpContext.Response.StatusCode = BAD_REQUEST_STATUS_CODE;
HttpContext.Response.ContentType = ExceptionHandler.TEXT_MIME_TYPE;
- await HttpContext.Response.Body.WriteAsync(message, HttpContext.RequestAborted).DynamicContext();
+ HttpContext.Response.Body = new MemoryStream(message);
}
}
}
diff --git a/src/wan24-AutoDiscover/Program.cs b/src/wan24-AutoDiscover/Program.cs
index f1b942a..bfc3988 100644
--- a/src/wan24-AutoDiscover/Program.cs
+++ b/src/wan24-AutoDiscover/Program.cs
@@ -45,7 +45,7 @@ async Task LoadConfigAsync()
Logging.Logger ??= !string.IsNullOrWhiteSpace(DiscoveryConfig.Current.LogFile)
? await FileLogger.CreateAsync(DiscoveryConfig.Current.LogFile, next: new VividConsoleLogger(), cancellationToken: cts.Token).DynamicContext()
: new VividConsoleLogger();
-Logging.WriteInfo($"wan24-AutoDiscover {VersionInfo.Current} Using configuration \"{configFile}\"");
+Logging.WriteInfo($"wan24-AutoDiscover {VersionInfo.Current} using configuration \"{configFile}\"");
// Watch configuration changes
using SemaphoreSync configSync = new();
@@ -128,11 +128,11 @@ async Task LoadConfigAsync()
if (ENV.IsLinux)
builder.Logging.AddSystemdConsole();
builder.Services.AddControllers();
-builder.Services.AddSingleton(typeof(XmlResponseInstances), services => new XmlResponseInstances(capacity: DiscoveryConfig.Current.PreForkResponses))
+builder.Services.AddExceptionHandler()
+ .AddSingleton(typeof(InstancePool), services => new InstancePool(capacity: DiscoveryConfig.Current.PreForkResponses))
.AddSingleton(cts)
- .AddHostedService(services => services.GetRequiredService())
+ .AddHostedService(services => services.GetRequiredService>())
.AddHostedService(services => fsw)
- .AddExceptionHandler()
.AddHttpLogging(options => options.LoggingFields = HttpLoggingFields.RequestPropertiesAndHeaders);
builder.Services.Configure(options =>
{
@@ -150,7 +150,8 @@ async Task LoadConfigAsync()
Logging.WriteInfo("Autodiscovery service app shutdown");
cts.Cancel();
});
- app.MapDefaultEndpoints();
+ app.UseExceptionHandler(builder => { });// .NET 8 bugfix :(
+ app.MapDefaultEndpoints();// Aspire
app.UseForwardedHeaders();
if (app.Environment.IsDevelopment())
{
@@ -158,8 +159,7 @@ async Task LoadConfigAsync()
Logging.WriteTrace("Using development environment");
app.UseHttpLogging();
}
- app.UseExceptionHandler(builder => { });// .NET 8 bugfix :(
- if (!app.Environment.IsDevelopment())
+ else
{
if (Logging.Trace)
Logging.WriteTrace("Using production environment");
diff --git a/src/wan24-AutoDiscover/Services/ExceptionHandler.cs b/src/wan24-AutoDiscover/Services/ExceptionHandler.cs
index e72111a..9f6e037 100644
--- a/src/wan24-AutoDiscover/Services/ExceptionHandler.cs
+++ b/src/wan24-AutoDiscover/Services/ExceptionHandler.cs
@@ -22,14 +22,18 @@ public sealed class ExceptionHandler : IExceptionHandler
///
public const string TEXT_MIME_TYPE = "text/plain";
+ ///
+ /// Bad request message bytes
+ ///
+ private static readonly byte[] BadRequestMessage = "Bad Request".GetBytes();
///
/// Internal server error message bytes
///
- private static readonly byte[] InternalServerErrorMessage = "Internal server error".GetBytes();
+ private static readonly byte[] InternalServerErrorMessage = "Internal Server Error".GetBytes();
///
/// Maintenance message bytes
///
- private static readonly byte[] MaintenanceMessage = "Temporary not available".GetBytes();
+ private static readonly byte[] MaintenanceMessage = "Temporary Not Available".GetBytes();
///
/// Constructor
@@ -37,8 +41,9 @@ public sealed class ExceptionHandler : IExceptionHandler
public ExceptionHandler() { }
///
- public async ValueTask TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
+ public ValueTask TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
+ if (httpContext.Response.HasStarted) return ValueTask.FromResult(false);
CancellationTokenSource cts = httpContext.RequestServices.GetRequiredService();
httpContext.Response.ContentType = TEXT_MIME_TYPE;
if (exception is BadHttpRequestException badRequest)
@@ -46,7 +51,7 @@ public async ValueTask TryHandleAsync(HttpContext httpContext, Exception e
if (Logging.Trace)
Logging.WriteTrace($"http handling bad request exception for {httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort} request to \"{httpContext.Request.Method} {httpContext.Request.Path}\": {exception}");
httpContext.Response.StatusCode = badRequest.StatusCode;
- await httpContext.Response.BodyWriter.WriteAsync((badRequest.Message ?? "Bad request").GetBytes(), cancellationToken).DynamicContext();
+ httpContext.Response.Body = new MemoryStream(badRequest.Message is null? BadRequestMessage : badRequest.Message.GetBytes());
}
else if (exception is OperationCanceledException)
{
@@ -59,15 +64,15 @@ public async ValueTask TryHandleAsync(HttpContext httpContext, Exception e
Logging.WriteWarning($"http handling operation canceled exception for {httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort} request to \"{httpContext.Request.Method} {httpContext.Request.Path}\": {exception}");
}
httpContext.Response.StatusCode = MAINTENANCE_STATUS_CODE;
- await httpContext.Response.BodyWriter.WriteAsync(MaintenanceMessage, cancellationToken).DynamicContext();
+ httpContext.Response.Body = new MemoryStream(MaintenanceMessage);
}
else
{
Logging.WriteError($"http handling exception for {httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort} request to \"{httpContext.Request.Method} {httpContext.Request.Path}\": {exception}");
httpContext.Response.StatusCode = INTERNAL_SERVER_ERROR_STATUS_CODE;
- await httpContext.Response.BodyWriter.WriteAsync(InternalServerErrorMessage, cancellationToken).DynamicContext();
+ httpContext.Response.Body = new MemoryStream(InternalServerErrorMessage);
}
- return true;
+ return ValueTask.FromResult(true);
}
}
}
diff --git a/src/wan24-AutoDiscover/Services/XmlResponseInstances.cs b/src/wan24-AutoDiscover/Services/XmlResponseInstances.cs
deleted file mode 100644
index 1db9a92..0000000
--- a/src/wan24-AutoDiscover/Services/XmlResponseInstances.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using wan24.Core;
-
-namespace wan24.AutoDiscover.Services
-{
- ///
- /// instances
- ///
- public sealed class XmlResponseInstances(in int capacity) : InstancePool(capacity)
- {
- }
-}
diff --git a/src/wan24-AutoDiscover Shared/XmlResponse.cs b/src/wan24-AutoDiscover/XmlResponse.cs
similarity index 74%
rename from src/wan24-AutoDiscover Shared/XmlResponse.cs
rename to src/wan24-AutoDiscover/XmlResponse.cs
index 2ea05be..101b011 100644
--- a/src/wan24-AutoDiscover Shared/XmlResponse.cs
+++ b/src/wan24-AutoDiscover/XmlResponse.cs
@@ -1,4 +1,5 @@
-using System.Xml;
+using System.Text;
+using System.Xml;
using wan24.Core;
namespace wan24.AutoDiscover
@@ -8,16 +9,31 @@ namespace wan24.AutoDiscover
///
public class XmlResponse : DisposableBase
{
+ ///
+ /// Buffer size in bytes
+ ///
+ private const int BUFFER_SIZE = 1024;
+
+ ///
+ /// XML writer settings
+ ///
+ private static readonly XmlWriterSettings Settings = new()
+ {
+ Indent = false,
+ Encoding = Encoding.UTF8,
+ OmitXmlDeclaration = true
+ };
+
///
/// Constructor
///
public XmlResponse() : base(asyncDisposing: false)
{
- XmlOutput = new(bufferSize: 1024)
+ XmlOutput = new(BUFFER_SIZE)
{
AggressiveReadBlocking = false
};
- XML = XmlWriter.Create(XmlOutput);
+ XML = XmlWriter.Create(XmlOutput, Settings);
XML.WriteStartElement(Constants.AUTODISCOVER_NODE_NAME, Constants.AUTO_DISCOVER_NS);
XML.WriteStartElement(Constants.RESPONSE_NODE_NAME, Constants.RESPONSE_NS);
XML.WriteStartElement(Constants.ACCOUNT_NODE_NAME);
diff --git a/src/wan24-AutoDiscover/wan24-AutoDiscover.csproj b/src/wan24-AutoDiscover/wan24-AutoDiscover.csproj
index dffd1a7..715b9b3 100644
--- a/src/wan24-AutoDiscover/wan24-AutoDiscover.csproj
+++ b/src/wan24-AutoDiscover/wan24-AutoDiscover.csproj
@@ -20,8 +20,8 @@
-
-
+
+