diff --git a/examples/WireMock.Net.Console.NETCoreApp/Program.cs b/examples/WireMock.Net.Console.NETCoreApp/Program.cs index 90297c8c..e5f99e98 100644 --- a/examples/WireMock.Net.Console.NETCoreApp/Program.cs +++ b/examples/WireMock.Net.Console.NETCoreApp/Program.cs @@ -1,11 +1,21 @@ -using WireMock.Net.ConsoleApplication; +using System.IO; +using System.Reflection; +using log4net; +using log4net.Config; +using log4net.Repository; +using WireMock.Net.ConsoleApplication; namespace WireMock.Net.Console.NETCoreApp { static class Program { + private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); + private static readonly ILog Log = LogManager.GetLogger(typeof(Program)); + static void Main(params string[] args) { + XmlConfigurator.Configure(LogRepository, new FileInfo("log4net.config")); + MainApp.Run(); } } diff --git a/examples/WireMock.Net.Console.NETCoreApp/WireMock.Net.Console.NETCoreApp.csproj b/examples/WireMock.Net.Console.NETCoreApp/WireMock.Net.Console.NETCoreApp.csproj index 37a4ed8d..4b6c9735 100644 --- a/examples/WireMock.Net.Console.NETCoreApp/WireMock.Net.Console.NETCoreApp.csproj +++ b/examples/WireMock.Net.Console.NETCoreApp/WireMock.Net.Console.NETCoreApp.csproj @@ -22,10 +22,17 @@ + + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/examples/WireMock.Net.Console.NETCoreApp/log4net.config b/examples/WireMock.Net.Console.NETCoreApp/log4net.config new file mode 100644 index 00000000..feae9952 --- /dev/null +++ b/examples/WireMock.Net.Console.NETCoreApp/log4net.config @@ -0,0 +1,20 @@ + + + +
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/WireMock.Net.Console.NETCoreApp/nlog.config b/examples/WireMock.Net.Console.NETCoreApp/nlog.config new file mode 100644 index 00000000..de6d9072 --- /dev/null +++ b/examples/WireMock.Net.Console.NETCoreApp/nlog.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/WireMock.Net.ConsoleApplication/MainApp.cs b/examples/WireMock.Net.ConsoleApplication/MainApp.cs index 6549b171..09e4e290 100644 --- a/examples/WireMock.Net.ConsoleApplication/MainApp.cs +++ b/examples/WireMock.Net.ConsoleApplication/MainApp.cs @@ -54,8 +54,9 @@ namespace WireMock.Net.ConsoleApplication .Given(Request.Create().WithPath("/headers", "/headers_test").UsingPost().WithHeader("Content-Type", "application/json*")) .RespondWith(Response.Create() .WithStatusCode(201) - .WithHeader("MyHeader", "application/json", "application/json2") - .WithBody(@"{ ""result"": ""data posted with 201""}")); + //.WithHeader("MyHeader", "application/json", "application/json2") + .WithHeader("Content-Type", "application/json") + .WithBodyAsJson(new { result = "data:headers posted with 201" })); server .Given(Request.Create().WithPath("/file").UsingGet()) @@ -117,7 +118,7 @@ namespace WireMock.Net.ConsoleApplication .RespondWith(Response.Create() .WithStatusCode(201) .WithHeader("Content-Type", "application/json") - .WithBody(@"{ ""result"": ""data posted with FUNC 201""}")); + .WithBodyAsJson(new { result = "data posted with FUNC 201" })); server .Given(Request.Create().WithPath("/json").UsingPost().WithBody(new JsonPathMatcher("$.things[?(@.name == 'RequiredThing')]"))) diff --git a/examples/WireMock.Net.ConsoleApplication/Program.cs b/examples/WireMock.Net.ConsoleApplication/Program.cs index 5765ad6c..c143a0f3 100644 --- a/examples/WireMock.Net.ConsoleApplication/Program.cs +++ b/examples/WireMock.Net.ConsoleApplication/Program.cs @@ -1,9 +1,14 @@ -namespace WireMock.Net.ConsoleApplication +using System.IO; +using log4net.Config; + +namespace WireMock.Net.ConsoleApplication { static class Program { static void Main(params string[] args) { + XmlConfigurator.Configure(new FileInfo("log4net.config")); + MainApp.Run(); } } diff --git a/examples/WireMock.Net.ConsoleApplication/WireMock.Net.Console.NET452.csproj b/examples/WireMock.Net.ConsoleApplication/WireMock.Net.Console.NET452.csproj index 9c5a42ca..5f0a26a8 100644 --- a/examples/WireMock.Net.ConsoleApplication/WireMock.Net.Console.NET452.csproj +++ b/examples/WireMock.Net.ConsoleApplication/WireMock.Net.Console.NET452.csproj @@ -36,17 +36,22 @@ ..\..\WireMock.Net-Logo.ico + + ..\..\packages\log4net.2.0.8\lib\net45-full\log4net.dll + + ..\..\packages\Microsoft.Owin.Host.HttpListener.3.1.0\lib\net45\Microsoft.Owin.Host.HttpListener.dll ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll - - ..\..\packages\SimMetrics.Net.1.0.3\lib\net45\SimMetrics.Net.dll + + ..\..\packages\SimMetrics.Net.1.0.4\lib\net45\SimMetrics.Net.dll + @@ -57,6 +62,9 @@ Designer + + PreserveNewest + Designer diff --git a/examples/WireMock.Net.ConsoleApplication/log4net.config b/examples/WireMock.Net.ConsoleApplication/log4net.config new file mode 100644 index 00000000..feae9952 --- /dev/null +++ b/examples/WireMock.Net.ConsoleApplication/log4net.config @@ -0,0 +1,20 @@ + + + +
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/WireMock.Net.ConsoleApplication/packages.config b/examples/WireMock.Net.ConsoleApplication/packages.config index 26f32c8a..a1ad973f 100644 --- a/examples/WireMock.Net.ConsoleApplication/packages.config +++ b/examples/WireMock.Net.ConsoleApplication/packages.config @@ -1,6 +1,7 @@  + - + \ No newline at end of file diff --git a/examples/WireMock.Net.StandAlone.NETCoreApp/Program.cs b/examples/WireMock.Net.StandAlone.NETCoreApp/Program.cs index b3af5d9d..b7d69600 100644 --- a/examples/WireMock.Net.StandAlone.NETCoreApp/Program.cs +++ b/examples/WireMock.Net.StandAlone.NETCoreApp/Program.cs @@ -1,16 +1,26 @@ using System; +using System.IO; +using System.Reflection; using System.Threading; +using log4net; +using log4net.Config; +using log4net.Repository; using WireMock.Server; namespace WireMock.Net.StandAlone.NETCoreApp { - class Program + static class Program { + private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); + // private static readonly ILog Log = LogManager.GetLogger(typeof(Program)); + private static int sleepTime = 30000; private static FluentMockServer _server; static void Main(string[] args) { + XmlConfigurator.Configure(LogRepository, new FileInfo("log4net.config")); + _server = StandAloneApp.Start(args); Console.WriteLine($"{DateTime.UtcNow} Press Ctrl+C to shut down"); diff --git a/examples/WireMock.Net.StandAlone.NETCoreApp/WireMock.Net.StandAlone.NETCoreApp.csproj b/examples/WireMock.Net.StandAlone.NETCoreApp/WireMock.Net.StandAlone.NETCoreApp.csproj index 6b7ea76f..5b2ac302 100644 --- a/examples/WireMock.Net.StandAlone.NETCoreApp/WireMock.Net.StandAlone.NETCoreApp.csproj +++ b/examples/WireMock.Net.StandAlone.NETCoreApp/WireMock.Net.StandAlone.NETCoreApp.csproj @@ -6,8 +6,18 @@ ../../WireMock.Net-Logo.ico + + + + + + + PreserveNewest + + + \ No newline at end of file diff --git a/examples/WireMock.Net.StandAlone.NETCoreApp/log4net.config b/examples/WireMock.Net.StandAlone.NETCoreApp/log4net.config new file mode 100644 index 00000000..feae9952 --- /dev/null +++ b/examples/WireMock.Net.StandAlone.NETCoreApp/log4net.config @@ -0,0 +1,20 @@ + + + +
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/WireMock.Net.StandAlone.Net452/Program.cs b/examples/WireMock.Net.StandAlone.Net452/Program.cs index 36bd441f..69d444e4 100644 --- a/examples/WireMock.Net.StandAlone.Net452/Program.cs +++ b/examples/WireMock.Net.StandAlone.Net452/Program.cs @@ -1,4 +1,6 @@ using System; +using System.IO; +using log4net.Config; namespace WireMock.Net.StandAlone.Net452 { @@ -6,6 +8,8 @@ namespace WireMock.Net.StandAlone.Net452 { static void Main(params string[] args) { + XmlConfigurator.Configure(new FileInfo("log4net.config")); + StandAloneApp.Start(args); Console.WriteLine("Press any key to stop the server"); diff --git a/examples/WireMock.Net.StandAlone.Net452/WireMock.Net.StandAlone.Net452.csproj b/examples/WireMock.Net.StandAlone.Net452/WireMock.Net.StandAlone.Net452.csproj index 81075ad1..1157e052 100644 --- a/examples/WireMock.Net.StandAlone.Net452/WireMock.Net.StandAlone.Net452.csproj +++ b/examples/WireMock.Net.StandAlone.Net452/WireMock.Net.StandAlone.Net452.csproj @@ -39,6 +39,9 @@ WireMock.Net.StandAlone.Net452.Program + + ..\..\packages\log4net.2.0.8\lib\net45-full\log4net.dll + @@ -50,6 +53,9 @@ + + PreserveNewest + diff --git a/examples/WireMock.Net.StandAlone.Net452/log4net.config b/examples/WireMock.Net.StandAlone.Net452/log4net.config new file mode 100644 index 00000000..feae9952 --- /dev/null +++ b/examples/WireMock.Net.StandAlone.Net452/log4net.config @@ -0,0 +1,20 @@ + + + +
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/WireMock.Net.StandAlone.Net452/packages.config b/examples/WireMock.Net.StandAlone.Net452/packages.config index 6b8deb9c..a3909492 100644 --- a/examples/WireMock.Net.StandAlone.Net452/packages.config +++ b/examples/WireMock.Net.StandAlone.Net452/packages.config @@ -1,3 +1,4 @@  + \ No newline at end of file diff --git a/src/WireMock.Net.StandAlone/StandAloneApp.cs b/src/WireMock.Net.StandAlone/StandAloneApp.cs index aedea35d..426a4744 100644 --- a/src/WireMock.Net.StandAlone/StandAloneApp.cs +++ b/src/WireMock.Net.StandAlone/StandAloneApp.cs @@ -1,10 +1,9 @@ -using System; -using System.Linq; +using System.Linq; using WireMock.Server; using WireMock.Settings; using WireMock.Validation; using JetBrains.Annotations; -using Newtonsoft.Json; +using log4net; namespace WireMock.Net.StandAlone { @@ -13,8 +12,10 @@ namespace WireMock.Net.StandAlone /// public static class StandAloneApp { + private static readonly ILog Log = LogManager.GetLogger(typeof(StandAloneApp)); + /// - /// Start WireMock.Net standalone based on the FluentMockServerSettings. + /// Start WireMock.Net standalone Server based on the FluentMockServerSettings. /// /// The FluentMockServerSettings [PublicAPI] @@ -26,7 +27,7 @@ namespace WireMock.Net.StandAlone } /// - /// Start WireMock.Net standalone based on the commandline arguments. + /// Start WireMock.Net standalone Server based on the commandline arguments. /// /// The commandline arguments [PublicAPI] @@ -34,7 +35,7 @@ namespace WireMock.Net.StandAlone { Check.NotNull(args, nameof(args)); - Console.WriteLine("WireMock.Net server arguments [{0}]", string.Join(", ", args.Select(a => $"'{a}'"))); + Log.DebugFormat("WireMock.Net server arguments [{0}]", string.Join(", ", args.Select(a => $"'{a}'"))); var parser = new SimpleCommandLineParser(); parser.Parse(args); @@ -73,11 +74,9 @@ namespace WireMock.Net.StandAlone }; } - Console.WriteLine("WireMock.Net server settings {0}", JsonConvert.SerializeObject(settings, Formatting.Indented)); - FluentMockServer server = Start(settings); - Console.WriteLine("WireMock.Net server listening at {0}", string.Join(",", server.Urls)); + Log.InfoFormat("WireMock.Net server listening at {0}", string.Join(",", server.Urls)); return server; } diff --git a/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj b/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj index 2d48a7ad..a33887f9 100644 --- a/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj +++ b/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj @@ -3,7 +3,7 @@ Lightweight StandAlone Http Mocking Server for .Net. WireMock.Net.StandAlone - 1.0.3 + 1.0.3.1 Stef Heyenrath net452;net46;netstandard1.3;netstandard2.0 true @@ -36,10 +36,7 @@ All - - - - + diff --git a/src/WireMock.Net/Admin/Requests/LogEntryModel.cs b/src/WireMock.Net/Admin/Requests/LogEntryModel.cs index d9d5263c..8a97f0fd 100644 --- a/src/WireMock.Net/Admin/Requests/LogEntryModel.cs +++ b/src/WireMock.Net/Admin/Requests/LogEntryModel.cs @@ -8,51 +8,33 @@ namespace WireMock.Admin.Requests public class LogEntryModel { /// - /// Gets or sets the unique identifier. - /// - /// /// The unique identifier. - /// + /// public Guid Guid { get; set; } /// - /// Gets or sets the request. - /// - /// /// The request. - /// + /// public LogRequestModel Request { get; set; } /// - /// Gets or sets the response. - /// - /// /// The response. - /// + /// public LogResponseModel Response { get; set; } /// - /// Gets or sets the mapping unique identifier. - /// - /// /// The mapping unique identifier. - /// + /// public Guid? MappingGuid { get; set; } /// - /// Gets or sets the mapping unique title. - /// - /// /// The mapping unique title. - /// + /// public string MappingTitle { get; set; } /// - /// Gets or sets the request match result. - /// - /// /// The request match result. - /// + /// public LogRequestMatchModel RequestMatchResult { get; set; } } } \ No newline at end of file diff --git a/src/WireMock.Net/Admin/Requests/LogRequestModel.cs b/src/WireMock.Net/Admin/Requests/LogRequestModel.cs index c6e04fcc..20b4ea0a 100644 --- a/src/WireMock.Net/Admin/Requests/LogRequestModel.cs +++ b/src/WireMock.Net/Admin/Requests/LogRequestModel.cs @@ -11,52 +11,62 @@ namespace WireMock.Admin.Requests public class LogRequestModel { /// - /// Gets the Client IP Address. + /// The Client IP Address. /// public string ClientIP { get; set; } /// - /// Gets the DateTime. + /// The DateTime. /// public DateTime DateTime { get; set; } /// - /// Gets or sets the Path. + /// The Path. /// public string Path { get; set; } /// - /// Gets or sets the absolete URL. + ///The absolete URL. /// public string AbsoluteUrl { get; set; } /// - /// Gets the query. + /// The query. /// public IDictionary> Query { get; set; } /// - /// Gets or sets the method. + /// The method. /// public string Method { get; set; } /// - /// Gets or sets the Headers. + /// The Headers. /// public IDictionary> Headers { get; set; } /// - /// Gets or sets the Cookies. + /// Tthe Cookies. /// public IDictionary Cookies { get; set; } /// - /// Gets or sets the body. + /// The body (as string). /// public string Body { get; set; } /// - /// Gets or sets the body encoding. + /// The body (as JSON object). + /// + public object BodyAsJson { get; set; } + + /// + /// The body (as bytearray). + /// + public byte[] BodyAsBytes { get; set; } + + /// + /// The body encoding. /// public EncodingModel BodyEncoding { get; set; } } diff --git a/src/WireMock.Net/Admin/Requests/LogResponseModel.cs b/src/WireMock.Net/Admin/Requests/LogResponseModel.cs index edb88a77..60190c63 100644 --- a/src/WireMock.Net/Admin/Requests/LogResponseModel.cs +++ b/src/WireMock.Net/Admin/Requests/LogResponseModel.cs @@ -25,12 +25,17 @@ namespace WireMock.Admin.Requests public string BodyDestination { get; set; } /// - /// Gets or sets the body. + /// The body (as string). /// public string Body { get; set; } /// - /// Gets or sets the body as bytes. + /// The body (as JSON object). + /// + public object BodyAsJson { get; set; } + + /// + /// The body (as bytearray). /// public byte[] BodyAsBytes { get; set; } diff --git a/src/WireMock.Net/Http/HttpClientHelper.cs b/src/WireMock.Net/Http/HttpClientHelper.cs index b7634efc..431ab487 100644 --- a/src/WireMock.Net/Http/HttpClientHelper.cs +++ b/src/WireMock.Net/Http/HttpClientHelper.cs @@ -4,8 +4,10 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; +using JetBrains.Annotations; using Newtonsoft.Json; using WireMock.HttpsCertificate; +using WireMock.Util; using WireMock.Validation; namespace WireMock.Http @@ -51,20 +53,36 @@ namespace WireMock.Http return client; } - public static async Task SendAsync(HttpClient client, RequestMessage requestMessage, string url) + public static async Task SendAsync([NotNull] HttpClient client, [NotNull] RequestMessage requestMessage, string url) { Check.NotNull(client, nameof(client)); + Check.NotNull(requestMessage, nameof(requestMessage)); var originalUri = new Uri(requestMessage.Url); var requiredUri = new Uri(url); var httpRequestMessage = new HttpRequestMessage(new HttpMethod(requestMessage.Method), url); + WireMockList contentTypeHeader = null; + bool contentTypeHeaderPresent = requestMessage.Headers.Any(header => string.Equals(header.Key, HttpKnownHeaderNames.ContentType, StringComparison.OrdinalIgnoreCase)); + if (contentTypeHeaderPresent) + { + contentTypeHeader = requestMessage.Headers[HttpKnownHeaderNames.ContentType]; + } + // Set Body if present if (requestMessage.BodyAsBytes != null) { httpRequestMessage.Content = new ByteArrayContent(requestMessage.BodyAsBytes); } + else if (requestMessage.BodyAsJson != null) + { + httpRequestMessage.Content = new StringContent(JsonConvert.SerializeObject(requestMessage.BodyAsJson), requestMessage.BodyEncoding); + } + else if (requestMessage.Body != null) + { + httpRequestMessage.Content = new StringContent(requestMessage.Body, requestMessage.BodyEncoding); + } // Overwrite the host header httpRequestMessage.Headers.Host = requiredUri.Authority; @@ -90,10 +108,13 @@ namespace WireMock.Http // Set both content and response headers, replacing URLs in values var headers = (httpResponseMessage.Content?.Headers.Union(httpResponseMessage.Headers) ?? Enumerable.Empty>>()).ToArray(); - var contentTypeHeader = headers.FirstOrDefault(header => string.Equals(header.Key, HttpKnownHeaderNames.ContentType, StringComparison.OrdinalIgnoreCase)); if (httpResponseMessage.Content != null) { - SetBody(httpResponseMessage.Content, contentTypeHeader, responseMessage); + var stream = await httpResponseMessage.Content.ReadAsStreamAsync(); + var body = await BodyParser.Parse(stream, contentTypeHeader?.FirstOrDefault()); + responseMessage.Body = body.BodyAsString; + responseMessage.BodyAsJson = body.BodyAsJson; + responseMessage.BodyAsBytes = body.BodyAsBytes; } foreach (var header in headers) @@ -115,41 +136,5 @@ namespace WireMock.Http return responseMessage; } - - private static async void SetBody(HttpContent content, KeyValuePair> contentTypeHeader, ResponseMessage responseMessage) - { - bool contentTypeIsDefault = contentTypeHeader.Equals(default(KeyValuePair>)); - string[] textContentTypes = { "text/", "application/xml", "application/javascript", "application/typescript", "application/xhtml+xml" }; - - if (!contentTypeIsDefault && contentTypeHeader.Value.Any(value => textContentTypes.Any(t => value != null && value.StartsWith(t, StringComparison.OrdinalIgnoreCase)))) - { - try - { - responseMessage.Body = await content.ReadAsStringAsync(); - } - catch - { - // Reading as string failed, just get the ByteArray. - responseMessage.BodyAsBytes = await content.ReadAsByteArrayAsync(); - } - } - else if (!contentTypeIsDefault && contentTypeHeader.Value.Any(value => value != null && value.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))) - { - string stringContent = await content.ReadAsStringAsync(); - try - { - responseMessage.BodyAsJson = JsonConvert.DeserializeObject(stringContent, new JsonSerializerSettings { Formatting = Formatting.Indented }); - } - catch - { - // JsonConvert failed, just set the Body as string. - responseMessage.Body = stringContent; - } - } - else - { - responseMessage.BodyAsBytes = await content.ReadAsByteArrayAsync(); - } - } } } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/GlobalExceptionMiddleware.cs b/src/WireMock.Net/Owin/GlobalExceptionMiddleware.cs index faf275ac..f2e790f4 100644 --- a/src/WireMock.Net/Owin/GlobalExceptionMiddleware.cs +++ b/src/WireMock.Net/Owin/GlobalExceptionMiddleware.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using log4net; using Newtonsoft.Json; #if !NETSTANDARD using Microsoft.Owin; @@ -15,6 +16,7 @@ namespace WireMock.Owin internal class GlobalExceptionMiddleware #endif { + private static readonly ILog Log = LogManager.GetLogger(typeof(GlobalExceptionMiddleware)); #if !NETSTANDARD public GlobalExceptionMiddleware(OwinMiddleware next) : base(next) { } #else @@ -42,6 +44,7 @@ namespace WireMock.Owin } catch (Exception ex) { + Log.Error("HttpStatusCode set to 500", ex); await _responseMapper.MapAsync(new ResponseMessage { StatusCode = 500, Body = JsonConvert.SerializeObject(ex) }, ctx.Response); } } diff --git a/src/WireMock.Net/Owin/OwinRequestMapper.cs b/src/WireMock.Net/Owin/OwinRequestMapper.cs index 4ac662f3..ef086115 100644 --- a/src/WireMock.Net/Owin/OwinRequestMapper.cs +++ b/src/WireMock.Net/Owin/OwinRequestMapper.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using WireMock.Util; #if !NETSTANDARD using Microsoft.Owin; #else @@ -16,7 +17,7 @@ namespace WireMock.Owin /// /// OwinRequestMapper /// - public class OwinRequestMapper + internal class OwinRequestMapper { /// /// MapAsync IOwinRequest to RequestMessage @@ -43,20 +44,6 @@ namespace WireMock.Owin #endif string method = request.Method; - string bodyAsString = null; - byte[] body = null; - Encoding bodyEncoding = null; - if (ParseBody(method) && request.Body != null) - { - using (var streamReader = new StreamReader(request.Body)) - { - bodyAsString = await streamReader.ReadToEndAsync(); - bodyEncoding = streamReader.CurrentEncoding; - } - - body = bodyEncoding.GetBytes(bodyAsString); - } - Dictionary headers = null; if (request.Headers.Any()) { @@ -77,10 +64,16 @@ namespace WireMock.Owin } } - return new RequestMessage(url, method, clientIP, body, bodyAsString, bodyEncoding, headers, cookies) { DateTime = DateTime.Now }; + BodyData body = null; + if (request.Body != null && ShouldParseBody(method)) + { + body = await BodyParser.Parse(request.Body, request.ContentType); + } + + return new RequestMessage(url, method, clientIP, body, headers, cookies) { DateTime = DateTime.Now }; } - private bool ParseBody(string method) + private bool ShouldParseBody(string method) { /* HEAD - No defined body semantics. diff --git a/src/WireMock.Net/Owin/WireMockMiddleware.cs b/src/WireMock.Net/Owin/WireMockMiddleware.cs index 881458d8..d4dd61c7 100644 --- a/src/WireMock.Net/Owin/WireMockMiddleware.cs +++ b/src/WireMock.Net/Owin/WireMockMiddleware.cs @@ -3,9 +3,11 @@ using System.Threading.Tasks; using WireMock.Logging; using WireMock.Matchers.Request; using System.Linq; +using log4net; using WireMock.Matchers; using WireMock.Util; using Newtonsoft.Json; +using WireMock.Http; #if !NETSTANDARD using Microsoft.Owin; #else @@ -20,6 +22,7 @@ namespace WireMock.Owin internal class WireMockMiddleware #endif { + private static readonly ILog Log = LogManager.GetLogger(typeof(WireMockMiddleware)); private static readonly Task CompletedTask = Task.FromResult(false); private readonly WireMockMiddlewareOptions _options; @@ -95,6 +98,7 @@ namespace WireMock.Owin if (targetMapping == null) { logRequest = true; + Log.Warn("HttpStatusCode set to 404 : No matching mapping found"); response = new ResponseMessage { StatusCode = 404, Body = "No matching mapping found" }; return; } @@ -103,9 +107,10 @@ namespace WireMock.Owin if (targetMapping.IsAdminInterface && _options.AuthorizationMatcher != null) { - bool present = request.Headers.TryGetValue("Authorization", out WireMockList authorization); + bool present = request.Headers.TryGetValue(HttpKnownHeaderNames.Authorization, out WireMockList authorization); if (!present || _options.AuthorizationMatcher.IsMatch(authorization.ToString()) < MatchScores.Perfect) { + Log.Error("HttpStatusCode set to 401"); response = new ResponseMessage { StatusCode = 401 }; return; } @@ -125,6 +130,7 @@ namespace WireMock.Owin } catch (Exception ex) { + Log.Error("HttpStatusCode set to 500", ex); response = new ResponseMessage { StatusCode = 500, Body = JsonConvert.SerializeObject(ex) }; } finally diff --git a/src/WireMock.Net/RequestMessage.cs b/src/WireMock.Net/RequestMessage.cs index 75177ca1..f2d3346b 100644 --- a/src/WireMock.Net/RequestMessage.cs +++ b/src/WireMock.Net/RequestMessage.cs @@ -60,15 +60,20 @@ namespace WireMock public string RawQuery { get; } /// - /// Gets the bodyAsBytes. - /// - public byte[] BodyAsBytes { get; } - - /// - /// Gets the body. + /// The body as string. /// public string Body { get; } + /// + /// The body (as JSON object). + /// + public object BodyAsJson { get; set; } + + /// + /// The body (as bytearray). + /// + public byte[] BodyAsBytes { get; set; } + /// /// Gets the Host /// @@ -90,10 +95,45 @@ namespace WireMock public string Origin { get; } /// - /// Gets the body encoding. + /// The body encoding. /// public Encoding BodyEncoding { get; } + /// + /// Initializes a new instance of the class. + /// + /// The original url. + /// The HTTP method. + /// The client IP Address. + /// The body. + /// The headers. + /// The cookies. + public RequestMessage([NotNull] Uri url, [NotNull] string method, [NotNull] string clientIP, [CanBeNull] BodyData body, [CanBeNull] IDictionary headers = null, [CanBeNull] IDictionary cookies = null) + { + Check.NotNull(url, nameof(url)); + Check.NotNull(method, nameof(method)); + Check.NotNull(clientIP, nameof(clientIP)); + + Url = url.ToString(); + Protocol = url.Scheme; + Host = url.Host; + Port = url.Port; + Origin = $"{url.Scheme}://{url.Host}:{url.Port}"; + Path = WebUtility.UrlDecode(url.AbsolutePath); + Method = method.ToLower(); + ClientIP = clientIP; + + Body = body?.BodyAsString; + BodyEncoding = body?.Encoding; + BodyAsJson = body?.BodyAsJson; + BodyAsBytes = body?.BodyAsBytes; + + Headers = headers?.ToDictionary(header => header.Key, header => new WireMockList(header.Value)); + Cookies = cookies; + RawQuery = WebUtility.UrlDecode(url.Query); + Query = ParseQuery(RawQuery); + } + /// /// Initializes a new instance of the class. /// diff --git a/src/WireMock.Net/ResponseBuilders/Response.cs b/src/WireMock.Net/ResponseBuilders/Response.cs index 920869a9..f55181eb 100644 --- a/src/WireMock.Net/ResponseBuilders/Response.cs +++ b/src/WireMock.Net/ResponseBuilders/Response.cs @@ -247,16 +247,9 @@ namespace WireMock.ResponseBuilders { Check.NotNull(body, nameof(body)); - string jsonBody = JsonConvert.SerializeObject(body, new JsonSerializerSettings { Formatting = Formatting.None, NullValueHandling = NullValueHandling.Ignore }); - - if (encoding != null && !encoding.Equals(Encoding.UTF8)) - { - jsonBody = encoding.GetString(Encoding.UTF8.GetBytes(jsonBody)); - ResponseMessage.BodyEncoding = encoding; - } - ResponseMessage.BodyDestination = null; - ResponseMessage.Body = jsonBody; + ResponseMessage.BodyAsJson = body; + ResponseMessage.BodyEncoding = encoding; return this; } diff --git a/src/WireMock.Net/Server/FluentMockServer.Admin.cs b/src/WireMock.Net/Server/FluentMockServer.Admin.cs index 8202f58d..3f51c570 100644 --- a/src/WireMock.Net/Server/FluentMockServer.Admin.cs +++ b/src/WireMock.Net/Server/FluentMockServer.Admin.cs @@ -7,6 +7,7 @@ using System.Text; using System.Threading.Tasks; using JetBrains.Annotations; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using WireMock.Admin.Mappings; using WireMock.Admin.Requests; using WireMock.Admin.Settings; @@ -112,6 +113,7 @@ namespace WireMock.Server foreach (string filename in Directory.EnumerateFiles(folder).OrderBy(f => f)) { + Log.InfoFormat("Reading Static MappingFile : '{0}'", filename); ReadStaticMappingAndAddOrUpdate(filename); } } @@ -133,17 +135,22 @@ namespace WireMock.Server return; } - var watcher = new EnhancedFileSystemWatcher(folder, "*.json", 500); + Log.InfoFormat("Watching folder '{0}' for new, updated and deleted MappingFiles.", folder); + + var watcher = new EnhancedFileSystemWatcher(folder, "*.json", 1000); watcher.Created += (sender, args) => { + Log.InfoFormat("New MappingFile created : '{0}'", args.FullPath); ReadStaticMappingAndAddOrUpdate(args.FullPath); }; watcher.Changed += (sender, args) => { + Log.InfoFormat("New MappingFile updated : '{0}'", args.FullPath); ReadStaticMappingAndAddOrUpdate(args.FullPath); }; watcher.Deleted += (sender, args) => { + Log.InfoFormat("New MappingFile deleted : '{0}'", args.FullPath); string filenameWithoutExtension = Path.GetFileNameWithoutExtension(args.FullPath); if (Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename)) @@ -170,13 +177,14 @@ namespace WireMock.Server string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path); + MappingModel mappingModel = JsonConvert.DeserializeObject(FileHelper.ReadAllText(path)); if (Guid.TryParse(filenameWithoutExtension, out Guid guidFromFilename)) { - DeserializeAndAddOrUpdateMapping(FileHelper.ReadAllText(path), guidFromFilename, path); + DeserializeAndAddOrUpdateMapping(mappingModel, guidFromFilename, path); } else { - DeserializeAndAddOrUpdateMapping(FileHelper.ReadAllText(path), null, path); + DeserializeAndAddOrUpdateMapping(mappingModel, null, path); } } #endregion @@ -256,7 +264,7 @@ namespace WireMock.Server private ResponseMessage SettingsUpdate(RequestMessage requestMessage) { - var settings = JsonConvert.DeserializeObject(requestMessage.Body); + var settings = requestMessage.Body != null ? JsonConvert.DeserializeObject(requestMessage.Body) : ((JObject)requestMessage.BodyAsJson).ToObject(); if (settings.AllowPartialMapping != null) _options.AllowPartialMapping = settings.AllowPartialMapping.Value; @@ -280,6 +288,7 @@ namespace WireMock.Server if (mapping == null) { + Log.Warn("HttpStatusCode set to 404 : Mapping not found"); return new ResponseMessage { StatusCode = 404, Body = "Mapping not found" }; } @@ -292,7 +301,8 @@ namespace WireMock.Server { Guid guid = Guid.Parse(requestMessage.Path.TrimStart(AdminMappings.ToCharArray())); - DeserializeAndAddOrUpdateMapping(requestMessage.Body, guid); + MappingModel mappingModel = requestMessage.Body != null ? JsonConvert.DeserializeObject(requestMessage.Body) : ((JObject)requestMessage.BodyAsJson).ToObject(); + DeserializeAndAddOrUpdateMapping(mappingModel, guid); return new ResponseMessage { Body = "Mapping added or updated" }; } @@ -330,10 +340,12 @@ namespace WireMock.Server } var model = MappingConverter.ToMappingModel(mapping); - string json = JsonConvert.SerializeObject(model, _settings); string filename = !string.IsNullOrEmpty(mapping.Title) ? SanitizeFileName(mapping.Title) : mapping.Guid.ToString(); - File.WriteAllText(Path.Combine(folder, filename + ".json"), json); + string filePath = Path.Combine(folder, filename + ".json"); + Log.InfoFormat("Saving Mapping to file {0}", filePath); + + File.WriteAllText(filePath, JsonConvert.SerializeObject(model, _settings)); } private static string SanitizeFileName(string name, char replaceChar = '_') @@ -357,24 +369,25 @@ namespace WireMock.Server { try { - DeserializeAndAddOrUpdateMapping(requestMessage.Body); + MappingModel mappingModel = requestMessage.Body != null ? JsonConvert.DeserializeObject(requestMessage.Body) : ((JObject)requestMessage.BodyAsJson).ToObject(); + DeserializeAndAddOrUpdateMapping(mappingModel); } catch (ArgumentException a) { + Log.Error("HttpStatusCode set to 400", a); return new ResponseMessage { StatusCode = 400, Body = a.Message }; } catch (Exception e) { + Log.Error("HttpStatusCode set to 500", e); return new ResponseMessage { StatusCode = 500, Body = e.ToString() }; } return new ResponseMessage { StatusCode = 201, Body = "Mapping added" }; } - private void DeserializeAndAddOrUpdateMapping(string json, Guid? guid = null, string path = null) + private void DeserializeAndAddOrUpdateMapping(MappingModel mappingModel, Guid? guid = null, string path = null) { - var mappingModel = JsonConvert.DeserializeObject(json); - Check.NotNull(mappingModel, nameof(mappingModel)); Check.NotNull(mappingModel.Request, nameof(mappingModel.Request)); Check.NotNull(mappingModel.Response, nameof(mappingModel.Response)); @@ -435,7 +448,10 @@ namespace WireMock.Server var entry = LogEntries.FirstOrDefault(r => !r.RequestMessage.Path.StartsWith("/__admin/") && r.Guid == guid); if (entry == null) - return new ResponseMessage { StatusCode = 404, Body = "Request not found" }; + { + Log.Warn("HttpStatusCode set to 404 : Request not found"); + return new ResponseMessage {StatusCode = 404, Body = "Request not found"}; + } var model = ToLogEntryModel(entry); @@ -477,6 +493,8 @@ namespace WireMock.Server Query = logEntry.RequestMessage.Query, Method = logEntry.RequestMessage.Method, Body = logEntry.RequestMessage.Body, + BodyAsJson = logEntry.RequestMessage.BodyAsJson, + BodyAsBytes = logEntry.RequestMessage.BodyAsBytes, Headers = logEntry.RequestMessage.Headers, Cookies = logEntry.RequestMessage.Cookies, BodyEncoding = logEntry.RequestMessage.BodyEncoding != null ? new EncodingModel @@ -491,6 +509,7 @@ namespace WireMock.Server StatusCode = logEntry.ResponseMessage.StatusCode, BodyDestination = logEntry.ResponseMessage.BodyDestination, Body = logEntry.ResponseMessage.Body, + BodyAsJson = logEntry.ResponseMessage.BodyAsJson, BodyAsBytes = logEntry.ResponseMessage.BodyAsBytes, BodyOriginal = logEntry.ResponseMessage.BodyOriginal, BodyAsFile = logEntry.ResponseMessage.BodyAsFile, @@ -531,7 +550,7 @@ namespace WireMock.Server #region Requests/find private ResponseMessage RequestsFind(RequestMessage requestMessage) { - var requestModel = JsonConvert.DeserializeObject(requestMessage.Body); + var requestModel = requestMessage.Body != null ? JsonConvert.DeserializeObject(requestMessage.Body) : ((JObject)requestMessage.BodyAsJson).ToObject(); var request = (Request)InitRequestBuilder(requestModel); diff --git a/src/WireMock.Net/Server/FluentMockServer.cs b/src/WireMock.Net/Server/FluentMockServer.cs index 0ed4e989..c1cc220d 100644 --- a/src/WireMock.Net/Server/FluentMockServer.cs +++ b/src/WireMock.Net/Server/FluentMockServer.cs @@ -6,6 +6,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using JetBrains.Annotations; +using log4net; +using Newtonsoft.Json; using WireMock.Http; using WireMock.Matchers; using WireMock.Matchers.Request; @@ -13,7 +15,6 @@ using WireMock.RequestBuilders; using WireMock.Settings; using WireMock.Validation; using WireMock.Owin; -using WireMock.Serialization; namespace WireMock.Server { @@ -22,6 +23,7 @@ namespace WireMock.Server /// public partial class FluentMockServer : IDisposable { + private static readonly ILog Log = LogManager.GetLogger(typeof(FluentMockServer)); private const int ServerStartDelay = 100; private readonly IOwinSelfHost _httpServer; private readonly WireMockMiddlewareOptions _options = new WireMockMiddlewareOptions(); @@ -156,6 +158,8 @@ namespace WireMock.Server private FluentMockServer(IFluentMockServerSettings settings) { + Log.DebugFormat("WireMock.Net server settings {0}", JsonConvert.SerializeObject(settings, Formatting.Indented)); + if (settings.Urls != null) { Urls = settings.Urls.Select(u => u.EndsWith("/") ? u : $"{u}/").ToArray(); @@ -315,9 +319,10 @@ namespace WireMock.Server /// Allows the partial mapping. /// [PublicAPI] - public void AllowPartialMapping() + public void AllowPartialMapping(bool allow = true) { - _options.AllowPartialMapping = true; + Log.InfoFormat("AllowPartialMapping is set to {0}", allow); + _options.AllowPartialMapping = allow; } /// diff --git a/src/WireMock.Net/Settings/FluentMockServerSettings.cs b/src/WireMock.Net/Settings/FluentMockServerSettings.cs index 2784e620..9ef91538 100644 --- a/src/WireMock.Net/Settings/FluentMockServerSettings.cs +++ b/src/WireMock.Net/Settings/FluentMockServerSettings.cs @@ -1,5 +1,6 @@ using System; using JetBrains.Annotations; +using Newtonsoft.Json; namespace WireMock.Settings { @@ -63,10 +64,12 @@ namespace WireMock.Settings /// [PublicAPI] + [JsonIgnore] public Action PreWireMockMiddlewareInit { get; set; } /// [PublicAPI] + [JsonIgnore] public Action PostWireMockMiddlewareInit { get; set; } } } \ No newline at end of file diff --git a/src/WireMock.Net/Util/BodyData.cs b/src/WireMock.Net/Util/BodyData.cs new file mode 100644 index 00000000..3e9d2d1d --- /dev/null +++ b/src/WireMock.Net/Util/BodyData.cs @@ -0,0 +1,30 @@ +using System.Text; + +namespace WireMock.Util +{ + /// + /// BodyData + /// + public class BodyData + { + /// + /// The body encoding. + /// + public Encoding Encoding { get; set; } + + /// + /// The body as string. + /// + public string BodyAsString { get; set; } + + /// + /// The body (as JSON object). + /// + public object BodyAsJson { get; set; } + + /// + /// The body (as bytearray). + /// + public byte[] BodyAsBytes { get; set; } + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Util/BodyParser.cs b/src/WireMock.Net/Util/BodyParser.cs new file mode 100644 index 00000000..51a31533 --- /dev/null +++ b/src/WireMock.Net/Util/BodyParser.cs @@ -0,0 +1,75 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace WireMock.Util +{ + internal static class BodyParser + { + private static readonly string[] TextContentTypes = { "text/", "application/xml", "application/javascript", "application/typescript", "application/xhtml+xml" }; + + private static async Task> ReadStringAsync(Stream stream) + { + using (var streamReader = new StreamReader(stream)) + { + string content = await streamReader.ReadToEndAsync(); + + return new Tuple(content, streamReader.CurrentEncoding); + } + } + + private static async Task ReadBytesAsync(Stream stream) + { + using (var memoryStream = new MemoryStream()) + { + await stream.CopyToAsync(memoryStream); + return memoryStream.ToArray(); + } + } + + public static async Task Parse([NotNull] Stream stream, [CanBeNull] string contentTypeHeaderValue) + { + var data = new BodyData(); + + if (contentTypeHeaderValue != null && TextContentTypes.Any(t => contentTypeHeaderValue.StartsWith(t, StringComparison.OrdinalIgnoreCase))) + { + try + { + var stringData = await ReadStringAsync(stream); + data.BodyAsString = stringData.Item1; + data.Encoding = stringData.Item2; + } + catch + { + // Reading as string failed, just get the ByteArray. + data.BodyAsBytes = await ReadBytesAsync(stream); + } + } + else if (contentTypeHeaderValue != null && contentTypeHeaderValue.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)) + { + var stringData = await ReadStringAsync(stream); + data.Encoding = stringData.Item2; + + try + { + data.BodyAsJson = JsonConvert.DeserializeObject(stringData.Item1, new JsonSerializerSettings { Formatting = Formatting.Indented }); + } + catch + { + // JsonConvert failed, just set the Body as string. + data.BodyAsString = stringData.Item1; + } + } + else + { + data.BodyAsBytes = await ReadBytesAsync(stream); + } + + return data; + } + } +} \ No newline at end of file diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj index af1d6660..fed79ca2 100644 --- a/src/WireMock.Net/WireMock.Net.csproj +++ b/src/WireMock.Net/WireMock.Net.csproj @@ -3,7 +3,7 @@ Lightweight Http Mocking Server for .Net, inspired by WireMock from the Java landscape. WireMock.Net - 1.0.3 + 1.0.3.1 Alexandre Victoor;Stef Heyenrath net452;net46;netstandard1.3;netstandard2.0 true @@ -32,6 +32,10 @@ NETSTANDARD + + + + All @@ -41,7 +45,7 @@ - + diff --git a/test/WireMock.Net.Tests/FluentMockServerTests.Proxy.cs b/test/WireMock.Net.Tests/FluentMockServerTests.Proxy.cs index 775182cc..2b0db480 100644 --- a/test/WireMock.Net.Tests/FluentMockServerTests.Proxy.cs +++ b/test/WireMock.Net.Tests/FluentMockServerTests.Proxy.cs @@ -72,7 +72,7 @@ namespace WireMock.Net.Tests var receivedRequest = _serverForProxyForwarding.LogEntries.First().RequestMessage; Check.That(receivedRequest.Body).IsEqualTo("stringContent"); Check.That(receivedRequest.Headers).ContainsKey("Content-Type"); - Check.That(receivedRequest.Headers["Content-Type"]).ContainsExactly("text/plain"); + Check.That(receivedRequest.Headers["Content-Type"].First()).Contains("text/plain"); Check.That(receivedRequest.Headers).ContainsKey("bbb"); var mapping = _server.Mappings.Last(); @@ -151,7 +151,7 @@ namespace WireMock.Net.Tests var receivedRequest = _serverForProxyForwarding.LogEntries.First().RequestMessage; Check.That(receivedRequest.Body).IsEqualTo(""); Check.That(receivedRequest.Headers).ContainsKey("Content-Type"); - Check.That(receivedRequest.Headers["Content-Type"]).ContainsExactly("text/plain"); + Check.That(receivedRequest.Headers["Content-Type"].First()).Contains("text/plain"); } [Fact] diff --git a/test/WireMock.Net.Tests/ResponseWithBodyTests.cs b/test/WireMock.Net.Tests/ResponseWithBodyTests.cs index 78a3d95f..d3d7e85e 100644 --- a/test/WireMock.Net.Tests/ResponseWithBodyTests.cs +++ b/test/WireMock.Net.Tests/ResponseWithBodyTests.cs @@ -75,13 +75,15 @@ namespace WireMock.Net.Tests byte[] body = Encoding.UTF8.GetBytes(bodyAsString); var request = new RequestMessage(new Uri("http://localhost/foo"), "POST", ClientIp, body, bodyAsString, Encoding.UTF8); - var response = Response.Create().WithBodyAsJson(new { value = "test" }, Encoding.ASCII); + object x = new { value = "test" }; + var response = Response.Create().WithBodyAsJson(x, Encoding.ASCII); // act var responseMessage = await response.ProvideResponseAsync(request); // then - Check.That(responseMessage.Body).Equals("{\"value\":\"test\"}"); + Check.That(responseMessage.BodyAsJson).IsNotNull(); + Check.That(responseMessage.BodyAsJson).Equals(x); Check.That(responseMessage.BodyEncoding).Equals(Encoding.ASCII); } }