diff --git a/src/WireMock.Net/Http/HttpClientHelper.cs b/src/WireMock.Net/Http/HttpClientHelper.cs index b08a7e08..a2aa69c1 100644 --- a/src/WireMock.Net/Http/HttpClientHelper.cs +++ b/src/WireMock.Net/Http/HttpClientHelper.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; +using Newtonsoft.Json; using WireMock.Validation; namespace WireMock.Http @@ -29,7 +31,6 @@ namespace WireMock.Http var x509Certificate2 = CertificateUtil.GetCertificate(clientX509Certificate2ThumbprintOrSubjectName); handler.ClientCertificates.Add(x509Certificate2); #else - var webRequestHandler = new WebRequestHandler { ClientCertificateOptions = ClientCertificateOption.Manual, @@ -86,23 +87,46 @@ namespace WireMock.Http // Call the URL var httpResponseMessage = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseContentRead); - // Transform response - var responseMessage = new ResponseMessage - { - StatusCode = (int)httpResponseMessage.StatusCode, - - BodyAsBytes = await httpResponseMessage.Content.ReadAsByteArrayAsync(), - Body = await httpResponseMessage.Content.ReadAsStringAsync() - }; + // Create transform response + var responseMessage = new ResponseMessage { StatusCode = (int)httpResponseMessage.StatusCode }; // Set both content and response headers, replacing URLs in values - var headers = httpResponseMessage.Content?.Headers.Union(httpResponseMessage.Headers); + var headers = (httpResponseMessage.Content?.Headers.Union(httpResponseMessage.Headers) ?? Enumerable.Empty>>()).ToArray(); + + // In case the Content-Type header is application/json, try to set BodyAsJson, else set Body and BodyAsBytes. + bool bodyAsJson = false; + var contentTypeHeader = headers.FirstOrDefault(header => string.Equals(header.Key, HttpKnownHeaderNames.ContentType, StringComparison.OrdinalIgnoreCase)); + if (!contentTypeHeader.Equals(default(KeyValuePair>)) && + contentTypeHeader.Value.Any(value => value != null && value.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))) + { + if (httpResponseMessage.Content != null) + { + string content = await httpResponseMessage.Content.ReadAsStringAsync(); + try + { + responseMessage.BodyAsJson = JsonConvert.DeserializeObject(content, new JsonSerializerSettings { Formatting = Formatting.Indented }); + bodyAsJson = true; + } + catch + { + } + } + } + + if (!bodyAsJson) + { + if (httpResponseMessage.Content != null) + { + responseMessage.BodyAsBytes = await httpResponseMessage.Content.ReadAsByteArrayAsync(); + responseMessage.Body = await httpResponseMessage.Content.ReadAsStringAsync(); + } + } foreach (var header in headers) { - // if Location header contains absolute redirect URL, and base URL is one that we proxy to, + // If Location header contains absolute redirect URL, and base URL is one that we proxy to, // we need to replace it to original one. - if (string.Equals(header.Key, "Location", StringComparison.OrdinalIgnoreCase) + if (string.Equals(header.Key, HttpKnownHeaderNames.Location, StringComparison.OrdinalIgnoreCase) && Uri.TryCreate(header.Value.First(), UriKind.Absolute, out Uri absoluteLocationUri) && string.Equals(absoluteLocationUri.Host, requiredUri.Host, StringComparison.OrdinalIgnoreCase)) { diff --git a/src/WireMock.Net/Owin/OwinResponseMapper.cs b/src/WireMock.Net/Owin/OwinResponseMapper.cs index c9985299..471cfb58 100644 --- a/src/WireMock.Net/Owin/OwinResponseMapper.cs +++ b/src/WireMock.Net/Owin/OwinResponseMapper.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using Newtonsoft.Json; using WireMock.Util; #if !NETSTANDARD using Microsoft.Owin; @@ -65,7 +66,7 @@ namespace WireMock.Owin } } - if (responseMessage.Body == null && responseMessage.BodyAsBytes == null && responseMessage.BodyAsFile == null) + if (responseMessage.Body == null && responseMessage.BodyAsBytes == null && responseMessage.BodyAsFile == null && responseMessage.BodyAsJson == null) { return; } @@ -84,11 +85,20 @@ namespace WireMock.Owin return; } - Encoding encoding = responseMessage.BodyEncoding ?? _utf8NoBom; - using (var writer = new StreamWriter(response.Body, encoding)) + if (responseMessage.BodyAsJson != null) + { + string jsonBody = JsonConvert.SerializeObject(responseMessage.BodyAsJson, new JsonSerializerSettings { Formatting = Formatting.None, NullValueHandling = NullValueHandling.Ignore }); + using (var writer = new StreamWriter(response.Body, responseMessage.BodyEncoding ?? _utf8NoBom)) + { + await writer.WriteAsync(jsonBody); + } + + return; + } + + using (var writer = new StreamWriter(response.Body, responseMessage.BodyEncoding ?? _utf8NoBom)) { await writer.WriteAsync(responseMessage.Body); - // TODO : response.ContentLength = responseMessage.Body.Length; } } } diff --git a/src/WireMock.Net/ResponseMessage.cs b/src/WireMock.Net/ResponseMessage.cs index a4ac7d51..a6c150e9 100644 --- a/src/WireMock.Net/ResponseMessage.cs +++ b/src/WireMock.Net/ResponseMessage.cs @@ -37,6 +37,11 @@ namespace WireMock /// public string Body { get; set; } + /// + /// Gets or sets the body as a json object. + /// + public object BodyAsJson { get; set; } + /// /// Gets or sets the body as bytes. /// diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index 9eb635fe..d65c365b 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -97,6 +97,7 @@ namespace WireMock.Serialization mappingModel.Response.StatusCode = null; mappingModel.Response.Headers = null; mappingModel.Response.BodyDestination = null; + mappingModel.Response.BodyAsJson = null; mappingModel.Response.Body = null; mappingModel.Response.BodyAsBytes = null; mappingModel.Response.BodyAsFile = null; @@ -110,6 +111,7 @@ namespace WireMock.Serialization mappingModel.Response.BodyDestination = response.ResponseMessage.BodyDestination; mappingModel.Response.StatusCode = response.ResponseMessage.StatusCode; mappingModel.Response.Headers = Map(response.ResponseMessage.Headers); + mappingModel.Response.BodyAsJson = response.ResponseMessage.BodyAsJson; mappingModel.Response.Body = response.ResponseMessage.Body; mappingModel.Response.BodyAsBytes = response.ResponseMessage.BodyAsBytes; mappingModel.Response.BodyAsFile = response.ResponseMessage.BodyAsFile; @@ -150,7 +152,9 @@ namespace WireMock.Serialization private static MatcherModel[] Map([CanBeNull] IEnumerable matchers) { if (matchers == null || !matchers.Any()) + { return null; + } return matchers.Select(Map).Where(x => x != null).ToArray(); } @@ -158,7 +162,9 @@ namespace WireMock.Serialization private static MatcherModel Map([CanBeNull] IMatcher matcher) { if (matcher == null) + { return null; + } var patterns = matcher.GetPatterns(); diff --git a/test/WireMock.Net.Tests/FluentMockServerTests.Proxy.cs b/test/WireMock.Net.Tests/FluentMockServerTests.Proxy.cs index 32dacb9c..d3d28c77 100644 --- a/test/WireMock.Net.Tests/FluentMockServerTests.Proxy.cs +++ b/test/WireMock.Net.Tests/FluentMockServerTests.Proxy.cs @@ -17,7 +17,7 @@ namespace WireMock.Net.Tests private FluentMockServer _serverForProxyForwarding; [Fact] - public async Task FluentMockServer_Should_proxy_responses() + public async Task FluentMockServer_Proxy_Should_proxy_responses() { // given _server = FluentMockServer.Start(); @@ -33,7 +33,7 @@ namespace WireMock.Net.Tests } [Fact] - public async Task FluentMockServer_Should_preserve_content_header_in_proxied_request() + public async Task FluentMockServer_Proxy_Should_preserve_content_header_in_proxied_request() { // given _serverForProxyForwarding = FluentMockServer.Start(); @@ -64,7 +64,7 @@ namespace WireMock.Net.Tests } [Fact] - public async Task FluentMockServer_Should_preserve_content_header_in_proxied_request_with_empty_content() + public async Task FluentMockServer_Proxy_Should_preserve_content_header_in_proxied_request_with_empty_content() { // given _serverForProxyForwarding = FluentMockServer.Start(); @@ -95,7 +95,7 @@ namespace WireMock.Net.Tests } [Fact] - public async Task FluentMockServer_Should_preserve_content_header_in_proxied_response() + public async Task FluentMockServer_Proxy_Should_preserve_content_header_in_proxied_response() { // given _serverForProxyForwarding = FluentMockServer.Start(); @@ -125,7 +125,7 @@ namespace WireMock.Net.Tests } [Fact] - public async Task FluentMockServer_Should_change_absolute_location_header_in_proxied_response() + public async Task FluentMockServer_Proxy_Should_change_absolute_location_header_in_proxied_response() { // given _serverForProxyForwarding = FluentMockServer.Start(); @@ -155,7 +155,7 @@ namespace WireMock.Net.Tests } [Fact] - public async Task FluentMockServer_Should_preserve_cookie_header_in_proxied_request() + public async Task FluentMockServer_Proxy_Should_preserve_cookie_header_in_proxied_request() { // given _serverForProxyForwarding = FluentMockServer.Start(); @@ -184,5 +184,35 @@ namespace WireMock.Net.Tests Check.That(receivedRequest.Cookies).IsNotNull(); Check.That(receivedRequest.Cookies).ContainsPair("name", "value"); } + + [Fact] + public async Task FluentMockServer_Proxy_Should_set_BodyAsJson_in_proxied_response() + { + // Assign + _serverForProxyForwarding = FluentMockServer.Start(); + _serverForProxyForwarding + .Given(Request.Create().WithPath("/*")) + .RespondWith(Response.Create() + .WithBodyAsJson(new { i = 42 }) + .WithHeader("Content-Type", "application/json; charset=utf-8")); + + _server = FluentMockServer.Start(); + _server + .Given(Request.Create().WithPath("/*")) + .RespondWith(Response.Create().WithProxy(_serverForProxyForwarding.Urls[0])); + + // Act + var requestMessage = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri(_server.Urls[0]) + }; + var response = await new HttpClient().SendAsync(requestMessage); + + // Assert + string content = await response.Content.ReadAsStringAsync(); + Check.That(content).IsEqualTo("{\"i\":42}"); + Check.That(response.Content.Headers.GetValues("Content-Type")).ContainsExactly("application/json; charset=utf-8"); + } } } \ No newline at end of file