Part 10 – Assigning Multiple Policies to an API Proxy
Recap of Part 9
In Part 9 of this document series, we looked at how the “Verify API Key” policy can be assigned to the inbound PreFlow stage of the ProxyEndpoint segment. That’s all very technical language for saying that when the request arrives at API Management, the very check to be made will be to look for the presence of an API Key. If this check fails, then the entire request is rejected and the client receives an HTTP 401 Unauthorised response.
Now in this tenth and final document, we will look at assigning multiple policies to various processing stages, and how the outcome of one policy can be used to influence the behaviour of another policy.
For more detailed documentation on this topic, please refer to Apigee’s documentation website. From the navigation pane on the left, select Apigee Edge –> Reference and from here, you will find all the necessary documentation.
We will take the basic GWSAMPLE_BASIC proxy created in the previous document and enhance its functionality by assigning several new policies.
Since we will be testing this proxy multiple times, you may want to use a test tool such as Postman since it can store all your test parameters, whereas the built-in API Management test tool cannot.
Assigning a Quota Policy
The first addition we can make is to assign a Quote policy. The purpose of this policy is to check that the API has not been called more than the maximum permitted number of times within a given time period.
For instance, we might decide that our API may only be called 5 times in any one-minute period; therefore we can add a Quota check to enforce this restriction.
Logon on to your API Portal, select the GWSAMPLE_BASIC proxy and start the Policy Designer. Make sure you are in edit mode.
From the Flows menu on the left, select PreFlow underneath ProxyEndpoint.
Assign a Quota policy to this processing stage by clicking on the plus sign to the right of the Quota policy in the right-hand side list of Policies. In this case, we will call the policy CheckQuotaLimit.
The graphical display now shows that two policies have been assigned to the PreFlow processing stage of the ProxyEndpoint segment.
In the editor at the bottom of the screen, you will now see the XML containing the default settings for this policy.
Default Settings for the Quota Policy |
---|
<!– can be used to configure the number of request messages that an app is allowed to submit to an API over a course of unit time –> <Quota async=”false” continueOnError=”false” enabled=”true” type=”calendar” xmlns=”http://www.sap.com/apimgmt“> <Identifier ref=”verifyapikey.CheckAPIKey.client_id” /> <!– specifies the number of requests allowed for the API Proxy –> <Allow count=”2″/> <!– the interval of time for which the quota should be applied –> <Interval>1</Interval> <!– used to specify if a central counter should be maintained and continuously synchronized across all message processors –> <Distributed>true</Distributed> <!– Use to specify the date and time when the quota counter will begin counting, regardless of whether any requests have been received from any apps –> <StartTime>2015-2-11 12:00:00</StartTime> <!– if set to true, the distributed quota counter is updated synchronously. This means that the update to the counter will be made at the same time the API call is quota-checked –> <Synchronous>true</Synchronous> <!– Use to specify the unit of time applicable to the quota. Can be second, minute, hour, day, or month –> <TimeUnit>minute</TimeUnit> </Quota> |
Firstly, the line highlighted in yellow creates a per-application quota check. Without this, the API could only be called 2 times in one minute – irrespective of who was calling it. Now that the quota is being referenced off the API Key, each user can call this API twice in one minute, irrespective of what any other user is doing.
Notice also that the instance name of the previous policy is contained in this string. The “verifyapikey” part is the policy type and this name is fixed by API Management; however, the second part “CheckAPIKey” is the instance name of the policy we created to check the API Key. This name must match the name of your policy instance.
Secondly, the three values highlighted in red between them define how often this API can be called. In this case, we can see that the default settings permit only 2 calls in any one minute interval. In our case however, we’d like to permit 5 calls per user, per minute. Therefore, change the Allow count value from 2 to 5
Now update and save your proxy.
Testing the Quota Policy
Using your chosen test tool, add the API Key value as the HTTP header field APIKey and supply you userid and password using Basic Authentication.
Now call the API multiple times. The first five executions will run correctly, but then the sixth execution should fail with the following message.
Quota Limit Exceeded |
---|
{ “fault”: { “faultstring”: “Rate limit quota violation. Quota limit exceeded. Identifier : _default”, “detail”: { “errorcode”: “policies.ratelimit.QuotaViolation” } } } |
To test this more thoroughly, you should create a second Application (thus generating a new API Key) and then open two test screens. Call the API using both screens and you’ll find that the quota limits are API Key specific. This demonstrates that the quota limit is working correctly.
Or is it…?
Look at the HTTP Status code that is returned after the quota limit has been exceeded.
Its an “HTTP 500 Internal Server Error”. This is not exactly the correct status code because it leads to the impression that somewhere in the server, some sort of fatal error has occurred, when in fact, all that has happened is that a quota limit has been exceeded.
Therefore, we should add a policy that sets the correct HTTP status code in the event of a quota limit failure.
Setting the Correct HTTP Status Code After a Quota Failure
Now we need to make a couple of changes. Go back into the Policy Designer for GWSAMPLE_BASIC and make sure you are in edit mode.
Continue On Error
The first thing we need to do is tell the Quota policy to continue processing if it detects an error. Without this, as soon as the quota limit is exceeded, the failure of the Quota policy would cause the entire request to be rejected (this is where the HTTP 500 comes from). In this case however, we want processing to continue because will are going to create a separate policy for checking the success or failure of the Quota policy.
Look again at the configuration of the Quota policy. We need to change the continueOnError setting (highlighted below in red) from false to true in order to prevent API Management from automatically generating the HTTP 500 status code response.
Modified Settings for the Quota Policy |
---|
<!– can be used to configure the number of request messages that an app is allowed to submit to an API over a course of unit time –> <Quota async=”false” continueOnError=”true” enabled=”true” type=”calendar” xmlns=”http://www.sap.com/apimgmt“> <Identifier ref=”verifyapikey.CheckAPIKey.client_id” /> <!– specifies the number of requests allowed for the API Proxy –> <Allow count=”5″/> <!– the interval of time for which the quota should be applied –> <Interval>1</Interval> <!– used to specify if a central counter should be maintained and continuously synchronized across all message processors –> <Distributed>true</Distributed> <!– Use to specify the date and time when the quota counter will begin counting, regardless of whether any requests have been received from any apps –> <StartTime>2015-2-11 12:00:00</StartTime> <!– if set to true, the distributed quota counter is updated synchronously. This means that the update to the counter will be made at the same time the API call is quota-checked –> <Synchronous>true</Synchronous> <!– Use to specify the unit of time applicable to the quota. Can be second, minute, hour, day, or month –> <TimeUnit>minute</TimeUnit> </Quota> |
Assign a Policy For Raising A Fault
Now that we have told the CheckQuotaLimit policy to continue even if an error is detected, we must ensure that we trap that error further down the processing chain. Therefore, we need to assign a specific “Raise Fault” policy to the PreFlow processing stage.
Click on the plus sign next to the “Raise Fault” policy and create a new policy instance called HandleQuotaError.
The graphical display now looks like this
We now want to invoke the HandleQuotaError policy only if the previous CheckQuotaLimit policy has failed. We can do this by adding a condition string to the HandleQuotaError policy. This condition string tests the failed flag belonging to the previous policy; therefore, we must ensure that we enter the name of the previous policy correctly.
The policy for checking the quota limit is called CheckQuotaLimit; therefore add the following condition string to the HandleQuotaError policy:
(ratelimit.CheckQuotaLimit.failed = “true”)
Our HandleQuotaError policy will now only be executed if this condition is true.
Now look at the contents of the HandleQuotaError policy. Here we need to do a few things:
- We need to set the correct HTTP header to inform the browser how long it should wait before retrying this request
- We need to set the HTTP status code and error message
- We can optionally define a payload to contain a more informative error message.
The following XML accomplishes these tasks. The changed parts are highlighted in red.
HandleQuotaError Policy |
---|
<!– can be used to create custom messages in case of an error condition –> <RaiseFault async=”true” continueOnError=”false” enabled=”true” xmlns=”http://www.sap.com/apimgmt“> <!– Defines the response message returned to the requesting client –> <FaultResponse> <Set> <!– Sets or overwrites HTTP headers in the response message –> <Headers> <Header name=”Retry-After”>{rateLimit.CheckQuotaLimit.expiry.time}</Header> </Headers> <Payload contentType=”text/plain”>Your quota has been exceeded</Payload> <StatusCode>429</StatusCode> <!– sets the reason phrase of the response –> <ReasonPhrase>Quota Limit Exceeded</ReasonPhrase> </Set> </FaultResponse> <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables> </RaiseFault> |
Paste this XML into your HandleQuotaError policy, then save and update your policy.
Now, when you test this policy again, the sixth execution will cause an “HTTP 429 Quota Limit Exceeded” message and a response body containing the text “Your quota has been exceeded”. Also, look at the returned HTTP Headers. There is now one called Retry-After field containing the number of seconds after which the browser should try the request again.
Masking Internal Server Names
Finally, we will use a policy that parses the outbound response from the backend server, and changes internal server names to the public, proxied external names. Unfortunately, there is no specific policy that can do this for us, so we will need to use a generic JavaSCript policy to invoke a few lines of JavaScript code.
So, back in edit mode in the Policy Designer, from the Flow menu select PostFlow underneath ProxyEndpoint. This is because we want this policy to run during the last processing stage before the response is sent back to the client.
From the Policies menu on the right side of the screen, click on the plus sign next to JavaScript and call the new policy instance MaskServerName.
Notice to which Steam this policy is being assigned – the Outgoing Response. We want this policy’s processing applied to the response, not the request.
Now the graphical display will look like this:
Now look at the editor for this JavaScript policy. What do you notice? There’s not a line of JavaScript code any where in sight – in fact it’s all XML.
The point here is that this XML allows you to reference a variety of JavaScript files (that we’ll create in just a minute). By default, two JavaScript file names are assumed helper.js and maincode.js – neither of which we are going to use.
Default Contents of JavaScript Policy |
---|
<!– this policy allows us to execute java script code during execution of an API Proxy –> <Javascript async=”false” continueOnError=”false” enabled=”true” timeLimit=”200″ xmlns=’http://www.sap.com/apimgmt‘> <!– contains reference to any library scripts that help the main code file –> <IncludeURL>jsc://helper.js</IncludeURL> <!– contains the name of the main code file –> <ResourceURL>jsc://maincode.js</ResourceURL> </Javascript> |
Delete the two lines highlighted in yellow and where you see maincode highlighted in red, change this to urlRewrite.
Your JavaScript policy will now look like this:
Modified Contents of JavaScript Policy |
---|
<!– this policy allows us to execute java script code during execution of an API Proxy –> <Javascript async=”false” continueOnError=”false” enabled=”true” timeLimit=”200″ xmlns=’http://www.sap.com/apimgmt‘> <!– contains the name of the main code file –> <ResourceURL>jsc://urlRewrite.js</ResourceURL> </Javascript> |
Next, we need to create the JavaScript file we’ve just referenced. To do this, look in the bottom left corner of the Policy Designer screen and underneath the Flows section, there is another section called Scripts. Click on the plus sign next to scripts and in the popup window enter the name urlRewrite.
Notice what is missing from the file name – there is no .js extension. Do not attempt to add this file name extension because it will be added for you based on the fact that the file is of type JavaScript.
Once, you’ve pressed Add, you will be shown an empty editor screen. Into this empty file, copy and paste the following code:
urlRewrite.js |
---|
var newpath = context.getVariable(“response.content”). replace(/SAPES4.SAPDEVCENTER.COM:443/gi, “trial.apim1.hanatrial.ondemand.com”). replace(/\/sap\/opu\/odata\/iwbep/gi, “\/i003638trial”); context.setVariable(“response.content”, newpath); |
The first statement uses function chaining to do following 4 things all in one statement:
- Create a variable called newpath
- Fetch the contents of Apigee’s standard context variable response.content (no prizes for guessing what this variable contains… 🙂 )
- Use a regular expression to scan for the server name and port number “SAPES4.SAPDEVCENTER.COM:443” and replace it with the server name and portnumber of API Management running in the HCP Trial landscape “trial.apim1.hanatrial.ondemand.com“
- Use a second regular expression to replace the standard OData pathname used in ABAP systems with the HCP account name
Once these steps have been completed, the response variable response.content is replaced with the contents of the newpath variable.
In your case, you will need to obtain the external URL of your API Management system. To do this, open a new browser tab/window and logon to your API Portal again. Select any API Proxy and go to the overview screen. At the top of this screen, you’ll see the public URL to this API. Copy and paste out the following parts:
Into the above regular expression, you will need to paste in the correct values for your API Management server name and port number, and also you HCP account name.
Now save and update your API Proxy.
Testing Server Name Masking
Go back to you test tool and make a not of the XML response that you are currently seeing. It will look something like this:
XML Response Before Server Name Masking |
---|
<feed xmlns=”http://www.w3.org/2005/Atom” xmlns:m=”http://schemas.microsoft.com/ado/2007/08/dataservices/metadata” xmlns:d=”http://schemas.microsoft.com/ado/2007/08/dataservices” xml:base=”https://SAPES4.SAPDEVCENTER.COM:443/sap/opu/odata/iwbep/GWSAMPLE_BASIC/”> <id>https://SAPES4.SAPDEVCENTER.COM:443/sap/opu/odata/iwbep/GWSAMPLE_BASIC/BusinessPartnerSet</id> <title type=”text”>BusinessPartnerSet</title> <updated>2016-06-24T14:34:54Z</updated> <author> <name/> </author> |
After server name masking has been implemented, it will look like this:
XML Response After Server Name Masking |
---|
<feed xmlns=”http://www.w3.org/2005/Atom” xmlns:m=”http://schemas.microsoft.com/ado/2007/08/dataservices/metadata” xmlns:d=”http://schemas.microsoft.com/ado/2007/08/dataservices” xml:base=”https://trial.apim1.hanatrial.ondemand.com/i003638trial/GWSAMPLE_BASIC/”> <id>https://trial.apim1.hanatrial.ondemand.com/i003638trial/GWSAMPLE_BASIC/BusinessPartnerSet</id> <title type=”text”>BusinessPartnerSet</title> <updated>2016-06-24T15:31:08Z</updated> <author> <name/> </author> |
Conclusion
This document series has spent quite a long time covering the foundational concepts of API Management and how the various tools work. The reason for this is very simple – a building can be no stronger than its foundations.
Hopefully, you are beginning to see the potential for how much functionality could be packed into a single API Proxy. To be honest, we’ve hardly scratched the surface of what API Management can do. For instance, you could create an API Proxy that does all of the following things:
- Receives a simple URL in which all the parameters are in the query string
- Unpacks the query string parameters and HTTP header fields into internal variables
- Checks whether this combination of input parameters has been seen before, and if it has, generates a response from an internal cache rather than hirtting the backend.
- If either the combination of input parameters has not been seen before, or the cache has expired, a SOAP XML body is constructed using the values from the query string and sen to the Web Service backend
- The XML response from the Web Service backend is cached
- The XML response is also unpacked into a JSON string and returned to the client
There are many, many more things API Management could do for you, and I trust this document series has laid a sufficiently solid foundation for you to start building your own powerful API Proxies.
Have fun!
Chris W
Very nice blogs..!! Chris
Hi Chris,
this is indeed a really a great blog series introducing to the foundations of API management.
Thank you very much for that,
kc
PS:
There is a little typo in the HandleQuotaPolicy xml: rateLimit needs to be all lower case at
<Header name="Retry-After">{rateLimit.CheckQuotaLimit.expiry.time}</Header>
I did this but now the Value is over 1.6 trillion (before, the field was empty)
Very nice blog. I loved it.
Hi Chris,
Cool blog and clear in each step and explanation. Though at the moment I am trying to add a extension policy but stumbled upon a bump. My python script uses some modules that isn't recognized. Local I can install packages, but in API Management I am not sure if it is possible. My code uses an import of OpenCV, so it doesn't understand import cv2. Do you know how to make this possible within API management with proxies?
Kind regards,
HN
Noticed that in the blog the condition for Raise Fault policy is mentioned as “ratelimit.CheckQuotaLimit.failed = “true”
But it should be “ratelimit.CheckQuotaLimit.failed = true –> There should not be any quote around true else it is picked as String not boolean
Regards, Ashwani Kr Sharma
Hi Chris,
Thanks in advance for the amazing blog and detailed explanation that made this topic easy to read and really comprehensive. Now i have a question for you if you don't mind helping. You or someone from the SAP community.
Imagine this case, i want to integrate the services available in the products i have subscribed in my Application at Dev portal in a third party application(e.g. Google cloud app), meaning that i want to call this services there and get the response they provide.
In your point of view, what would be the best approach on that?
Thank you once again,
Best Regards,
Francisco Ferreira.