diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs index 71db3db2..9cc6b1ae 100644 --- a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs +++ b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using WireMock.Http; using WireMock.HttpsCertificate; +using WireMock.Logging; using WireMock.Validation; namespace WireMock.Owin @@ -18,6 +19,8 @@ namespace WireMock.Owin private readonly CancellationTokenSource _cts = new CancellationTokenSource(); private readonly WireMockMiddlewareOptions _options; private readonly string[] _urls; + private readonly IWireMockLogger _logger; + private Exception _runningException; private IWebHost _host; @@ -27,11 +30,15 @@ namespace WireMock.Owin public List Ports { get; } = new List(); + public Exception RunningException => _runningException; + public AspNetCoreSelfHost([NotNull] WireMockMiddlewareOptions options, [NotNull] params string[] uriPrefixes) { Check.NotNull(options, nameof(options)); Check.NotNullOrEmpty(uriPrefixes, nameof(uriPrefixes)); + _logger = options.Logger ?? new WireMockConsoleLogger(); + foreach (string uriPrefix in uriPrefixes) { Urls.Add(uriPrefix); @@ -89,20 +96,35 @@ namespace WireMock.Owin IsStarted = true; -#if NETSTANDARD1_3 - Console.WriteLine("WireMock.Net server using netstandard1.3"); return Task.Run(() => { - _host.Run(_cts.Token); + StartServers(); }, _cts.Token); -#else - System.Console.WriteLine("WireMock.Net server using netstandard2.0"); + } - return Task.Run(() => + private void StartServers() + { + try { + IsStarted = true; +#if NETSTANDARD1_3 + _logger.Info("WireMock.Net server using netstandard1.3"); + _host.Run(_cts.Token); +#else + _logger.Info("WireMock.Net server using netstandard2.0"); _host.Run(); - }, _cts.Token); #endif + + } + catch (Exception e) + { + _runningException = e; + _logger.Error(e.ToString()); + } + finally + { + IsStarted = false; + } } public Task StopAsync() diff --git a/src/WireMock.Net/Owin/IOwinSelfHost.cs b/src/WireMock.Net/Owin/IOwinSelfHost.cs index 9cf9f598..9fcb5455 100644 --- a/src/WireMock.Net/Owin/IOwinSelfHost.cs +++ b/src/WireMock.Net/Owin/IOwinSelfHost.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using System; namespace WireMock.Owin { @@ -29,6 +30,11 @@ namespace WireMock.Owin /// List Ports { get; } + /// + /// The exception occurred when the host is running + /// + Exception RunningException { get; } + Task StartAsync(); Task StopAsync(); diff --git a/src/WireMock.Net/Owin/OwinSelfHost.cs b/src/WireMock.Net/Owin/OwinSelfHost.cs index 3e6a1cde..675d88f5 100644 --- a/src/WireMock.Net/Owin/OwinSelfHost.cs +++ b/src/WireMock.Net/Owin/OwinSelfHost.cs @@ -1,13 +1,14 @@ #if !NETSTANDARD +using JetBrains.Annotations; +using Microsoft.Owin.Hosting; +using Owin; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using JetBrains.Annotations; -using WireMock.Validation; -using Owin; -using Microsoft.Owin.Hosting; using WireMock.Http; +using WireMock.Logging; +using WireMock.Validation; namespace WireMock.Owin { @@ -15,12 +16,16 @@ namespace WireMock.Owin { private readonly WireMockMiddlewareOptions _options; private readonly CancellationTokenSource _cts = new CancellationTokenSource(); + private readonly IWireMockLogger _logger; + private Exception _runningException; public OwinSelfHost([NotNull] WireMockMiddlewareOptions options, [NotNull] params string[] uriPrefixes) { Check.NotNull(options, nameof(options)); Check.NotNullOrEmpty(uriPrefixes, nameof(uriPrefixes)); + _logger = options.Logger ?? new WireMockConsoleLogger(); + foreach (string uriPrefix in uriPrefixes) { Urls.Add(uriPrefix); @@ -38,6 +43,8 @@ namespace WireMock.Owin public List Ports { get; } = new List(); + public Exception RunningException => _runningException; + [PublicAPI] public Task StartAsync() { @@ -58,37 +65,46 @@ namespace WireMock.Owin private void StartServers() { #if NET46 - Console.WriteLine("WireMock.Net server using .net 4.6.x or higher"); + _logger.Info("WireMock.Net server using .net 4.6.x or higher"); #else - Console.WriteLine("WireMock.Net server using .net 4.5.x or higher"); + _logger.Info("WireMock.Net server using .net 4.5.x or higher"); #endif - - Action startup = app => - { - app.Use(_options); - _options.PreWireMockMiddlewareInit?.Invoke(app); - app.Use(_options); - _options.PostWireMockMiddlewareInit?.Invoke(app); - }; - var servers = new List(); - foreach (var url in Urls) + + try { - servers.Add(WebApp.Start(url, startup)); + Action startup = app => + { + app.Use(_options); + _options.PreWireMockMiddlewareInit?.Invoke(app); + app.Use(_options); + _options.PostWireMockMiddlewareInit?.Invoke(app); + }; + + foreach (var url in Urls) + { + servers.Add(WebApp.Start(url, startup)); + } + + IsStarted = true; + + // WaitHandle is signaled when the token is cancelled, + // which will be more efficent than Thread.Sleep in while loop + _cts.Token.WaitHandle.WaitOne(); } - - IsStarted = true; - - while (!_cts.IsCancellationRequested) + catch (Exception e) { - Thread.Sleep(30000); + // Expose exception of starting host, otherwise it's hard to be troubleshooting if keeping quiet + // For example, WebApp.Start will fail with System.MissingMemberException if Microsoft.Owin.Host.HttpListener.dll is being located + // https://stackoverflow.com/questions/25090211/owin-httplistener-not-located/31369857 + _runningException = e; + _logger.Error(e.ToString()); } - - IsStarted = false; - - foreach (var server in servers) + finally { - server.Dispose(); + IsStarted = false; + // Dispose all servers in finally block to make sure clean up allocated resource on error happening + servers.ForEach((s) => s.Dispose()); } } } diff --git a/src/WireMock.Net/Server/FluentMockServer.cs b/src/WireMock.Net/Server/FluentMockServer.cs index e1242858..aafb5b8b 100644 --- a/src/WireMock.Net/Server/FluentMockServer.cs +++ b/src/WireMock.Net/Server/FluentMockServer.cs @@ -1,20 +1,20 @@ +using JetBrains.Annotations; +using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; -using System.Threading.Tasks; -using JetBrains.Annotations; -using Newtonsoft.Json; +using System.Threading; using WireMock.Http; using WireMock.Logging; using WireMock.Matchers; using WireMock.Matchers.Request; +using WireMock.Owin; using WireMock.RequestBuilders; +using WireMock.ResponseProviders; using WireMock.Settings; using WireMock.Validation; -using WireMock.Owin; -using WireMock.ResponseProviders; namespace WireMock.Server { @@ -32,7 +32,7 @@ namespace WireMock.Server /// Gets a value indicating whether this server is started. /// [PublicAPI] - public bool IsStarted { get; } + public bool IsStarted { get => _httpServer == null ? false : _httpServer.IsStarted; } /// /// Gets the ports. @@ -186,10 +186,23 @@ namespace WireMock.Server _httpServer.StartAsync(); - // Fix for 'Bug: Server not listening after Start() returns (on macOS)' - Task.Delay(ServerStartDelay).Wait(); - - IsStarted = _httpServer.IsStarted; + using (var ctsStartTimeout = new CancellationTokenSource(settings.StartTimeout)) + { + while (!_httpServer.IsStarted) + { + // Throw out exception if service start fails + if (_httpServer.RunningException != null) + { + throw new Exception($"Service start failed with error: {_httpServer.RunningException.Message}", _httpServer.RunningException); + } + // Respect start timeout setting by throwing TimeoutException + if (ctsStartTimeout.IsCancellationRequested) + { + throw new TimeoutException($"Service start timed out after {TimeSpan.FromMilliseconds(settings.StartTimeout)}"); + } + ctsStartTimeout.Token.WaitHandle.WaitOne(100); + } + } if (settings.AllowPartialMapping == true) {