public static void TestCacheInvalidationForPost(string method)
		{
			// setup
			var locationUrl = new Uri("http://api/SomeLocationUrl");
			var mocks = new MockRepository();
			var request = new HttpRequestMessage(new HttpMethod(method), TestUrl);
			string routePattern = "http://myserver/api/stuffs/*";
			var entityTagStore = mocks.StrictMock<IEntityTagStore>();
			var linkedUrls = new[] { "url1", "url2" };
			var cachingHandler = new CachingHandler(entityTagStore)
			{
				LinkedRoutePatternProvider = (url, mthd) => linkedUrls
			};
			var entityTagKey = new EntityTagKey(TestUrl, new string[0], routePattern);
			var response = new HttpResponseMessage();
			response.Headers.Location = locationUrl;
			var invalidateCacheForPost = cachingHandler.PostInvalidationRule(entityTagKey, request, response);
			if(method == "POST")
			{
				entityTagStore.Expect(x => x.RemoveAllByRoutePattern(locationUrl.ToString())).Return(1);				
			}
			mocks.ReplayAll();

			// run
			invalidateCacheForPost();

			// verify
			mocks.VerifyAll();

			
		}
		public static void TestCacheInvalidation(string method)
		{
			// setup
			var mocks = new MockRepository();
			var request = new HttpRequestMessage(new HttpMethod(method), TestUrl);
			string routePattern = "http://myserver/api/stuffs/*";
			var entityTagStore = mocks.StrictMock<IEntityTagStore>();
			var linkedUrls = new []{"url1", "url2"};
			var cachingHandler = new CachingHandler(entityTagStore)
			                     	{
										LinkedRoutePatternProvider = (url, mthd) => linkedUrls
			                     	};
			var entityTagKey = new EntityTagKey(TestUrl, new string[0], routePattern);
			var response = new HttpResponseMessage();
			var invalidateCache = cachingHandler.InvalidateCache(entityTagKey, request, response);
			entityTagStore.Expect(x => x.RemoveAllByRoutePattern(routePattern)).Return(1);
			entityTagStore.Expect(x => x.RemoveAllByRoutePattern(linkedUrls[0])).Return(0);
			entityTagStore.Expect(x => x.RemoveAllByRoutePattern(linkedUrls[1])).Return(0);
			mocks.ReplayAll();

			// run
			invalidateCache();

			// verify
			mocks.VerifyAll();


		}
Пример #3
0
        public static void TestCachingContinuation(
			string method,
			bool existsInStore,
			bool addVaryHeader,
			bool addLastModifiedHeader,
			bool alreadyHasLastModified,
			string[] varyByHeader)
        {
            // setup
            var mocks = new MockRepository();
            var request = new HttpRequestMessage(new HttpMethod(method), TestUrl);
            request.Headers.Add(HttpHeaderNames.Accept, "text/xml");
            request.Headers.Add(HttpHeaderNames.AcceptLanguage, "en-GB");
            var entityTagStore = mocks.StrictMock<IEntityTagStore>();
            var cachingHandler = new CachingHandler(entityTagStore, varyByHeader)
                                 	{
                                 		AddLastModifiedHeader = addLastModifiedHeader,
                                        AddVaryHeader = addVaryHeader
                                 	};

            var entityTagHeaderValue = new TimedEntityTagHeaderValue("\"12345678\"");
            var entityTagKey = new EntityTagKey(TestUrl, new string[0]);
            cachingHandler.EntityTagKeyGenerator = (x, y) => entityTagKey;
            cachingHandler.ETagValueGenerator = (x, y) => new EntityTagHeaderValue(entityTagHeaderValue.Tag);
            entityTagStore.Expect(x => x.TryGet(Arg<EntityTagKey>.Matches(etg => etg.ResourceUri == TestUrl),
                out Arg<TimedEntityTagHeaderValue>.Out(entityTagHeaderValue).Dummy)).Return(existsInStore);

            if (!existsInStore  || request.Method == HttpMethod.Put || request.Method == HttpMethod.Post)
            {
                entityTagStore.Expect(
                    x => x.AddOrUpdate(Arg<EntityTagKey>.Matches(etk => etk == entityTagKey),
                        Arg<TimedEntityTagHeaderValue>.Matches(ethv => ethv.Tag == entityTagHeaderValue.Tag)));
            }

            mocks.ReplayAll();

            var response = new HttpResponseMessage();
            response.Content = new ByteArrayContent(new byte[0]);
            if(alreadyHasLastModified)
                response.Content.Headers.Add(HttpHeaderNames.LastModified, DateTimeOffset.Now.ToString("r"));

            var cachingContinuation = cachingHandler.GetCachingContinuation(request);
            var taskCompletionSource = new TaskCompletionSource<HttpResponseMessage>();
            taskCompletionSource.SetResult(response);

            // run
            var httpResponseMessage = cachingContinuation(taskCompletionSource.Task);

            // test
            if(addLastModifiedHeader)
            {
                Assert.That(httpResponseMessage.Content.Headers.Any(x=>x.Key == HttpHeaderNames.LastModified),
                    "LastModified does not exist");
            }
            if(!addLastModifiedHeader && !alreadyHasLastModified)
            {
                Assert.That(!httpResponseMessage.Content.Headers.Any(x => x.Key == HttpHeaderNames.LastModified),
                    "LastModified exists");
            }
        }
Пример #4
0
        /// <summary>
        /// This invalidates the resource based on routePattern
        /// for methods POST, PUT and DELETE.
        /// It also removes for all linked URLs
        /// </summary>
        /// <param name="entityTagKey"></param>
        /// <param name="request"></param>
        /// <param name="response"></param>
        /// <returns></returns>
        internal Action InvalidateCache(
            EntityTagKey entityTagKey,
            HttpRequestMessage request,
            HttpResponseMessage response)
        {
            return
                (() =>
            {
                if (!request.Method.Method.IsIn("PUT", "DELETE", "POST"))
                {
                    return;
                }

                string uri = UriTrimmer(request.RequestUri);

                // remove pattern
                _entityTagStore.RemoveAllByRoutePattern(entityTagKey.RoutePattern);

                // remove all related URIs
                var linkedUrls = LinkedRoutePatternProvider(uri, request.Method);
                foreach (var linkedUrl in linkedUrls)
                {
                    _entityTagStore.RemoveAllByRoutePattern(linkedUrl);
                }
            });
        }
		public void AddOrUpdate(EntityTagKey key, TimedEntityTagHeaderValue eTag)
		{
			_eTagCache.AddOrUpdate(key, eTag, (theKey, oldValue) => eTag);
			_routePatternCache.AddOrUpdate(key.RoutePattern, new HashSet<EntityTagKey>() { key }, 
				(routePattern, hashSet) =>
					{
						hashSet.Add(key);
						return hashSet;
					});
		}
Пример #6
0
 protected void ExecuteCacheInvalidationRules(EntityTagKey entityTagKey,
                                              HttpRequestMessage request,
                                              HttpResponseMessage response)
 {
     new[]
     {
         InvalidateCache(entityTagKey, request, response),                         // general invalidation
         PostInvalidationRule(entityTagKey, request, response)
     }
     .Chain()();
 }
Пример #7
0
 protected void ExecuteCacheAdditionRules(EntityTagKey entityTagKey,
                                          HttpRequestMessage request,
                                          HttpResponseMessage response,
                                          IEnumerable <KeyValuePair <string, IEnumerable <string> > > varyHeaders)
 {
     new[]
     {
         AddCaching(entityTagKey, request, response, varyHeaders),                         // general caching
     }
     .Chain()();
 }
Пример #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="entityTagKey"></param>
        /// <param name="request"></param>
        /// <param name="response"></param>
        /// <param name="varyHeaders"></param>
        /// <returns></returns>
        internal Action AddCaching(
            EntityTagKey entityTagKey,
            HttpRequestMessage request,
            HttpResponseMessage response,
            IEnumerable <KeyValuePair <string, IEnumerable <string> > > varyHeaders)
        {
            return
                (() =>
            {
                var cacheControlHeaderValue = CacheControlHeaderProvider(request);
                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(entityTagKey, out eTagValue))
                    {
                        eTagValue = new TimedEntityTagHeaderValue(ETagValueGenerator(uri, varyHeaders));
                        _entityTagStore.AddOrUpdate(entityTagKey, 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);
                    }

                    response.Headers.TryAddWithoutValidation(HttpHeaderNames.CacheControl, cacheControlHeaderValue.ToString());
                }
            });
        }
Пример #9
0
 public void AddOrUpdate(EntityTagKey key, TimedEntityTagHeaderValue eTag)
 {
     _eTagCache.AddOrUpdate(key, eTag, (theKey, oldValue) => eTag);
     _routePatternCache.AddOrUpdate(key.RoutePattern, new HashSet <EntityTagKey>()
     {
         key
     },
                                    (routePattern, hashSet) =>
     {
         hashSet.Add(key);
         return(hashSet);
     });
 }
Пример #10
0
        /// <summary>
        /// This is a scenario where we have a POST to a resource
        /// and it needs to invalidate the cache to that resource
        /// and all its linked URLs
        ///
        /// For example:
        /// POST /api/cars => invalidate /api/cars
        /// also it might invalidate /api/cars/fastest in which case
        /// /api/cars/fastest must be one of the linked URLs
        /// </summary>
        /// <param name="entityTagKey">entityTagKey</param>
        /// <param name="request">request</param>
        /// <param name="response">response</param>
        /// <returns>returns the function to execute</returns>
        internal Action PostInvalidationRule(
            EntityTagKey entityTagKey,
            HttpRequestMessage request,
            HttpResponseMessage response)
        {
            return(() =>
            {
                if (request.Method != HttpMethod.Post)
                {
                    return;
                }

                // if location header is set (for newly created resource), invalidate cache for it
                // this normally should not be necessary as the item is new and should not be in the cache
                // but releasing a non-existent item from cache should not have a big overhead
                if (response.Headers.Location != null)
                {
                    _entityTagStore.RemoveAllByRoutePattern(response.Headers.Location.ToString());
                }
            });
        }
Пример #11
0
 public bool TryRemove(EntityTagKey key)
 {
     TimedEntityTagHeaderValue entityTagHeaderValue;
     return _eTagCache.TryRemove(key, out entityTagHeaderValue);
 }
Пример #12
0
		protected void ExecuteCacheAdditionRules(EntityTagKey entityTagKey,
			HttpRequestMessage request,
			HttpResponseMessage response,
			IEnumerable<KeyValuePair<string, IEnumerable<string>>> varyHeaders)
		{
			new[]
				{
					AddCaching(entityTagKey, request, response, varyHeaders), // general caching
				}
				.Chain()();
		}
Пример #13
0
 public void AddOrUpdate(EntityTagKey key, TimedEntityTagHeaderValue eTag)
 {
     _eTagCache.AddOrUpdate(key, eTag, (theKey, oldValue) => eTag);
 }
Пример #14
0
 public bool TryGet(EntityTagKey key, out TimedEntityTagHeaderValue eTag)
 {
     return _eTagCache.TryGetValue(key, out eTag);
 }
Пример #15
0
		protected void ExecuteCacheInvalidationRules(EntityTagKey entityTagKey,
			HttpRequestMessage request,
			HttpResponseMessage response)
		{
			new[]
				{
					InvalidateCache(entityTagKey, request, response), // general invalidation
					PostInvalidationRule(entityTagKey, request, response)
				}
				.Chain()();
		}
Пример #16
0
        public bool TryRemove(EntityTagKey key)
        {
            TimedEntityTagHeaderValue entityTagHeaderValue;

            return(_eTagCache.TryRemove(key, out entityTagHeaderValue));
        }
Пример #17
0
 public bool TryGetValue(EntityTagKey key, out TimedEntityTagHeaderValue eTag)
 {
     return(_eTagCache.TryGetValue(key, out eTag));
 }
		public static void AddCaching(string method,
			bool existsInStore,
			bool addVaryHeader,
			bool addLastModifiedHeader,
			bool alreadyHasLastModified,
			string[] varyByHeader)
		{
			// setup 
			var mocks = new MockRepository();
			var request = new HttpRequestMessage(new HttpMethod(method), TestUrl);
			request.Headers.Add(HttpHeaderNames.Accept, "text/xml");
			request.Headers.Add(HttpHeaderNames.AcceptLanguage, "en-GB");
			var entityTagStore = mocks.StrictMock<IEntityTagStore>();
			var entityTagHeaderValue = new TimedEntityTagHeaderValue("\"12345678\"");
			var cachingHandler = new CachingHandler(entityTagStore, varyByHeader)
			{
				AddLastModifiedHeader = addLastModifiedHeader,
				AddVaryHeader = addVaryHeader,
				ETagValueGenerator = (x,y) => entityTagHeaderValue
			};

			
			var entityTagKey = new EntityTagKey(TestUrl, new[] {"text/xml", "en-GB"}, TestUrl + "/*");

			entityTagStore.Expect(x => x.TryGetValue(Arg<EntityTagKey>.Matches(etg => etg.ResourceUri == TestUrl),
				out Arg<TimedEntityTagHeaderValue>.Out(entityTagHeaderValue).Dummy)).Return(existsInStore);

			if (!existsInStore)
			{
				entityTagStore.Expect(
					x => x.AddOrUpdate(Arg<EntityTagKey>.Matches(etk => etk == entityTagKey),
						Arg<TimedEntityTagHeaderValue>.Matches(ethv => ethv.Tag == entityTagHeaderValue.Tag)));
			}

			var response = new HttpResponseMessage();
			response.Content = new ByteArrayContent(new byte[0]);
			if (alreadyHasLastModified)
				response.Content.Headers.Add(HttpHeaderNames.LastModified, DateTimeOffset.Now.ToString("r"));

			var cachingContinuation = cachingHandler.AddCaching(entityTagKey, request, response, request.Headers);
			mocks.ReplayAll();

			// run
			cachingContinuation();

			// verify

			// test kast modified only if it is GET and PUT
			if (addLastModifiedHeader && method.IsIn("PUT", "GET"))
			{
				Assert.That(response.Content.Headers.Any(x => x.Key == HttpHeaderNames.LastModified),
					"LastModified does not exist");
			}
			if (!addLastModifiedHeader && !alreadyHasLastModified)
			{
				Assert.That(!response.Content.Headers.Any(x => x.Key == HttpHeaderNames.LastModified),
					"LastModified exists");
			}
			mocks.VerifyAll();

		}
Пример #19
0
		/// <summary>
		/// This is a scenario where we have a POST to a resource
		/// and it needs to invalidate the cache to that resource
		/// and all its linked URLs
		/// 
		/// For example:
		/// POST /api/cars => invalidate /api/cars
		/// also it might invalidate /api/cars/fastest in which case
		/// /api/cars/fastest must be one of the linked URLs
		/// </summary>
		/// <param name="entityTagKey">entityTagKey</param>
		/// <param name="request">request</param>
		/// <param name="response">response</param>
		/// <returns>returns the function to execute</returns>
		internal Action PostInvalidationRule(
			EntityTagKey entityTagKey,
			HttpRequestMessage request, 
			HttpResponseMessage response)
		{
			return () =>
			{
				if(request.Method!=HttpMethod.Post)
					return;

				// if location header is set (for newly created resource), invalidate cache for it
				// this normally should not be necessary as the item is new and should not be in the cache
				// but releasing a non-existent item from cache should not have a big overhead
				if (response.Headers.Location != null)
				{
					_entityTagStore.RemoveAllByRoutePattern(response.Headers.Location.ToString());
				}

			};
		}
Пример #20
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="entityTagKey"></param>
		/// <param name="request"></param>
		/// <param name="response"></param>
		/// <param name="varyHeaders"></param>
		/// <returns></returns>
		internal Action AddCaching(
			EntityTagKey entityTagKey,
			HttpRequestMessage request,
			HttpResponseMessage response,
			IEnumerable<KeyValuePair<string, IEnumerable<string>>> varyHeaders)
		{
			return
				() =>
				{

					var cacheControlHeaderValue = CacheControlHeaderProvider(request);
					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(entityTagKey, out eTagValue))
						{
							eTagValue = new TimedEntityTagHeaderValue(ETagValueGenerator(uri, varyHeaders));
							_entityTagStore.AddOrUpdate(entityTagKey, 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);
						}

						response.Headers.TryAddWithoutValidation(HttpHeaderNames.CacheControl, cacheControlHeaderValue.ToString());

					}

				};
		}
Пример #21
0
		/// <summary>
		/// This invalidates the resource based on routePattern
		/// for methods POST, PUT and DELETE.
		/// It also removes for all linked URLs
		/// </summary>
		/// <param name="entityTagKey"></param>
		/// <param name="request"></param>
		/// <param name="response"></param>
		/// <returns></returns>
		internal Action InvalidateCache(
			EntityTagKey entityTagKey,
			HttpRequestMessage request,
			HttpResponseMessage response)
		{
			return
				() =>
			{

				if (!request.Method.Method.IsIn("PUT", "DELETE", "POST"))
					return;

				string uri = UriTrimmer(request.RequestUri);

				// remove pattern
				_entityTagStore.RemoveAllByRoutePattern(entityTagKey.RoutePattern);

				// remove all related URIs
				var linkedUrls = LinkedRoutePatternProvider(uri, request.Method);
				foreach (var linkedUrl in linkedUrls)
					_entityTagStore.RemoveAllByRoutePattern(linkedUrl);

			};

		}