Not sure about sending friend requests, but Azure APIM supports Send Request Policy that helps to make API calls while your request being processed at APIM level. In this article, we will explore how in this world APIM Send Request Policy works and how to create one.
There can be need to make API request while the traffic flows (inbound or outbound) through APIM, this can be handled through the legendary Send Request Policy. For an instance there can be scenario where we want to hit some endpoints to monitor traffic, or we need to authenticate/authorize incoming requests or we need to trigger some additional functionality when the request completes at the outbound level. The policy contains properties and allows to add tags to formulate the request headers and payload along with authentication if needed.
APIM policies are pretty helpful overall, one such policy we have explored in previous article, which helps to perform JWT Authentication of request, read more from here Creating Validate Jwt Policy In Azure Apim.
In this article, we will go through the code snippets and supporting steps to understand how to create and apply send-request policy. We will head in this direction:
Follow below steps to add policy to your API endpoint:
Below shows the template of the "send-request" policy:
<send-request response-variable-name="apiResponse">
<set-url>YOUR_REQUEST_URL</set-url>
<set-method>YOUR_REQUEST_METHOD</set-method>
<set-header>YOUR_REQUEST_HEADER</set-header>
<set-body>YOUR_REQUEST_BODY</set-body>
</send-request>
Here, we have a template of "send-request" policy with some placeholder values like YOUR_REQUEST_URL, YOUR_REQUEST_METHOD, these can be replaced with actual values.
Let us break down the policy and understand the what-is-what, for this below shows the send-request policy with some fake real values:
<send-request response-variable-name="apiResponse">
<set-url>https://jsonplaceholder.typicode.com/users</set-url>
<set-method>POST</set-method>
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>@{
string username = "jackwood";
string email = "jackwood@mail.com";
return new JObject(
new JProperty("username", username),
new JProperty("email", email)
).ToString();
}
</set-body>
</send-request>
Here, we are using the JSONPlaceholder user API endpoint and triggering the POST method, just for the sake of example. We can make any type of method for the request.
Properties
Tags
Time to consume the response variable from and let us see how we can even control our flow if needed. For this will continue with the previous example code only:
<inbound>
<send-request response-variable-name="apiResponse">
<set-url>https://jsonplaceholder.typicode.com/users</set-url>
<set-method>POST</set-method>
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>@{
string username = (string)context.Variables.GetValueOrDefault("username");
string email = (string)context.Variables.GetValueOrDefault("email");
return new JObject(
new JProperty("username", username),
new JProperty("email", email)
).ToString();
}
</set-body>
</send-request>
<choose>
<!-- !200: return -->
<when condition='@(((IResponse)context.Variables["apiResponse"]).StatusCode != 200)'>
<return-response>
<set-status code='@(((IResponse)context.Variables["apiResponse"]).StatusCode)' reason='@(((IResponse)context.Variables["apiResponse"]).StatusReason)' />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>@(JsonConvert.SerializeObject(((IResponse)context.Variables["apiResponse"]).Body.As<JObject>()))</set-body>
</return-response>
</when>
<!-- 200: next -->
<when condition='@((((IResponse)context.Variables["apiResponse"]).StatusCode == 200))'>
<set-header name="CO-Id" exists-action="override">
<value>@{
var body = ((IResponse)context.Variables["apiResponse"])?.Body?.As<JObject>(preserveContent: true);
var id = (string)body["id"];
return id ?? "";
}</value>
</set-header>
<set-header name="CO-Name" exists-action="override"> <value>@{
var body = ((IResponse)context.Variables["apiResponse"])?.Body?.As<JObject>(preserveContent: true);
var name = (string)body["name"];
return name ?? ""; }</value>
</set-header>
</when>
</choose>
</inbound>
Here, we have used the user request, but this time we have taken the username and email values from some context variable instead of hard coded values, to simulate more real time policies. Once the request completes, we are considering the response variable "apiResponse" and further evaluating the response. The "apiResponse" is an IResponse object and the body is of IMessage type.
We are now checking if the "apiResponse" status code is not 200 OK then we are using return-response policy to return the response from the inbound level itself, this can be seen in below code:
<!-- !200: return -->
<when condition='@(((IResponse)context.Variables["apiResponse"]).StatusCode != 200)'>
<return-response>
<set-status code='@(((IResponse)context.Variables["apiResponse"]).StatusCode)' reason='@(((IResponse)context.Variables["apiResponse"]).StatusReason)' />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>@(JsonConvert.SerializeObject(((IResponse)context.Variables["apiResponse"]).Body.As<JObject>()))</set-body>
</return-response>
</when>
Here, we are setting the response as what we have obtained from the "send-request" call, also we are keeping the same body.
Additionally, when the request results in 200 OK we are adding some custom headers as "CO-Id" and "CO-Name", these headers will be received at the downstream calls, this can be seen in below code:
<!-- 200: next -->
<when condition='@((((IResponse)context.Variables["apiResponse"]).StatusCode == 200))'>
<set-header name="CO-Id" exists-action="override">
<value>@{
var body = ((IResponse)context.Variables["apiResponse"])?.Body?.As<JObject>(preserveContent: true);
var id = (string)body["id"];
return id ?? "";
}</value>
</set-header>
<set-header name="CO-Name" exists-action="override">
<value>@{
var body = ((IResponse)context.Variables["apiResponse"])?.Body?.As<JObject>(preserveContent: true);
var name = (string)body["name"];
return name ?? "";
}</value>
</set-header>
</when>
Here, setting the "preserveContent" property to "true" is very important, since we are using the "Body.As" method twice. If we dont use it, then the first time when you access the apiResponse data using "Body.As" method, it will bring the values, the next time the content will not be preserved, so we must need to add set this property to true. We can also create variable to hold the values of apiResponse post getting the value from "Body.As" method, but in this example, we are not doing so. Read more about "preserveContent" from here Azure API Management policy expressions.
Will explore some more examples for send-request policy. These are some variants where we are calling a GET method or calling Graph API to fetch user data.
<send-request mode="new" response-variable-name="userApiResponse" timeout="10" ignore-error="true">
<set-url>https://jsonplaceholder.typicode.com/users</set-url>
<set-method>GET</set-method>
</send-request>
Here, we are making and API call to the users API which is a get all call to JSONPlaceHolder API. We have mentioned the mode, timeout and ignore-error properties as well.
<send-request mode="new" response-variable-name="graphApiResponse" timeout="20" ignore-error="true">
<set-url>https://graph.microsoft.com/v1.0/users</set-url>
<set-method>GET</set-method>
</send-request>
Here, we are making and API call to the users API which is a get all call to Graph API. We have mentioned the mode, timeout and ignore-error properties as well.
If we want to use the same policy across the APIM, we can simply create Policy Fragment. We can have them defined once and used at many places. Below we have created a policy named "UserPolicyFragment.xml".
<fragment>
<send-request response-variable-name="apiResponse">
<set-url>https://jsonplaceholder.typicode.com/users</set-url>
<set-method>POST</set-method>
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>@{
string username = (string)context.Variables.GetValueOrDefault("username");
string email = (string)context.Variables.GetValueOrDefault("email");
return new JObject(
new JProperty("username", username),
new JProperty("email", email)
).ToString();
}
</set-body>
</send-request>
<choose>
<!-- !200: return -->
<when condition='@(((IResponse)context.Variables["apiResponse"]).StatusCode != 200)'>
<return-response>
<set-status code='@(((IResponse)context.Variables["apiResponse"]).StatusCode)' reason='@(((IResponse)context.Variables["apiResponse"]).StatusReason)' />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>@(JsonConvert.SerializeObject(((IResponse)context.Variables["apiResponse"]).Body.As<JObject>()))</set-body>
</return-response>
</when>
<!-- 200: next -->
<when condition='@((((IResponse)context.Variables["apiResponse"]).StatusCode == 200))'>
<set-header name="CO-Id" exists-action="override">
<value>@{
var body = ((IResponse)context.Variables["apiResponse"])?.Body?.As<JObject>(preserveContent: true);
var id = (string)body["id"];
return id ?? ""; }</value>
</set-header>
<set-header name="CO-Name" exists-action="override">
<value>@{
var body = ((IResponse)context.Variables["apiResponse"])?.Body?.As<JObject>(preserveContent: true);
var name = (string)body["name"];
return name ?? ""; }</value>
</set-header>
</when>
</choose>
</fragment>
We can now simply add this to API endpoint or All Operations or All APIs like shown below:
<policies>
<inbound>
<base />
<include-fragment fragment-id="UserPolicyFragment" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
You can find the above codes from the Gist created and attached to this article.
That's all folks.
December 31, 2020
October 19, 2020
March 02, 2022