In Azure API Management, is it possible to skip the backend call if some simple validation fails? I require this because every call to the backend service in this case uses a portion the clients quota, and this is undesirable if we know that the request will fail.
Take the following example, where the URL template is /MyOperation/{MyParameter}:
the <inbound> portion of the policy first checks whether or not {MyParameter} is numeric, and then rewrites the URI.
the <outbound> portion of the policy checks whether or not {MyParameter} was valid, and if not instead returns some custom text to the client.
Here is the example policy -
<policies>
<inbound>
<set-variable name="isValidMyParameter" value="#{
Match match = Regex.Match(context.Request.MatchedParameters["MyParameter"], "^[0-9]*$");
return ( match.Value.ToString() != "" ) ? true : false;
}" />
<rewrite-uri template="#("/Path/To/Application/" + ""+context.Request.MatchedParameters["MyParameter"])" />
</inbound>
<outbound>
<choose>
<when condition=""#(!Convert.ToBoolean(context.Variables["isValidMyParameter"]))">
<set-status code="400" reason="Bad Request" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>{ "statusCode": 400, "message": "Invalid 'MyParameter'." }</set-body>
</when>
</outbound>
</policies>
While the works, even if {MyParameter} is invalid (say the client has passed "asdf"), a request to the backend service is made. As explained above this is undesirable because it eats in to the clients quota.
I've considered using <choose> and checking the value of isValidMyParameter, but the trouble there is that a request to the backend service is still made, just without the rewritten URI. This again eats in to the clients quota.
Is it at all possible to just skip the <backend> portion of the policy and go straight back to the client?
Move choose and add return-response policy within inbound body. This will result in immediate response to client skipping the backend request.
<inbound>
<set-variable name="isValidMyParameter" value="#{
Match match = Regex.Match(context.Request.MatchedParameters["MyParameter"], "^[0-9]*$");
return ( match.Value.ToString() != "" ) ? true : false;
}" />
<choose>
<when condition="#(!Convert.ToBoolean(context.Variables["isValidMyParameter"]))">
<return-response>
<set-status code="400" reason="Bad Request" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>{ "statusCode": 400, "message": "Invalid 'MyParameter'." }</set-body>
</return-response>
</when>
</choose>
<rewrite-uri template="#("/Path/To/Application/" + ""+context.Request.MatchedParameters["MyParameter"])" />
</inbound>
Look into return-response policy. It would allow you to immediately stop request processing and return response to a client.
Related
I have an inbound policy that extracts the User Email from the context like this:
<inbound>
<base />
<set-backend-service id="apim-generated-policy" backend-id="azure-func-staging" />
<set-header name="customer-email" exists-action="override">
<value>#(context.User.Email)</value>
</set-header>
</inbound>
But no matter what I always get the following message:
set-header (0.788 ms)
{
"messages": [
{
"message": "Expression evaluation failed.",
"expression": "context.User.Email",
"details": "Object reference not set to an instance of an object."
},
"Expression evaluation failed. Object reference not set to an instance of an object.",
"Object reference not set to an instance of an object."
]
}
Can you help me understand what I am doing wrong?
When you set the user of the developer into the header of the request, you would add the following to the APIM policy:
Here you have missed the return statement in policy code:
<set-header name="customer-email" exists-action="override">
<value>#(context.User.Email)</value>
</set-header>
Modify your above code bit like below and try:
<set-header name="user" exists-action="override">
<value>#{var usr = context.User;
return usr.Email;}</value>
</set-header>
For more details on examples of context.user, refer this
Using the send-request Policy in Azure APIM.
Able to get the response and extract the Body.
However, struggling with extracting the response.status.code
The value of this #(context.Response.StatusCode) is 200 if the request is sent, i am trying to capture this value...
send-request (88 ms)
{
"response": {
"status": {
"code": 200,
"reason": "OK"
}
}
}
Hope below solution will work for you.
<choose>
<when condition="#(((IResponse)context.Variables["response-variable-name"]).StatusCode == 200)">
<set-variable name="xxx" value="#(((IResponse)context.Variables["response-variable-name"]).Body.As<JObject>(preserveContent: true).ToString())" />
</when>
<otherwise>
<return-response>
<set-status code="404" reason="Not Found" />
<set-body>#(((IResponse)context.Variables["response"]).Body.As<JObject>(preserveContent: true).ToString())</set-body>
</return-response>
</otherwise>
</choose>
I would like to add query parameter for the request by using response from my auth service. These are the example:
<policies>
<inbound>
<!-- Extract Token from Authorization header parameter -->
<set-variable name="token" value="#(context.Request.Headers.GetValueOrDefault("Authorization","JWT").Split(' ').Last())" />
<!-- Send request to Token Server to validate token (see RFC 7662) -->
<send-request mode="new" response-variable-name="tokenstate" timeout="20" ignore-error="false">
<set-url>AUTH Service</set-url>
<set-method>POST</set-method>
<set-header name="Content-Type" exists-action="override">
<value>application/x-www-form-urlencoded</value>
</set-header>
<set-body>#($"token={(string)context.Variables["token"]}")</set-body>
</send-request>
<choose>
<when condition="#((bool)((IResponse)context.Variables["tokenstate"]).Body.As<JObject>()["active"] == false)">
<return-response response-variable-name="existing response variable">
<set-status code="401" reason="Unauthorized" />
</return-response>
</when>
<otherwise>
<set-query-parameter name="domain_id" exists-action="append">
<value>
#((string)((IResponse)context.Variables["tokenstate"]).Body.As<JObject>()["content"]["domain_id"])
</value>
</set-query-parameter>
</otherwise>
</choose>
<base />
</inbound>
</policies>
But I am getting this error:
{
"messages": [
{
"message": "Expression evaluation failed.",
"expression": "(string)((IResponse)context.Variables[\"tokenstate\"]).Body.As<JObject>()[\"content\"]",
"details": "Object reference not set to an instance of an object."
},
"Expression evaluation failed. Object reference not set to an instance of an object.",
"Object reference not set to an instance of an object."
]
}
Any idea how should I do it ?
By getting decoded JWT from my AUTH service and added it to the request to backend
Thank You
For performance reasons APIM service always avoids caching full request/response bodies in memory, thus when you call ((IResponse)context.Variables["tokenstate"]).Body.As() response is streamed directly from auth server and at the same time converted to JObject, so it is consumed after that and second call to .Body.As() will produce null value.
To avoid that you have two options:
call ((IResponse)context.Variables["tokenstate"]).Body.As<JObject>(true) - this additional parameter instructs service to cache response in memory so it will be preserved for later inspection. but using call to .As() later on again will once more parse raw response into JSON doing same work again and being a performance hit.
or do <set-variable name="tokenstate" value="((IResponse)context.Variables["tokenstate"]).Body.As<JObject>()" />. This will overwrite value of tokenstate variable with parsed JSON body that can be used later on multiple times as it will be stored in memory now.
just to add a solution, if you need validate some fields from request api policy may use:
place this below inbound tag:
<set-variable name="<some>" value="#(context.Request.Body.As<string>())" />
<choose>
<when condition="#(!((string) context.Variables["<some>"]).Contains("<some>"))">
<return-response>
<set-status code="400" reason="badRequest" />
<set-header name="<some>" exists-action="override">
<value><some></value>
</set-header>
<set-body />
</return-response>
</when>
<otherwise>
<return-response />
</otherwise>
</choose>
I am trying to capture error status from one API & and send it to the calling API. In my downstream API I have the following catch exception strategy:
<catch-exception-strategy>
<set-payload value= "500" />
<logger message="***BACKEND API: #[payload]" level="INFO" doc:name="Logger"/>
</catch-exception-strategy>
In my calling API I have the following:
<http:request config-ref="downstream API " path="/downstream/{id}" method="POST " doc:name="Generate "/>
<json:object-to-json-transformer doc:name="Object to JSON"/>
<logger message="PAYLOAD: #[payload]" level="INFO" doc:name="Logger"/>
However I am able to print the payload in the downstream API but not in the calling API. Am I missing something?
If your API throw http.status == 500, it should trigger this condition
<choice-exception-strategy doc:name="Choice Exception Strategy">
<catch-exception-strategy when="#[message.inboundProperties.'http.status' == 500]" doc:name="Error 500">
<set-payload value= "500" />
<logger message="***BACKEND API: #[payload]" level="INFO" doc:name="Logger"/>
</catch-exception-strategy>
</choice-exception-strategy>
I you didn't add any condition, it should be the default catch exception
everything should work properly.
In my Mule app, I've configured some of the flows to use a catch exception strategy in order to do some special processing. For these cases, I want to pop the error and the original payload into an object store. Everywhere else, the default exception strategy is fine.
<flow name="saveLookup">
<vm:inbound-endpoint exchange-pattern="one-way" ref="Lookup_Save_VM" />
<component>
<spring-object bean="insertLookupMDCvalues"/>
</component>
<set-variable variableName="originalPayload" value="#[payload]"/>
<json:json-to-object-transformer returnClass="com.company.LookupData"/>
<set-variable variableName="transactionId" value="#[payload.transactionId]"/>
<transactional action="ALWAYS_BEGIN">
<logger message="${lookup.SQL}" level="INFO"/>
<jdbc:outbound-endpoint exchange-pattern="request-response" queryKey="saveLookup" queryTimeout="-1" connector-ref="JdbcConnector" />
<foreach collection="#[payload.transactional.lookupItems.items]">
<logger message="${lookup.item.SQL}" level="INFO" />
<jdbc:outbound-endpoint exchange-pattern="request-response" queryKey="saveLookupItem" queryTimeout="-1" connector-ref="JdbcConnector"/>
</foreach>
</transactional>
<component>
<spring-object bean="clearLookupMDCvalues"/>
</component>
<catch-exception-strategy>
<message-properties-transformer scope="invocation">
<add-message-property key="errorMap" value="#[['id' : transactionId, 'body' : originalPayload, 'error' : exception.summaryMessage]]"/>
</message-properties-transformer>
<choice>
<when expression="#[message.inboundProperties['resubmit']]">
<logger message="Resubmission of lookup data failed, saving to Dead Letter object store. ID=#[transactionId]" level="INFO"/>
<objectstore:store config-ref="lookupDeadLetterOS" key="#[transactionId]" overwrite="true" value-ref="#[errorMap]"/>
</when>
<otherwise>
<logger message="Saving lookup data failed, saving to Error object store. ID=#[transactionId]" level="INFO"/>
<objectstore:store config-ref="lookupErrorOS" key="#[transactionId]" overwrite="true" value-ref="#[errorMap]"/>
</otherwise>
</choice>
<set-payload value="Error: #[exception.summaryMessage]"/>
<component>
<spring-object bean="clearLookupMDCvalues"/>
</component>
</catch-exception-strategy>
</flow>
My problem is that when an error is encountered, let's say a Null Pointer Exception in the foreach component, I'm seeing four ERROR log statements for each event:
Exception stack is: 1. null (java.lang.NullPointerException) ...and so on. This is logged twice.
CatchMessagingExceptionStrategy - Message : Execution of the expression "payload.transactional.lookupItems.items" failed. (org.mule.api.expression.ExpressionRuntimeException). Message payload is of type: LookupData
DefaultMessagingExceptionStrategy - Message : Execution of the expression "payload.transactional.lookupItems.items" failed. (org.mule.api.expression.ExpressionRuntimeException). Message payload is of type: LookupData
I thought that a flow-specific exception strategy should override the default strategy. Why the duplicate log messages, and is there a way to shush them? I'd like to avoid having to configure the default exception strategy, as it's perfectly acceptable behavior in the majority of the flows.
The issue is that the built in exception strategies inherit from AbstractExceptionListener, and they all use the logException template method. The base implementation always logs at ERROR level, which is sometimes not appropriate for your application.
You can create a simple subclass of CatchMessagingExceptionStrategy that overrides the logException method, and logs however you want. Then, use it in your flow in place of the <catch-exception-strategy> like so:
<custom-exception-strategy class="com.mycompany.mule.QuietCatchExceptionStrategy">
<!-- your message processors here -->
</custom-exception-strategy>