using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using EmbedIO.Internal;
using EmbedIO.Routing;
using EmbedIO.Utilities;
using Swan.Configuration;
namespace EmbedIO
{
///
/// Base class to define web modules.
/// Although it is not required that a module inherits from this class,
/// it provides some useful features:
///
/// - validation and immutability of the property,
/// which are of paramount importance for the correct functioning of a web server;
/// - support for configuration locking upon web server startup
/// (see the property
/// and the method);
/// - a basic implementation of the method
/// for modules that do not need to do anything upon web server startup;
/// - implementation of the callback property.
///
///
public abstract class WebModuleBase : ConfiguredObject, IWebModule
{
private readonly RouteMatcher _routeMatcher;
private ExceptionHandlerCallback? _onUnhandledException;
private HttpExceptionHandlerCallback? _onHttpException;
///
/// Initializes a new instance of the class.
///
/// The base route served by this module.
/// is .
/// is not a valid base route.
///
///
protected WebModuleBase(string baseRoute)
{
BaseRoute = Validate.Route(nameof(baseRoute), baseRoute, true);
_routeMatcher = RouteMatcher.Parse(baseRoute, true);
LogSource = GetType().Name;
}
///
public string BaseRoute { get; }
///
/// The module's configuration is locked.
public ExceptionHandlerCallback? OnUnhandledException
{
get => _onUnhandledException;
set
{
EnsureConfigurationNotLocked();
_onUnhandledException = value;
}
}
///
/// The module's configuration is locked.
public HttpExceptionHandlerCallback? OnHttpException
{
get => _onHttpException;
set
{
EnsureConfigurationNotLocked();
_onHttpException = value;
}
}
///
public abstract bool IsFinalHandler { get; }
///
/// Gets a string to use as a source for log messages.
///
protected string LogSource { get; }
///
///
/// The module's configuration is locked before returning from this method.
///
public void Start(CancellationToken cancellationToken)
{
OnStart(cancellationToken);
LockConfiguration();
}
///
public RouteMatch? MatchUrlPath(string urlPath) => _routeMatcher.Match(urlPath);
///
public async Task HandleRequestAsync(IHttpContext context)
{
var contextImpl = context.GetImplementation();
var mimeTypeProvider = this as IMimeTypeProvider;
if (mimeTypeProvider != null)
contextImpl?.MimeTypeProviders.Push(mimeTypeProvider);
try
{
await OnRequestAsync(context).ConfigureAwait(false);
if (IsFinalHandler)
context.SetHandled();
}
catch (RequestHandlerPassThroughException)
{ }
catch (OperationCanceledException) when (context.CancellationToken.IsCancellationRequested)
{
throw; // Let the web server handle it
}
catch (HttpListenerException)
{
throw; // Let the web server handle it
}
catch (Exception exception) when (exception is IHttpException)
{
await HttpExceptionHandler.Handle(LogSource, context, exception, _onHttpException)
.ConfigureAwait(false);
}
catch (Exception exception)
{
await ExceptionHandler.Handle(LogSource, context, exception, _onUnhandledException, _onHttpException)
.ConfigureAwait(false);
}
finally
{
if (mimeTypeProvider != null)
contextImpl?.MimeTypeProviders.Pop();
}
}
///
/// Called to handle a request from a client.
///
/// The context of the request being handled.
/// A representing the ongoing operation.
protected abstract Task OnRequestAsync(IHttpContext context);
///
/// Called when a module is started, immediately before locking the module's configuration.
///
/// A used to stop the web server.
protected virtual void OnStart(CancellationToken cancellationToken)
{
}
}
}