-
Notifications
You must be signed in to change notification settings - Fork 9
Flatwhite.WebApi
Flatwhite.WebApi is an output cache library for WebApi with VaryByParam (on action method) and VaryByHeader support, facilitate usages of cache control and HTTP Cache-Control Extensions for Stale Content
In your Global.asax.cs class:
GlobalConfiguration.Configure(WebApiConfig.Register);
//Optional custom cache store
Global.CacheStoreProvider.RegisterAsyncStore(new MyAsyncCacheStore());
GlobalConfiguration.Configure(x => x.UseFlatwhiteCache(new FlatwhiteWebApiConfiguration
{
EnableStatusController = true,
IgnoreVaryCustomKeys = false, // set to true could improve performance if you don't use vary by headers and vary by custom
LoopbackAddress = null // Set it to web server loopback address if server is behind load balancer
}));
In your Startup.cs class:
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
WebApiConfig.Register(config);
//Optional custom cache store
Global.CacheStoreProvider.RegisterAsyncStore(new MyAsyncCacheStore());
config.UseFlatwhiteCache(new FlatwhiteWebApiConfiguration
{
EnableStatusController = true,
IgnoreVaryCustomKeys = false, // set to true could improve performance if you don't use vary by headers and vary by custom
LoopbackAddress = null // Set it to web server loopback address if server is behind load balancer
});
app.UseWebApi(config);
}
You will need package Flatwhite.Autofac to use this obviously. Basically you will need it when you want to inject your own implementation of below interfaces. In your Startup.cs class:
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
var container = BuildAutofacContainer(config);
WebApiConfig.Register(config);
//Optional custom cache store
Global.CacheStoreProvider.RegisterAsyncStore(container.Resolve<IAsyncCacheStore>());
config.UseFlatwhiteCache(new FlatwhiteWebApiConfiguration
{
EnableStatusController = true,
IgnoreVaryCustomKeys = false, // set to true could improve performance if you don't use vary by headers and vary by custom
LoopbackAddress = null // Set it to web server loopback address if server is behind load balancer
});
app.UseWebApi(config);
}
private IContainer BuildAutofacContainer(HttpConfiguration config)
{
var builder = new ContainerBuilder().EnableFlatwhite();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterWebApiFilterProvider(config);
// Register your custom Flatwhite stuff below
builder.RegisterType<MyCustomAsyncCacheStore>().As<IAsyncCacheStore>().SingleInstance();
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
return container;
}
The server will response HttpStatusCode 304 by the eTag sent by browser immediately when it sees the cache is available without creating controllers, descriptors, etc which could slow down the performance.
NOTE: The OutputCache decorated here is from Flatwhite.WebApi as there is the same attribute from Flatwhite package
[HttpGet]
[Route("api/vary-by-param-async/{packageId}")]
[OutputCache(
MaxAge = 10,
StaleWhileRevalidate = 5,
VaryByParam = "packageId",
RevalidateKeyFormat = "Package_{packageId}",
IgnoreRevalidationRequest = true)]
public async Task<HttpResponseMessage> VaryByParamAsync(string packageId)
{
var sw = Stopwatch.StartNew();
var content = await new WebClient().DownloadStringTaskAsync(new Uri($"https://www.nuget.org/packages/" + packageId));
return new HttpResponseMessage()
{
Content = new StringContent($"Elapsed {sw.ElapsedMilliseconds} Milliseconds", Encoding.UTF8, "text/html")
};
}
[HttpGet]
[Route("api/vary-by-param/{packageId}")]
[OutputCache(
MaxAge = 10,
StaleWhileRevalidate = 5,
VaryByParam = "packageId",
RevalidateKeyFormat = "Package_{packageId}",
IgnoreRevalidationRequest = true)]
public HttpResponseMessage VaryByParam(string packageId)
{
var sw = Stopwatch.StartNew();
var content = new WebClient().DownloadString(new Uri($"https://www.nuget.org/packages/" + packageId));
return new HttpResponseMessage()
{
Content = new StringContent($"Elapsed {sw.ElapsedMilliseconds} Milliseconds", Encoding.UTF8,"text/html")
};
}
[HttpGet]
[Route("api/vary-by-header")]
[OutputCache(
MaxAge = 10,
StaleWhileRevalidate = 5,
VaryByHeader = "UserAgent")]
public async Task<HttpResponseMessage> VaryByHeader()
{
var sw = Stopwatch.StartNew();
var content = await new WebClient().DownloadStringTaskAsync(new Uri($"https://www.nuget.org/packages/Flatwhite"));
return new HttpResponseMessage()
{
Content = new StringContent($"Elapsed {sw.ElapsedMilliseconds} Milliseconds", Encoding.UTF8, "text/html")
};
}
[HttpGet]
[Route("api/reset")]
[Revalidate("Package_{packageId}")]
public HttpResponseMessage ResetCache(string packageId)
{
return new HttpResponseMessage(HttpStatusCode.OK);
}
If you call method UseFlatwhiteCache() with enableStatusController = true, Flatwhite will register the status controller which can be accessed at /_flatwhite/store/{storeId} (default store id is 0 which is MemoryCache). Go there and you can see json objects of cache items:
- Go to _/flatwhite/store{storeId} to see all cache item in current cache store. Below is sample payload:
[
{
"_type": "WebApiCacheItem",
"key": "fw-0-D274D4F7CF6AFC6D81283435F1977E85",
"size": 296,
"createdTime": "2015-12-11T09:46:18.3391372Z",
"maxAge": 2,
"staleWhileRevalidate": 5,
"storeId": 0,
"age": 1,
"isStale": false,
"checksum": "8445C4B330D87783166BF19B078A0813",
"responseMediaType": "text/html",
"responseCharSet": "utf-8",
"staleIfError": 0,
"autoRefresh": true,
"phoenixStatus": "inactive for 0.6 seconds"
},
{
"_type": "CacheItem",
"key": "Flatwhite::Flatwhite.WebApi2.Controllers.ICoffeeService.OrderCoffeeAsync()",
"size": 146791,
"createdTime": "2015-12-11T09:46:15.0149471Z",
"maxAge": 2,
"staleWhileRevalidate": 5,
"storeId": 0,
"age": 4,
"isStale": true,
"autoRefresh": false,
"phoenixStatus": "raising"
}
]
- Go to _/flatwhite/phoenixes to see all phoenix objects in memory
- In distributed environment, there is certainly cases where phoenix object is created from a server but not available in others. So hitting this enpdpoint from load balancer may show different results. The phoenix object may be created on other servers when cache is stale.
- Below is sample payload:
[
{
"_type": "Phoenix",
"key": "Flatwhite::Flatwhite.WebApi2.Controllers.ICoffeeService.OrderCoffeeAsync()",
"size": 359171,
"createdTime": "2015-12-11T09:45:46.7323294Z",
"maxAge": 2,
"staleWhileRevalidate": 5,
"storeId": 0,
"age": 4,
"isStale": true,
"autoRefresh": false,
"phoenixStatus": "raising"
},
{
"_type": "WebApiPhoenix",
"key": "fw-0-D274D4F7CF6AFC6D81283435F1977E85",
"size": 296,
"createdTime": "2015-12-11T09:45:50.3125342Z",
"maxAge": 2,
"staleWhileRevalidate": 5,
"storeId": 0,
"age": 1,
"isStale": false,
"checksum": "8445C4B330D87783166BF19B078A0813",
"responseMediaType": "text/html",
"responseCharSet": "utf-8",
"staleIfError": 0,
"autoRefresh": true,
"phoenixStatus": "inactive for 0.6 seconds"
}
]
Similar to the Core Flatwhite package, Flatwhite.WebApi can automatically refresh the cache item if you have staleWhileRevalidate greater than 0. The first request come to the server when the cache item started to be stale will kick off the refresh process by sending a background request to the loopback address.
So with a OutputCache setting like below:
- max-age: 2 seconds
- staleWhileRevalidate: 5 seconds
Given that it normally takes 1 to 2 seconds to refresh a heavy cache, most of requests (except the special request sent by the server itself to refresh the stale item) sent to server will be served cached data.
Please check out a screen cast here for more details, you would see that "age" is always below 2 seconds as the cache item is refreshed fast enough within a few seconds.