Example #1
0
        internal Func <HttpRequestMessage, Task <HttpResponseMessage> > PutIfUnmodifiedSince()
        {
            return((request) =>
            {
                if (request.Method != HttpMethod.Put)
                {
                    return null;
                }

                DateTimeOffset?ifUnmodifiedSince = request.Headers.IfUnmodifiedSince;
                if (ifUnmodifiedSince == null)
                {
                    return null;
                }

                DateTimeOffset modifiedInQuestion = ifUnmodifiedSince.Value;

                var entityTagKey = GenerateCacheKey(request);
                TimedEntityTagHeaderValue actualEtag = null;

                bool isModified = true;
                if (_entityTagStore.TryGetValue(entityTagKey, out actualEtag))
                {
                    isModified = actualEtag.LastModified > modifiedInQuestion;
                }

                return isModified ? request.CreateResponse(HttpStatusCode.PreconditionFailed)
                .ToTask()
                                        : null;
            });
        }
Example #2
0
        internal Func <HttpRequestMessage, Task <HttpResponseMessage> > PutIfMatch()
        {
            return((request) =>
            {
                if (request.Method != HttpMethod.Put)
                {
                    return null;
                }

                ICollection <EntityTagHeaderValue> matchTags = request.Headers.IfMatch;
                if (matchTags == null || matchTags.Count == 0)
                {
                    return null;
                }

                var entityTagKey = GenerateCacheKey(request);
                TimedEntityTagHeaderValue actualEtag = null;

                bool matchFound = false;
                if (_entityTagStore.TryGetValue(entityTagKey, out actualEtag))
                {
                    if (matchTags.Any(etag => etag.Tag == actualEtag.Tag))
                    {
                        matchFound = true;
                    }
                }

                return matchFound ? null
                                        : request.CreateResponse(HttpStatusCode.PreconditionFailed)
                .ToTask();
            });
        }
Example #3
0
        protected virtual void CheckExpiry(HttpRequestMessage request)
        {
            // not interested if not GET
            if (request.Method != HttpMethod.Get)
            {
                return;
            }

            var cacheExpiry = CacheRefreshPolicyProvider(request, _configuration);

            if (cacheExpiry == TimeSpan.MaxValue)
            {
                return; // infinity
            }
            var cacheKey = GenerateCacheKey(request);
            TimedEntityTagHeaderValue value = null;

            if (!_entityTagStore.TryGetValue(cacheKey, out value))
            {
                return;
            }

            if (value.LastModified.Add(cacheExpiry) < DateTimeOffset.Now)
            {
                _entityTagStore.TryRemove(cacheKey);
            }
        }
Example #4
0
 public static void ApplyTimedETag(this HttpResponse response, TimedEntityTagHeaderValue timedETag)
 {
     if (timedETag.LastModified.HasValue)
     {
         response.Headers[HttpHeaderNames.LastModified] = timedETag.LastModified.Value.ToUniversalTime().ToString("r");
     }
     else if (timedETag.ETag != null)
     {
         response.Headers[HttpHeaderNames.ETag] = timedETag.ETag.ToString();
     }
 }
Example #5
0
        internal Func <HttpRequestMessage, Task <HttpResponseMessage> > GetIfMatchNoneMatch()
        {
            return((request) =>
            {
                try
                {
                    if (request.Method != HttpMethod.Get)
                    {
                        return null;
                    }

                    ICollection <EntityTagHeaderValue> noneMatchTags = request.Headers.IfNoneMatch;
                    ICollection <EntityTagHeaderValue> matchTags = request.Headers.IfMatch;

                    if (matchTags.Count == 0 && noneMatchTags.Count == 0)
                    {
                        return null;                                    // no etag
                    }
                    if (matchTags.Count > 0 && noneMatchTags.Count > 0) // both if-match and if-none-match exist
                    {
                        return request.CreateResponse(HttpStatusCode.BadRequest)
                        .ToTask();
                    }

                    var isNoneMatch = noneMatchTags.Count > 0;
                    var etags = isNoneMatch ? noneMatchTags : matchTags;

                    var entityTagKey = GenerateCacheKey(request);
                    // compare the Etag with the one in the cache
                    // do conditional get.
                    TimedEntityTagHeaderValue actualEtag = null;

                    bool matchFound = false;
                    if (_entityTagStore.TryGetValue(entityTagKey, out actualEtag))
                    {
                        if (etags.Any(etag => etag.Tag == actualEtag.Tag))
                        {
                            matchFound = true;
                        }
                    }

                    return matchFound ^ isNoneMatch ? null : new NotModifiedResponse(request, null,
                                                                                     actualEtag.ToEntityTagHeaderValue()).ToTask();
                }
                catch (Exception e)
                {
                    throw new CacheCowServerException("Error in GetIfMatchNoneMatch", e);
                }
            });
        }
Example #6
0
        internal Func <HttpRequestMessage, Task <HttpResponseMessage> > GetIfModifiedUnmodifiedSince()
        {
            return((request) =>
            {
                try
                {
                    if (request.Method != HttpMethod.Get)
                    {
                        return null;
                    }

                    DateTimeOffset?ifModifiedSince = request.Headers.IfModifiedSince;
                    DateTimeOffset?ifUnmodifiedSince = request.Headers.IfUnmodifiedSince;

                    if (ifModifiedSince == null && ifUnmodifiedSince == null)
                    {
                        return null;                                          // no etag
                    }
                    if (ifModifiedSince != null && ifUnmodifiedSince != null) // both exist
                    {
                        return request.CreateResponse(HttpStatusCode.BadRequest)
                        .ToTask();
                    }
                    bool ifModified = (ifUnmodifiedSince == null);
                    DateTimeOffset modifiedInQuestion = ifModified ? ifModifiedSince.Value : ifUnmodifiedSince.Value;


                    var entityTagKey = GenerateCacheKey(request);

                    TimedEntityTagHeaderValue actualEtag = null;

                    bool isModified = true;
                    if (_entityTagStore.TryGetValue(entityTagKey, out actualEtag))
                    {
                        isModified = actualEtag.LastModified > modifiedInQuestion;
                    }

                    return isModified ^ ifModified
                            ? new NotModifiedResponse(request, null, actualEtag.ToEntityTagHeaderValue()).ToTask()
                            : null;
                }
                catch (Exception e)
                {
                    throw new CacheCowServerException("Error in GetIfModifiedUnmodifiedSince", e);
                }
            });
        }
        public void AddOrUpdate(CacheKey key, TimedEntityTagHeaderValue eTag)
        {
            _eTagCache.Set(key.HashBase64, eTag, DateTimeOffset.MaxValue);

            // route pattern
            var bag = new ConcurrentBag <CacheKey>();

            bag = (ConcurrentBag <CacheKey>)_routePatternCache.AddOrGetExisting(key.RoutePattern, bag
                                                                                , DateTimeOffset.MaxValue) ?? bag;
            bag.Add(key);

            // resource
            var rbag = new ConcurrentBag <CacheKey>();

            rbag = (ConcurrentBag <CacheKey>)_resourceCache.AddOrGetExisting(key.ResourceUri, rbag
                                                                             , DateTimeOffset.MaxValue) ?? rbag;
            rbag.Add(key);
        }
Example #8
0
        /// <summary>
        /// Adds caching for GET and PUT if
        /// cache control provided is not null
        /// With PUT, since cache has been alreay invalidated,
        /// we provide the new ETag (old one has been cleared in invalidation phase)
        /// </summary>
        /// <param name="cacheKey"></param>
        /// <param name="request"></param>
        /// <param name="response"></param>
        /// <param name="varyHeaders"></param>
        /// <returns></returns>
        internal Action AddCaching(
            CacheKey cacheKey,
            HttpRequestMessage request,
            HttpResponseMessage response)
        {
            return
                (() =>
            {
                var cacheControlHeaderValue = CacheControlHeaderProvider(request, _configuration);
                if (cacheControlHeaderValue == null)
                {
                    return;
                }

                TimedEntityTagHeaderValue eTagValue;

                string uri = UriTrimmer(request.RequestUri);

                // in case of GET and no ETag
                // in case of PUT, we should return the new ETag of the resource
                // NOTE: No need to check if it is in the cache. If it were, it would not get
                // here
                if (request.Method == HttpMethod.Get || request.Method == HttpMethod.Put)
                {
                    // create new ETag only if it does not already exist
                    if (!_entityTagStore.TryGetValue(cacheKey, out eTagValue))
                    {
                        eTagValue = new TimedEntityTagHeaderValue(ETagValueGenerator(uri, request.Headers));
                        _entityTagStore.AddOrUpdate(cacheKey, eTagValue);
                    }

                    // set ETag
                    response.Headers.ETag = eTagValue.ToEntityTagHeaderValue();

                    // set last-modified
                    if (AddLastModifiedHeader && response.Content != null && !response.Content.Headers.Any(x => x.Key.Equals(HttpHeaderNames.LastModified,
                                                                                                                             StringComparison.CurrentCultureIgnoreCase)))
                    {
                        response.Content.Headers.Add(HttpHeaderNames.LastModified, eTagValue.LastModified.ToString("r"));
                    }

                    // set Vary
                    if (AddVaryHeader && _varyByHeaders != null && _varyByHeaders.Length > 0)
                    {
                        response.Headers.Add(HttpHeaderNames.Vary, _varyByHeaders);
                    }

                    // harmonise Pragma header with cachecontrol header
                    if (cacheControlHeaderValue.NoStore)
                    {
                        response.Headers.TryAddWithoutValidation(HttpHeaderNames.Pragma, "no-cache");
                        if (response.Content != null)
                        {
                            response.Content.Headers.Expires = DateTimeOffset.Now.Subtract(TimeSpan.FromSeconds(1));
                        }
                    }
                    else
                    {
                        if (response.Headers.Contains(HttpHeaderNames.Pragma))
                        {
                            response.Headers.Remove(HttpHeaderNames.Pragma);
                        }
                    }

                    response.Headers.TryAddWithoutValidation(HttpHeaderNames.CacheControl, cacheControlHeaderValue.ToString());
                }
            });
        }
Example #9
0
        /// <summary>
        /// Happens at the incoming (executING)
        /// </summary>
        /// <param name="timedEtag"></param>
        /// <param name="cacheValidationStatus"></param>
        /// <param name="context">
        /// </param>
        /// <returns>
        /// True: applied and the call can exit (short-circuit)
        /// False: tried to apply but did not match hence the call should continue
        /// null: could not apply (timedEtag was null)
        /// </returns>
        protected bool?ApplyCacheValidation(TimedEntityTagHeaderValue timedEtag,
                                            CacheValidationStatus cacheValidationStatus,
                                            HttpContext context)
        {
            if (timedEtag == null)
            {
                return(null);
            }

            var headers = context.Request.GetTypedHeadersWithCaching();

            switch (cacheValidationStatus)
            {
            case CacheValidationStatus.GetIfModifiedSince:
                if (timedEtag.LastModified == null)
                {
                    return(false);
                }
                else
                {
                    if (timedEtag.LastModified > headers.IfModifiedSince.Value)
                    {
                        return(false);
                    }
                    else
                    {
                        context.Response.StatusCode = StatusCodes.Status304NotModified;
                        return(true);
                    }
                }

            case CacheValidationStatus.GetIfNoneMatch:
                if (timedEtag.ETag == null)
                {
                    return(false);
                }
                else
                {
                    if (headers.IfNoneMatch.Any(x => x.Tag == timedEtag.ETag.Tag))
                    {
                        context.Response.StatusCode = StatusCodes.Status304NotModified;
                        return(true);
                    }
                    else
                    {
                        return(false);
                    }
                }

            case CacheValidationStatus.PutPatchDeleteIfMatch:
                if (timedEtag.ETag == null)
                {
                    return(false);
                }
                else
                {
                    if (headers.IfMatch.Any(x => x.Tag == timedEtag.ETag.Tag))
                    {
                        return(false);
                    }
                    else
                    {
                        context.Response.StatusCode = StatusCodes.Status412PreconditionFailed;
                        return(true);
                    }
                }

            case CacheValidationStatus.PutPatchDeleteIfUnModifiedSince:
                if (timedEtag.LastModified == null)
                {
                    return(false);
                }
                else
                {
                    if (timedEtag.LastModified > headers.IfUnmodifiedSince.Value)
                    {
                        context.Response.StatusCode = StatusCodes.Status412PreconditionFailed;
                        return(true);
                    }
                    else
                    {
                        return(false);
                    }
                }

            default:
                return(null);
            }
        }
Example #10
0
        /// <summary>
        /// Applies after the controller assigned a result to the action
        /// </summary>
        /// <param name="context">HttpContext</param>
        /// <param name="viewModel">action result</param>
        public async Task After(HttpContext context, object viewModel)
        {
            var  ms          = context.Response.Body as MemoryStream;
            bool mustReflush = ms != null && ms.Length > 0;

            context.Response.Body = _stream;

            try
            {
                if (HttpMethods.IsGet(context.Request.Method))
                {
                    context.Response.Headers.Add(HeaderNames.Vary, string.Join(";", _cacheDirectiveProvider.GetVaryHeaders(context)));
                    var cacheControl        = _cacheDirectiveProvider.GetCacheControl(context, this.ConfiguredExpiry);
                    var isResponseCacheable = _validator.IsCacheable(context.Response);
                    if (!cacheControl.NoStore && isResponseCacheable) // _______ is cacheable
                    {
                        TimedEntityTagHeaderValue tet = null;
                        if (viewModel != null)
                        {
                            tet = _cacheDirectiveProvider.Extract(viewModel);
                        }

                        if (_cacheValidated == null && // could not validate
                            tet != null &&
                            _cacheValidationStatus != CacheValidationStatus.None)    // can only do GET validation, PUT is already impacted backend stores
                        {
                            _cacheValidated = ApplyCacheValidation(tet, _cacheValidationStatus, context);
                            _cacheCowHeader.ValidationApplied = true;
                            // the response would have been set and no need to run the rest of the pipeline

                            if (_cacheValidated ?? false)
                            {
                                _cacheCowHeader.ValidationMatched = true;
                                context.Response.Headers.Add(CacheCowHeader.Name, _cacheCowHeader.ToString());
                                return;
                            }
                        }

                        if (tet != null)
                        {
                            context.Response.ApplyTimedETag(tet);
                        }
                    }

                    if (!_isRequestCacheable || !isResponseCacheable)
                    {
                        context.Response.MakeNonCacheable();
                    }
                    else
                    {
                        context.Response.Headers[HttpHeaderNames.CacheControl] = cacheControl.ToString();
                    }

                    context.Response.Headers.Add(CacheCowHeader.Name, _cacheCowHeader.ToString());
                }
            }
            finally
            {
                if (mustReflush)
                {
                    ms.Position = 0;
                    await ms.CopyToAsync(context.Response.Body);
                }
            }
        }
 public bool TryGetValue(CacheKey key, out TimedEntityTagHeaderValue eTag)
 {
     eTag = (TimedEntityTagHeaderValue)_eTagCache.Get(key.HashBase64);
     return(eTag != null);
 }