Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request CachePolicy isn't applied in HTTP request header #60913

Merged
merged 7 commits into from
Nov 9, 2021

Conversation

pedrobsaila
Copy link
Contributor

Fixes #31320

@ghost ghost added the community-contribution Indicates that the PR has been added by a community member label Oct 27, 2021
@ghost
Copy link

ghost commented Oct 27, 2021

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

Fixes #31320

Author: pedrobsaila
Assignees: -
Labels:

area-System.Net

Milestone: -

@pedrobsaila
Copy link
Contributor Author

pedrobsaila commented Oct 27, 2021

I tried to do the same processing as in https://referencesource.microsoft.com/#System/net/System/Net/Cache/_Rfc2616CacheValidators.cs (Rfc2616.OnValidateRequest method)

@scalablecory
Copy link
Contributor

How does this interact with the static HttpWebRequest.DefaultCachePolicy property?

@pedrobsaila
Copy link
Contributor Author

pedrobsaila commented Nov 2, 2021

How does this interact with the static HttpWebRequest.DefaultCachePolicy property?

Thanks for spotting that I almost missed it ;). In .NET 4.8, there's a class called RequestCacheManager that manages global CachePolicy configuration for HTTP/HTTPS or FTP. The rule is if a DefaultCachePolicy is set, it applies to all HTTP requests unless some specific HTTP request defines its own CachePolicy, then it's overridden by the last. The problem in .NET Core, .NET 5/6 is that there's no RequestCacheManager class so there's no way to apply it by default. In this actual state, DefaultCachePolicy is useless. What do you think @scalablecory : should i go for implementing RequestCacheManager ?

@scalablecory
Copy link
Contributor

should i go for implementing RequestCacheManager ?

Judging by the Framework code, something like this (plus relevant tests) seems enough:

HttpRequestCachePolicy cachePolicy = request.CachePolicy ?? HttpWebRequest.DefaultCachePolicy ?? WebRequest.DefaultCachePolicy

Copy link
Contributor

@scalablecory scalablecory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks. some more comments.

Comment on lines 1214 to 1217
if (request.Headers.CacheControl == null)
{
request.Headers.CacheControl = new CacheControlHeaderValue();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CacheControl will always be null here. I would:

var cacheControl = new CacheControlHeaderValue();

And accessing request.Headers.CacheControl over and over will do non-trivial work, so I'd use this local variable in the switch body and then after setting everything:

request.Headers.CacheControl = cacheControl;

Comment on lines 1281 to 1300
private RequestCachePolicy? GetApplicableCachePolicy()
{
if (CachePolicy != null)
{
return CachePolicy;
}
else if (IsDefaultCachePolicySet(DefaultCachePolicy))
{
return DefaultCachePolicy;
}
else if (IsDefaultCachePolicySet(WebRequest.DefaultCachePolicy))
{
return WebRequest.DefaultCachePolicy;
}

return null;
}

private static bool IsDefaultCachePolicySet(RequestCachePolicy? policy) => policy != null
&& policy.Level != RequestCacheLevel.BypassCache;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem correct: if I explicitly want my request to bypass cache, this will end up using the default policy (which might be to cache).

I think correct behavior is to stop at the first non-null policy, and return null if that policy says bypass.

Copy link
Contributor

@scalablecory scalablecory Nov 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, can you make sure BypassCache doesn't need to do anything to the request?

The docs say it bypasses all caches "between client and server", and it's not clear if this needs to set some header related to proxies.

Copy link
Contributor Author

@pedrobsaila pedrobsaila Nov 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem correct: if I explicitly want my request to bypass cache, this will end up using the default policy (which might be to cache).

I think correct behavior is to stop at the first non-null policy, and return null if that policy says bypass.

I did it because the DefaultCachePolicy is initialized with BypassCache in WebRequest and HttpWebRequest classes by default. if a user sets WebRequest.DefaultCachePolicy and didn't do it for HttpWebRequest.DefaultCachePolicy we will presume that no cache gonna be applied, which may seem misleading for the user

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it because the DefaultCachePolicy is initialized with BypassCache in WebRequest and HttpWebRequest classes by default. if a user sets WebRequest.DefaultCachePolicy and didn't do it for HttpWebRequest.DefaultCachePolicy we will presume that no cache gonna be applied, which may seem misleading for the user

It sounds like we need to update those properties to track "explicitly set" vs "default" state. Pull the first one that is explicitly set.

Comment on lines 1273 to 1275
case RequestCacheLevel.CacheOnly:
request.Headers.CacheControl.OnlyIfCached = true;
break;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CacheOnly is intended to throw if it's not in the local cache already. This is different from OnlyIfCached, which is a directive to the server/proxy.

.NET no longer has a local cache, so this can always throw a WebException. The text used in Framework is "The request was aborted: The request cache-only policy does not allow a network request and the response is not found in cache." and should be added as a resource string.

@pedrobsaila
Copy link
Contributor Author

pedrobsaila commented Nov 4, 2021

should i go for implementing RequestCacheManager ?

Judging by the Framework code, something like this (plus relevant tests) seems enough:

HttpRequestCachePolicy cachePolicy = request.CachePolicy ?? HttpWebRequest.DefaultCachePolicy ?? WebRequest.DefaultCachePolicy

It seems that adding test cases for DefaultCachePolicy broke other tests as it is static and tests are not executed sequentially, can I drop the test cases for DefaultCachePolicy ? or there's a way around this problem?

@scalablecory
Copy link
Contributor

should i go for implementing RequestCacheManager ?

Judging by the Framework code, something like this (plus relevant tests) seems enough:

HttpRequestCachePolicy cachePolicy = request.CachePolicy ?? HttpWebRequest.DefaultCachePolicy ?? WebRequest.DefaultCachePolicy

It seems that adding test cases for DefaultCachePolicy broke other tests as it is static and tests are not executed sequentially, can I drop the test cases for DefaultCachePolicy ? or there's a way around this problem?

There's a class RemoteExecutor that we use for this, which will launch your test in a separate process.

Copy link
Contributor

@scalablecory scalablecory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking good, only some minor things.

Comment on lines 1259 to 1282
if (httpRequestCachePolicy.MinFresh > TimeSpan.Zero)
{
cacheControl = new CacheControlHeaderValue
{
MinFresh = httpRequestCachePolicy.MinFresh
};
}

if (httpRequestCachePolicy.MaxAge != TimeSpan.MaxValue)
{
cacheControl = new CacheControlHeaderValue
{
MaxAge = httpRequestCachePolicy.MaxAge
};
}

if (httpRequestCachePolicy.MaxStale > TimeSpan.Zero)
{
cacheControl = new CacheControlHeaderValue
{
MaxStale = true,
MaxStaleLimit = httpRequestCachePolicy.MaxStale
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MaxAge and MaxStale will both override the previous settings here. Seems like there should only be one new CacheControlHeaderValue.

if (policy != null && policy.Level != RequestCacheLevel.BypassCache)
{
CacheControlHeaderValue? cacheControl = null;
var pragmaHeaders = request.Headers.Pragma;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only var if the right hand side is new T. This makes sure the code is easy to read outside of an IDE.

{
return DefaultCachePolicy;
}
else if (WebRequest.DefaultCachePolicy != null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
else if (WebRequest.DefaultCachePolicy != null)
else

Copy link
Contributor

@scalablecory scalablecory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good, thanks for the PR!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Net community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Request CachePolicy isn't applied in HTTP request header
3 participants