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");
            }
        }
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 headers =
                    request.Headers.Where(h => _varyByHeaders.Any(v => v.Equals(h.Key, StringComparison.CurrentCultureIgnoreCase)));
                var resource = UriTrimmer(request.RequestUri);
                var entityTagKey = EntityTagKeyGenerator(resource, headers);
                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
                                        : new HttpResponseMessage(HttpStatusCode.PreconditionFailed).ToTask();
            });
        }
		public static void GetMatchNonMatchTest(
			string headerName, 
			string[] values, 
			bool existsInStore,
			bool expectReturnNull,
			HttpStatusCode expectedStatus = HttpStatusCode.Unused)
		{
			// setup 
			var mocks = new MockRepository();
			var entityTagStore = mocks.StrictMock<IEntityTagStore>();
			var entityTagHandler = new CachingHandler(entityTagStore);
			var request = new HttpRequestMessage(HttpMethod.Get, TestUrl);
			request.Headers.Add(headerName, values);
			TimedEntityTagHeaderValue entityTagHeaderValue = new TimedEntityTagHeaderValue("\"12345678\"");

			if(values.Length>0) // if 
				entityTagStore.Expect(x => x.TryGetValue(Arg<EntityTagKey>.Matches(etg => etg.ResourceUri == entityTagHandler.UriTrimmer(new Uri(TestUrl))),
					out Arg<TimedEntityTagHeaderValue>.Out(entityTagHeaderValue).Dummy)).Return(existsInStore);

			mocks.ReplayAll();


			// run 
			var matchNoneMatch = entityTagHandler.GetIfMatchNoneMatch();
			// verify 
			Task<HttpResponseMessage> resultTask = matchNoneMatch(request);
			Assert.That(expectReturnNull ^ resultTask != null, "result was not as expected");
			if(resultTask!=null && expectedStatus != HttpStatusCode.Unused)
			{
				Assert.AreEqual(expectedStatus, resultTask.Result.StatusCode, "Status code");				
			}
			mocks.VerifyAll();
		}
		public static bool TryParse(string timedETagValue, out TimedEntityTagHeaderValue value)
		{
			value = null;
			if (timedETagValue == null)
				return false;

			var strings = timedETagValue.Split(new[] { "\r\n" }, StringSplitOptions.None);
			if (strings.Length != 2)
				return false;

			EntityTagHeaderValue etag = null;
			DateTimeOffset lastModified;
			if (!EntityTagHeaderValue.TryParse(strings[0], out etag))
				return false;

			if (!DateTimeOffset.TryParse(strings[1], out lastModified))
				return false;

			value = new TimedEntityTagHeaderValue(etag.Tag, etag.IsWeak)
			{
				LastModified = lastModified
			};
			return true;

		}
Example #5
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 headers =
                    request.Headers.Where(h => _varyByHeaders.Any(v => v.Equals(h.Key, StringComparison.CurrentCultureIgnoreCase)));
                var resource = UriTrimmer(request.RequestUri);
                var entityTagKey = EntityTagKeyGenerator(resource, headers);
                TimedEntityTagHeaderValue actualEtag = null;

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

                return isModified ? new HttpResponseMessage(HttpStatusCode.PreconditionFailed).ToTask()
                                        : null;
            });
        }
        public static bool TryParse(string timedETagValue, out TimedEntityTagHeaderValue value)
        {
            value = null;
            if (timedETagValue == null)
            {
                return(false);
            }

            var strings = timedETagValue.Split(new[] { "\r\n" }, StringSplitOptions.None);

            if (strings.Length != 2)
            {
                return(false);
            }

            EntityTagHeaderValue etag = null;
            DateTimeOffset       lastModified;

            if (!EntityTagHeaderValue.TryParse(strings[0], out etag))
            {
                return(false);
            }

            if (!DateTimeOffset.TryParse(strings[1], out lastModified))
            {
                return(false);
            }

            value = new TimedEntityTagHeaderValue(etag.Tag, etag.IsWeak)
            {
                LastModified = lastModified
            };
            return(true);
        }
		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;
					});
		}
		public static void ToStringAndTryParseTest(string tag, bool isWeak)
		{
			var headerValue = new TimedEntityTagHeaderValue(tag, isWeak);
			var s = headerValue.ToString();
			TimedEntityTagHeaderValue headerValue2 = null;
			Assert.IsTrue(TimedEntityTagHeaderValue.TryParse(s, out headerValue2));
			Assert.AreEqual(headerValue.Tag, headerValue2.Tag);
			Assert.AreEqual(headerValue.LastModified.ToString(), headerValue2.LastModified.ToString());
			Assert.AreEqual(headerValue.IsWeak, headerValue2.IsWeak);
			Assert.AreEqual(headerValue.ToString(), headerValue2.ToString());
		}
Example #9
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());
                }
            });
        }
Example #10
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);
     });
 }
Example #11
0
        internal Func <HttpRequestMessage, Task <HttpResponseMessage> > GetIfMatchNoneMatch()
        {
            return((request) =>
            {
                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 new HttpResponseMessage(HttpStatusCode.BadRequest).ToTask();
                }

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

                var resource = UriTrimmer(request.RequestUri);
                var headers =
                    request.Headers.Where(h => _varyByHeaders.Any(v => v.Equals(h.Key, StringComparison.CurrentCultureIgnoreCase)));
                var entityTagKey = EntityTagKeyGenerator(resource, headers);
                // 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(actualEtag.ToEntityTagHeaderValue()).ToTask();
            });
        }
Example #12
0
        internal Func <HttpRequestMessage, Task <HttpResponseMessage> > GetIfModifiedUnmodifiedSince()
        {
            return((request) =>
            {
                if (request.Method != HttpMethod.Get && request.Headers != null)
                {
                    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 new HttpResponseMessage(HttpStatusCode.BadRequest).ToTask();
                }
                bool ifModified = (ifUnmodifiedSince == null);
                DateTimeOffset modifiedInQuestion = ifModified ? ifModifiedSince.Value : ifUnmodifiedSince.Value;

                var headers =
                    request.Headers.Where(h => _varyByHeaders.Any(v => v.Equals(h.Key, StringComparison.CurrentCultureIgnoreCase)));
                var resource = UriTrimmer(request.RequestUri);
                var entityTagKey = EntityTagKeyGenerator(resource, headers);

                TimedEntityTagHeaderValue actualEtag = null;

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

                return isModified ^ ifModified
                                        ? new NotModifiedResponse(actualEtag.ToEntityTagHeaderValue()).ToTask()
                                        : null;
            });
        }
		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();

		}
Example #14
0
 public bool TryGetValue(EntityTagKey key, out TimedEntityTagHeaderValue eTag)
 {
     return(_eTagCache.TryGetValue(key, out eTag));
 }
		public static void GetModifiedNotModifiedTest(
				string headerName,
				bool resourceHasChanged,
				bool expectReturnNull,
				HttpStatusCode expectedStatus = HttpStatusCode.Unused)
		{
			// setup 
			var mocks = new MockRepository();
			var entityTagStore = mocks.StrictMock<IEntityTagStore>();
			var entityTagHandler = new CachingHandler(entityTagStore);
			var request = new HttpRequestMessage(HttpMethod.Get, TestUrl);
			DateTimeOffset lastChanged = DateTimeOffset.Now.Subtract(TimeSpan.FromDays(7));
			DateTimeOffset lastModifiedInQuestion = resourceHasChanged
			                                        	? lastChanged.Subtract(TimeSpan.FromDays(1))
			                                        	: lastChanged.Add(TimeSpan.FromDays(1));

			request.Headers.Add(headerName, lastModifiedInQuestion.ToString("r"));
			var entityTagHeaderValue = new TimedEntityTagHeaderValue("\"12345678\"")
				{LastModified = lastChanged};

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

			mocks.ReplayAll();


			// run 
			var modifiedUnmodifiedSince = entityTagHandler.GetIfModifiedUnmodifiedSince();
			var task = modifiedUnmodifiedSince(request);
			HttpResponseMessage response = task == null ? null : task.Result;

			// verify 
			Assert.That(expectReturnNull ^ task != null, "result was not as expected");
			if (task != null && expectedStatus != HttpStatusCode.Unused)
			{
				Assert.AreEqual(expectedStatus, response.StatusCode, "Status code");
			}
			mocks.VerifyAll();

		}
Example #16
0
        internal Func<Task<HttpResponseMessage>, HttpResponseMessage> GetCachingContinuation(HttpRequestMessage request)
        {
            return task =>
            {
                var httpResponse = task.Result;
                var cacheControlHeaderValue = CacheController(request);
                if (cacheControlHeaderValue == null)
                    return httpResponse;

                string uri = request.RequestUri.ToString();
                var varyHeaders = request.Headers.Where(h =>
                    _varyByHeaders.Any(v => v.Equals(h.Key, StringComparison.CurrentCultureIgnoreCase)));

                var eTagKey = EntityTagKeyGenerator(uri, varyHeaders);

                TimedEntityTagHeaderValue eTagValue;

                if (!_entityTagStore.TryGet(eTagKey, out eTagValue) || request.Method == HttpMethod.Put ||
                   	request.Method == HttpMethod.Post)
                {
                    eTagValue = new TimedEntityTagHeaderValue(ETagValueGenerator(uri, varyHeaders));
                   	_entityTagStore.AddOrUpdate(eTagKey, eTagValue );
                }

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

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

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

                httpResponse.Headers.AddWithoutValidation(HttpHeaderNames.CacheControl,cacheControlHeaderValue.ToString());

                return httpResponse;
            };
        }
		/// <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());

					}

				};
		}
 public void AddOrUpdate(EntityTagKey key, TimedEntityTagHeaderValue eTag)
 {
     _eTagCache.AddOrUpdate(key, eTag, (theKey, oldValue) => eTag);
 }
 public bool TryGet(EntityTagKey key, out TimedEntityTagHeaderValue eTag)
 {
     return _eTagCache.TryGetValue(key, out eTag);
 }