Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/WebApi.OutputCache.Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ public sealed class Constants
public const string ContentTypeKey = ":response-ct";
public const string EtagKey = ":response-etag";
public const string GenerationTimestampKey = ":response-generationtimestamp";
public const string CustomHeaders = ":custom-headers";
public const string CustomContentHeaders = ":custom-content-headers";
}
}
52 changes: 50 additions & 2 deletions src/WebApi.OutputCache.V2/CacheOutputAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
Expand Down Expand Up @@ -81,6 +82,11 @@ public int SharedTimeSpan
/// </summary>
public Type CacheKeyGenerator { get; set; }

/// <summary>
/// Comma seperated list of HTTP headers to cache
/// </summary>
public string IncludeCustomHeaders { get; set; }

/// <summary>
/// If set to something else than an empty string, this value will always be used for the Content-Type header, regardless of content negotiation.
/// </summary>
Expand Down Expand Up @@ -185,6 +191,9 @@ public override void OnActionExecuting(HttpActionContext actionContext)

if (!_webApiCache.Contains(cachekey)) return;

var responseHeaders = _webApiCache.Get<Dictionary<string, List<string>>>(cachekey + Constants.CustomHeaders);
var responseContentHeaders = _webApiCache.Get<Dictionary<string, List<string>>>(cachekey + Constants.CustomContentHeaders);

if (actionContext.Request.Headers.IfNoneMatch != null)
{
var etag = _webApiCache.Get<string>(cachekey + Constants.EtagKey);
Expand All @@ -194,7 +203,8 @@ public override void OnActionExecuting(HttpActionContext actionContext)
{
var time = CacheTimeQuery.Execute(DateTime.Now);
var quickResponse = actionContext.Request.CreateResponse(HttpStatusCode.NotModified);

if (responseHeaders != null) AddCustomCachedHeaders(quickResponse, responseHeaders, responseContentHeaders);

SetEtag(quickResponse, etag);
ApplyCacheHeaders(quickResponse, time);
actionContext.Response = quickResponse;
Expand Down Expand Up @@ -225,6 +235,8 @@ public override void OnActionExecuting(HttpActionContext actionContext)
var responseEtag = _webApiCache.Get<string>(cachekey + Constants.EtagKey);
if (responseEtag != null) SetEtag(actionContext.Response, responseEtag);

if (responseHeaders != null) AddCustomCachedHeaders(actionContext.Response, responseHeaders, responseContentHeaders);

var cacheTime = CacheTimeQuery.Execute(DateTime.Now);
ApplyCacheHeaders(actionContext.Response, cacheTime, contentGenerationTimestamp);
}
Expand Down Expand Up @@ -275,10 +287,27 @@ public override async Task OnActionExecutedAsync(HttpActionExecutedContext actio
etag,
cacheTime.AbsoluteExpiration, baseKey);


_webApiCache.Add(cachekey + Constants.GenerationTimestampKey,
actionExecutionTimestamp.ToString(),
cacheTime.AbsoluteExpiration, baseKey);

if (!String.IsNullOrEmpty(IncludeCustomHeaders))
{
// convert to dictionary of lists to ensure thread safety if implementation of IEnumerable is changed
var headers = actionExecutedContext.Response.Headers.Where(h => IncludeCustomHeaders.Contains(h.Key))
.ToDictionary(x => x.Key, x => x.Value.ToList());

var contentHeaders = actionExecutedContext.Response.Content.Headers.Where(h => IncludeCustomHeaders.Contains(h.Key))
.ToDictionary(x => x.Key, x => x.Value.ToList());

_webApiCache.Add(cachekey + Constants.CustomHeaders,
headers,
cacheTime.AbsoluteExpiration, baseKey);

_webApiCache.Add(cachekey + Constants.CustomContentHeaders,
contentHeaders,
cacheTime.AbsoluteExpiration, baseKey);
}
}
}
}
Expand Down Expand Up @@ -311,6 +340,25 @@ protected virtual void ApplyCacheHeaders(HttpResponseMessage response, CacheTime
}
}

protected virtual void AddCustomCachedHeaders(HttpResponseMessage response, Dictionary<string, List<string>> headers, Dictionary<string, List<string>> contentHeaders)
{
foreach (var headerKey in headers.Keys)
{
foreach (var headerValue in headers[headerKey])
{
response.Headers.Add(headerKey, headerValue);
}
}

foreach (var headerKey in contentHeaders.Keys)
{
foreach (var headerValue in contentHeaders[headerKey])
{
response.Content.Headers.Add(headerKey, headerValue);
}
}
}

protected virtual string CreateEtag(HttpActionExecutedContext actionExecutedContext, string cachekey, CacheTime cacheTime)
{
return Guid.NewGuid().ToString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
<Name>WebApi.OutputCache.Core</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Expand Down
4 changes: 2 additions & 2 deletions test/WebApi.OutputCache.V2.Tests/ClientSideTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ public void maxage_private_true_headers_correct()
public void maxage_mustrevalidate_headers_correct_with_cacheuntil()
{
var client = new HttpClient(_server);
var result = client.GetAsync(_url + "Get_until25012015_1700").Result;
var clientTimeSpanSeconds = new SpecificTime(2019, 01, 25, 17, 0, 0).Execute(DateTime.Now).ClientTimeSpan.TotalSeconds;
var result = client.GetAsync(_url + "Get_until25012100_1700").Result;
var clientTimeSpanSeconds = new SpecificTime(2100, 01, 25, 17, 0, 0).Execute(DateTime.Now).ClientTimeSpan.TotalSeconds;
var resultCacheControlSeconds = ((TimeSpan) result.Headers.CacheControl.MaxAge).TotalSeconds;
Assert.IsTrue(Math.Round(clientTimeSpanSeconds - resultCacheControlSeconds) == 0);
Assert.IsFalse(result.Headers.CacheControl.MustRevalidate);
Expand Down
62 changes: 62 additions & 0 deletions test/WebApi.OutputCache.V2.Tests/CustomHeadersContent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Results;

namespace WebApi.OutputCache.V2.Tests
{
public class CustomHeadersContent<T> : OkNegotiatedContentResult<T>
{
public string ContentDisposition { get; set; }

public List<string> ContentEncoding { get; set; }

public string RequestHeader1 { get; set; }

public List<string> RequestHeader2 { get; set; }

public CustomHeadersContent(T content, ApiController controller)
: base(content, controller) { }

public CustomHeadersContent(T content, IContentNegotiator contentNegotiator, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
: base(content, contentNegotiator, request, formatters) { }

public override async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = await base.ExecuteAsync(cancellationToken);

if (!string.IsNullOrWhiteSpace(ContentDisposition))
{
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue(ContentDisposition);
}
if (ContentEncoding != null)
{
foreach (var contentEncoding in ContentEncoding)
{
response.Content.Headers.ContentEncoding.Add(contentEncoding);
}
}

if (!string.IsNullOrWhiteSpace(RequestHeader1))
{
response.Headers.Add("RequestHeader1", RequestHeader1);
}
if (RequestHeader2 != null)
{
foreach (var requestHeader2Value in RequestHeader2)
{
response.Headers.Add("RequestHeader2", requestHeader2Value);
}
}

return response;
}
}
}
176 changes: 176 additions & 0 deletions test/WebApi.OutputCache.V2.Tests/CustomHeadersTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Principal;
using System.Threading;
using System.Web.Http;
using Autofac;
using Autofac.Integration.WebApi;
using Moq;
using NUnit.Framework;
using WebApi.OutputCache.Core;
using WebApi.OutputCache.Core.Cache;
using System.Collections.Generic;
using System.Linq;

namespace WebApi.OutputCache.V2.Tests
{
[TestFixture]
public class CustomHeadersTests
{
private HttpServer _server;
private string _url = "http://www.strathweb.com/api/customheaders/";
private IApiOutputCache _cache;

[SetUp]
public void init()
{
Thread.CurrentPrincipal = null;

_cache = new SimpleCacheForTests();

var conf = new HttpConfiguration();
var builder = new ContainerBuilder();
builder.RegisterInstance(_cache);

conf.DependencyResolver = new AutofacWebApiDependencyResolver(builder.Build());
conf.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);

_server = new HttpServer(conf);
}

[Test]
public void cache_custom_content_header() {
var client = new HttpClient(_server);
var req = new HttpRequestMessage(HttpMethod.Get, _url + "Cache_Custom_Content_Header");
var result = client.SendAsync(req).Result;

var req2 = new HttpRequestMessage(HttpMethod.Get, _url + "Cache_Custom_Content_Header");
var result2 = client.SendAsync(req2).Result;

Assert.That(result.Content.Headers.ContentDisposition.DispositionType, Is.EqualTo("attachment"));
Assert.That(result2.Content.Headers.ContentDisposition.DispositionType, Is.EqualTo("attachment"));
}

[Test]
public void cache_custom_content_header_with_multiply_values()
{
var client = new HttpClient(_server);
var req = new HttpRequestMessage(HttpMethod.Get, _url + "Cache_Custom_Content_Header_Multiply_Values");
var result = client.SendAsync(req).Result;

var req2 = new HttpRequestMessage(HttpMethod.Get, _url + "Cache_Custom_Content_Header_Multiply_Values");
var result2 = client.SendAsync(req2).Result;

Assert.That(result.Content.Headers.ContentEncoding.Count, Is.EqualTo(2));
Assert.That(result.Content.Headers.ContentEncoding.First(), Is.EqualTo("deflate"));
Assert.That(result.Content.Headers.ContentEncoding.Last(), Is.EqualTo("gzip"));

Assert.That(result2.Content.Headers.ContentEncoding.Count, Is.EqualTo(2));
Assert.That(result2.Content.Headers.ContentEncoding.First(), Is.EqualTo("deflate"));
Assert.That(result2.Content.Headers.ContentEncoding.Last(), Is.EqualTo("gzip"));
}

[Test]
public void cache_custom_response_header()
{
var client = new HttpClient(_server);
var req = new HttpRequestMessage(HttpMethod.Get, _url + "Cache_Custom_Response_Header");
var result = client.SendAsync(req).Result;

var req2 = new HttpRequestMessage(HttpMethod.Get, _url + "Cache_Custom_Response_Header");
var result2 = client.SendAsync(req2).Result;

Assert.That(result.Headers.GetValues("RequestHeader1").First(), Is.EqualTo("value1"));
Assert.That(result2.Headers.GetValues("RequestHeader1").First(), Is.EqualTo("value1"));
}

[Test]
public void cache_custom_response_header_with_multiply_values()
{
var client = new HttpClient(_server);
var req = new HttpRequestMessage(HttpMethod.Get, _url + "Cache_Custom_Response_Header_Multiply_Values");
var result = client.SendAsync(req).Result;

var req2 = new HttpRequestMessage(HttpMethod.Get, _url + "Cache_Custom_Response_Header_Multiply_Values");
var result2 = client.SendAsync(req2).Result;

Assert.That(result.Headers.GetValues("RequestHeader2").Count(), Is.EqualTo(2));
Assert.That(result.Headers.GetValues("RequestHeader2").First(), Is.EqualTo("value2"));
Assert.That(result.Headers.GetValues("RequestHeader2").Last(), Is.EqualTo("value3"));

Assert.That(result2.Headers.GetValues("RequestHeader2").Count(), Is.EqualTo(2));
Assert.That(result2.Headers.GetValues("RequestHeader2").First(), Is.EqualTo("value2"));
Assert.That(result2.Headers.GetValues("RequestHeader2").Last(), Is.EqualTo("value3"));
}

[Test]
public void cache_multiply_custom_headers()
{
var client = new HttpClient(_server);
var req = new HttpRequestMessage(HttpMethod.Get, _url + "Cache_Multiply_Custom_Headers");
var result = client.SendAsync(req).Result;

var req2 = new HttpRequestMessage(HttpMethod.Get, _url + "Cache_Multiply_Custom_Headers");
var result2 = client.SendAsync(req2).Result;

Assert.That(result.Content.Headers.ContentDisposition.DispositionType, Is.EqualTo("attachment"));
Assert.That(result.Content.Headers.ContentEncoding.Count, Is.EqualTo(2));
Assert.That(result.Content.Headers.ContentEncoding.First(), Is.EqualTo("deflate"));
Assert.That(result.Content.Headers.ContentEncoding.Last(), Is.EqualTo("gzip"));
Assert.That(result.Headers.GetValues("RequestHeader1").First(), Is.EqualTo("value1"));
Assert.That(result.Headers.GetValues("RequestHeader2").Count(), Is.EqualTo(2));
Assert.That(result.Headers.GetValues("RequestHeader2").First(), Is.EqualTo("value2"));
Assert.That(result.Headers.GetValues("RequestHeader2").Last(), Is.EqualTo("value3"));

Assert.That(result2.Content.Headers.ContentDisposition.DispositionType, Is.EqualTo("attachment"));
Assert.That(result2.Content.Headers.ContentEncoding.Count, Is.EqualTo(2));
Assert.That(result2.Content.Headers.ContentEncoding.First(), Is.EqualTo("deflate"));
Assert.That(result2.Content.Headers.ContentEncoding.Last(), Is.EqualTo("gzip"));
Assert.That(result2.Headers.GetValues("RequestHeader1").First(), Is.EqualTo("value1"));
Assert.That(result2.Headers.GetValues("RequestHeader2").Count(), Is.EqualTo(2));
Assert.That(result2.Headers.GetValues("RequestHeader2").First(), Is.EqualTo("value2"));
Assert.That(result2.Headers.GetValues("RequestHeader2").Last(), Is.EqualTo("value3"));
}

[Test]
public void cache_part_of_custom_headers()
{
var client = new HttpClient(_server);
var req = new HttpRequestMessage(HttpMethod.Get, _url + "Cache_Part_Of_Custom_Headers");
var result = client.SendAsync(req).Result;

var req2 = new HttpRequestMessage(HttpMethod.Get, _url + "Cache_Part_Of_Custom_Headers");
var result2 = client.SendAsync(req2).Result;

Assert.That(result.Content.Headers.ContentDisposition.DispositionType, Is.EqualTo("attachment"));
Assert.That(result.Content.Headers.ContentEncoding.Count, Is.EqualTo(2));
Assert.That(result.Content.Headers.ContentEncoding.First(), Is.EqualTo("deflate"));
Assert.That(result.Content.Headers.ContentEncoding.Last(), Is.EqualTo("gzip"));
Assert.That(result.Headers.GetValues("RequestHeader1").First(), Is.EqualTo("value1"));
Assert.That(result.Headers.GetValues("RequestHeader2").Count(), Is.EqualTo(2));
Assert.That(result.Headers.GetValues("RequestHeader2").First(), Is.EqualTo("value2"));
Assert.That(result.Headers.GetValues("RequestHeader2").Last(), Is.EqualTo("value3"));

Assert.That(result2.Content.Headers.ContentDisposition, Is.Null);
Assert.That(result2.Content.Headers.ContentEncoding.Count, Is.EqualTo(2));
Assert.That(result2.Content.Headers.ContentEncoding.First(), Is.EqualTo("deflate"));
Assert.That(result2.Content.Headers.ContentEncoding.Last(), Is.EqualTo("gzip"));

IEnumerable<string> headerValue = null;
Assert.That(result2.Headers.TryGetValues("RequestHeader1", out headerValue), Is.False);
Assert.That(result2.Headers.TryGetValues("RequestHeader2", out headerValue), Is.False);
}

[TearDown]
public void fixture_dispose()
{
if (_server != null) _server.Dispose();
}
}
}
Loading