Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
nd1012 committed Apr 13, 2024
1 parent 3fbccf1 commit 80bcb28
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageReference Include="ObjectValidation" Version="2.5.0" Condition="'$(Configuration)' != 'Trunk'" />
<PackageReference Include="wan24-Core" Version="2.17.0" Condition="'$(Configuration)' != 'Trunk'" />
<PackageReference Include="wan24-Core" Version="2.18.0" Condition="'$(Configuration)' != 'Trunk'" />
</ItemGroup>

<ItemGroup>
Expand Down
73 changes: 46 additions & 27 deletions src/wan24-AutoDiscover/Controllers/DiscoveryController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace wan24.AutoDiscover.Controllers
/// </remarks>
/// <param name="responses">Responses</param>
[ApiController, Route("autodiscover")]
public sealed class DiscoveryController(XmlResponseInstances responses) : ControllerBase()
public sealed class DiscoveryController(InstancePool<XmlResponse> responses) : ControllerBase()
{
/// <summary>
/// Max. request length in bytes
Expand All @@ -40,22 +40,26 @@ public sealed class DiscoveryController(XmlResponseInstances responses) : Contro
private const string EMAIL_NODE_NAME = "EMailAddress";

/// <summary>
/// Missing email address message
/// Invalid request XML message bytes
/// </summary>
private static readonly byte[] MissingEmailMessage = "Missing email address in request".GetBytes();
private static readonly byte[] InvalidRequestMessage = "Invalid Request XML".GetBytes();
/// <summary>
/// Invalid email address message
/// Missing email address message bytes
/// </summary>
private static readonly byte[] InvalidEmailMessage = "Invalid email address in request".GetBytes();
private static readonly byte[] MissingEmailMessage = "Missing Email Address".GetBytes();
/// <summary>
/// Unknown domain name message
/// Invalid email address message bytes
/// </summary>
private static readonly byte[] UnknownDomainMessage = "Unknown domain name".GetBytes();
private static readonly byte[] InvalidEmailMessage = "Invalid Email Address".GetBytes();
/// <summary>
/// Unknown domain name message bytes
/// </summary>
private static readonly byte[] UnknownDomainMessage = "Unknown Domain Name".GetBytes();

/// <summary>
/// Responses
/// </summary>
private readonly XmlResponseInstances Responses = responses;
private readonly InstancePool<XmlResponse> Responses = responses;

/// <summary>
/// Autodiscover (POX request body required)
Expand All @@ -72,59 +76,74 @@ 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)
Logging.WriteTrace($"POX request from {HttpContext.Connection.RemoteIpAddress}:{HttpContext.Connection.RemotePort} for email address {emailAddress.ToQuotedLiteral()}");
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");
}
}

/// <summary>
/// Respond with a bad request message
/// </summary>
/// <param name="message">Message</param>
private async Task BadRequestAsync(ReadOnlyMemory<byte> 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);
}
}
}
14 changes: 7 additions & 7 deletions src/wan24-AutoDiscover/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ async Task<IConfigurationRoot> 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();
Expand Down Expand Up @@ -128,11 +128,11 @@ async Task<IConfigurationRoot> 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<ExceptionHandler>()
.AddSingleton(typeof(InstancePool<XmlResponse>), services => new InstancePool<XmlResponse>(capacity: DiscoveryConfig.Current.PreForkResponses))
.AddSingleton(cts)
.AddHostedService(services => services.GetRequiredService<XmlResponseInstances>())
.AddHostedService(services => services.GetRequiredService<InstancePool<XmlResponse>>())
.AddHostedService(services => fsw)
.AddExceptionHandler<ExceptionHandler>()
.AddHttpLogging(options => options.LoggingFields = HttpLoggingFields.RequestPropertiesAndHeaders);
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
Expand All @@ -150,16 +150,16 @@ async Task<IConfigurationRoot> 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())
{
if (Logging.Trace)
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");
Expand Down
19 changes: 12 additions & 7 deletions src/wan24-AutoDiscover/Services/ExceptionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,36 @@ public sealed class ExceptionHandler : IExceptionHandler
/// </summary>
public const string TEXT_MIME_TYPE = "text/plain";

/// <summary>
/// Bad request message bytes
/// </summary>
private static readonly byte[] BadRequestMessage = "Bad Request".GetBytes();
/// <summary>
/// Internal server error message bytes
/// </summary>
private static readonly byte[] InternalServerErrorMessage = "Internal server error".GetBytes();
private static readonly byte[] InternalServerErrorMessage = "Internal Server Error".GetBytes();
/// <summary>
/// Maintenance message bytes
/// </summary>
private static readonly byte[] MaintenanceMessage = "Temporary not available".GetBytes();
private static readonly byte[] MaintenanceMessage = "Temporary Not Available".GetBytes();

/// <summary>
/// Constructor
/// </summary>
public ExceptionHandler() { }

/// <inheritdoc/>
public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
public ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
if (httpContext.Response.HasStarted) return ValueTask.FromResult(false);
CancellationTokenSource cts = httpContext.RequestServices.GetRequiredService<CancellationTokenSource>();
httpContext.Response.ContentType = TEXT_MIME_TYPE;
if (exception is BadHttpRequestException badRequest)
{
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)
{
Expand All @@ -59,15 +64,15 @@ public async ValueTask<bool> 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);
}
}
}
11 changes: 0 additions & 11 deletions src/wan24-AutoDiscover/Services/XmlResponseInstances.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Xml;
using System.Text;
using System.Xml;
using wan24.Core;

namespace wan24.AutoDiscover
Expand All @@ -8,16 +9,31 @@ namespace wan24.AutoDiscover
/// </summary>
public class XmlResponse : DisposableBase
{
/// <summary>
/// Buffer size in bytes
/// </summary>
private const int BUFFER_SIZE = 1024;

/// <summary>
/// XML writer settings
/// </summary>
private static readonly XmlWriterSettings Settings = new()
{
Indent = false,
Encoding = Encoding.UTF8,
OmitXmlDeclaration = true
};

/// <summary>
/// Constructor
/// </summary>
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);
Expand Down
4 changes: 2 additions & 2 deletions src/wan24-AutoDiscover/wan24-AutoDiscover.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@

<ItemGroup>
<PackageReference Include="ObjectValidation" Version="2.5.0" Condition="'$(Configuration)' != 'Trunk'" />
<PackageReference Include="wan24-CLI" Version="1.3.0" Condition="'$(Configuration)' != 'Trunk'" />
<PackageReference Include="wan24-Core" Version="2.17.0" Condition="'$(Configuration)' != 'Trunk'" />
<PackageReference Include="wan24-CLI" Version="1.4.0" Condition="'$(Configuration)' != 'Trunk'" />
<PackageReference Include="wan24-Core" Version="2.18.0" Condition="'$(Configuration)' != 'Trunk'" />
</ItemGroup>

<ItemGroup>
Expand Down

0 comments on commit 80bcb28

Please sign in to comment.