Serving stale responses on errors
editA server can allow the cache to serve a stale response when it fails, with the stale-if-error=<seconds> directive of Cache-Control (RFC 5861). The cache then serves the stored response instead of the error for that long after the response became stale:
// The server answers with Cache-Control: max-age=60, stale-if-error=3600
using var response1 = await httpClient.GetAsync("https://example.com/data");
// Two minutes later, the response is stale and the server answers 503 Service Unavailable.
// The response is stale for less than an hour, so the cache serves it instead of the error.
timeProvider.Advance(TimeSpan.FromMinutes(2));
using var response2 = await httpClient.GetAsync("https://example.com/data");
Assert.Equal(HttpStatusCode.OK, response2.StatusCode);
Assert.Equal("content", await response2.Content.ReadAsStringAsync());
Assert.Equal("110 - \"Response is Stale\", 111 - \"Revalidation Failed\"", string.Join(", ", response2.Headers.GetValues("Warning")));httpClient is an HttpClient with a caching handler, and timeProvider the FakeTimeProvider of its options.
What counts as an error
The cache serves the stale response, with the Warning: 110 - "Response is Stale", 111 - "Revalidation Failed" header, when:
- the server answers
500,502,503or504; - the server cannot be reached: the inner handler throws an
HttpRequestException, or aTaskCanceledExceptionwhile the cancellation token of the request is not canceled.
Any other answer of the server reaches the caller: a 404 or a 410 means the resource is gone, a 401 or a 403 that it is no longer accessible. A canceled request is not an error either: it still throws. This includes the timeout of HttpClient.Timeout, which cancels the token the handler receives.
When it does not apply
- The response is stale for longer than
stale-if-errorallows: the error reaches the caller. - The stored response has
must-revalidateorno-cache: the cache never serves it without the server's approval. - The response is still fresh: the cache serves it without contacting the server anyway.
The stored response is kept while it can be served on errors: PruneObsoleteEntriesAsync does not remove it. See Cache stores.