From 59b5a15208fa09c230a05f847424084093a4864a Mon Sep 17 00:00:00 2001 From: pedrobsaila Date: Wed, 27 Oct 2021 14:38:01 +0200 Subject: [PATCH 1/7] Request CachePolicy isn't applied in HTTP request header --- .../src/System/Net/HttpWebRequest.cs | 13 +++++++++++ .../tests/HttpWebRequestTest.cs | 23 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 3adae087059e31..7394ccc12c77a7 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -7,6 +7,7 @@ using System.IO; using System.Net.Cache; using System.Net.Http; +using System.Net.Http.Headers; using System.Net.Security; using System.Net.Sockets; using System.Runtime.Serialization; @@ -1137,6 +1138,18 @@ private async Task SendRequest(bool async) request.Headers.Host = Host; } + if (CachePolicy != null && CachePolicy.Level == RequestCacheLevel.NoCacheNoStore) + { + if (request.Headers.CacheControl == null) + { + request.Headers.CacheControl = new CacheControlHeaderValue(); + } + + request.Headers.CacheControl.NoCache = true; + request.Headers.CacheControl.NoStore = true; + request.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); + } + // Copy the HttpWebRequest request headers from the WebHeaderCollection into HttpRequestMessage.Headers and // HttpRequestMessage.Content.Headers. foreach (string headerName in _webHeaderCollection) diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index d5062de525162b..b0aee9957a6bd9 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -1924,6 +1924,29 @@ public void Abort_CreateRequestThenAbort_Success(Uri remoteServer) request.Abort(); } + [Fact] + public async Task SendGetRequest_NoCacheNoStorePolicy_AddNoCacheNoStoreHeaders() + { + await LoopbackServer.CreateServerAsync(async (server, uri) => + { + HttpWebRequest request = WebRequest.CreateHttp(uri); + request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore); + Task getResponse = GetResponseAsync(request); + + await server.AcceptConnectionAsync(async connection => + { + List headers = await connection.ReadRequestHeaderAndSendResponseAsync(); + Assert.Contains($"Pragma: no-cache", headers); + Assert.Contains($"Cache-Control: no-store, no-cache", headers); + }); + + using (var response = (HttpWebResponse)await getResponse) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + }); + } + private void RequestStreamCallback(IAsyncResult asynchronousResult) { RequestState state = (RequestState)asynchronousResult.AsyncState; From 3d4bf86e7d91950d24f9a983aec8035fd5e9a56d Mon Sep 17 00:00:00 2001 From: pedrobsaila Date: Wed, 27 Oct 2021 16:29:34 +0200 Subject: [PATCH 2/7] handle other CachePolicy levels --- .../src/System/Net/HttpWebRequest.cs | 76 ++++++++++++++++--- .../tests/HttpWebRequestTest.cs | 53 +++++++++++-- 2 files changed, 113 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 7394ccc12c77a7..737dc39aa6bae2 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1138,17 +1138,7 @@ private async Task SendRequest(bool async) request.Headers.Host = Host; } - if (CachePolicy != null && CachePolicy.Level == RequestCacheLevel.NoCacheNoStore) - { - if (request.Headers.CacheControl == null) - { - request.Headers.CacheControl = new CacheControlHeaderValue(); - } - - request.Headers.CacheControl.NoCache = true; - request.Headers.CacheControl.NoStore = true; - request.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); - } + AddCacheControlHeaders(request); // Copy the HttpWebRequest request headers from the WebHeaderCollection into HttpRequestMessage.Headers and // HttpRequestMessage.Content.Headers. @@ -1215,6 +1205,70 @@ private async Task SendRequest(bool async) } } + private void AddCacheControlHeaders(HttpRequestMessage request) + { + if (CachePolicy != null) + { + if (request.Headers.CacheControl == null) + { + request.Headers.CacheControl = new CacheControlHeaderValue(); + } + + if (CachePolicy is HttpRequestCachePolicy httpRequestCachePolicy) + { + switch (httpRequestCachePolicy.Level) + { + case HttpRequestCacheLevel.NoCacheNoStore: + request.Headers.CacheControl.NoCache = true; + request.Headers.CacheControl.NoStore = true; + request.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); + break; + case HttpRequestCacheLevel.Reload: + request.Headers.CacheControl.NoCache = true; + request.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); + break; + case HttpRequestCacheLevel.CacheOnly: + case HttpRequestCacheLevel.CacheOrNextCacheOnly: + request.Headers.CacheControl.OnlyIfCached = true; + break; + case HttpRequestCacheLevel.Default: + if (httpRequestCachePolicy.MinFresh > TimeSpan.Zero) + { + request.Headers.CacheControl.MinFresh = httpRequestCachePolicy.MinFresh; + } + + if (httpRequestCachePolicy.MaxAge != TimeSpan.MaxValue) + { + request.Headers.CacheControl.MaxAge = httpRequestCachePolicy.MaxAge; + } + break; + case HttpRequestCacheLevel.Refresh: + request.Headers.CacheControl.MaxAge = TimeSpan.Zero; + request.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); + break; + } + } + else + { + switch (CachePolicy.Level) + { + case RequestCacheLevel.NoCacheNoStore: + request.Headers.CacheControl.NoCache = true; + request.Headers.CacheControl.NoStore = true; + request.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); + break; + case RequestCacheLevel.Reload: + request.Headers.CacheControl.NoCache = true; + request.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); + break; + case RequestCacheLevel.CacheOnly: + request.Headers.CacheControl.OnlyIfCached = true; + break; + } + } + } + } + public override IAsyncResult BeginGetResponse(AsyncCallback? callback, object? state) { CheckAbort(); diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index b0aee9957a6bd9..d2bc3cf6fc5912 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -1924,20 +1924,63 @@ public void Abort_CreateRequestThenAbort_Success(Uri remoteServer) request.Abort(); } - [Fact] - public async Task SendGetRequest_NoCacheNoStorePolicy_AddNoCacheNoStoreHeaders() + [Theory] + [InlineData(HttpRequestCacheLevel.NoCacheNoStore, null, null, new string[] { "Pragma: no-cache", "Cache-Control: no-store, no-cache"})] + [InlineData(HttpRequestCacheLevel.Reload, null, null, new string[] { "Pragma: no-cache", "Cache-Control: no-cache" })] + [InlineData(HttpRequestCacheLevel.CacheOnly, null, null, new string[] { "Cache-Control: only-if-cached" })] + [InlineData(HttpRequestCacheLevel.CacheOrNextCacheOnly, null, null, new string[] { "Cache-Control: only-if-cached" })] + [InlineData(HttpRequestCacheLevel.Default, HttpCacheAgeControl.MinFresh, 10, new string[] { "Cache-Control: min-fresh=10" })] + [InlineData(HttpRequestCacheLevel.Default, HttpCacheAgeControl.MaxAge, 10, new string[] { "Cache-Control: max-age=10" })] + [InlineData(HttpRequestCacheLevel.Refresh, null, null, new string[] { "Pragma: no-cache", "Cache-Control: max-age=0" })] + public async Task SendGetRequest_WithHttpCachePolicy_AddCacheHeaders( + HttpRequestCacheLevel requestCacheLevel, HttpCacheAgeControl? ageControl, int? age, string[] expectedHeaders) { await LoopbackServer.CreateServerAsync(async (server, uri) => { HttpWebRequest request = WebRequest.CreateHttp(uri); - request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore); + request.CachePolicy = ageControl != null ? + new HttpRequestCachePolicy(ageControl.Value, TimeSpan.FromSeconds((double)age)) + : new HttpRequestCachePolicy(requestCacheLevel); Task getResponse = GetResponseAsync(request); await server.AcceptConnectionAsync(async connection => { List headers = await connection.ReadRequestHeaderAndSendResponseAsync(); - Assert.Contains($"Pragma: no-cache", headers); - Assert.Contains($"Cache-Control: no-store, no-cache", headers); + + foreach (string header in expectedHeaders) + { + Assert.Contains(header, headers); + } + }); + + using (var response = (HttpWebResponse)await getResponse) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + }); + } + + [Theory] + [InlineData(RequestCacheLevel.NoCacheNoStore, new string[] { "Pragma: no-cache", "Cache-Control: no-store, no-cache" })] + [InlineData(RequestCacheLevel.Reload, new string[] { "Pragma: no-cache", "Cache-Control: no-cache" })] + [InlineData(RequestCacheLevel.CacheOnly, new string[] { "Cache-Control: only-if-cached" })] + public async Task SendGetRequest_WithCachePolicy_AddCacheHeaders( + RequestCacheLevel requestCacheLevel, string[] expectedHeaders) + { + await LoopbackServer.CreateServerAsync(async (server, uri) => + { + HttpWebRequest request = WebRequest.CreateHttp(uri); + request.CachePolicy = new RequestCachePolicy(requestCacheLevel); + Task getResponse = GetResponseAsync(request); + + await server.AcceptConnectionAsync(async connection => + { + List headers = await connection.ReadRequestHeaderAndSendResponseAsync(); + + foreach (string header in expectedHeaders) + { + Assert.Contains(header, headers); + } }); using (var response = (HttpWebResponse)await getResponse) From e272a65826244a558528cde5a3737301912756b7 Mon Sep 17 00:00:00 2001 From: pedrobsaila Date: Wed, 27 Oct 2021 19:16:52 +0200 Subject: [PATCH 3/7] manage max-stale cache policy case --- .../System.Net.Requests/src/System/Net/HttpWebRequest.cs | 7 +++++++ .../System.Net.Requests/tests/HttpWebRequestTest.cs | 1 + 2 files changed, 8 insertions(+) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 737dc39aa6bae2..d839f6df8b22c8 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1241,6 +1241,13 @@ private void AddCacheControlHeaders(HttpRequestMessage request) { request.Headers.CacheControl.MaxAge = httpRequestCachePolicy.MaxAge; } + + if (httpRequestCachePolicy.MaxStale > TimeSpan.Zero) + { + request.Headers.CacheControl.MaxStale = true; + request.Headers.CacheControl.MaxStaleLimit = httpRequestCachePolicy.MaxStale; + } + break; case HttpRequestCacheLevel.Refresh: request.Headers.CacheControl.MaxAge = TimeSpan.Zero; diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index d2bc3cf6fc5912..c41609417b5905 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -1931,6 +1931,7 @@ public void Abort_CreateRequestThenAbort_Success(Uri remoteServer) [InlineData(HttpRequestCacheLevel.CacheOrNextCacheOnly, null, null, new string[] { "Cache-Control: only-if-cached" })] [InlineData(HttpRequestCacheLevel.Default, HttpCacheAgeControl.MinFresh, 10, new string[] { "Cache-Control: min-fresh=10" })] [InlineData(HttpRequestCacheLevel.Default, HttpCacheAgeControl.MaxAge, 10, new string[] { "Cache-Control: max-age=10" })] + [InlineData(HttpRequestCacheLevel.Default, HttpCacheAgeControl.MaxStale, 10, new string[] { "Cache-Control: max-stale=10" })] [InlineData(HttpRequestCacheLevel.Refresh, null, null, new string[] { "Pragma: no-cache", "Cache-Control: max-age=0" })] public async Task SendGetRequest_WithHttpCachePolicy_AddCacheHeaders( HttpRequestCacheLevel requestCacheLevel, HttpCacheAgeControl? ageControl, int? age, string[] expectedHeaders) From 9ed19f373bf4d6c051061d82055362665b2e50c3 Mon Sep 17 00:00:00 2001 From: pedrobsaila Date: Tue, 2 Nov 2021 20:22:07 +0100 Subject: [PATCH 4/7] handle DefaultCachePolicy --- .../src/System/Net/HttpWebRequest.cs | 29 +++++++++++++-- .../tests/HttpWebRequestTest.cs | 35 +++++++++++++++++-- .../tests/WebRequestTest.cs | 34 ++++++++++++++++++ 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index d839f6df8b22c8..53744ad03ca780 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1207,14 +1207,16 @@ private async Task SendRequest(bool async) private void AddCacheControlHeaders(HttpRequestMessage request) { - if (CachePolicy != null) + RequestCachePolicy? policy = GetApplicableCachePolicy(); + + if (policy != null) { if (request.Headers.CacheControl == null) { request.Headers.CacheControl = new CacheControlHeaderValue(); } - if (CachePolicy is HttpRequestCachePolicy httpRequestCachePolicy) + if (policy is HttpRequestCachePolicy httpRequestCachePolicy) { switch (httpRequestCachePolicy.Level) { @@ -1257,7 +1259,7 @@ private void AddCacheControlHeaders(HttpRequestMessage request) } else { - switch (CachePolicy.Level) + switch (policy.Level) { case RequestCacheLevel.NoCacheNoStore: request.Headers.CacheControl.NoCache = true; @@ -1276,6 +1278,27 @@ private void AddCacheControlHeaders(HttpRequestMessage request) } } + private RequestCachePolicy? GetApplicableCachePolicy() + { + if (IsCachePolicySet(CachePolicy)) + { + return CachePolicy; + } + else if (IsCachePolicySet(DefaultCachePolicy)) + { + return DefaultCachePolicy; + } + else if (IsCachePolicySet(WebRequest.DefaultCachePolicy)) + { + return WebRequest.DefaultCachePolicy; + } + + return null; + } + + private static bool IsCachePolicySet(RequestCachePolicy? policy) => policy != null + && policy.Level != RequestCacheLevel.BypassCache; + public override IAsyncResult BeginGetResponse(AsyncCallback? callback, object? state) { CheckAbort(); diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index c41609417b5905..aacc107481b6bc 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -258,7 +258,6 @@ public void Ctor_VerifyDefaults_Success(Uri remoteServer) Assert.Equal("GET", request.Method); Assert.Equal(64, HttpWebRequest.DefaultMaximumResponseHeadersLength); Assert.NotNull(HttpWebRequest.DefaultCachePolicy); - Assert.Equal(RequestCacheLevel.BypassCache, HttpWebRequest.DefaultCachePolicy.Level); Assert.Equal(0, HttpWebRequest.DefaultMaximumErrorResponseLength); Assert.NotNull(request.Proxy); Assert.Equal(remoteServer, request.RequestUri); @@ -1933,7 +1932,7 @@ public void Abort_CreateRequestThenAbort_Success(Uri remoteServer) [InlineData(HttpRequestCacheLevel.Default, HttpCacheAgeControl.MaxAge, 10, new string[] { "Cache-Control: max-age=10" })] [InlineData(HttpRequestCacheLevel.Default, HttpCacheAgeControl.MaxStale, 10, new string[] { "Cache-Control: max-stale=10" })] [InlineData(HttpRequestCacheLevel.Refresh, null, null, new string[] { "Pragma: no-cache", "Cache-Control: max-age=0" })] - public async Task SendGetRequest_WithHttpCachePolicy_AddCacheHeaders( + public async Task SendHttpGetRequest_WithHttpCachePolicy_AddCacheHeaders( HttpRequestCacheLevel requestCacheLevel, HttpCacheAgeControl? ageControl, int? age, string[] expectedHeaders) { await LoopbackServer.CreateServerAsync(async (server, uri) => @@ -1965,7 +1964,7 @@ await server.AcceptConnectionAsync(async connection => [InlineData(RequestCacheLevel.NoCacheNoStore, new string[] { "Pragma: no-cache", "Cache-Control: no-store, no-cache" })] [InlineData(RequestCacheLevel.Reload, new string[] { "Pragma: no-cache", "Cache-Control: no-cache" })] [InlineData(RequestCacheLevel.CacheOnly, new string[] { "Cache-Control: only-if-cached" })] - public async Task SendGetRequest_WithCachePolicy_AddCacheHeaders( + public async Task SendHttpGetRequest_WithCachePolicy_AddCacheHeaders( RequestCacheLevel requestCacheLevel, string[] expectedHeaders) { await LoopbackServer.CreateServerAsync(async (server, uri) => @@ -1991,6 +1990,36 @@ await server.AcceptConnectionAsync(async connection => }); } + [Theory] + [InlineData(RequestCacheLevel.NoCacheNoStore, new string[] { "Pragma: no-cache", "Cache-Control: no-store, no-cache" })] + [InlineData(RequestCacheLevel.Reload, new string[] { "Pragma: no-cache", "Cache-Control: no-cache" })] + [InlineData(RequestCacheLevel.CacheOnly, new string[] { "Cache-Control: only-if-cached" })] + public async Task SendHttpGetRequest_WithGlobalCachePolicy_AddCacheHeaders( + RequestCacheLevel requestCacheLevel, string[] expectedHeaders) + { + await LoopbackServer.CreateServerAsync(async (server, uri) => + { + HttpWebRequest.DefaultCachePolicy = new RequestCachePolicy(requestCacheLevel); + HttpWebRequest request = WebRequest.CreateHttp(uri); + Task getResponse = GetResponseAsync(request); + + await server.AcceptConnectionAsync(async connection => + { + List headers = await connection.ReadRequestHeaderAndSendResponseAsync(); + + foreach (string header in expectedHeaders) + { + Assert.Contains(header, headers); + } + }); + + using (var response = (HttpWebResponse)await getResponse) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + }); + } + private void RequestStreamCallback(IAsyncResult asynchronousResult) { RequestState state = (RequestState)asynchronousResult.AsyncState; diff --git a/src/libraries/System.Net.Requests/tests/WebRequestTest.cs b/src/libraries/System.Net.Requests/tests/WebRequestTest.cs index a6746fc33f219f..dd13e775a7c77a 100644 --- a/src/libraries/System.Net.Requests/tests/WebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/WebRequestTest.cs @@ -1,6 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; +using System.Net.Cache; +using System.Net.Test.Common; +using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; using Xunit; @@ -186,6 +190,36 @@ public void RegisterPrefix_DuplicateHttpWithFakeFactory_ExpectFalse() Assert.False(success); } + [Theory] + [InlineData(RequestCacheLevel.NoCacheNoStore, new string[] { "Pragma: no-cache", "Cache-Control: no-store, no-cache" })] + [InlineData(RequestCacheLevel.Reload, new string[] { "Pragma: no-cache", "Cache-Control: no-cache" })] + [InlineData(RequestCacheLevel.CacheOnly, new string[] { "Cache-Control: only-if-cached" })] + public async Task SendGetRequest_WithGlobalCachePolicy_AddCacheHeaders( + RequestCacheLevel requestCacheLevel, string[] expectedHeaders) + { + await LoopbackServer.CreateServerAsync(async (server, uri) => + { + WebRequest.DefaultCachePolicy = new RequestCachePolicy(requestCacheLevel); + WebRequest request = WebRequest.Create(uri); + Task getResponse = request.GetResponseAsync(); + + await server.AcceptConnectionAsync(async connection => + { + List headers = await connection.ReadRequestHeaderAndSendResponseAsync(); + + foreach (string header in expectedHeaders) + { + Assert.Contains(header, headers); + } + }); + + using (var response = (HttpWebResponse)await getResponse) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + }); + } + private class FakeRequest : WebRequest { private readonly Uri _uri; From ac36aec0452034eec7e08a0b8b4cfea8cd84fd6d Mon Sep 17 00:00:00 2001 From: pedrobsaila Date: Wed, 3 Nov 2021 09:57:35 +0100 Subject: [PATCH 5/7] CachePolicy is nullable by default --- .../System.Net.Requests/src/System/Net/HttpWebRequest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 53744ad03ca780..8c941b2b08a51e 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1280,15 +1280,15 @@ private void AddCacheControlHeaders(HttpRequestMessage request) private RequestCachePolicy? GetApplicableCachePolicy() { - if (IsCachePolicySet(CachePolicy)) + if (CachePolicy != null) { return CachePolicy; } - else if (IsCachePolicySet(DefaultCachePolicy)) + else if (IsDefaultCachePolicySet(DefaultCachePolicy)) { return DefaultCachePolicy; } - else if (IsCachePolicySet(WebRequest.DefaultCachePolicy)) + else if (IsDefaultCachePolicySet(WebRequest.DefaultCachePolicy)) { return WebRequest.DefaultCachePolicy; } @@ -1296,7 +1296,7 @@ private void AddCacheControlHeaders(HttpRequestMessage request) return null; } - private static bool IsCachePolicySet(RequestCachePolicy? policy) => policy != null + private static bool IsDefaultCachePolicySet(RequestCachePolicy? policy) => policy != null && policy.Level != RequestCacheLevel.BypassCache; public override IAsyncResult BeginGetResponse(AsyncCallback? callback, object? state) From 32dffd6ab4479854af60bfdacf766ffa7faa9df6 Mon Sep 17 00:00:00 2001 From: pedrobsaila Date: Sat, 6 Nov 2021 02:00:25 +0100 Subject: [PATCH 6/7] explicitely set DefaultCachePolicy and DefaultCachePolicy tests execution on side process --- .../src/Resources/Strings.resx | 3 + .../src/System/Net/HttpWebRequest.cs | 101 ++++++++++++------ .../tests/HttpWebRequestTest.cs | 86 +++++++++++++-- .../tests/WebRequestTest.cs | 61 ++++++++--- 4 files changed, 196 insertions(+), 55 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/Resources/Strings.resx b/src/libraries/System.Net.Requests/src/Resources/Strings.resx index 5240ec2908eec6..a3b2bb922d3c78 100644 --- a/src/libraries/System.Net.Requests/src/Resources/Strings.resx +++ b/src/libraries/System.Net.Requests/src/Resources/Strings.resx @@ -270,4 +270,7 @@ System.Net.Requests is not supported on this platform. + + The request was aborted: The request cache-only policy does not allow a network request and the response is not found in cache. + diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 8c941b2b08a51e..f8441b78f8d448 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -690,7 +690,21 @@ public static int DefaultMaximumErrorResponseLength get; set; } - public static new RequestCachePolicy? DefaultCachePolicy { get; set; } = new RequestCachePolicy(RequestCacheLevel.BypassCache); + private static RequestCachePolicy? _defaultCachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); + private static bool _isDefaultCachePolicySet; + + public static new RequestCachePolicy? DefaultCachePolicy + { + get + { + return _defaultCachePolicy; + } + set + { + _isDefaultCachePolicySet = true; + _defaultCachePolicy = value; + } + } public DateTime IfModifiedSince { @@ -1209,51 +1223,71 @@ private void AddCacheControlHeaders(HttpRequestMessage request) { RequestCachePolicy? policy = GetApplicableCachePolicy(); - if (policy != null) + if (policy != null && policy.Level != RequestCacheLevel.BypassCache) { - if (request.Headers.CacheControl == null) - { - request.Headers.CacheControl = new CacheControlHeaderValue(); - } + CacheControlHeaderValue? cacheControl = null; + var pragmaHeaders = request.Headers.Pragma; if (policy is HttpRequestCachePolicy httpRequestCachePolicy) { switch (httpRequestCachePolicy.Level) { case HttpRequestCacheLevel.NoCacheNoStore: - request.Headers.CacheControl.NoCache = true; - request.Headers.CacheControl.NoStore = true; - request.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); + cacheControl = new CacheControlHeaderValue + { + NoCache = true, + NoStore = true + }; + pragmaHeaders.Add(new NameValueHeaderValue("no-cache")); break; case HttpRequestCacheLevel.Reload: - request.Headers.CacheControl.NoCache = true; - request.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); + cacheControl = new CacheControlHeaderValue + { + NoCache = true + }; + pragmaHeaders.Add(new NameValueHeaderValue("no-cache")); break; case HttpRequestCacheLevel.CacheOnly: + throw new WebException(SR.CacheEntryNotFound, WebExceptionStatus.CacheEntryNotFound); case HttpRequestCacheLevel.CacheOrNextCacheOnly: - request.Headers.CacheControl.OnlyIfCached = true; + cacheControl = new CacheControlHeaderValue + { + OnlyIfCached = true + }; break; case HttpRequestCacheLevel.Default: if (httpRequestCachePolicy.MinFresh > TimeSpan.Zero) { - request.Headers.CacheControl.MinFresh = httpRequestCachePolicy.MinFresh; + cacheControl = new CacheControlHeaderValue + { + MinFresh = httpRequestCachePolicy.MinFresh + }; } if (httpRequestCachePolicy.MaxAge != TimeSpan.MaxValue) { - request.Headers.CacheControl.MaxAge = httpRequestCachePolicy.MaxAge; + cacheControl = new CacheControlHeaderValue + { + MaxAge = httpRequestCachePolicy.MaxAge + }; } if (httpRequestCachePolicy.MaxStale > TimeSpan.Zero) { - request.Headers.CacheControl.MaxStale = true; - request.Headers.CacheControl.MaxStaleLimit = httpRequestCachePolicy.MaxStale; + cacheControl = new CacheControlHeaderValue + { + MaxStale = true, + MaxStaleLimit = httpRequestCachePolicy.MaxStale + }; } break; case HttpRequestCacheLevel.Refresh: - request.Headers.CacheControl.MaxAge = TimeSpan.Zero; - request.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); + cacheControl = new CacheControlHeaderValue + { + MaxAge = TimeSpan.Zero + }; + pragmaHeaders.Add(new NameValueHeaderValue("no-cache")); break; } } @@ -1262,19 +1296,29 @@ private void AddCacheControlHeaders(HttpRequestMessage request) switch (policy.Level) { case RequestCacheLevel.NoCacheNoStore: - request.Headers.CacheControl.NoCache = true; - request.Headers.CacheControl.NoStore = true; - request.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); + cacheControl = new CacheControlHeaderValue + { + NoCache = true, + NoStore = true + }; + pragmaHeaders.Add(new NameValueHeaderValue("no-cache")); break; case RequestCacheLevel.Reload: - request.Headers.CacheControl.NoCache = true; - request.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); + cacheControl = new CacheControlHeaderValue + { + NoCache = true + }; + pragmaHeaders.Add(new NameValueHeaderValue("no-cache")); break; case RequestCacheLevel.CacheOnly: - request.Headers.CacheControl.OnlyIfCached = true; - break; + throw new WebException(SR.CacheEntryNotFound, WebExceptionStatus.CacheEntryNotFound); } } + + if (cacheControl != null) + { + request.Headers.CacheControl = cacheControl; + } } } @@ -1284,11 +1328,11 @@ private void AddCacheControlHeaders(HttpRequestMessage request) { return CachePolicy; } - else if (IsDefaultCachePolicySet(DefaultCachePolicy)) + else if (_isDefaultCachePolicySet && DefaultCachePolicy != null) { return DefaultCachePolicy; } - else if (IsDefaultCachePolicySet(WebRequest.DefaultCachePolicy)) + else if (WebRequest.DefaultCachePolicy != null) { return WebRequest.DefaultCachePolicy; } @@ -1296,9 +1340,6 @@ private void AddCacheControlHeaders(HttpRequestMessage request) return null; } - private static bool IsDefaultCachePolicySet(RequestCachePolicy? policy) => policy != null - && policy.Level != RequestCacheLevel.BypassCache; - public override IAsyncResult BeginGetResponse(AsyncCallback? callback, object? state) { CheckAbort(); diff --git a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs index aacc107481b6bc..c54fe136cef073 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebRequestTest.cs @@ -258,6 +258,7 @@ public void Ctor_VerifyDefaults_Success(Uri remoteServer) Assert.Equal("GET", request.Method); Assert.Equal(64, HttpWebRequest.DefaultMaximumResponseHeadersLength); Assert.NotNull(HttpWebRequest.DefaultCachePolicy); + Assert.Equal(RequestCacheLevel.BypassCache, HttpWebRequest.DefaultCachePolicy.Level); Assert.Equal(0, HttpWebRequest.DefaultMaximumErrorResponseLength); Assert.NotNull(request.Proxy); Assert.Equal(remoteServer, request.RequestUri); @@ -1926,7 +1927,6 @@ public void Abort_CreateRequestThenAbort_Success(Uri remoteServer) [Theory] [InlineData(HttpRequestCacheLevel.NoCacheNoStore, null, null, new string[] { "Pragma: no-cache", "Cache-Control: no-store, no-cache"})] [InlineData(HttpRequestCacheLevel.Reload, null, null, new string[] { "Pragma: no-cache", "Cache-Control: no-cache" })] - [InlineData(HttpRequestCacheLevel.CacheOnly, null, null, new string[] { "Cache-Control: only-if-cached" })] [InlineData(HttpRequestCacheLevel.CacheOrNextCacheOnly, null, null, new string[] { "Cache-Control: only-if-cached" })] [InlineData(HttpRequestCacheLevel.Default, HttpCacheAgeControl.MinFresh, 10, new string[] { "Cache-Control: min-fresh=10" })] [InlineData(HttpRequestCacheLevel.Default, HttpCacheAgeControl.MaxAge, 10, new string[] { "Cache-Control: max-age=10" })] @@ -1963,7 +1963,6 @@ await server.AcceptConnectionAsync(async connection => [Theory] [InlineData(RequestCacheLevel.NoCacheNoStore, new string[] { "Pragma: no-cache", "Cache-Control: no-store, no-cache" })] [InlineData(RequestCacheLevel.Reload, new string[] { "Pragma: no-cache", "Cache-Control: no-cache" })] - [InlineData(RequestCacheLevel.CacheOnly, new string[] { "Cache-Control: only-if-cached" })] public async Task SendHttpGetRequest_WithCachePolicy_AddCacheHeaders( RequestCacheLevel requestCacheLevel, string[] expectedHeaders) { @@ -1990,26 +1989,95 @@ await server.AcceptConnectionAsync(async connection => }); } - [Theory] + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] [InlineData(RequestCacheLevel.NoCacheNoStore, new string[] { "Pragma: no-cache", "Cache-Control: no-store, no-cache" })] [InlineData(RequestCacheLevel.Reload, new string[] { "Pragma: no-cache", "Cache-Control: no-cache" })] - [InlineData(RequestCacheLevel.CacheOnly, new string[] { "Cache-Control: only-if-cached" })] - public async Task SendHttpGetRequest_WithGlobalCachePolicy_AddCacheHeaders( + public void SendHttpGetRequest_WithGlobalCachePolicy_AddCacheHeaders( RequestCacheLevel requestCacheLevel, string[] expectedHeaders) + { + RemoteExecutor.Invoke(async (async, reqCacheLevel, eh0, eh1) => + { + await LoopbackServer.CreateServerAsync(async (server, uri) => + { + HttpWebRequest.DefaultCachePolicy = new RequestCachePolicy(Enum.Parse(reqCacheLevel)); + HttpWebRequest request = WebRequest.CreateHttp(uri); + Task getResponse = bool.Parse(async) ? request.GetResponseAsync() : Task.Run(() => request.GetResponse()); + + await server.AcceptConnectionAsync(async connection => + { + List headers = await connection.ReadRequestHeaderAndSendResponseAsync(); + Assert.Contains(eh0, headers); + Assert.Contains(eh1, headers); + }); + + using (var response = (HttpWebResponse)await getResponse) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + }); + }, (this is HttpWebRequestTest_Async).ToString(), requestCacheLevel.ToString(), expectedHeaders[0], expectedHeaders[1]).Dispose(); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SendHttpGetRequest_WithCachePolicyCacheOnly_ThrowException( + bool isHttpCachePolicy) + { + HttpWebRequest request = WebRequest.CreateHttp("http://anything"); + request.CachePolicy = isHttpCachePolicy ? new HttpRequestCachePolicy(HttpRequestCacheLevel.CacheOnly) + : new RequestCachePolicy(RequestCacheLevel.CacheOnly); + WebException exception = await Assert.ThrowsAsync(() => GetResponseAsync(request)); + Assert.Equal(SR.CacheEntryNotFound, exception.Message); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void SendHttpGetRequest_WithGlobalCachePolicyBypassCache_DoNotAddCacheHeaders() + { + RemoteExecutor.Invoke(async () => + { + await LoopbackServer.CreateServerAsync(async (server, uri) => + { + HttpWebRequest.DefaultCachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); + HttpWebRequest request = WebRequest.CreateHttp(uri); + Task getResponse = request.GetResponseAsync(); + + await server.AcceptConnectionAsync(async connection => + { + List headers = await connection.ReadRequestHeaderAndSendResponseAsync(); + + foreach (string header in headers) + { + Assert.DoesNotContain("Pragma", header); + Assert.DoesNotContain("Cache-Control", header); + } + }); + + using (var response = (HttpWebResponse)await getResponse) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + }); + }).Dispose(); + } + + [Fact] + public async Task SendHttpGetRequest_WithCachePolicyBypassCache_DoNotAddHeaders() { await LoopbackServer.CreateServerAsync(async (server, uri) => { - HttpWebRequest.DefaultCachePolicy = new RequestCachePolicy(requestCacheLevel); HttpWebRequest request = WebRequest.CreateHttp(uri); - Task getResponse = GetResponseAsync(request); + request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); + Task getResponse = request.GetResponseAsync(); await server.AcceptConnectionAsync(async connection => { List headers = await connection.ReadRequestHeaderAndSendResponseAsync(); - foreach (string header in expectedHeaders) + foreach (string header in headers) { - Assert.Contains(header, headers); + Assert.DoesNotContain("Pragma", header); + Assert.DoesNotContain("Cache-Control", header); } }); diff --git a/src/libraries/System.Net.Requests/tests/WebRequestTest.cs b/src/libraries/System.Net.Requests/tests/WebRequestTest.cs index dd13e775a7c77a..5b640e65bdfc9b 100644 --- a/src/libraries/System.Net.Requests/tests/WebRequestTest.cs +++ b/src/libraries/System.Net.Requests/tests/WebRequestTest.cs @@ -190,34 +190,63 @@ public void RegisterPrefix_DuplicateHttpWithFakeFactory_ExpectFalse() Assert.False(success); } - [Theory] + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] [InlineData(RequestCacheLevel.NoCacheNoStore, new string[] { "Pragma: no-cache", "Cache-Control: no-store, no-cache" })] [InlineData(RequestCacheLevel.Reload, new string[] { "Pragma: no-cache", "Cache-Control: no-cache" })] - [InlineData(RequestCacheLevel.CacheOnly, new string[] { "Cache-Control: only-if-cached" })] - public async Task SendGetRequest_WithGlobalCachePolicy_AddCacheHeaders( + public void SendGetRequest_WithGlobalCachePolicy_AddCacheHeaders( RequestCacheLevel requestCacheLevel, string[] expectedHeaders) { - await LoopbackServer.CreateServerAsync(async (server, uri) => + RemoteExecutor.Invoke(async (reqCacheLevel, eh0, eh1) => { - WebRequest.DefaultCachePolicy = new RequestCachePolicy(requestCacheLevel); - WebRequest request = WebRequest.Create(uri); - Task getResponse = request.GetResponseAsync(); - - await server.AcceptConnectionAsync(async connection => + await LoopbackServer.CreateServerAsync(async (server, uri) => { - List headers = await connection.ReadRequestHeaderAndSendResponseAsync(); + WebRequest.DefaultCachePolicy = new RequestCachePolicy(Enum.Parse(reqCacheLevel)); + WebRequest request = WebRequest.Create(uri); + Task getResponse = request.GetResponseAsync(); + + await server.AcceptConnectionAsync(async connection => + { + List headers = await connection.ReadRequestHeaderAndSendResponseAsync(); + Assert.Contains(eh0, headers); + Assert.Contains(eh1, headers); + }); - foreach (string header in expectedHeaders) + using (var response = (HttpWebResponse)await getResponse) { - Assert.Contains(header, headers); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); } }); + }, requestCacheLevel.ToString(), expectedHeaders[0], expectedHeaders[1]).Dispose(); + } - using (var response = (HttpWebResponse)await getResponse) + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void SendGetRequest_WithGlobalCachePolicyBypassCache_DoNotAddCacheHeaders() + { + RemoteExecutor.Invoke(async () => + { + await LoopbackServer.CreateServerAsync(async (server, uri) => { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - }); + WebRequest.DefaultCachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); + WebRequest request = WebRequest.Create(uri); + Task getResponse = request.GetResponseAsync(); + + await server.AcceptConnectionAsync(async connection => + { + List headers = await connection.ReadRequestHeaderAndSendResponseAsync(); + + foreach(string header in headers) + { + Assert.DoesNotContain("Pragma", header); + Assert.DoesNotContain("Cache-Control", header); + } + }); + + using (var response = (HttpWebResponse)await getResponse) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + }); + }).Dispose(); } private class FakeRequest : WebRequest From f499323c3cf8146f2ef7c680572678e2d71ead53 Mon Sep 17 00:00:00 2001 From: pedrobsaila Date: Mon, 8 Nov 2021 23:04:09 +0100 Subject: [PATCH 7/7] fix minor issues --- .../src/System/Net/HttpWebRequest.cs | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index f8441b78f8d448..c92ea9f28d5d24 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -1226,7 +1226,7 @@ private void AddCacheControlHeaders(HttpRequestMessage request) if (policy != null && policy.Level != RequestCacheLevel.BypassCache) { CacheControlHeaderValue? cacheControl = null; - var pragmaHeaders = request.Headers.Pragma; + HttpHeaderValueCollection pragmaHeaders = request.Headers.Pragma; if (policy is HttpRequestCachePolicy httpRequestCachePolicy) { @@ -1256,29 +1256,22 @@ private void AddCacheControlHeaders(HttpRequestMessage request) }; break; case HttpRequestCacheLevel.Default: + cacheControl = new CacheControlHeaderValue(); + if (httpRequestCachePolicy.MinFresh > TimeSpan.Zero) { - cacheControl = new CacheControlHeaderValue - { - MinFresh = httpRequestCachePolicy.MinFresh - }; + cacheControl.MinFresh = httpRequestCachePolicy.MinFresh; } if (httpRequestCachePolicy.MaxAge != TimeSpan.MaxValue) { - cacheControl = new CacheControlHeaderValue - { - MaxAge = httpRequestCachePolicy.MaxAge - }; + cacheControl.MaxAge = httpRequestCachePolicy.MaxAge; } if (httpRequestCachePolicy.MaxStale > TimeSpan.Zero) { - cacheControl = new CacheControlHeaderValue - { - MaxStale = true, - MaxStaleLimit = httpRequestCachePolicy.MaxStale - }; + cacheControl.MaxStale = true; + cacheControl.MaxStaleLimit = httpRequestCachePolicy.MaxStale; } break; @@ -1332,12 +1325,10 @@ private void AddCacheControlHeaders(HttpRequestMessage request) { return DefaultCachePolicy; } - else if (WebRequest.DefaultCachePolicy != null) + else { return WebRequest.DefaultCachePolicy; } - - return null; } public override IAsyncResult BeginGetResponse(AsyncCallback? callback, object? state)