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) { } } }