#210 Re-enable caching tests

This commit is contained in:
Chris R 2016-09-01 10:17:42 -07:00
parent 88114568b9
commit 8105a272d2
2 changed files with 73 additions and 34 deletions

View File

@ -19,6 +19,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
{ {
httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.ContentLength = 10;
return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
})) }))
{ {
@ -27,7 +28,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
} }
} }
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_JustPublic_NotCached() public async Task Caching_JustPublic_NotCached()
{ {
var requestCount = 1; var requestCount = 1;
@ -37,6 +38,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Cache-Control"] = "public"; httpContext.Response.Headers["Cache-Control"] = "public";
httpContext.Response.ContentLength = 10;
return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
})) }))
{ {
@ -45,7 +47,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
} }
} }
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_MaxAge_Cached() public async Task Caching_MaxAge_Cached()
{ {
var requestCount = 1; var requestCount = 1;
@ -55,6 +57,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Cache-Control"] = "public, max-age=10"; httpContext.Response.Headers["Cache-Control"] = "public, max-age=10";
httpContext.Response.ContentLength = 10;
return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
})) }))
{ {
@ -63,7 +66,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
} }
} }
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_SMaxAge_Cached() public async Task Caching_SMaxAge_Cached()
{ {
var requestCount = 1; var requestCount = 1;
@ -73,6 +76,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Cache-Control"] = "public, s-maxage=10"; httpContext.Response.Headers["Cache-Control"] = "public, s-maxage=10";
httpContext.Response.ContentLength = 10;
return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
})) }))
{ {
@ -81,7 +85,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
} }
} }
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_SMaxAgeAndMaxAge_SMaxAgePreferredCached() public async Task Caching_SMaxAgeAndMaxAge_SMaxAgePreferredCached()
{ {
var requestCount = 1; var requestCount = 1;
@ -91,6 +95,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Cache-Control"] = "public, max-age=0, s-maxage=10"; httpContext.Response.Headers["Cache-Control"] = "public, max-age=0, s-maxage=10";
httpContext.Response.ContentLength = 10;
return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
})) }))
{ {
@ -99,7 +104,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
} }
} }
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_Expires_Cached() public async Task Caching_Expires_Cached()
{ {
var requestCount = 1; var requestCount = 1;
@ -110,6 +115,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Cache-Control"] = "public"; httpContext.Response.Headers["Cache-Control"] = "public";
httpContext.Response.Headers["Expires"] = (DateTime.UtcNow + TimeSpan.FromSeconds(10)).ToString("r"); httpContext.Response.Headers["Expires"] = (DateTime.UtcNow + TimeSpan.FromSeconds(10)).ToString("r");
httpContext.Response.ContentLength = 10;
return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
})) }))
{ {
@ -118,7 +124,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
} }
} }
[Theory(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Theory]
[InlineData("Set-cookie")] [InlineData("Set-cookie")]
[InlineData("vary")] [InlineData("vary")]
[InlineData("pragma")] [InlineData("pragma")]
@ -132,6 +138,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Cache-Control"] = "public, max-age=10"; httpContext.Response.Headers["Cache-Control"] = "public, max-age=10";
httpContext.Response.Headers[headerName] = "headerValue"; httpContext.Response.Headers[headerName] = "headerValue";
httpContext.Response.ContentLength = 10;
return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
})) }))
{ {
@ -153,6 +160,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Cache-Control"] = "public"; httpContext.Response.Headers["Cache-Control"] = "public";
httpContext.Response.Headers["Expires"] = expiresValue; httpContext.Response.Headers["Expires"] = expiresValue;
httpContext.Response.ContentLength = 10;
return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
})) }))
{ {
@ -161,7 +169,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
} }
} }
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_ExpiresWithoutPublic_NotCached() public async Task Caching_ExpiresWithoutPublic_NotCached()
{ {
var requestCount = 1; var requestCount = 1;
@ -171,6 +179,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching httpContext.Response.ContentType = "some/thing"; // Http.Sys requires content-type for caching
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Expires"] = (DateTime.UtcNow + TimeSpan.FromSeconds(10)).ToString("r"); httpContext.Response.Headers["Expires"] = (DateTime.UtcNow + TimeSpan.FromSeconds(10)).ToString("r");
httpContext.Response.ContentLength = 10;
return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
})) }))
{ {
@ -179,7 +188,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
} }
} }
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_MaxAgeAndExpires_MaxAgePreferred() public async Task Caching_MaxAgeAndExpires_MaxAgePreferred()
{ {
var requestCount = 1; var requestCount = 1;
@ -190,6 +199,7 @@ namespace Microsoft.AspNetCore.Server.WebListener.FunctionalTests
httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString(); httpContext.Response.Headers["x-request-count"] = (requestCount++).ToString();
httpContext.Response.Headers["Cache-Control"] = "public, max-age=10"; httpContext.Response.Headers["Cache-Control"] = "public, max-age=10";
httpContext.Response.Headers["Expires"] = (DateTime.UtcNow - TimeSpan.FromSeconds(10)).ToString("r"); // In the past httpContext.Response.Headers["Expires"] = (DateTime.UtcNow - TimeSpan.FromSeconds(10)).ToString("r"); // In the past
httpContext.Response.ContentLength = 10;
return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10); return httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
})) }))
{ {

View File

@ -56,7 +56,7 @@ namespace Microsoft.Net.Http.Server
} }
} }
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_SetTtlWithContentType_Cached() public async Task Caching_SetTtlWithContentType_Cached()
{ {
string address; string address;
@ -83,7 +83,7 @@ namespace Microsoft.Net.Http.Server
} }
} }
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
// Http.Sys does not set the optional Age header for cached content. // Http.Sys does not set the optional Age header for cached content.
// http://tools.ietf.org/html/rfc7234#section-5.1 // http://tools.ietf.org/html/rfc7234#section-5.1
public async Task Caching_CheckAge_NotSentWithCachedContent() public async Task Caching_CheckAge_NotSentWithCachedContent()
@ -114,7 +114,7 @@ namespace Microsoft.Net.Http.Server
} }
} }
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
// Http.Sys does not update the optional Age header for cached content. // Http.Sys does not update the optional Age header for cached content.
// http://tools.ietf.org/html/rfc7234#section-5.1 // http://tools.ietf.org/html/rfc7234#section-5.1
public async Task Caching_SetAge_AgeHeaderCachedAndNotUpdated() public async Task Caching_SetAge_AgeHeaderCachedAndNotUpdated()
@ -250,7 +250,7 @@ namespace Microsoft.Net.Http.Server
} }
} }
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_SetTtlHuge_Cached() public async Task Caching_SetTtlHuge_Cached()
{ {
string address; string address;
@ -277,7 +277,7 @@ namespace Microsoft.Net.Http.Server
} }
} }
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_SetTtlAndWriteBody_Cached() public async Task Caching_SetTtlAndWriteBody_Cached()
{ {
string address; string address;
@ -288,21 +288,50 @@ namespace Microsoft.Net.Http.Server
var context = await server.AcceptAsync(); var context = await server.AcceptAsync();
context.Response.Headers["x-request-count"] = "1"; context.Response.Headers["x-request-count"] = "1";
context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache
context.Response.ContentLength = 10;
context.Response.CacheTtl = TimeSpan.FromSeconds(10); context.Response.CacheTtl = TimeSpan.FromSeconds(10);
context.Response.Body.Write(new byte[10], 0, 10); context.Response.Body.Write(new byte[10], 0, 10);
context.Dispose();
var response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault());
Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync());
// Send a second request and make sure we get the same response (without listening for one on the server).
response = await SendRequestAsync(address);
Assert.Equal(200, (int)response.StatusCode);
Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault());
Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync());
}
}
[Fact]
public async Task Caching_SetTtlAndWriteAsyncBody_Cached()
{
string address;
using (var server = Utilities.CreateHttpServer(out address))
{
var responseTask = SendRequestAsync(address);
var context = await server.AcceptAsync();
context.Response.Headers["x-request-count"] = "1";
context.Response.Headers["content-type"] = "some/thing"; // Http.sys requires a content-type to cache
context.Response.ContentLength = 10;
context.Response.CacheTtl = TimeSpan.FromSeconds(10);
await context.Response.Body.WriteAsync(new byte[10], 0, 10); await context.Response.Body.WriteAsync(new byte[10], 0, 10);
context.Dispose(); context.Dispose();
var response = await responseTask; var response = await responseTask;
Assert.Equal(200, (int)response.StatusCode); Assert.Equal(200, (int)response.StatusCode);
Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault());
Assert.Equal(new byte[20], await response.Content.ReadAsByteArrayAsync()); Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync());
// Send a second request and make sure we get the same response (without listening for one on the server). // Send a second request and make sure we get the same response (without listening for one on the server).
response = await SendRequestAsync(address); response = await SendRequestAsync(address);
Assert.Equal(200, (int)response.StatusCode); Assert.Equal(200, (int)response.StatusCode);
Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault()); Assert.Equal("1", response.Headers.GetValues("x-request-count").FirstOrDefault());
Assert.Equal(new byte[20], await response.Content.ReadAsByteArrayAsync()); Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync());
} }
} }
@ -373,7 +402,7 @@ namespace Microsoft.Net.Http.Server
} }
} }
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_WriteFullContentLength_Cached() public async Task Caching_WriteFullContentLength_Cached()
{ {
string address; string address;
@ -435,7 +464,7 @@ namespace Microsoft.Net.Http.Server
} }
} }
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_SendFileWithFullContentLength_Cached() public async Task Caching_SendFileWithFullContentLength_Cached()
{ {
string address; string address;
@ -464,19 +493,20 @@ namespace Microsoft.Net.Http.Server
} }
} }
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_SetTtlAndStatusCode_Cached() public async Task Caching_SetTtlAndStatusCode_Cached()
{ {
string address; string address;
using (var server = Utilities.CreateHttpServer(out address)) using (var server = Utilities.CreateHttpServer(out address))
{ {
// Http.Sys will cache any status code. // Http.Sys will cache almost any status code.
for (int status = 200; status < 600; status++) for (int status = 200; status < 600; status++)
{ {
// 407 (Proxy Authentication Required) makes CoreCLR's HttpClient throw switch (status)
if (status == 407)
{ {
continue; case 206: // 206 (Partial Content) is not cached
case 407: // 407 (Proxy Authentication Required) makes CoreCLR's HttpClient throw
continue;
} }
var responseTask = SendRequestAsync(address + status); var responseTask = SendRequestAsync(address + status);
@ -561,7 +591,7 @@ namespace Microsoft.Net.Http.Server
// RFC violation. http://tools.ietf.org/html/rfc7234#section-4.4 // RFC violation. http://tools.ietf.org/html/rfc7234#section-4.4
// "A cache MUST invalidate the effective Request URI ... when a non-error status code // "A cache MUST invalidate the effective Request URI ... when a non-error status code
// is received in response to an unsafe request method." // is received in response to an unsafe request method."
[Theory(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Theory]
// See HTTP_VERB for known verbs // See HTTP_VERB for known verbs
[InlineData("HEAD")] [InlineData("HEAD")]
[InlineData("UNKNOWN")] [InlineData("UNKNOWN")]
@ -628,7 +658,7 @@ namespace Microsoft.Net.Http.Server
// RFC violation / implementation limiation, Vary is not respected. // RFC violation / implementation limiation, Vary is not respected.
// http://tools.ietf.org/html/rfc7234#section-4.1 // http://tools.ietf.org/html/rfc7234#section-4.1
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_SetVary_NotRespected() public async Task Caching_SetVary_NotRespected()
{ {
string address; string address;
@ -729,7 +759,7 @@ namespace Microsoft.Net.Http.Server
// Responses can be cached for requests with Pragma: no-cache. // Responses can be cached for requests with Pragma: no-cache.
// http://tools.ietf.org/html/rfc7234#section-5.2.1.4 // http://tools.ietf.org/html/rfc7234#section-5.2.1.4
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_RequestPragmaNoCache_Cached() public async Task Caching_RequestPragmaNoCache_Cached()
{ {
string address; string address;
@ -758,7 +788,7 @@ namespace Microsoft.Net.Http.Server
// RFC violation, Requests with Pragma: no-cache should not be served from cache. // RFC violation, Requests with Pragma: no-cache should not be served from cache.
// http://tools.ietf.org/html/rfc7234#section-5.4 // http://tools.ietf.org/html/rfc7234#section-5.4
// http://tools.ietf.org/html/rfc7234#section-5.2.1.4 // http://tools.ietf.org/html/rfc7234#section-5.2.1.4
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_RequestPragmaNoCache_NotRespectedAndServedFromCache() public async Task Caching_RequestPragmaNoCache_NotRespectedAndServedFromCache()
{ {
string address; string address;
@ -786,7 +816,7 @@ namespace Microsoft.Net.Http.Server
// Responses can be cached for requests with cache-control: no-cache. // Responses can be cached for requests with cache-control: no-cache.
// http://tools.ietf.org/html/rfc7234#section-5.2.1.4 // http://tools.ietf.org/html/rfc7234#section-5.2.1.4
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_RequestCacheControlNoCache_Cached() public async Task Caching_RequestCacheControlNoCache_Cached()
{ {
string address; string address;
@ -814,7 +844,7 @@ namespace Microsoft.Net.Http.Server
// RFC violation, Requests with Cache-Control: no-cache should not be served from cache. // RFC violation, Requests with Cache-Control: no-cache should not be served from cache.
// http://tools.ietf.org/html/rfc7234#section-5.2.1.4 // http://tools.ietf.org/html/rfc7234#section-5.2.1.4
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_RequestCacheControlNoCache_NotRespectedAndServedFromCache() public async Task Caching_RequestCacheControlNoCache_NotRespectedAndServedFromCache()
{ {
string address; string address;
@ -842,7 +872,7 @@ namespace Microsoft.Net.Http.Server
// RFC violation // RFC violation
// http://tools.ietf.org/html/rfc7234#section-5.2.1.1 // http://tools.ietf.org/html/rfc7234#section-5.2.1.1
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_RequestCacheControlMaxAgeZero_NotRespectedAndServedFromCache() public async Task Caching_RequestCacheControlMaxAgeZero_NotRespectedAndServedFromCache()
{ {
string address; string address;
@ -870,7 +900,7 @@ namespace Microsoft.Net.Http.Server
// RFC violation // RFC violation
// http://tools.ietf.org/html/rfc7234#section-5.2.1.3 // http://tools.ietf.org/html/rfc7234#section-5.2.1.3
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_RequestCacheControlMinFreshOutOfRange_NotRespectedAndServedFromCache() public async Task Caching_RequestCacheControlMinFreshOutOfRange_NotRespectedAndServedFromCache()
{ {
string address; string address;
@ -941,8 +971,7 @@ namespace Microsoft.Net.Http.Server
} }
// http://tools.ietf.org/html/rfc7233#section-4.1 // http://tools.ietf.org/html/rfc7233#section-4.1
[ConditionalFact] [Fact]
[FrameworkSkipCondition(RuntimeFrameworks.CoreCLR, SkipReason = "Cached response contains duplicate Content-Length headers (#167).")]
public async Task Caching_RequestRangeFromCache_RangeServedFromCache() public async Task Caching_RequestRangeFromCache_RangeServedFromCache()
{ {
string address; string address;
@ -972,7 +1001,7 @@ namespace Microsoft.Net.Http.Server
} }
// http://tools.ietf.org/html/rfc7233#section-4.1 // http://tools.ietf.org/html/rfc7233#section-4.1
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_RequestMultipleRangesFromCache_RangesServedFromCache() public async Task Caching_RequestMultipleRangesFromCache_RangesServedFromCache()
{ {
string address; string address;
@ -1032,7 +1061,7 @@ namespace Microsoft.Net.Http.Server
} }
} }
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")] [Fact]
public async Task Caching_RequestMultipleRangesFromCachedFile_ServedFromCache() public async Task Caching_RequestMultipleRangesFromCachedFile_ServedFromCache()
{ {
string address; string address;