/// <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();
 }
Ejemplo n.º 2
0
 /// <summary>
 /// EWF use only.
 /// </summary>
 public EwfResponse(FullResponse fullResponse)
 {
     ContentType     = fullResponse.ContentType;
     FileNameCreator = () => fullResponse.FileName;
     BodyCreator     = fullResponse.TextBody != null
                                       ? new EwfResponseBodyCreator(() => fullResponse.TextBody)
                                       : new EwfResponseBodyCreator(() => fullResponse.BinaryBody);
 }
        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 = new EwfResponse(
                        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);
                }
            });
        }