Skip to content

Commit

Permalink
Fix proxy headers handling (#60)
Browse files Browse the repository at this point in the history
* Fix proxy headers handling

* Small refactor and moved proxy tests to new test-file.
  • Loading branch information
Dreamescaper authored and StefH committed Oct 28, 2017
1 parent d134684 commit c8c9ab9
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 131 deletions.
5 changes: 4 additions & 1 deletion WireMock.Net Solution.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2002
VisualStudioVersion = 15.0.27004.2005
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EF242EDF-7133-4277-9A0C-18744DE08707}"
EndProject
Expand All @@ -14,6 +14,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{F0C22C47-DF71-463C-9B04-B4E0F3B8708A}"
ProjectSection(SolutionItems) = preProject
examples\WireMock.Net.Console.Record.NETCoreApp\__admin\mappings\ab38efae-4e4d-4f20-8afe-635533ec2535.json = examples\WireMock.Net.Console.Record.NETCoreApp\__admin\mappings\ab38efae-4e4d-4f20-8afe-635533ec2535.json
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{890A1DED-C229-4FA1-969E-AAC3BBFC05E5}"
EndProject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@
<PackageReference Include="Newtonsoft.Json" Version="10.0.1" />
</ItemGroup>

<ItemGroup>
<Folder Include="__admin\mappings\" />
</ItemGroup>

</Project>

This file was deleted.

This file was deleted.

76 changes: 56 additions & 20 deletions src/WireMock.Net/Http/HttpClientHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using WireMock.Validation;

namespace WireMock.Http
{
internal static class HttpClientHelper
{
private static HttpClient CreateHttpClient(string clientX509Certificate2ThumbprintOrSubjectName = null)
public static HttpClient CreateHttpClient(string clientX509Certificate2ThumbprintOrSubjectName = null)
{
if (!string.IsNullOrEmpty(clientX509Certificate2ThumbprintOrSubjectName))
HttpClientHandler handler;

if (string.IsNullOrEmpty(clientX509Certificate2ThumbprintOrSubjectName))
{
handler = new HttpClientHandler();
}
else
{
#if NETSTANDARD || NET46
var handler = new HttpClientHandler
handler = new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls,
Expand All @@ -22,46 +29,60 @@ private static HttpClient CreateHttpClient(string clientX509Certificate2Thumbpri
var x509Certificate2 = CertificateUtil.GetCertificate(clientX509Certificate2ThumbprintOrSubjectName);
handler.ClientCertificates.Add(x509Certificate2);
#else
var handler = new WebRequestHandler

var webRequestHandler = new WebRequestHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true,
AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate
};

var x509Certificate2 = CertificateUtil.GetCertificate(clientX509Certificate2ThumbprintOrSubjectName);
handler.ClientCertificates.Add(x509Certificate2);
return new HttpClient(handler);
webRequestHandler.ClientCertificates.Add(x509Certificate2);
handler = webRequestHandler;
#endif
}

return new HttpClient();
// For proxy we shouldn't follow auto redirects
handler.AllowAutoRedirect = false;

// If UseCookies enabled, httpClient ignores Cookie header
handler.UseCookies = false;

return new HttpClient(handler);
}

public static async Task<ResponseMessage> SendAsync(RequestMessage requestMessage, string url, string clientX509Certificate2ThumbprintOrSubjectName = null)
public static async Task<ResponseMessage> SendAsync(HttpClient client, RequestMessage requestMessage, string url)
{
var client = CreateHttpClient(clientX509Certificate2ThumbprintOrSubjectName);
Check.NotNull(client, nameof(client));

var originalUri = new Uri(requestMessage.Url);
var requiredUri = new Uri(url);

var httpRequestMessage = new HttpRequestMessage(new HttpMethod(requestMessage.Method), url);

// Set Body if present
if (requestMessage.BodyAsBytes != null && requestMessage.BodyAsBytes.Length > 0)
{
httpRequestMessage.Content = new ByteArrayContent(requestMessage.BodyAsBytes);
}

// Overwrite the host header
httpRequestMessage.Headers.Host = new Uri(url).Authority;
httpRequestMessage.Headers.Host = requiredUri.Authority;

// Set headers if present
if (requestMessage.Headers != null)
{
foreach (var headerName in requestMessage.Headers.Keys.Where(k => k.ToUpper() != "HOST"))
foreach (var header in requestMessage.Headers.Where(header => !string.Equals(header.Key, "HOST", StringComparison.OrdinalIgnoreCase)))
{
httpRequestMessage.Headers.TryAddWithoutValidation(headerName, requestMessage.Headers[headerName]);
// Try to add to request headers. If failed - try to add to content headers
if (!httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value))
{
httpRequestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
}

// Set Body if present
if (requestMessage.BodyAsBytes != null && requestMessage.BodyAsBytes.Length > 0)
{
httpRequestMessage.Content = new ByteArrayContent(requestMessage.BodyAsBytes);
}

// Call the URL
var httpResponseMessage = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseContentRead);

Expand All @@ -74,9 +95,24 @@ public static async Task<ResponseMessage> SendAsync(RequestMessage requestMessag
Body = await httpResponseMessage.Content.ReadAsStringAsync()
};

foreach (var header in httpResponseMessage.Headers)
// Set both content and response headers, replacing URLs in values
var headers = httpResponseMessage.Content?.Headers.Union(httpResponseMessage.Headers);

foreach (var header in headers)
{
responseMessage.AddHeader(header.Key, header.Value.FirstOrDefault());
// 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)
&& Uri.TryCreate(header.Value.First(), UriKind.Absolute, out Uri absoluteLocationUri)
&& string.Equals(absoluteLocationUri.Host, requiredUri.Host, StringComparison.OrdinalIgnoreCase))
{
var replacedLocationUri = new Uri(originalUri, absoluteLocationUri.PathAndQuery);
responseMessage.AddHeader(header.Key, replacedLocationUri.ToString());
}
else
{
responseMessage.AddHeader(header.Key, header.Value.ToArray());
}
}

return responseMessage;
Expand Down
8 changes: 6 additions & 2 deletions src/WireMock.Net/ResponseBuilders/Response.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
Expand All @@ -19,6 +20,8 @@ namespace WireMock.ResponseBuilders
/// </summary>
public class Response : IResponseBuilder
{
private HttpClient httpClientForProxy;

/// <summary>
/// The delay
/// </summary>
Expand Down Expand Up @@ -317,6 +320,7 @@ public IResponseBuilder WithProxy(string proxyUrl, string clientX509Certificate2

ProxyUrl = proxyUrl;
X509Certificate2ThumbprintOrSubjectName = clientX509Certificate2ThumbprintOrSubjectName;
httpClientForProxy = HttpClientHelper.CreateHttpClient(clientX509Certificate2ThumbprintOrSubjectName);
return this;
}

Expand All @@ -332,12 +336,12 @@ public async Task<ResponseMessage> ProvideResponseAsync(RequestMessage requestMe
if (Delay != null)
await Task.Delay(Delay.Value);

if (ProxyUrl != null)
if (ProxyUrl != null && httpClientForProxy != null)
{
var requestUri = new Uri(requestMessage.Url);
var proxyUri = new Uri(ProxyUrl);
var proxyUriWithRequestPathAndQuery = new Uri(proxyUri, requestUri.PathAndQuery);
return await HttpClientHelper.SendAsync(requestMessage, proxyUriWithRequestPathAndQuery.AbsoluteUri, X509Certificate2ThumbprintOrSubjectName);
return await HttpClientHelper.SendAsync(httpClientForProxy, requestMessage, proxyUriWithRequestPathAndQuery.AbsoluteUri);
}

if (UseTransformer)
Expand Down
6 changes: 5 additions & 1 deletion src/WireMock.Net/Server/FluentMockServer.Admin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
Expand Down Expand Up @@ -129,8 +130,11 @@ private void InitAdmin()
}

#region Proxy and Record
private HttpClient httpClientForProxy;

private void InitProxyAndRecord(ProxyAndRecordSettings settings)
{
httpClientForProxy = HttpClientHelper.CreateHttpClient(settings.X509Certificate2ThumbprintOrSubjectName);
Given(Request.Create().WithPath("/*").UsingAnyVerb()).RespondWith(new ProxyAsyncResponseProvider(ProxyAndRecordAsync, settings));
}

Expand All @@ -140,7 +144,7 @@ private async Task<ResponseMessage> ProxyAndRecordAsync(RequestMessage requestMe
var proxyUri = new Uri(settings.Url);
var proxyUriWithRequestPathAndQuery = new Uri(proxyUri, requestUri.PathAndQuery);

var responseMessage = await HttpClientHelper.SendAsync(requestMessage, proxyUriWithRequestPathAndQuery.AbsoluteUri, settings.X509Certificate2ThumbprintOrSubjectName);
var responseMessage = await HttpClientHelper.SendAsync(httpClientForProxy, requestMessage, proxyUriWithRequestPathAndQuery.AbsoluteUri);

if (settings.SaveMapping)
{
Expand Down
Loading

0 comments on commit c8c9ab9

Please sign in to comment.