ASP.net ashx handler not caching
We recently installed Robohash as a great fallback for Gravatar:
http://static2.scirra.net/avatars/128/5df4bf5d460c9497fdb35578e923ad1f.png
As you can see robohashes are hilariously brilliant and served from our static domain. The URL is actually rewritten:
<action type="Rewrite" url="gravatar.ashx?hash={R:2}&size={R:1}" appendQueryString="false" />
And in the 开发者_JAVA百科same web.config file we have the cache profiles:
<staticContent>
<clientCache httpExpires="Sun, 29 Mar 2020 00:00:00 GMT" cacheControlMode="UseExpires" />
</staticContent>
<caching>
<profiles>
<add extension=".ashx" policy="CacheForTimePeriod" kernelCachePolicy="DontCache" duration="01:00:00" />
<add extension=".png" policy="CacheUntilChange" kernelCachePolicy="CacheUntilChange" location="Any" />
<add extension=".jpg" policy="CacheUntilChange" kernelCachePolicy="CacheUntilChange" location="Any" />
<add extension=".gif" policy="CacheUntilChange" kernelCachePolicy="CacheUntilChange" location="Any" />
<add extension=".ico" policy="CacheUntilChange" kernelCachePolicy="CacheUntilChange" location="Any" />
</profiles>
</caching>
For good measure in the gravatar.ashx file we also set a cache policy:
<%@ WebHandler Language="C#" Class="GravatarImage" %>
using System;
using System.Web;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Net;
public class GravatarImage : IHttpHandler {
public void ProcessRequest (HttpContext context) {
// Adds document content type
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetExpires(DateTime.Now.AddMinutes(60));
context.Response.Cache.SetMaxAge(new TimeSpan(1, 0, 0));
context.Response.AddHeader("Last-Modified", DateTime.Now.ToLongDateString());
// Get params and send initial request to gravatar
string Hash = context.Request.QueryString["hash"];
string Size = context.Request.QueryString["size"];
string URL = "http://www.gravatar.com/avatar/" + Hash + "?r=pg&s=" + Size + "&d=404";
// Make request to gravatar
bool Fetched = makeAvatarRequest(URL);
// Backup to robo hash
if (!Fetched)
{
URL = "http://robohash.org/" + Hash + ".png?size=" + Size + "x" + Size + "&bgset=bg2";
Fetched = makeAvatarRequest(URL);
}
// Fallback if gravatar doesn't match and robohash is down
if (!Fetched)
{
}
// Cache this handler response for 1 hour.
HttpCachePolicy c = context.Response.Cache;
c.SetCacheability(HttpCacheability.Public);
c.SetMaxAge(new TimeSpan(1, 0, 0));
}
// Attempt a request for avatar
private bool makeAvatarRequest(string URL)
{
try
{
WebRequest request = WebRequest.Create(URL);
using (WebResponse response = request.GetResponse())
{
using (Stream responseStream = response.GetResponseStream())
{
displayImage(responseStream);
return true;
}
}
}
catch (WebException ex)
{
return false;
}
}
// Display the image from stream
private void displayImage(Stream stream)
{
HttpContext.Current.Response.ContentType = "image/png";
Image img = Image.FromStream(stream);
MemoryStream temp = new MemoryStream();
img.Save(temp, ImageFormat.Png);
byte[] buffer = temp.GetBuffer();
HttpContext.Current.Response.OutputStream.Write(buffer, 0, buffer.Length);
img.Dispose();
temp.Dispose();
}
public bool IsReusable {
get {
return false;
}
}
}
Note we use Response.cache AND CachePolicy at the bottom
When I use YSlow, every image on the page has a future expiry date, EXCEPT these avatars which have no expiry date. Every time a page is requested they are fetched again.
The idea of the script is to fetch the avatar from the external URL and cache it for 1 hour. It's then served from our website.
Can anyone help us out here? An example page with the avatars being used without cache is: http://www.scirra.com/forum/construct-in-ludum-dare-21_topic44523.html
Because you are using a handler you need to handle the caching headers in your own code, ie returning the 304 repsonse as well. So check the dates on the headers against the files and return 304's where appropriate.
We have code similar to the following at the start of some our image handlers
var lastModified = this.LastModifiedFileTime(path);
var isNotModified = this.WriteConditional304(context, lastModified);
if (isNotModified)
return;
the two methods used look roughly as below.
protected bool WriteConditional304(HttpContext context, DateTime lastWrite)
{
if (context.Request.Headers[since] != null || context.Request.Headers[eTag] != null)
{
try
{
DateTime date = context.Request.Headers[since] != null ? DateTime.Parse(context.Request.Headers[since]) : new DateTime(long.Parse(context.Request.Headers[eTag]));
if (lastWrite <= date)
{
Write304(context);
return true;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
return false;
}
protected DateTime LastModifiedFileTime(string path)
{
FileInfo fi = new FileInfo(path);
var modificationTime = fi.LastWriteTime;
// negates the smaller parts of the date as the header doesnt carry them
var date = new DateTime(modificationTime.Year, modificationTime.Month, modificationTime.Day, modificationTime.Hour,
modificationTime.Minute, modificationTime.Second);
return date;
}
精彩评论