If you need to catch up on the previous posts then see part 1 & 2, 3, 4, 5, 6. The source code for this post is at github.
This post will take a look at RFC 7234 which is about HTTP 1.1 caching. It is mainly about two headers Expires and Cache-Control. The first one is for server responses. The second one can be used both on requests and responses. Cache-Control has the highest precedence. If your API returns a GET (safe method) with a 200 response then it can be subject to caching. If you don’t specify anything in your GET response the cache may assign a heuristic expiration time. Other status codes where this can happen is
Responses with status codes that are defined as cacheable by default (e.g., 200, 203, 204, 206, 300, 301, 404, 405, 410, 414, and 501 in this specification) can be reused by a cache with heuristic expiration unless otherwise indicated by the method definition or explicit cache controls [RFC7234]; all other status codes are not cacheable by default.
If you specify Last-Modified or ETag these are taken into account.
Expires Header
You can read about the Expires header here. Basically it allows you to specify a date after which the response is considered stale. Like this
Expires: Fri, 16 Dec 2016 16:00:00 GMT
Cache-Control Header
You can read about the Cache-Control header here. The directives you can use depends on if we are talking about the request or the response. So let’s shortly look at some of these
Request Cache-Control Directives
no-cache - Specify this directive if the client is not willing to get a cached response.
max-age - Specifying Cache-Control: max-age=5, means that the client is willing to accept a response that is up to 5 seconds old.
max-stale - Here the client is willing to accept a stale result, eg Cache-Control: max-stale=5.
min-fresh - Here you say that you want a result that will be fresh for at least the time specified.
no-transform - Says no intermediary may transform the payload.
only-if-cached - Can be used to explicitly go for a cached response. If not in the cache you will get a 504 (Gateway timeout).
Response Cache-Control Directives
Some of the response directives are
must-revalidate - Says that when the response is stale the cache must not use the response without successfull validation on the origin server. If it cannot reach the origin server it will return 504 (Gateway Timeout).
no-cache - Says that the response may not be stored without validating the subsequent requests.
no-store - Says that the response must not be stored in the cache.
public - Says that the response may be stored in either a shared or private (local) cache.
private - Says that the response may be stored in the user’s cache
max-age - Says that the response is stale after the specified number of seconds.
s-maxage - Says that the response is stale in a shared cache after the specified number of seconds.
Using Caching
You should use caching to provide scalability and decrease the load on your server. In the previous posts we made a FileController that can return files. This could potentially put a load on server we may want to minimize. You can read more about caching with aspnet core here. Basically we just have to use the ResponseCacheAttribute. Let’s try it out with curl and Nginx (or whatever your preferred tool might be). Here I’ve put nginx in front of the aspnet core app running on port 5000. So when hitting 8080 nginx will be the cache intermediate.
|
|
Let’s try to get a file without any cache response header using
|
|
Notice the X-Cache-Status saying it is a cache MISS. Repeating the command will give the same result. In principle it could have stored it according to the RFC. Let’s try to add the attribute as shown here
|
|
We get the following response.
|
|
Saying that the response can be cached for 60 seconds in private + shared caches. Retrying it within the time limit and we get X-Cache-Status: HIT. After the 60 seconds we can see nginx makes a new request and returns X-Cache-Status: EXPIRED (make one more request and it will give a HIT).
Now let’s specify that only the client may cache the result.
|
|
Now we will always get
|
|
or rather MISS as X-Cache-Status but the cache already has the entry from before. If we specify ResponseCacheLocation.None like below we must also specify the Duration
|
|
|
|
If we do not want the result cached at all we can do it like this
|
|
and then we get
|
|
The VARY header is also supported
[ResponseCache(Location = ResponseCacheLocation.Any, Duration = 60, VaryByHeader = “Accept-Encoding”)]
|
|
Using Cache Profiles
You probably don’t want to duplicate this attribute settings on many controllers. So instead you can define a profile in ConfigureServices in Startup.cs like shown here
|
|
in the FilesController we can then change the attribute to
|
|
If you don’t want attributes or profiles
In this case then you can do everything using the ResponseHeaders, it has a CacheControl property and Expires.
Without an External Server
If you do not want to use an external server like nginx then AspNet itself has response caching middelware. The default you get is a memory cache. This may work for you depending on your use case.
To enable it you basically have to add the following to your Startup.cs file
|
|
Running the basic sample in the AspNet github repository and making two requests,
|
|
It’s the Age header that tell us we got caching in place.
Wrap-up
That’s all I wanted to mention about caching. I will continue this REST example in future posts.