private async Task <AgilityOutputCacheResponse> CaptureResponse(HttpContext context) { var responseStream = context.Response.Body; AgilityOutputCacheResponse page = new AgilityOutputCacheResponse(); using (var buffer = new MemoryStream()) { try { context.Response.Body = buffer; await _next.Invoke(context); } finally { context.Response.Body = responseStream; } if (buffer.Length == 0) { return(null); } var bytes = buffer.ToArray(); // you could gzip here responseStream.Write(bytes, 0, bytes.Length); return(new AgilityOutputCacheResponse(context, bytes)); } }
/// <summary> ///Returns true if the content wasn't modified and has outputted a 304 /// </summary> /// <param name="context"></param> /// <returns></returns> private bool CheckForNotModified(HttpContext context, AgilityOutputCacheResponse pageResponse) { string modSinceStr = context.Request.Headers["If-Modified-Since"]; if (string.IsNullOrWhiteSpace(modSinceStr)) { return(false); } string etagMatch = context.Request.Headers["If-None-Match"]; DateTimeOffset dtIfModifiedSince = DateTimeOffset.MinValue; if (DateTimeOffset.TryParse(modSinceStr, out dtIfModifiedSince)) { TimeSpan expiration = GetOutputCacheExpiration(); TimeSpan timeSinceCache = DateTimeOffset.UtcNow - dtIfModifiedSince; if (timeSinceCache < expiration && pageResponse.ETag == etagMatch) { //if the page has NOT expired and the etags //return a 304... context.Response.StatusCode = 304; var typedHeaders = context.Response.GetTypedHeaders(); typedHeaders.CacheControl = new CacheControlHeaderValue { Public = true, MaxAge = expiration }; typedHeaders.Expires = dtIfModifiedSince + expiration; typedHeaders.LastModified = dtIfModifiedSince; return(true); } } return(false); }
public static AgilityOutputCacheResponse ReadFromFile(string url, string filePath) { try { AgilityOutputCacheResponse res = null; BinaryFormatter bf = new BinaryFormatter(); using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) { res = bf.Deserialize(fs) as AgilityOutputCacheResponse; } return(res); } catch (Exception ex) { throw new ApplicationException(string.Format("Could not read cached response for url:{0}, file {1}", url, filePath), ex); } }
public async Task WriteCachedPage(AgilityOutputCacheResponse page, HttpResponse response, string hitStatus) { response.StatusCode = page.StatusCode; foreach (string key in page.Headers.Keys) { if (string.Equals(key, "Transfer-Encoding", StringComparison.CurrentCultureIgnoreCase)) { continue; } string value = page.Headers[key]; response.Headers[key] = value; } response.Headers["X-AgilityCache"] = hitStatus; response.Headers["X-MachineName"] = Environment.MachineName; if (!string.IsNullOrWhiteSpace(page.ETag)) { response.Headers["ETag"] = page.ETag; } if (page.Body != null && page.Body.Length > 0) { try { await response.Body.WriteAsync(page.Body, 0, page.Body.Length); } catch { response.HttpContext.Abort(); } } return; }
public async Task Invoke(HttpContext context) { //initialize the offline processing... //HACK: OFFLINE: this should be started in the Startup //if (!OfflineProcessing.IsOfflineThreadRunning) //{ // OfflineProcessing.StartOfflineThread(); //} //Don't do ANYTHING if we are in preview mode, or if output cache is disabled if (!string.IsNullOrWhiteSpace(context.Request.Query["agilitypreviewkey"]) || AgilityContext.IsPreview || AgilityContext.CurrentMode == Agility.Web.Enum.Mode.Staging || (AgilityContext.Domain != null && AgilityContext.Domain.EnableOutputCache == false)) { await _next.Invoke(context); return; } string key = Data.GetAgilityVaryByCustomString(context); var cachedPage = AgilityCache.Get <AgilityOutputCacheResponse>(key); if (cachedPage != null) { if (CheckForNotModified(context, cachedPage)) { return; } await WriteCachedPage(cachedPage, context.Response, "hit"); return; } if (AgilityContext.IsResponseEnded) { return; } ApplyClientHeaders(context, key); if (IsCacheable(context)) { AgilityOutputCacheResponse page = await CaptureResponse(context); if (page != null) { TimeSpan serverCacheDuration = GetOutputCacheExpiration(); //TODO: determine if we need to include AgilityContext.OutputCacheKeys here... var depKeys = AgilityContext.OutputCacheDependencies; CacheDependency dep = new CacheDependency(cacheKeys: depKeys.ToArray()); AgilityCache.Set(key, page, serverCacheDuration, dep); } } else { await _next.Invoke(context); } }