Skip to content

HTTP Caching

Demis Bellot edited this page Oct 25, 2016 · 6 revisions

This page has moved to docs.servicestack.net/http-caching


ServiceStack's HTTP Caching support transparently improves the behavior of existing ToOptimized Cached Responses, provides a typed API to opt-in to HTTP Client features, introduces a simpler declarative API for enabling both Server and Client Caching of Services and also includes Cache-aware clients that are able to improve the performance and robustness of all existing .NET Service Clients - functionality that's especially valuable to bandwidth-constrained Xamarin.iOS / Xamarin.Android clients offering improved performance and greater resilience.

The new caching functionality is encapsulated in the new HttpCacheFeature plugin that's pre-registered by default and can be removed to disable the new HTTP Caching behavior with:

Plugins.RemoveAll(x => x is HttpCacheFeature);

Caching options in ServiceStack include using the existing ToOptimizedResult* API's to create Server Caches within your Services as well as returning a customized HttpResult to take advantage of HTTP Caching Client features.

CacheResponse Attribute

The new declarative [CacheResponse] Request Filter attribute provides the best of both worlds supporting both Server and HTTP Client features which is both non-invasive and simple to enhance, as a result we expect it to be the most popular option for adding caching to your Services in future.

Cache-Aware Clients

The Server Caching and HTTP Caching features in ServiceStack will automatically benefit Websites as browsers have excellent support for HTTP Caching. But .NET Desktop Apps or Xamarin.iOS and Xamarin.Android mobile clients wont see any of these benefits since none of the existing Service Clients have support for HTTP Caching. To complete the story we've also developed cache-aware Service Clients that can be used to enhance all existing .NET Service Clients which manages its own local cache as instructed by the HTTP Caching directives.

Server Caching

To explain the new HTTP Caching features we'll revisit the ServiceStack's previous caching support which enables what we refer to as Server Caching where the response of a Service is cached in the registered Caching Provider by calling the ToOptimizedResult* API's which lets you programmatically construct the Cache Key and/or how long the cache should be persisted for, e.g:

public class OrdersService : Service
{
    public object Any(GetCustomers request)
    {
        //Good candidates: request.ToGetUrl() or base.Request.RawUrl
        var cacheKey = "unique_key_for_this_request";
        var expireCacheIn = TimeSpan.FromHours(1);

        return Request.ToOptimizedResultUsingCache(Cache, cacheKey, expireCacheIn, 
            () => {
                //Delegate executed only if item doesn't already exist in cache 
                //Any response DTO returned is cached on subsequent requests
            });
    }
}

As the name indicates this stores the most optimal representation of the Service Response in the registered ICacheClient, so for example if a client called the above API with Accept: application/json and Accept-Encoding: deflate, gzip HTTP Request Headers ServiceStack would store the deflated serialized JSON bytes in the Cache and on subsequent requests resulting in the same cache key would write the compressed deflated bytes directly to the Response OutputStream - saving both CPU and bandwidth.

More Optimal OptimizedResults

But there's an even more optimal result we could return instead: Nothing :) and save even more CPU and bandwidth! Which is precisely what the HTTP Caching directives built into the HTTP spec allow for. To enable it you'd need to return additional HTTP Headers to the client containing the necessary metadata they can use to determine when their locally cached responses are valid. Typically this is the Last-Modified date of when the Response/Resource was last modified or an Entity Tag containing an opaque string the Server can use to determine whether the client has the most up-to-date response.

The most optimal cache validator we can use for existing ToOptimizedResult* API's is the Last Modified date which is now being cached along with the response. An additional instruction we need to return to the client is the Cache-Control HTTP Response Header which instructs the client what caching behavior to apply to the server response. As we want the new behavior to work transparently without introducing caching issues to existing Services, we've opted for a conservative:

Cache-Control: max-age=0

Which tells the client to treat the server response as immediately stale and that it should send another request to the Server for identical requests, but this time the client will append a If-Modified-Since HTTP Request Header which ServiceStack now automatically looks up to determine if the cache the client has is valid. If no newer cache for this request has been created since, ServiceStack returns a 304 NotModified Response with an empty Request Body which tells the client it's safe to use their local cache instead.

Modify Cache-Control for OptimizedResults

You can change the Cache-Control Header returned for existing ToOptimizedResult responses by modifying the pre-registered HttpCacheFeature, e.g:

var cacheFeature = this.GetPlugin<HttpCacheFeature>();
cacheFeature.CacheControlForOptimizedResults = "max-age=3600, must-revalidate";

This tells the client that they can treat their locally cached server responses as valid for 1hr
but after 1hr they must check back with the Server to determine if their cache is still valid.

HTTP Client Caching

The Caching features above revolve around enhancing existing Server Cached responses with HTTP Caching features to further reduce latency and save CPU and bandwidth resources. In addition to Server Caching pure stand-alone HTTP Caching features (i.e. that don't cache server responses) are now available as first-class properties on HttpResult:

// Only one Cache Validator below needs to be specified:
string ETag            // opaque string representing integrity of response
DateTime? LastModified // the last time the response was Modified

// Specify Client/Middleware Cache Behavior
TimeSpan? MaxAge          // How long cached response is considered valid
CacheControl CacheControl // More options to specify Cache-Control behavior:
enum CacheControl {
    None,
    Public,
    Private,
    MustRevalidate,
    NoCache,
    NoStore,
    NoTransform,
    ProxyRevalidate,
}

// Available, but rarely used
TimeSpan? Age      // Used by proxies to indicate how old cache is
DateTime? Expires  // Less preferred alternative to MaxAge

We'll walk through a couple of real-world examples to show how we can make use of these new properties and explain the HTTP Caching behavior they enable.

Using ETags

The minimum properties required for HTTP Caching is to specify either an ETag or LastModified Caching Validator. You can use any opaque string for the ETag that uniquely represents a version of the response that you can use to determine what version the client has, which could be an MD5 or SHA Hash of the response but can also be a unique version string. E.g. Adding a RowVersion property to your OrmLite POCO Data Models turns on OrmLite's Optimistic Concurrency feature where each time a record is modified it's automatically populated with a new version, these characteristics makes it ideal for use as an ETag which we can just return as a string:

public object Any(GetCustomer request)
{
    var response = Db.SingleById<Customer>(request.Id);    
    return new HttpResult(response) {
        ETag = response.RowVersion.ToString(), 
    };
}

Whilst this is the minimum info required in your Services, the client also needs to know how long the cache is valid for as typically indicated by the MaxAge property. If it's omitted ServiceStack falls back to use the HttpCacheFeature.DefaultMaxAge of 10 minutes which can be changed in your AppHost with:

this.GetPlugin<HttpCacheFeature>().DefaultMaxAge = TimeSpan.FromHours(1);

So the HTTP Response Headers the client receives when calling this Service for the first time is something similar to:

ETag: "42"
Cache-Control: max-age=600

Which tells the client that they can use their local cache for identical requests issued within the next 10 minutes. After 10 minutes the cache is considered stale and the client will issue a new request to the server but this time it will include the ETag it has associated with the Response, i.e:

If-None-Match: "42"

When this happens the Service is still executed as normal and if the Customer hasn't changed, the HttpCacheFeature will compare the HttpResult.ETag response with the clients ETag above and if they match ServiceStack will instead return a 304 NotModified with an Empty Response Body to indicate to the client that it can continue to use their locally cached response.

So whilst using a HttpResult doesn't cache the response on the Server and save further resources used in executing the Service, it still benefits from allowing the client to use their local cache for 10 minutes - eliminating server requests and yielding instant response times. Then after 10 minutes the 304 NotModified Response Status and Empty Body improves latency and saves the Server CPU and bandwidth resources it didn't have to use for serializing and writing the executed Services response it would have need to do if no Caching was enabled.

Using LastModified

The alternative to using an ETag is to use the Last-Modified Caching Validator. When you're constructing a complex response you'll want to use the most recent Last Modified Date from all sources so that you can determine that the cache is no longer valid when any of the sources have been updated.

If you also want to customize the clients Cache-Control behavior you can use the additional HttpResult properties, below is an example of doing both:

public object Any(GetCustomerOrders request)
{
    var response = new GetCustomerOrdersResponse {
        Customer = Db.SingleById<Customer>(request.Id),
        Orders = Db.Select<Order>(x => x.CustomerId == request.Id),
    };

    var allDates = new List<DateTime>(response.Orders.Select(x => x.ModifiedDate)) {
        response.Customer.ModifiedDate,
    };

    return new HttpResult(response)
    {
        LastModified = allDates.OrderByDescending(x => x).First(),
        MaxAge = TimeSpan.FromSeconds(60),
        CacheControl = CacheControl.Public | CacheControl.MustRevalidate,
    };
}

Which returns the Last Modified Date of the Customer record or any of their Orders as well as the customized Cache-Control Header which together returns Response Headers similar to:

Last-Modified: Fri, 19 April 2016 05:00:00 GMT
Cache-Control: max-age=60, public, must-revalidate

Then after 60 seconds have elapsed the client will re-issue a request but instead of sending a If-None-Match Request Header and ETag, instead sends If-Modified-Since and the Last-Modified date:

If-Modified-Since: Fri, 19 April 2016 05:00:00 GMT

The resulting behavior is identical to that of the ETag above but instead compares the LastModified dates instead of ETag strings for validity.

Short-circuiting HTTP Cache Validation

A further optimization that can be added to the HTTP Cache workflow is using IRequest.HasValidCache() to short-circuit the execution of a Service after you've processed enough information to determine either the ETag or LastModified for the response.

For example if you had a Service that transcodes video on-the-fly, you can use Request.HasValidCache() to check whether the client already has the latest version of the video, if it does we can return a 304 NotModified result directly, short-circuiting the Service and saving any resources in executing the remainder of the implementation, which in this case would bypass reading and transcoding the .mp4 video:

public object Any(GetOggVideo request)
{
    var mp4Video = VirtualFileSources.GetFile($"/videos/{request.Id}.mp4");   
    if (mp4Video == null)
        throw HttpError.NotFound($"Video #{request.Id} does not exist");
    
    if (Request.HasValidCache(mp4Video.LastModified))
        return HttpResult.NotModified();
    
    var encodedOggBytes = EncodeToOggVideo(file.ReadAllBytes());
    return new HttpResult(encodedOggBytes, "video/ogg") 
    {
        LastModified = mp4Video.LastModified,
        MaxAge = TimeSpan.FromDays(1),
    };
}


  1. Getting Started

    1. Creating your first project
    2. Create Service from scratch
    3. Your first webservice explained
    4. Example Projects Overview
    5. Learning Resources
  2. Designing APIs

    1. ServiceStack API Design
    2. Designing a REST-ful service with ServiceStack
    3. Simple Customer REST Example
    4. How to design a Message-Based API
    5. Software complexity and role of DTOs
  3. Reference

    1. Order of Operations
    2. The IoC container
    3. Configuration and AppSettings
    4. Metadata page
    5. Rest, SOAP & default endpoints
    6. SOAP support
    7. Routing
    8. Service return types
    9. Customize HTTP Responses
    10. Customize JSON Responses
    11. Plugins
    12. Validation
    13. Error Handling
    14. Security
    15. Debugging
    16. JavaScript Client Library (ss-utils.js)
  4. Clients

    1. Overview
    2. C#/.NET client
      1. .NET Core Clients
    3. Add ServiceStack Reference
      1. C# Add Reference
      2. F# Add Reference
      3. VB.NET Add Reference
      4. Swift Add Reference
      5. Java Add Reference
    4. Silverlight client
    5. JavaScript client
      1. Add TypeScript Reference
    6. Dart Client
    7. MQ Clients
  5. Formats

    1. Overview
    2. JSON/JSV and XML
    3. HTML5 Report Format
    4. CSV Format
    5. MessagePack Format
    6. ProtoBuf Format
  6. View Engines 4. Razor & Markdown Razor

    1. Markdown Razor
  7. Hosts

    1. IIS
    2. Self-hosting
    3. Messaging
    4. Mono
  8. Security

    1. Authentication
    2. Sessions
    3. Restricting Services
    4. Encrypted Messaging
  9. Advanced

    1. Configuration options
    2. Access HTTP specific features in services
    3. Logging
    4. Serialization/deserialization
    5. Request/response filters
    6. Filter attributes
    7. Concurrency Model
    8. Built-in profiling
    9. Form Hijacking Prevention
    10. Auto-Mapping
    11. HTTP Utils
    12. Dump Utils
    13. Virtual File System
    14. Config API
    15. Physical Project Structure
    16. Modularizing Services
    17. MVC Integration
    18. ServiceStack Integration
    19. Embedded Native Desktop Apps
    20. Auto Batched Requests
    21. Versioning
    22. Multitenancy
  10. Caching

  11. Caching Providers

  12. HTTP Caching 1. CacheResponse Attribute 2. Cache Aware Clients

  13. Auto Query

  14. Overview

  15. Why Not OData

  16. AutoQuery RDBMS

  17. AutoQuery Data 1. AutoQuery Memory 2. AutoQuery Service 3. AutoQuery DynamoDB

  18. Server Events

    1. Overview
    2. JavaScript Client
    3. C# Server Events Client
    4. Redis Server Events
  19. Service Gateway

    1. Overview
    2. Service Discovery
  20. Encrypted Messaging

    1. Overview
    2. Encrypted Client
  21. Plugins

    1. Auto Query
    2. Server Sent Events
    3. Swagger API
    4. Postman
    5. Request logger
    6. Sitemaps
    7. Cancellable Requests
    8. CorsFeature
  22. Tests

    1. Testing
    2. HowTo write unit/integration tests
  23. ServiceStackVS

    1. Install ServiceStackVS
    2. Add ServiceStack Reference
    3. TypeScript React Template
    4. React, Redux Chat App
    5. AngularJS App Template
    6. React Desktop Apps
  24. Other Languages

    1. FSharp
      1. Add ServiceStack Reference
    2. VB.NET
      1. Add ServiceStack Reference
    3. Swift
    4. Swift Add Reference
    5. Java
      1. Add ServiceStack Reference
      2. Android Studio & IntelliJ
      3. Eclipse
  25. Amazon Web Services

  26. ServiceStack.Aws

  27. PocoDynamo

  28. AWS Live Demos

  29. Getting Started with AWS

  30. Deployment

    1. Deploy Multiple Sites to single AWS Instance
      1. Simple Deployments to AWS with WebDeploy
    2. Advanced Deployments with OctopusDeploy
  31. Install 3rd Party Products

    1. Redis on Windows
    2. RabbitMQ on Windows
  32. Use Cases

    1. Single Page Apps
    2. HTML, CSS and JS Minifiers
    3. Azure
    4. Connecting to Azure Redis via SSL
    5. Logging
    6. Bundling and Minification
    7. NHibernate
  33. Performance

    1. Real world performance
  34. Other Products

    1. ServiceStack.Redis
    2. ServiceStack.OrmLite
    3. ServiceStack.Text
  35. Future

    1. Roadmap
Clone this wiki locally
You can’t perform that action at this time.