Entry-9: Cache Control, Long story short.
Tags: csharp, http, cache
Published: 2024-04-13 19:15:24

HTTP caching involves storing response messages locally to reduce response time and network bandwidth consumption for future equivalent requests. An HTTP "cache" acts as a local store managed by a subsystem that controls the storage, retrieval, and deletion of messages within it. By storing cacheable responses, this system optimizes performance by minimizing the need to fetch resources repeatedly from the origin server (source: [RFC 9111](https://www.rfc-editor.org/rfc/rfc9111#name-introduction)) and reduce the server load.

To implement effective HTTP caching, it's essential to include cache control headers in responses. These headers provide directives to clients and intermediaries(Or Proxies eg. CDNs), guiding them on how to cache responses. Here's a breakdown of some crucial cache control options and directives:

1. **Age**: This directive indicates the age of the response since it was generated, aiding in determining freshness or staleness.
2. **max-age**: Specifies the maximum time in seconds that a response can be considered fresh, enhancing caching efficiency.
3. **must-revalidate**: Mandates revalidation of a cached response with the origin server before use, ensuring clients receive updated content.
4. **private or public**: Determine whether a response can be cached by private client caches or shared caches, providing flexibility in caching strategies.

As such, there are other important directives which can be used. we can refer to the [Mozilla Cache Control documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#vocabulary).

Additionally, utilizing the Etag property can signal to clients that a resource has not changed therefore returning with a 304 not modified.

### Example Scenarios

Cache control headers find utility in various scenarios:

1. **Order Details**: Setting cache control headers with a `max-age` directive for order details allows caching for improved performance, especially considering the infrequent changes to this data.
2. **Static Content**: Applying cache control headers to static content like images and stylesheets reduces server load and enhances page load times for subsequent visits. Implementing cache-busting techniques can further refine cache management for static assets.
3. **Public API Responses**: For frequently accessed public API responses, configuring cache control headers appropriately can alleviate server load and enhance response times.

A reason these examples are picked is due to the [security considerations](https://www.rfc-editor.org/rfc/rfc9111#name-security-considerations) which one can be prone to and cannot be not to careful about hence, cache what you, your team or organization consider not sensitive information.

Some ways these risk can be mitigated are by using

1. CSRF Tokens
2. Not storing the cache, and if some item eventually ends up in the cache setting the no store and max-age immediately marks it as stale.
    
    ```json
    Cache-Control: no-store, max-age=0
    ```
    

## **Example Snippets**

```json
cache-control: max-age=3600, private, must-revalidate
```

This snippet instructs the consumer to cache the response for an hour using a private cache, ensuring revalidation with the server before the cached content can be reused after staleness.

**(C#)**

As I’m a csharp guy, In a C# environment, leveraging the **ICache** or **ResponseCacheAttribute** at the controller level helps with cache control. for Icache do see below.

```csharp
private readonly HttpClient client;
private readonly ICache cache;

public ProductCatalogClient(HttpClient client, ICache cache)
{
    client.BaseAddress = new Uri(productCatalogueBaseUrl);
    this.client = client;
    this.cache = cache;
}

private async Task<HttpResponseMessage> RequestProductFromProductCatalog(int[] productCatalogIds)
{
    var productsResource = "myProductResource";
    var response = this.cache.Get(productsResource) as HttpResponseMessage;
    if (response is null)
    {
        response = await this.client.GetAsync(productsResource);
        AddToCache(productsResource, response);
    }
    return response;
}

private void AddToCache(string resource, HttpResponseMessage response)
{
    var cacheHeader = response.Headers.FirstOrDefault(h => h.Key == "cache-control");
    if (!string.IsNullOrEmpty(cacheHeader.Key) &&
        CacheControlHeaderValue.TryParse(cacheHeader.Value.ToString(), out var cacheControl) &&
        cacheControl.MaxAge.HasValue)
    {
        this.cache.Add(resource, response, cacheControl.MaxAge.Value);
    }
}

```

Seems pretty straightforward ba?, if not here is an explanation.

The main goal which is to make use of the cache response, so to achieve this we first check if there is any available cache for the product resource and return the response else, we fetch from the product catalog resource, checking if we are allowed to cache and then add.

Some other ways we can do this is to make use of Redis or Valkey (Topic for another day) by the server and save/return cached responses from there!.

### Conclusion

So!,In essence, caching holds significant importance for business stakeholders by enhancing customer experience through quicker response times and aiding developers in supporting business operations. However, the efficiency of caching hinges on how it's implemented and with careful consideration of security implications. Thus, when implementing caching, it's vital to assess the benefits and drawbacks in alignment with specific business requirement and thought.

Thanks for watching.

Additional links and Resources

- https://qwiet.ai/tackling-caching-issues-understanding-cache-control-for-security/
- [https://www.rfc-editor.org/rfc/rfc9111](https://www.rfc-editor.org/rfc/rfc9111#name-introduction)
- https://securityintelligence.com/posts/data-security-defend-against-cache-poisoning/

Socials
- **Twitter**: [@benny_wayn](https://twitter.com/etieno_u)
- **Ongoing** **Research**: [Social Survey](https://docs.google.com/forms/d/e/1FAIpQLScKBo1VQvEVpvxKbOamhWq02uQU8HlgUPq576F-LD3yTljkwg/viewform)