My problem goes this way:
I have an Azure APIM, I have created an API and addeded the backend retry policy as below.
<backend>
<retry condition="#("{{Transient-ErrorCode}}".Contains(Convert.ToString(context.Response.StatusCode)))" count="3" interval="5" first-fast-retry="false">
<forward-request />
</retry>
</backend>
The server return success(statuscode: 200) for the first time, When it initiate the retry it encountered the following(I am retrying at its success also, for testing the retry is working fine.).
forward-request (1.326 ms)
{
"messages": [
"Content length mismatch",
"Content length mismatch"
]
}
Please help with your thoughts/experience on the same.
This is because request sent by client is not cached in memory by default in APIM, instead it's streamed right from client to backend. So when needs come to retry the request request payload is not there. I assume that you have problems only with requests that have body.
To resolve the issue you first need to cache request body:
<inbound>
<set-variable name="body" value="#(context.Request.Body.As<string>(preserveContent: true))" />
</inbound>
<backend>
<retry condition="#("{{Transient-ErrorCode}}".Contains(Convert.ToString(context.Response.StatusCode)))" count="3" interval="5" first-fast-retry="false">
<set-body>#((string)context.Variables["body"])</set-body>
<forward-request />
</retry>
</backend>
There appears to be an alternative simplification for including the body by using the attribute buffer-request-body on the forward-request policy:
<!-- no need to put body into variable in the inbound policy -->
<backend>
<retry condition="#("{{Transient-ErrorCode}}".Contains(Convert.ToString(context.Response.StatusCode)))" count="3" interval="5" first-fast-retry="false">
<!-- no need for the set-body policy -->
<forward-request buffer-request-body="true" />
</retry>
</backend>
See doco for forward-request's attributes
Related
we are using AIPM with AKS.I am trying to dynamically redirect the target URL based on the availability. For e.g say I have couple of backend url configured with load balancer. and when I hit the load balancer I want to to go to endpoint 1 or endpoint 2 based on the availability
I am tryin to do something similar as mentioned here in the HA proxy https://www.haproxy.com/blog/failover-and-worst-case-management-with-haproxy/. Since we already using APIM I am assuming it would be a overkill to have HA proxy just for this unless the feature is not available. while I can understand we can rewrite the URL or set the backend dynamically I am not sure if APIM can decide whether the URL is up and if not it can fallback to other URL's mentioned in the config something available in the link above.
What my current understand is that I can use the policy and if there is an error I can use an error policy where I can redirect. but that is for one condition. Any suggestion or pointers would be helpful.
You can react on the response code from your backend API and set a new backend url. This might not be perfect but it will get you started. In this example I assume that you get a 500 response from your backend if it's "down":
<backend>
<retry condition="#(context.Response.StatusCode == 500)" count="2" interval="1" first-fast-retry="true">
<choose>
<when condition="#(context.Response != null && context.Response.StatusCode == 500)">
<set-backend-service base-url="https://my-other-service.azurewebsites.net" />
</when>
<otherwise />
</choose>
<forward-request />
</retry>
</backend>
I'm using API Management with an external Azure Cache for Redis to cache responses that reach out to external services. I need to provide a mechanism for invalidating cached responses by refetching data from the server - in turn updating the cache with the new responses.
My current policy (shown below) receives up-to-date data from the server when the cached response expires or a new request with a Cache-Control: must-revalidate header is received. Again, when the header is received, I'd like to update the cached response with the new response. Am I missing anything either conceptually or in my policy?
<policies>
<inbound>
<base />
<set-backend-service id="apim-generated-policy" backend-id="func-myapp-dev-001" />
<set-variable name="mustRevalidate" value="#(context.Request.Headers.GetValueOrDefault("Cache-Control","").Contains("must-revalidate"))" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault<bool>("mustRevalidate") == false)">
<cache-lookup vary-by-developer="false" vary-by-developer-groups="false" allow-private-response-caching="true" />
</when>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
<set-header name="Cached-At" exists-action="override">
<value>#(System.DateTime.Now.ToString())</value>
</set-header>
<cache-store duration="360" />
</outbound>
<on-error>
<base />
</on-error>
</policies>
The problem is that <cache-store> cannot live without <cache-lookup>. <cache-lookup> not only gets cached value from the cache but it is also kind of configuration for <cache-store>. And you can see that for yourself in the trace. When you call your API with Control-Cache: must-revalidate header, <cache-lookup> won't be triggered because of your <when> policy but also <cache-store> won't be triggered, unfortunately. In the logs you will see message:
cache-store (0.019 ms)
"Corresponding cache-lookup policy was not applied. Skipping cache-store."
So there you have it. Skipping <cache-lookup> will get you fresh response from the backend but won't cache it afterwards for future requests.
As for how to achieve what you want.
I've checked it twice but unfortunately <cache-lookup> does not have option to skip lookup based on parameter.
Also was thinking about different approach. I think this could work with use of <cache-lookup-value> policies. At the very beginning, based on your must-revalidate parameter if it is set to true you could remove your cached value. And then proceed normally. The hardest part would be to think of the mechanism that could generate the cache key based on the request. The same thing APIM is doing in cache-store. The key must be unique between different request but the same for the same request - exactly like caching works. The pattern that APIM applies for the cache keys, is something like this (you can check in the trace):
"cacheKey": "{apimName}.{apiId};{revision};{backendurl with all query parameters}"
example from my API:
"cacheKey": "my-apim.azure-api.net.1_weatherbit-io;rev=1.2432_get-current-city_id-city_id-key-key_4_https_api.weatherbit.io_443_/v2.0/current?city_id=4487042"
Once you have some smart generation of keys I think you are good to go, the final solution could look something like this:
<policies>
<inbound>
<set-variable name="cacheKey" value="#{
return ""; // here implement some smart key generation based on the request parameters from context.
}" />
<set-variable name="mustRevalidate" value="#(context.Request.Headers.GetValueOrDefault("Cache-Control","").Contains("must-revalidate"))" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault<bool>("mustRevalidate") == true)">
<cache-remove-value key="#(context.Variables.GetValueOrDefault<string>("cacheKey"))" />
</when>
</choose>
<cache-lookup-value key="#(context.Variables.GetValueOrDefault<string>("cacheKey"))" variable-name="cachedResponse" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault<JObject>("cachedResponse") != (JObject)null)">
<return-response>
<set-header name="Content-Type" exists-action="override">
<value>application/json; charset=utf-8</value>
</set-header>
<set-body>#{
return context.Variables.GetValueOrDefault<JObject>("cachedResponse").ToString();
}</set-body>
</return-response>
</when>
</choose>
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<cache-store-value key="#(context.Variables.GetValueOrDefault<string>("cacheKey"))" value="#(context.Response.Body.As<JObject>(preserveContent: true))" duration="30" />
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Of course this caches only the response, if you want to cache headers as well you would have to implement mechanism of how to store it, and then when its time to return it inside <return-response>, how to parse it to headers and body. I hope you can see what is the downside of this solution, you need to implement everything yourself. You no longer can use the built-in functionality of <cache-lookup> and <cache-store>. But at the same time you have more freedom and can implement what you want. Anyway, good luck.
PS. <cache-store-value> policy has caching-type attribute. You can use it to change cache to external.
We are using a <cache-lookup> policy in the <inbound> block (with an external Redis). Is it possible to execute a policy - to be specific, I would like to write a <trace>when the cache lookup was a hit and the response is served from cache?
Today we have this:
<inbound>
<cache-lookup vary-by-developer="false" vary-by-developer-groups="false" downstream-caching-type="none" caching-type="external">
<vary-by-query-parameter>xyz</vary-by-query-parameter>
</cache-lookup>
Is something like this possible? (pseudo code)
<choose>
<when condition="cache-lookup == true">
<trace>
<message>This request is served from Redis cache!</message>
</trace>
</when>
</choose>
</inbound>
According to some research, it seems there isn't a variable or status for us to judge if the request is served from Redis cache or the backend server. The <cache-lookup> policy wraps the functions connect with Redis, I don't think we can know where the request is served from. And as you mentioned the code is pseudo, I don't think the code <when condition="cache-lookup == true"> can work.
For this requirement, I think you can just ask azure develop team for this feature on feedback page.
I'm using the Azure API Management to transform the incoming query string into another query string.
My transformation code is:
<policies>
<inbound>
<rewrite-uri template="api/primes?a={a}&b={b}" />
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
When I try to save the edits, the error appears:
One or more fields contain incorrect values:
'=' is an unexpected token. The expected token is ';'. Line 15, position 50.
which refers to the equals symbol as in a={a}. How do I correct the template of the rewrite-uri? The input url is for example https://example.com/sum?a=7&b=5.
Try replacing:
<rewrite-uri template="api/primes?a={a}&b={b}" />
With:
<rewrite-uri template="api/primes?a={a}&b={b}" />
Find more details at https://azure.microsoft.com/en-us/blog/policy-expressions-in-azure-api-management/.
You only need to create "Query Parameters" in APIM instead of "Template parameters".
Then your rewrite uri doesn't need to include the Query parameters as APIM will add it to backend url automatically once it is provided via inbound.
<rewrite-uri template="api/primes" />
if request URL is something like this:
https://example.com/sum?a=7&b=5
then the HTTP request sent to backend would be like this:
GET backendapi/api/primes?a=7&b=5
and if request URL is without query strings like this:
https://example.com/sum
then the HTTP request sent to backend would be simply like this:
GET backendapi/api/primes
With a simple policy below:
<policies>
<inbound>
<cors>
<allowed-origins>
<origin>http://microfost.com/</origin>
</allowed-origins>
<allowed-methods preflight-result-max-age="300">
<method>GET</method>
<method>POST</method>
<method>PATCH</method>
<method>DELETE</method>
</allowed-methods>
<allowed-headers>
<header>content-type</header>
<header>accept</header>
<header>Authorization</header>
</allowed-headers>
</cors>
</inbound>
</policies>
HTTP request
OPTIONS https://XXXX.azure-api.net/demo/XXX/XXX/* HTTP/1.1
Host: XXXX.azure-api.net
Ocp-Apim-Trace: true
Ocp-Apim-Subscription-Key: <secret>
Origin: http://microfost.com
Access-Control-Request-Headers: Authorization
Access-Control-Request-Method: GET
Response content
Access-Control-Allow-Origin: http://microfost.com
Ocp-Apim-Trace-Location: <trace>
Date: Mon, 27 Feb 2017 20:09:14 GMT
Content-Length: 0
I get this message and expect Origin response header I do not receive anything for 2 out of 3 APIs (1 API is working with the same policy as expected).
**Inbound**
[...]
cors (0 ms)
"Cross domain request was well formed and was allowed to proceed. CORS related headers were added to the response."
**Backend**
No records.
Outbound
cors (0 ms)
{
"message": "Headers starting with 'Access-Control-' were removed from the response. ",
"headers": []
}
transfer-response (0 ms)
{
"message": "Response headers have been sent to the caller."
}
This seems to me a nonsense behavior and might be a bug. Before submitting it I would like to ask you if there is any explanation? Why do I get this?
Headers starting with 'Access-Control-' were removed from the
response.
There a two ways to do CORS in Azure API Management. Automatic - just drop and configure CORS policy in a desired scope and APIM will take care of responding on OPTIONS requests that match existing operations.
Or you can choose manual way - create a separate operation that responds to OPTIONS method and form response manually right in the policy, possibly using return-response policy.
The problem you're having is because you have both. They're basically in conflict. CORS policy identifies request as cross origin and schedules processing on after request is complete, but return-response policy on OPTIONS operation level breaks this processing pipeline and returns response immediately before CORS policy can take action.
Since you're using CORS policy you should remove OPTIONS operation from your API to make things work.
I was having a similar issue. Adding <base /> fixed it for me.
<policies>
<inbound>
<base />
<!-- your policy here -->
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Add in your tag cors the atrrib allow-credentials="true".
See:
https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/api-management/api-management-cross-domain-policies.md