Skip to content

Commit

Permalink
fixing issue #267
Browse files Browse the repository at this point in the history
  • Loading branch information
aliostad committed Mar 26, 2022
1 parent 38a5da3 commit 21bab33
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 79 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ artifacts/
script.sql
.idea/
.vs/
TestResults/
TestResults/
.vscode/
27 changes: 18 additions & 9 deletions src/CacheCow.Client/CachingHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ public class CachingHandler : DelegatingHandler
// 13.4: A response received with a status code of 200, 203, 206, 300, 301 or 410 MAY be stored
// TODO: Implement caching statuses other than 2xx
private static HttpStatusCode[] _cacheableStatuses = new HttpStatusCode[]
{
HttpStatusCode.OK, HttpStatusCode.NonAuthoritativeInformation,
HttpStatusCode.PartialContent, HttpStatusCode.MultipleChoices,
HttpStatusCode.MovedPermanently, HttpStatusCode.Gone
};
{
HttpStatusCode.OK, HttpStatusCode.NonAuthoritativeInformation,
HttpStatusCode.PartialContent, HttpStatusCode.MultipleChoices,
HttpStatusCode.MovedPermanently, HttpStatusCode.Gone
};

public CachingHandler()
: this(new InMemoryCacheStore())
Expand Down Expand Up @@ -338,7 +338,7 @@ protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage
if (isFreshOrStaleAcceptable.HasValue && isFreshOrStaleAcceptable.Value) // similar to OK
{
// TODO: CONSUME AND RELEASE Response !!!
if (! DoNotEmitCacheCowHeader)
if (!DoNotEmitCacheCowHeader)
cachedResponse.AddCacheCowHeader(cacheCowHeader);
return cachedResponse;
// EXIT !! ____________________________
Expand All @@ -357,6 +357,15 @@ protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage
// _______________________________ RESPONSE only GET ___________________________________________

var serverResponse = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (serverResponse.Content != null)
{
// these two prevent serialisation without ContentLength which barfs for chunked encoding - issue #267
TraceWriter.WriteLine($"Content Size: {serverResponse.Content.Headers.ContentLength}", TraceLevel.Verbose);
if (serverResponse.Content.Headers.ContentType == null)
{
serverResponse.Content.Headers.Add("Content-Type", "application/octet-stream");
}
}

// HERE IS LATE FOR APPLYING EXCEPTION POLICY !!!

Expand All @@ -380,7 +389,7 @@ protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage

await UpdateCachedResponseAsync(cacheKey, cachedResponse, serverResponse, _cacheStore).ConfigureAwait(false);
ConsumeAndDisposeResponse(serverResponse);
if (! DoNotEmitCacheCowHeader)
if (!DoNotEmitCacheCowHeader)
cachedResponse.AddCacheCowHeader(cacheCowHeader).CopyOtherCacheCowHeaders(serverResponse);
return cachedResponse;
// EXIT !! _______________
Expand Down Expand Up @@ -442,7 +451,7 @@ protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage
TraceWriter.WriteLine("{0} - Before returning response",
TraceLevel.Verbose, request.RequestUri.ToString());

if (! DoNotEmitCacheCowHeader)
if (!DoNotEmitCacheCowHeader)
serverResponse.AddCacheCowHeader(cacheCowHeader);

return serverResponse;
Expand Down Expand Up @@ -495,7 +504,7 @@ internal async static Task UpdateCachedResponseAsync(CacheKey cacheKey,
private static void CheckForCacheCowHeader(HttpResponseMessage responseMessage)
{
var header = responseMessage.Headers.GetCacheCowHeader();
if (header!=null)
if (header != null)
{
TraceWriter.WriteLine("!!WARNING!! response stored with CacheCowHeader!!", TraceLevel.Warning);
}
Expand Down
41 changes: 22 additions & 19 deletions src/CacheCow.Client/InMemoryCacheStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,40 +67,43 @@ public InMemoryCacheStore(TimeSpan minExpiry, IOptions<MemoryCacheOptions> optio
#endif


/// <inheritdoc />
/// <inheritdoc />
public void Dispose()
{
_responseCache.Dispose();
}
{
_responseCache.Dispose();
}

/// <inheritdoc />
public async Task<HttpResponseMessage> GetValueAsync(CacheKey key)
{
var result = _responseCache.Get(key.HashBase64);
if (result == null)
return null;
{
var result = (byte[])_responseCache.Get(key.HashBase64);
if (result == null)
return null;

return await _messageSerializer.DeserializeToResponseAsync(new MemoryStream((byte[]) result)).ConfigureAwait(false);
}
return await _messageSerializer.DeserializeToResponseAsync(new MemoryStream(result)).ConfigureAwait(false);
}

/// <inheritdoc />
public async Task AddOrUpdateAsync(CacheKey key, HttpResponseMessage response)
{
{
// removing reference to request so that the request can get GCed
// UPDATE 2022 - What on earth am I doing it for? I cannot remember.
var req = response.RequestMessage;
response.RequestMessage = null;
var memoryStream = new MemoryStream();
await _messageSerializer.SerializeAsync(response, memoryStream).ConfigureAwait(false);
await _messageSerializer.SerializeAsync(response, memoryStream).ConfigureAwait(false);
var buffer = memoryStream.ToArray();

response.RequestMessage = req;
var suggestedExpiry = response.GetExpiry() ?? DateTimeOffset.UtcNow.Add(_minExpiry);
var minExpiry = DateTimeOffset.UtcNow.Add(_minExpiry);
var optimalExpiry = (suggestedExpiry > minExpiry) ? suggestedExpiry : minExpiry;
_responseCache.Set(key.HashBase64, memoryStream.ToArray(), optimalExpiry);
}
_responseCache.Set(key.HashBase64, buffer, optimalExpiry);
}

/// <inheritdoc />
public Task<bool> TryRemoveAsync(CacheKey key)
{
{
#if NET452
return Task.FromResult(_responseCache.Remove(key.HashBase64) != null);
#else
Expand All @@ -111,14 +114,14 @@ public Task<bool> TryRemoveAsync(CacheKey key)

/// <inheritdoc />
public Task ClearAsync()
{
{
_responseCache.Dispose();
#if NET452
_responseCache = new MemoryCache(CacheStoreEntryName);
#else
_responseCache = new MemoryCache(_options);
#endif
return Task.FromResult(0);
}
}
return Task.FromResult(0);
}
}
}
18 changes: 14 additions & 4 deletions src/CacheCow.Client/MessageContentHttpMessageSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,12 @@ public async Task SerializeAsync(HttpResponseMessage response, Stream stream)
{
TraceWriter.WriteLine("SerializeAsync - before load",
TraceLevel.Verbose);
if(_bufferContent)
// this will prevent serialisation without ContentLength which barfs for chunked encoding - issue #267
var contentLength = response.Content.Headers.ContentLength;

if (_bufferContent)
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
TraceWriter.WriteLine("SerializeAsync - after load", TraceLevel.Verbose);
TraceWriter.WriteLine("SerializeAsync - after load", TraceLevel.Verbose);
}
else
{
Expand All @@ -51,6 +54,7 @@ public async Task SerializeAsync(HttpResponseMessage response, Stream stream)

var httpMessageContent = new HttpMessageContent(response);
var buffer = await httpMessageContent.ReadAsByteArrayAsync();

TraceWriter.WriteLine("SerializeAsync - after ReadAsByteArrayAsync", TraceLevel.Verbose);
stream.Write(buffer, 0, buffer.Length);
}
Expand All @@ -64,7 +68,7 @@ public async Task SerializeAsync(HttpRequestMessage request, Stream stream)

var httpMessageContent = new HttpMessageContent(request);
var buffer = await httpMessageContent.ReadAsByteArrayAsync().ConfigureAwait(false);
stream.Write(buffer, 0, buffer.Length);
stream.Write(buffer, 0, buffer.Length);
}

public async Task<HttpResponseMessage> DeserializeToResponseAsync(Stream stream)
Expand All @@ -76,7 +80,13 @@ public async Task<HttpResponseMessage> DeserializeToResponseAsync(Stream stream)
TraceLevel.Verbose);
var responseMessage = await response.Content.ReadAsHttpResponseMessageAsync().ConfigureAwait(false);
if (responseMessage.Content != null && _bufferContent)
await responseMessage.Content.LoadIntoBufferAsync().ConfigureAwait(false);
{
await responseMessage.Content.LoadIntoBufferAsync().ConfigureAwait(false);
}

if (responseMessage.Content == null)
TraceWriter.WriteLine("Content is NULL desering from cache", TraceLevel.Warning);

return responseMessage;
}

Expand Down
60 changes: 44 additions & 16 deletions test/CacheCow.Client.Tests/IntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,31 @@
using CacheCow.Client.Headers;
using CacheCow.Common;
using Xunit;
using System;
using System.IO;

namespace CacheCow.Client.Tests
{

public class IntegrationTests
{
{
public const string Url = "https://ssl.gstatic.com/gb/images/j_e6a6aca6.png";

[Fact]
public async Task Test_GoogleImage_WorksOnFirstSecondRequestNotThird()
{
var httpClient = new HttpClient(new CachingHandler()
{
InnerHandler = new HttpClientHandler()
});
[Fact]
public async Task Test_GoogleImage_WorksOnFirstSecondRequestNotThird()
{
var httpClient = new HttpClient(new CachingHandler()
{
InnerHandler = new HttpClientHandler()
});
httpClient.DefaultRequestHeaders.Add(HttpHeaderNames.Accept, "image/png");

var httpResponseMessage = await httpClient.GetAsync(Url);
var httpResponseMessage2 = await httpClient.GetAsync(Url);
var cacheCowHeader = httpResponseMessage2.Headers.GetCacheCowHeader();
Assert.NotNull(cacheCowHeader);
Assert.Equal(true, cacheCowHeader.RetrievedFromCache);
}
var httpResponseMessage = await httpClient.GetAsync(Url);
var httpResponseMessage2 = await httpClient.GetAsync(Url);
var cacheCowHeader = httpResponseMessage2.Headers.GetCacheCowHeader();
Assert.NotNull(cacheCowHeader);
Assert.Equal(true, cacheCowHeader.RetrievedFromCache);
}

[Fact]
public async Task Simple_Caching_Example_From_Issue263()
Expand All @@ -35,9 +37,35 @@ public async Task Simple_Caching_Example_From_Issue263()
var response = await client.GetAsync(CacheableResource);
var responseFromCache = await client.GetAsync(CacheableResource);
Assert.Equal(true, response.Headers.GetCacheCowHeader().DidNotExist);
Assert.Equal(true, responseFromCache.Headers.GetCacheCowHeader().RetrievedFromCache);
Assert.Equal(true, responseFromCache.Headers.GetCacheCowHeader()?.RetrievedFromCache);
}


[Fact] // Skip if the resource becomes unavailable
public async Task Simple_Caching_Example_From_Issue267()
{
var client = ClientExtensions.CreateClient();

// this one does not have a content-type too and that could be somehow related
// but not quite sure. Have used other places where a chunked encoding might
// be returned but did not cause the same problem
const string CacheableResource = "https://webhooks.truelayer-sandbox.com/.well-known/jwks";

var response = await client.GetAsync(CacheableResource);
var body = await response.Content.ReadAsByteArrayAsync();
var responseFromCache = await client.GetAsync(CacheableResource);
if (responseFromCache.Content == null)
{
throw new InvalidOperationException("Response content from cache is null");
}

var bodyFromCache = await responseFromCache.Content.ReadAsByteArrayAsync();
Assert.Equal(true, response.Headers.GetCacheCowHeader()?.DidNotExist);
Assert.Equal(body.Length, bodyFromCache.Length);
}



[Fact]
public async Task SettingNoHeaderWorks()
{
Expand All @@ -59,5 +87,5 @@ public async Task SettingNoHeaderWorks()

Assert.Null(h);
}
}
}
}
60 changes: 30 additions & 30 deletions test/CacheCow.Client.Tests/ResponseSerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@

namespace CacheCow.Client.Tests
{
public class ResponseSerializationTests
{
[Fact]
public async Task IntegrationTest_Deserialize()
{

public class ResponseSerializationTests
{
[Fact]
public async Task IntegrationTest_Deserialize()
{
var httpClient = new HttpClient();
var httpResponseMessage = await httpClient.GetAsync(IntegrationTests.Url);
Console.WriteLine(httpResponseMessage.Headers.ToString());
Expand All @@ -26,30 +26,30 @@ public async Task IntegrationTest_Deserialize()
fileStream.Close();

var fileStream2 = new FileStream("msg.bin", FileMode.Open);
var httpResponseMessage2 = await defaultHttpResponseMessageSerializer.DeserializeToResponseAsync(fileStream2);
fileStream.Close();
}
var httpResponseMessage2 = await defaultHttpResponseMessageSerializer.DeserializeToResponseAsync(fileStream2);
fileStream.Close();
}

[Fact]
public async Task IntegrationTest_Serialize_Deserialize()
{
var httpClient = new HttpClient();
var httpResponseMessage = await httpClient.GetAsync(IntegrationTests.Url);
var contentLength = httpResponseMessage.Content.Headers.ContentLength; // access to make sure is populated http://aspnetwebstack.codeplex.com/discussions/388196
var memoryStream = new MemoryStream();
var defaultHttpResponseMessageSerializer = new MessageContentHttpMessageSerializer();
await defaultHttpResponseMessageSerializer.SerializeAsync(httpResponseMessage, memoryStream);
memoryStream.Position = 0;
var httpResponseMessage2 = await defaultHttpResponseMessageSerializer.DeserializeToResponseAsync(memoryStream);
Assert.Equal(httpResponseMessage.StatusCode, httpResponseMessage2.StatusCode);
Assert.Equal(httpResponseMessage.ReasonPhrase, httpResponseMessage2.ReasonPhrase);
Assert.Equal(httpResponseMessage.Version, httpResponseMessage2.Version);
Assert.Equal(httpResponseMessage.Headers.ToString(), httpResponseMessage2.Headers.ToString());
Assert.Equal(await httpResponseMessage.Content.ReadAsStringAsync(),
await httpResponseMessage2.Content.ReadAsStringAsync());
Assert.Equal(httpResponseMessage.Content.Headers.ToString(),
httpResponseMessage2.Content.Headers.ToString());
[Fact]
public async Task IntegrationTest_Serialize_Deserialize()
{
var httpClient = new HttpClient();
var httpResponseMessage = await httpClient.GetAsync("https://webhooks.truelayer-sandbox.com/.well-known/jwks");
var memoryStream = new MemoryStream();

var defaultHttpResponseMessageSerializer = new MessageContentHttpMessageSerializer();
await defaultHttpResponseMessageSerializer.SerializeAsync(httpResponseMessage, memoryStream);

}
}
memoryStream.Position = 0;
var httpResponseMessage2 = await defaultHttpResponseMessageSerializer.DeserializeToResponseAsync(memoryStream);
Assert.Equal(httpResponseMessage.StatusCode, httpResponseMessage2.StatusCode);
Assert.Equal(httpResponseMessage.ReasonPhrase, httpResponseMessage2.ReasonPhrase);
Assert.Equal(httpResponseMessage.Version, httpResponseMessage2.Version);
Assert.Equal(httpResponseMessage.Headers.ToString(), httpResponseMessage2.Headers.ToString());
Assert.Equal(await httpResponseMessage.Content.ReadAsStringAsync(),
await httpResponseMessage2.Content.ReadAsStringAsync());
Assert.Equal(httpResponseMessage.Content.Headers.ToString(),
httpResponseMessage2.Content.Headers.ToString());
}
}
}

0 comments on commit 21bab33

Please sign in to comment.