Skip to content

Commit

Permalink
Merge pull request #22 from ppittle/binary-content-streams
Browse files Browse the repository at this point in the history
Binary content streams
  • Loading branch information
ppittle authored Mar 7, 2018
2 parents c696237 + c762c2e commit 3db37fd
Show file tree
Hide file tree
Showing 32 changed files with 1,402 additions and 356 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;

namespace HttpWebRequestWrapper.HttpClient.Extensions
{
Expand All @@ -28,6 +27,10 @@ internal static class HttpClientHandlerExtensions
typeof(HttpClientHandler)
.GetMethod("SetContentHeaders", BindingFlags.NonPublic | BindingFlags.Static);

private static readonly MethodInfo _initializeWebRequest =
typeof(HttpClientHandler)
.GetMethod("InitializeWebRequest", BindingFlags.NonPublic | BindingFlags.Instance);

private static readonly FieldInfo _getRequestStreamCallback =
typeof(HttpClientHandler)
.GetField("getRequestStreamCallback", BindingFlags.NonPublic | BindingFlags.Instance);
Expand Down Expand Up @@ -59,6 +62,8 @@ public static void PrepareWebRequest(
_setRequestHeaders.Invoke(null, new object[] { webRequest, requestMessage });
// HttpClientHandler.SetContentHeaders(HttpWebRequest webRequest, HttpRequestMessage request);
_setContentHeaders.Invoke(null, new object[] { webRequest, requestMessage });
// HttpClientHandler.InitializeWebRequest(HttpRequestMessage request, HttpWebRequest webRequest);
_initializeWebRequest.Invoke(httpClientHandler, new object[] { requestMessage, webRequest });
}

public static void SetGetRequestStreamCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ internal static class HttpWebRequestRefelctionExtensions

public static void SetReturnResponseOnFailureStatusCode(
this HttpWebRequest webRequest,
bool @value)
bool value)
{
_returnResponseOnFailureStatusCodeField.SetValue(webRequest, @value);
_returnResponseOnFailureStatusCodeField.SetValue(webRequest, value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ internal static class TaskSchedulerReflectionExtensons
"QueueTask",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

private static readonly MethodInfo _tryDequeueMethod =
typeof(TaskScheduler)
.GetMethod(
"TryDequeue",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

private static readonly MethodInfo _tryExecuteTaskInlineMethod =
typeof(TaskScheduler)
.GetMethod(
Expand Down Expand Up @@ -63,6 +69,21 @@ public static void QueueTask(this TaskScheduler scheduler, Task task)
});
}

/// <summary>
/// Uses reflection to execute the protected method
/// <see cref="TaskScheduler.TryDequeue"/>.
/// </summary>
public static bool TryDequeue(this TaskScheduler scheduler, Task task)
{
return (bool)
_tryDequeueMethod.Invoke(
scheduler,
new object[]
{
task
});
}

/// <summary>
/// Uses reflection to execute the protected method
/// <see cref="TaskScheduler.TryExecuteTaskInline"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ private void CustomGetRequestStreamCallback(IAsyncResult ar, HttpClientHandler h
var httpWebRequest = requestStateWrapper.GetHttpWebRequest();

// get a copy of the request streams
var requestStream = httpWebRequest.GetRequestStream();
var requestStream = httpWebRequest.EndGetRequestStream(ar);

// copy the request message content to the request stream
requestMessage.Content.CopyToAsync(requestStream).Wait();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ protected override void QueueTask(Task task)
_inner.QueueTask(task);
}

protected override bool TryDequeue(Task task)
{
return _inner.TryDequeue(task);
}

protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return _inner.TryExecuteTaskInline(task, taskWasPreviouslyQueued);
Expand Down
202 changes: 183 additions & 19 deletions src/HttpWebRequestWrapper.Tests/HttpClientTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using HttpWebRequestWrapper.HttpClient;
using HttpWebRequestWrapper.Recording;
using Should;
using Xunit;

Expand Down Expand Up @@ -46,7 +50,7 @@ public async Task CanRecord()

recordingSession.RecordedRequests[0].Url.ShouldEqual(url);
recordingSession.RecordedRequests[0].ResponseStatusCode.ShouldEqual(HttpStatusCode.OK);
recordingSession.RecordedRequests[0].ResponseBody.ShouldContain("<html");
recordingSession.RecordedRequests[0].ResponseBody.SerializedStream.ShouldContain("<html");
}

// WARNING!! Makes live request
Expand All @@ -72,10 +76,40 @@ public async Task CanRecordWebRequestException()
recordingSession.RecordedRequests[0].ResponseStatusCode.ShouldEqual(HttpStatusCode.BadRequest);

// HttpClient suppresses exceptions - so response will just be in ResponseBody
recordingSession.RecordedRequests[0].ResponseBody.ShouldContain("<html");
recordingSession.RecordedRequests[0].ResponseBody.SerializedStream.ShouldContain("<html");
recordingSession.RecordedRequests[0].ResponseException.ShouldBeNull();
}

// WARNING!! Makes live request
[Fact(Timeout = 10000)]
public async Task CanRecordPostWithRequestBody()
{
// ARRANGE
var url = "https://www.stackoverflow.com/";
var requestBody = "Test Post Body";

var recordingSession = new RecordingSession();
HttpResponseMessage response;

// ACT
using (new HttpClientAndRequestWrapperSession(new HttpWebRequestWrapperRecorderCreator(recordingSession)))
{
var httpClient = new System.Net.Http.HttpClient();

response = await httpClient.PostAsync(url, new StringContent(requestBody));
}

// ASSERT
response.ShouldNotBeNull();

recordingSession.RecordedRequests.Count.ShouldEqual(1);

recordingSession.RecordedRequests[0].Url.ShouldEqual(url);
recordingSession.RecordedRequests[0].Method.ShouldEqual("POST");
recordingSession.RecordedRequests[0].RequestPayload.ShouldEqual(requestBody);
recordingSession.RecordedRequests[0].ResponseBody.SerializedStream.ShouldContain("<html");
}

[Fact]
public async Task CanInterceptAndSpoofResponse()
{
Expand Down Expand Up @@ -114,12 +148,12 @@ public async Task CanInterceptPost()
{
if (req.HttpWebRequest.RequestUri == requestUrl &&
req.HttpWebRequest.Method == "POST" &&
req.RequestPayload == requestBody)
req.RequestPayload.SerializedStream == requestBody)
{
return req.HttpWebResponseCreator.Create(responseBody);
}

throw new Exception("Coulnd't match request");
throw new Exception("Couldn't match request");
});

HttpResponseMessage response;
Expand All @@ -140,16 +174,72 @@ public async Task CanInterceptPost()
}

[Fact]
public void CanInterceptWhenHttpClientUsesWebRequestHandler()
public async Task CanInterceptWhenHttpClientUsesWebRequestHandler()
{
// ARRANGE
var requestUrl = new Uri("http://fakesite.fake");
var responseBody = "web request testing";

var responseCreator = new Func<InterceptedRequest, HttpWebResponse>(req =>
{
if (req.HttpWebRequest.RequestUri == requestUrl)
{
return req.HttpWebResponseCreator.Create(responseBody);
}

throw new Exception("Couldn't match request");
});

string response;

// ACT
using (new HttpClientAndRequestWrapperSession(new HttpWebRequestWrapperInterceptorCreator(responseCreator)))
{
var httpClient = new System.Net.Http.HttpClient(new WebRequestHandler());

response = await httpClient.GetStringAsync(requestUrl);
}

// ASSERT
response.ShouldEqual(responseBody);
}

[Fact]
public void CanInterceptWhenHttpClientSetsBaseAddress()
public async Task CanInterceptWhenHttpClientSetsBaseAddress()
{
// ARRANGE
var requestBaseUrl = new Uri("http://fakesite.fake");
var requestRelativeUrl = "/2";
var requestFullUrl = new Uri(requestBaseUrl, requestRelativeUrl);
var responseBody = "web request testing";

var responseCreator = new Func<InterceptedRequest, HttpWebResponse>(req =>
{
if (req.HttpWebRequest.RequestUri == requestFullUrl)
{
return req.HttpWebResponseCreator.Create(responseBody);
}

throw new Exception("Couldn't match request");
});

string response;

// ACT
using (new HttpClientAndRequestWrapperSession(new HttpWebRequestWrapperInterceptorCreator(responseCreator)))
{
var httpClient = new System.Net.Http.HttpClient()
{
BaseAddress = requestBaseUrl
};

response = await httpClient.GetStringAsync(requestRelativeUrl);
}

// ASSERT
response.ShouldEqual(responseBody);
}

[Fact]
public async Task CanInterceptCustomRequestMessage()
{
Expand All @@ -162,12 +252,12 @@ public async Task CanInterceptCustomRequestMessage()
{
if (req.HttpWebRequest.RequestUri == requestUrl &&
req.HttpWebRequest.Method == "POST" &&
req.RequestPayload == requestBody)
req.RequestPayload.SerializedStream == requestBody)
{
return req.HttpWebResponseCreator.Create(responseBody);
}

throw new Exception("Coulnd't match request");
throw new Exception("Couldn't match request");
});

var requestMessage = new HttpRequestMessage(HttpMethod.Post, requestUrl)
Expand All @@ -192,17 +282,6 @@ public async Task CanInterceptCustomRequestMessage()
(await response.Content.ReadAsStringAsync()).ShouldEqual(responseBody);
}


// TODO - can intercept WebRequestHandler (inherits from HttpClientHandler)
// TODO - cna intercept when HttpClient has BaseAddress set
// TODO - test when using custom request message
// TODO - test when using Send with HttpCompletionOption
// TODO - can record post
// TODO - can record binary response stream
// TODO - can record post request payload
// TODO - can record binary request payload
// TODO - can match on binary request payload

[Fact(Timeout = 3000)]
public async Task CanSupportMultipleConcurrentHttpClients()
{
Expand Down Expand Up @@ -249,6 +328,91 @@ public async Task CanSupportMultipleConcurrentHttpClients()
}
}

/// <summary>
/// https://github.com/ppittle/HttpWebRequestWrapper/issues/21
/// found that after 2 successful intercepted requests sent via
/// <see cref="HttpClient.SendAsync(System.Net.Http.HttpRequestMessage)"/>,
/// a 3rd call would never return.
/// <para />
/// This test is *not* able to completly reproduce the bad behavior.
/// However, the solution was to add
/// an override for
/// <see cref="HttpWebRequestWrapperInterceptor.BeginGetRequestStream"/>.
/// Adding the override causes a 10x performance increase in this test, so it's good
/// to have, but it means this test is a bit flimsy - it relies on a Timeout to
/// determine failure, so it can get a false positive/negative based on the
/// execution environment. But not sure how to make it better at this time.
/// </summary>
/// <returns></returns>
[Fact(Timeout = 2000)]
public async Task CanInterceptMultipleSequentialPosts()
{
// ARRANGE
var numberOfSequentialRequests = 20;

var recordedRequest = new RecordedRequest
{
Method = "POST",
Url = "http://fakeSite.fake/",
RequestPayload = new RecordedStream
{
SerializedStream = "Test Request"
},
ResponseStatusCode = HttpStatusCode.OK,
ResponseBody = new RecordedStream
{
SerializedStream = "Test Response",
// improtant - force gzip so a compression stream gets plumbed
// through the http client as that changes behavior
IsGzippedCompressed = true
}
};

var requestBuilder = new RecordingSessionInterceptorRequestBuilder(
new RecordingSession
{
RecordedRequests = new List<RecordedRequest> {recordedRequest}
})
{
MatchingAlgorithm = (intercpeted, recorded) =>
string.Equals(
intercpeted.HttpWebRequest.RequestUri.ToString(),
recorded.Url,
StringComparison.OrdinalIgnoreCase)
};

// ACT
using (new HttpClientAndRequestWrapperSession(new HttpWebRequestWrapperInterceptorCreator(requestBuilder)))
{

for (var i = 0; i < numberOfSequentialRequests; i++)
{
var httpClient = new System.Net.Http.HttpClient(new WebRequestHandler());

var message = new HttpRequestMessage(HttpMethod.Post, recordedRequest.Url)
{
Content = new StringContent(recordedRequest.RequestPayload.ToString())
};

var response = await httpClient.SendAsync(message);

// decompress stream
var responseStream = await response.Content.ReadAsStreamAsync();

using (var zip = new GZipStream(responseStream, CompressionMode.Decompress, leaveOpen: true))
using (var sr = new StreamReader(zip))
//using (var sr = new StreamReader(responseStream))
sr.ReadToEnd().ShouldEqual(recordedRequest.ResponseBody.ToString());

Console.WriteLine("Completed " + i);
}
}

// ASSERT

// if we didn't timeout, then we're good
}

[Fact]
public async Task CanInterceptAndSpoofWebRequestException()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.WebRequest" />
Expand All @@ -94,6 +95,7 @@
<Compile Include="Properties\Extensions.cs" />
<Compile Include="RecorderTests.cs" />
<Compile Include="RecordingSessionInterceptorRequestBuilderTests.cs" />
<Compile Include="Recording\RecordedStreamTests.cs" />
<Compile Include="SessionTests.cs" />
<Compile Include="WrapperTests.cs" />
</ItemGroup>
Expand Down
Loading

0 comments on commit 3db37fd

Please sign in to comment.