using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Net; using System.Threading.Tasks; using JetBrains.Annotations; using WireMock.Http; using WireMock.Logging; using WireMock.Matchers.Request; using WireMock.Validation; namespace WireMock.Server { /// /// The fluent mock server. /// public partial class FluentMockServer { private readonly TinyHttpServer _httpServer; private IList _mappings = new List(); private readonly IList _logEntries = new List(); private readonly HttpListenerRequestMapper _requestMapper = new HttpListenerRequestMapper(); private readonly HttpListenerResponseMapper _responseMapper = new HttpListenerResponseMapper(); private readonly object _syncRoot = new object(); private TimeSpan _requestProcessingDelay = TimeSpan.Zero; /// /// Gets the ports. /// /// /// The ports. /// public List Ports { get; } /// /// Gets the urls. /// public string[] Urls { get; } /// /// Gets the request logs. /// public IEnumerable LogEntries { get { lock (((ICollection)_logEntries).SyncRoot) { return new ReadOnlyCollection(_logEntries); } } } /// /// Gets the mappings. /// public IEnumerable Mappings { get { lock (((ICollection)_mappings).SyncRoot) { return new ReadOnlyCollection(_mappings); } } } /// /// Start this FluentMockServer. /// /// The port. /// The SSL support. /// The . [PublicAPI] public static FluentMockServer Start(int port = 0, bool ssl = false) { Check.Condition(port, p => p >= 0, nameof(port)); if (port == 0) port = PortUtil.FindFreeTcpPort(); return new FluentMockServer(false, port, ssl); } /// /// Start this FluentMockServer. /// /// The urls to listen on. /// The . [PublicAPI] public static FluentMockServer Start(params string[] urls) { Check.NotEmpty(urls, nameof(urls)); return new FluentMockServer(false, urls); } /// /// Start this FluentMockServer with the admin interface. /// /// The port. /// The SSL support. /// The . [PublicAPI] public static FluentMockServer StartWithAdminInterface(int port = 0, bool ssl = false) { Check.Condition(port, p => p >= 0, nameof(port)); if (port == 0) port = PortUtil.FindFreeTcpPort(); return new FluentMockServer(true, port, ssl); } /// /// Start this FluentMockServer with the admin interface. /// /// The urls. /// The . [PublicAPI] public static FluentMockServer StartWithAdminInterface(params string[] urls) { Check.NotEmpty(urls, nameof(urls)); return new FluentMockServer(true, urls); } private FluentMockServer(bool startAdminInterface, int port, bool ssl) : this(startAdminInterface, (ssl ? "https" : "http") + "://localhost:" + port + "/") { } private FluentMockServer(bool startAdminInterface, params string[] urls) { Urls = urls; _httpServer = new TinyHttpServer(HandleRequestAsync, urls); Ports = _httpServer.Ports; _httpServer.Start(); if (startAdminInterface) { InitAdmin(); } } /// /// Stop this server. /// public void Stop() { _httpServer.Stop(); } /// /// The reset. /// public void Reset() { ResetLogEntries(); ResetMappings(); } /// /// Resets the log entries. /// public void ResetLogEntries() { lock (((ICollection)_logEntries).SyncRoot) { _logEntries.Clear(); } } /// /// Deletes the mapping. /// /// The unique identifier. [PublicAPI] public bool DeleteLogEntry(Guid guid) { lock (((ICollection)_logEntries).SyncRoot) { // Check a logentry exists with the same GUID, if so, remove it. var existing = _logEntries.FirstOrDefault(m => m.Guid == guid); if (existing != null) { _logEntries.Remove(existing); return true; } return false; } } /// /// Resets the mappings. /// public void ResetMappings() { lock (((ICollection)_mappings).SyncRoot) { _mappings = _mappings.Where(m => m.Provider is DynamicResponseProvider).ToList(); } } /// /// Deletes the mapping. /// /// The unique identifier. [PublicAPI] public bool DeleteMapping(Guid guid) { lock (((ICollection)_mappings).SyncRoot) { // Check a mapping exists with the same GUID, if so, remove it. var existingMapping = _mappings.FirstOrDefault(m => m.Guid == guid); if (existingMapping != null) { _mappings.Remove(existingMapping); return true; } return false; } } /// /// The search logs for. /// /// The matcher. /// The . public IEnumerable SearchLogsFor(IRequestMatcher matcher) { lock (((ICollection)_logEntries).SyncRoot) { return _logEntries.Where(log => matcher.IsMatch(log.RequestMessage)); } } /// /// The add request processing delay. /// /// /// The delay. /// public void AddRequestProcessingDelay(TimeSpan delay) { lock (_syncRoot) { _requestProcessingDelay = delay; } } /// /// The given. /// /// The request matcher. /// The . public IRespondWithAProvider Given(IRequestMatcher requestMatcher) { return new RespondWithAProvider(RegisterMapping, requestMatcher); } /// /// The register mapping. /// /// /// The mapping. /// private void RegisterMapping(Mapping mapping) { lock (((ICollection)_mappings).SyncRoot) { // Check a mapping exists with the same GUID, if so, remove it first. DeleteMapping(mapping.Guid); _mappings.Add(mapping); } } /// /// The log request. /// /// The request. private void LogRequest(LogEntry entry) { lock (((ICollection)_logEntries).SyncRoot) { _logEntries.Add(entry); } } /// /// The handle request. /// /// The HttpListenerContext. private async void HandleRequestAsync(HttpListenerContext ctx) { if (_requestProcessingDelay > TimeSpan.Zero) { lock (_syncRoot) { Task.Delay(_requestProcessingDelay).Wait(); } } var request = _requestMapper.Map(ctx.Request); ResponseMessage response = null; try { var targetMapping = _mappings .OrderBy(m => m.Priority) .FirstOrDefault(m => m.IsRequestHandled(request)); if (targetMapping == null) { response = new ResponseMessage { StatusCode = 404, Body = "No mapping found" }; } else { response = await targetMapping.ResponseTo(request); } } catch (Exception ex) { response = new ResponseMessage { StatusCode = 500, Body = ex.ToString() }; } finally { var log = new LogEntry { Guid = Guid.NewGuid(), RequestMessage = request, ResponseMessage = response }; LogRequest(log); _responseMapper.Map(response, ctx.Response); ctx.Response.Close(); } } } }