/// <summary> /// Creates a generic secondary response. /// </summary> /// <param name="responseCreator">The generic-response creator. Executes with the data-access cache enabled.</param> /// <param name="memoryCachingSetup">The memory-caching setup object for the response. Pass null if you do not want to use EWL's memory cache.</param> public SecondaryResponse(Func <EwfResponse> responseCreator, ResponseMemoryCachingSetup memoryCachingSetup = null) { fullResponseGetter = () => memoryCachingSetup != null ? FullResponse.GetFromCache(memoryCachingSetup.CacheKey, memoryCachingSetup.LastModificationDateAndTime, () => responseCreator().CreateFullResponse()) : responseCreator().CreateFullResponse(); }
private static Action <HttpRequest, HttpResponse> createWriter( Func <EwfResponse> responseCreator, string urlVersionString, string eTagBase, Func <DateTimeOffset> lastModificationDateAndTimeGetter, Func <string> memoryCacheKeyGetter) { return((aspNetRequest, aspNetResponse) => { // Disable ASP.NET output caching. aspNetResponse.Cache.SetNoServerCaching(); var response = new Lazy <EwfResponse>(responseCreator); // If we ever want to implement content negotiation, we can do so here. We can accept multiple response-creator functions instead of just one. Then, if // the request contains an Accept header, we can evaluate the response-creator functions here and then choose the response with the best content type. // If there is no match, we can send a 406 (Not Acceptable) status code and exit the method. Note that if we do add support for multiple // response-creator functions, we need to incorporate the chosen response's content type into the ETag if we want it to remain a strong ETag. We also // need to incorporate the content type into the memory-cache key. // Assume that all HTTPS responses are private. This isn't true for CSS, JavaScript, etc. requests that are only secure in order to match the security // of a page, but that's not a big deal since most shared caches can't open and cache HTTPS anyway. // // If we don't have caching information, the response is probably not shareable. // // Using ServerAndPrivate instead of just Private is a hack that is necessary because ASP.NET suppresses ETags with Private. But ASP.NET output caching // (i.e. server caching) will still be disabled because of our SetNoServerCaching call above. aspNetResponse.Cache.SetCacheability( !EwfApp.Instance.RequestIsSecure(aspNetRequest) && (urlVersionString.Any() || eTagBase.Any() || lastModificationDateAndTimeGetter != null) ? HttpCacheability.Public : HttpCacheability.ServerAndPrivate); aspNetResponse.Cache.SetMaxAge(urlVersionString.Any() ? TimeSpan.FromDays(365) : TimeSpan.Zero); var lastModificationDateAndTime = lastModificationDateAndTimeGetter != null ? new Lazy <DateTimeOffset>(lastModificationDateAndTimeGetter) : null; string eTag; if (urlVersionString.Any()) { eTag = urlVersionString; } else if (eTagBase.Any() || lastModificationDateAndTimeGetter != null) { eTag = eTagBase + (lastModificationDateAndTimeGetter != null ? GetUrlVersionString(lastModificationDateAndTime.Value) : ""); } else { // Buffer the response body. var responseWithBufferedBody = EwfResponse.Create( response.Value.ContentType, response.Value.BodyCreator.GetBufferedBodyCreator(), fileNameCreator: response.Value.FileNameCreator); response = new Lazy <EwfResponse>(() => responseWithBufferedBody); var bodyAsBinary = response.Value.BodyCreator.BodyIsText ? Encoding.UTF8.GetBytes(response.Value.BodyCreator.TextBodyCreator()) : response.Value.BodyCreator.BinaryBodyCreator(); eTag = Convert.ToBase64String(MD5.Create().ComputeHash(bodyAsBinary)); } // Strong ETags must vary by content coding. Since we don't know yet how this response will be encoded (gzip or otherwise), the best thing we can do is // use the Accept-Encoding header value in the ETag. var acceptEncoding = aspNetRequest.Headers["Accept-Encoding"]; // returns null if field missing if (acceptEncoding != null) { eTag += acceptEncoding.Replace(", ", ""); // On 4 Feb 2015 we saw the comma/space cause Chrome to incorrectly split the ETag apart. } aspNetResponse.Cache.SetETag(eTag); // Sending a Last-Modified header isn't a good enough reason to force evaluation of lastModificationDateAndTimeGetter, which could be expensive. if (lastModificationDateAndTimeGetter != null && lastModificationDateAndTime.IsValueCreated) { aspNetResponse.Cache.SetLastModified(lastModificationDateAndTime.Value.UtcDateTime); } // When we separate EWF from Web Forms, we may need to add a Vary header with "Accept-Encoding". Do that here. var ifNoneMatch = aspNetRequest.Headers.GetValues("If-None-Match"); // returns null if field missing if (ifNoneMatch != null && ifNoneMatch.Contains(eTag)) { aspNetResponse.StatusCode = 304; return; } var memoryCacheKey = memoryCacheKeyGetter(); if (memoryCacheKey.Any()) { var fullResponse = FullResponse.GetFromCache(memoryCacheKey, lastModificationDateAndTime.Value, () => response.Value.CreateFullResponse()); response = new Lazy <EwfResponse>(() => new EwfResponse(fullResponse)); } if (response.Value.ContentType.Length > 0) { aspNetResponse.ContentType = response.Value.ContentType; } var fileName = response.Value.FileNameCreator(); if (fileName.Any()) { aspNetResponse.AppendHeader("content-disposition", "attachment; filename=\"" + fileName + "\""); } if (aspNetRequest.HttpMethod != "HEAD") { response.Value.BodyCreator.WriteToResponse(aspNetResponse); } }); }
private static Action <HttpResponse, HttpRequest, bool> createWriter( Func <EwfResponse> responseCreator, string urlVersionString, string eTagBase, Func <DateTimeOffset> lastModificationDateAndTimeGetter, Func <string> memoryCacheKeyGetter) { return((aspNetResponse, aspNetRequest, forceImmediateResponseExpiration) => { var response = new Lazy <EwfResponse>(responseCreator); // If we ever want to implement content negotiation, we can do so here. We can accept multiple response-creator functions instead of just one. Then, if // the request contains an Accept header, we can evaluate the response-creator functions here and then choose the response with the best content type. // If there is no match, we can send a 406 (Not Acceptable) status code and exit the method. Note that if we do add support for multiple // response-creator functions, we need to incorporate the chosen response's content type into the ETag if we want it to remain a strong ETag. We also // need to incorporate the content type into the memory-cache key. AddCacheControlHeader( aspNetResponse, EwfApp.Instance.RequestIsSecure(aspNetRequest), urlVersionString.Any() || eTagBase.Any() || lastModificationDateAndTimeGetter != null, urlVersionString.Any() && !forceImmediateResponseExpiration); var lastModificationDateAndTime = lastModificationDateAndTimeGetter != null ? new Lazy <DateTimeOffset>(lastModificationDateAndTimeGetter) : null; string eTag; if (urlVersionString.Any()) { eTag = urlVersionString; } else if (eTagBase.Any() || lastModificationDateAndTimeGetter != null) { eTag = eTagBase + (lastModificationDateAndTimeGetter != null ? GetUrlVersionString(lastModificationDateAndTime.Value) : ""); } else { // Buffer the response body. var responseWithBufferedBody = EwfResponse.Create( response.Value.ContentType, response.Value.BodyCreator.GetBufferedBodyCreator(), fileNameCreator: response.Value.FileNameCreator, additionalHeaderFieldGetter: response.Value.AdditionalHeaderFieldGetter); response = new Lazy <EwfResponse>(() => responseWithBufferedBody); var bodyAsBinary = response.Value.BodyCreator.BodyIsText ? Encoding.UTF8.GetBytes(response.Value.BodyCreator.TextBodyCreator()) : response.Value.BodyCreator.BinaryBodyCreator(); eTag = Convert.ToBase64String(MD5.Create().ComputeHash(bodyAsBinary)); } // Strong ETags must vary by content coding. Since we don't know yet how this response will be encoded (gzip or otherwise), the best thing we can do is // use the Accept-Encoding header value in the ETag. var acceptEncoding = aspNetRequest.Headers["Accept-Encoding"]; // returns null if field missing if (acceptEncoding != null) { eTag += acceptEncoding.Replace(", ", ""); // On 4 Feb 2015 we saw the comma/space cause Chrome to incorrectly split the ETag apart. } aspNetResponse.Cache.SetETag(eTag); // Sending a Last-Modified header isn't a good enough reason to force evaluation of lastModificationDateAndTimeGetter, which could be expensive. if (lastModificationDateAndTimeGetter != null && lastModificationDateAndTime.IsValueCreated) { aspNetResponse.Cache.SetLastModified(lastModificationDateAndTime.Value.UtcDateTime); } // When we separate EWF from Web Forms, we may need to add a Vary header with "Accept-Encoding". Do that here. var ifNoneMatch = aspNetRequest.Headers.GetValues("If-None-Match"); // returns null if field missing if (ifNoneMatch != null && ifNoneMatch.Contains(eTag)) { aspNetResponse.StatusCode = 304; return; } var memoryCacheKey = memoryCacheKeyGetter(); if (memoryCacheKey.Any()) { var fullResponse = FullResponse.GetFromCache(memoryCacheKey, lastModificationDateAndTime.Value, () => response.Value.CreateFullResponse()); response = new Lazy <EwfResponse>(() => new EwfResponse(fullResponse)); } response.Value.WriteToAspNetResponse(aspNetResponse, omitBody: aspNetRequest.HttpMethod == "HEAD"); }); }