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);
            }
        }