Azure API Management cache policy
Some notes on writing an Azure APIM cache policy for an external Redis cache. There is also official documentation for this and you can look up each policy statement in the reference.1
Reading from the request
You can get path parameter and header values from the request as follows.
set-variable
< name="id"
value="@(context.Request.MatchedParameters["id"])" />
<set-variable
name="cachecontrol"
value="@((string)context.Request.Headers.GetValueOrDefault("Cache-Control", ""))" />
I’ll use the id value as part of the cache key and the Cache-Control
header to allow the caller to disable caching. Both will be available as context.Variables
from this point on.
Cache lookup
If no-cache
is not set, I try to retrieve a cached value for the request’s id parameter and write it into another variable cachevalue
.
choose>
<when condition="@(!((string)context.Variables["cachecontrol"]).Contains("no-cache"))">
<<cache-lookup-value
key="@("id-" + context.Variables["id"])"
variable-name="cachevalue" />
when>
</</choose>
The following steps are only executed if cachevalue
is not present. This works with <choose>
and <when>
again, but it’s ugly enough without these additional levels of nesting, so just imagine them.
Sending the actual request
send-request mode="copy" response-variable-name="apiresponse" timeout="10" ignore-error="true">
<set-header name="x-functions-key" exists-action="skip">
<value>{{functionkey}}</value>
<set-header>
</send-request> </
The request is sent to the backend and the response is stored in a variable apiresponse
. The backend in this case is an Azure Function and we have to set the x-functions-key
as header for authorization – here a named value is used.
After that, we store the body of the response in cachevalue
.
set-variable
< name="cachevalue"
value="@(((IResponse)context.Variables["apiresponse"]).Body.As<string>())" />
Cache write
If the user didn’t request no-store
, we write what’s in cachevalue
into the cache for later requests.
choose>
<when condition="@(!((string)context.Variables["cachecontrol"]).Contains("no-store"))">
<<cache-store-value
key="@("id-" + context.Variables["id"])"
value="@((string)context.Variables["cachevalue"])"
duration="9000000" />
when>
</</choose>
You might think that what you specify as key
will be used as the key in your Redis cache and certainly the documentation seems to support this idea:
key: Cache key the value will be stored under.
But Azure actually adds a prefix to the key (in my case “2_”) and doesn’t guarantee that it will always be the same prefix, so after an update it might change without warning. If you want to prefill or invalidate entries in your Redis cache, you need to set up another endpoint that manipulates the cache via Azure’s policy statements (which will then add the same prefix, whatever it may be).
Another somewhat surprising limitation is that you have to specify a duration
, even if Redis supports permanent entries. So if you want to manage cache invalidation based on events, you can only specify a very long duration.
Response body
So far everything we did was in the inbound
section of the policy. Now we need one final statement in the outbound
section that sets the content of cachevalue
as the body of our response.
set-body>@((string)context.Variables["cachevalue"])</set-body> <
Done.
I think Azure did a decent job at preventing any useful syntax highlighting with their mix of XML and C# code, but if you want to be absolutely sure, just dump the whole policy as a string into your terraform config and you’re golden. /s↩︎