Mule 4 Custom Policy Example
Implement a custom policy that injects a callback URL header and rejects requests with a custom message if it is not included in the request.
Join the DZone community and get the full member experience.
Join For FreeCustom policies allow you to define behaviors not covered by the OOTB policies. They can also recreate and expand upon these OOTB policies. Custom policies may also serve specific business use cases, such as additional authentication, custom responses, etc.
Any information you would like to process, add or remove on the inbound or outbound layer can be done with custom policies.
This tutorial will expand upon the tutorial using the maven archetype provided by MuleSoft.
*Note: This tutorial will start where the above tutorial ends, i.e., you should already have the template project in your workspace!
Let's take a look at our custom policies use case:
- All requests coming into this API should have a header named X-CALLBACK-URL
- The policy should reject any requests that do not contain this header, with a 400 status code and the ability to set a custom message.
You might be thinking, "Isn't this normally done in the application?" and you would be right, but there are some drawbacks. The biggest one is reusability. Would you rather apply a policy to an API or have the same code, error handling, and dependencies across each of your applications? To me, a policy is a consistent and easy way to handle this sort of use case (request validation).
The first thing to know about creating custom policies is that it doesn't necessarily follow the same syntax as a regular mule application. While you can use components found in a MuleSoft application, this tutorial will cover policy-specific components. There is no drag and drop, and the policies are created directly in XML. It may look weird and unfamiliar but don't let that intimidate you!
To start, we will set up our basic HTTP policy proxy; this is the main building block of our custom policy. You can read more about this in MuleSoft docs.
<http-policy:proxy name="callbackurl-policy-demo">
<http-policy:source>
<http-policy:execute-next/>
</http-policy:source>
</http-policy:proxy>
The above snippet doesn't actually do anything yet; to put it simply, we have a policy that continues the event execution chain (actually sending the request to the application).
To add our functionality, we can add in our validation module that checks is there and is a valid URL:
<http-policy:proxy name="callbackurl-policy-demo">
<http-policy:source>
<choice>
<!-- Validate that X-CALLBACK_URL is not null or empty -->
<when
expression="#[ !isEmpty(attributes.headers.'X-CALLBACK-URL' default '')]">
<!-- Validate that X-CALLBACK-URL is a valid URL -->
<try>
<validation:is-url url="#[attributes.headers.'X-CALLBACK-URL']" />
<!-- If X-CALLBACK-URL is a valid URL continue the execution chain -->
<http-policy:execute-next />
<!-- If X-CALLBACK-URL was not valid, we never call execute next so our response in the error handler is sent back to the client-->
<error-handler>
<on-error-continue>
<http-transform:set-response
statusCode="#[400]">
<http-transform:body>#[ 'X-CALLBACK-URL value was not a valid URL' ]</http-transform:body>
</http-transform:set-response>
</on-error-continue>
</error-handler>
</try>
</when>
<otherwise>
<!-- Log value of the header -->
<logger
message="Avoiding execution; no callback url was provided.\nX-CALLBACK-URL: #[attributes.headers.'X-CALLBACK-URL']" />
</otherwise>
</choice>
</http-policy:source>
</http-policy:proxy>
We use:
<when expression="#[ !isEmpty(attributes.headers.'X-CALLBACK-URL' default '')]">
To check that our header is not null or empty.
We then use our try block with the URL validation module to check that it is a valid URL. We use the HTTP transform module to send back a 400 error to our user.
Our final step is to include our requirement of being able to set a custom payload. We'll edit the YAML file to include a custom payload in the UI for the API Manager when you apply it. It will pull in the value defined in API Manager into our application. The syntax is weird and unfamiliar but is the way that the MuleSoft guides use (I'd prefer to use a choice router, but this works too):
Our YAML:
id: callbackurl-policy-demo
name: callbackurl-policy-demo
description: this is a policy demo
category: Custom
type: custom
resourceLevelSupported: true
encryptionSupported: false
standalone: true
requiredCharacteristics: []
providedCharacteristics: []
configuration:
- propertyName: payload
name: Payload
type: expression
description: Payload when X-CALLBACK-URL is not provided (a default is provided if this is left blank)
optional: true
sensitive: false
allowMultiple: false
Pulling the payload property we defined into the application:
<http-policy:proxy name="callbackurl-policy-demo">
<http-policy:source>
<choice>
<!-- Validate that X-CALLBACK_URL is not null or empty -->
<when
expression="#[ !isEmpty(attributes.headers.'X-CALLBACK-URL' default '')]">
<!-- Validate that X-CALLBACK-URL is a valid URL -->
<try>
<validation:is-url url="#[attributes.headers.'X-CALLBACK-URL']" />
<!-- If X-CALLBACK-URL is a valid URL continue the execution chain -->
<http-policy:execute-next />
<!-- If X-CALLBACK-URL was not valid, we never call execute next so our response in the error handler is sent back to the client-->
<error-handler>
<on-error-continue>
<http-transform:set-response
statusCode="#[400]">
<http-transform:body>#[ 'X-CALLBACK-URL value was not a valid URL' ]</http-transform:body>
</http-transform:set-response>
</on-error-continue>
</error-handler>
</try>
</when>
<otherwise>
<!-- Log value of the header -->
<logger
message="Avoiding execution; no callback url was provided.\nX-CALLBACK-URL: #[attributes.headers.'X-CALLBACK-URL']" />
{{#if payload}}
<!-- Set response defined in API manager -->
<http-transform:set-response
statusCode="#[400]">
<http-transform:body>{{{payload}}}</http-transform:body>
</http-transform:set-response>
{{else}}
<!-- Set default response -->
<http-transform:set-response
statusCode="#[400]">
<http-transform:body>#[ 'Please provide the callback url, X-CALLBACK-URL, in the request headers' ]</http-transform:body>
</http-transform:set-response>
{{/if}}
</otherwise>
</choice>
</http-policy:source>
</http-policy:proxy>
We use the HTTP transform module again to send back a default response when it is not defined in the API Manager.
The syntax of {{#if payload}} is unfamiliar, but this does work and pulls in values defined in the API Manager.
Our next step is to connect this policy to the exchange.
Once this policy is deployed, we can apply this policy to any API in the API Manager!
And that is all we need for our custom policy! You can find the whole code below:
<?xml version="1.0" encoding="UTF-8"?>
<mule
xmlns:validation="http://www.mulesoft.org/schema/mule/validation"
xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core"
xmlns="http://www.mulesoft.org/schema/mule/core"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:http-policy="http://www.mulesoft.org/schema/mule/http-policy"
xmlns:http-transform="http://www.mulesoft.org/schema/mule/http-policy-transform"
xsi:schemaLocation="
http://www.mulesoft.org/schema/mule/validation http://www.mulesoft.org/schema/mule/validation/current/mule-validation.xsd
http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/http-policy http://www.mulesoft.org/schema/mule/http-policy/current/mule-http-policy.xsd
http://www.mulesoft.org/schema/mule/http-policy-transform http://www.mulesoft.org/schema/mule/http-policy-transform/current/mule-http-policy-transform.xsd">
<validation:config name="Validation_Config"/>
<http-policy:proxy name="callbackurl-policy-demo">
<http-policy:source>
<choice>
<!-- Validate that X-CALLBACK_URL is not null or empty -->
<when
expression="#[ !isEmpty(attributes.headers.'X-CALLBACK-URL' default '')]">
<!-- Validate that X-CALLBACK-URL is a valid URL -->
<try>
<validation:is-url url="#[attributes.headers.'X-CALLBACK-URL']" />
<!-- If X-CALLBACK-URL is a valid URL continue the execution chain -->
<http-policy:execute-next />
<!-- If X-CALLBACK-URL was not valid, we never call execute next so our response in the error handler is sent back to the client-->
<error-handler>
<on-error-continue>
<http-transform:set-response
statusCode="#[400]">
<http-transform:body>#[ 'X-CALLBACK-URL value was not a valid URL' ]</http-transform:body>
</http-transform:set-response>
</on-error-continue>
</error-handler>
</try>
</when>
<otherwise>
<!-- Log value of the header -->
<logger
message="Avoiding execution; no callback url was provided.\nX-CALLBACK-URL: #[attributes.headers.'X-CALLBACK-URL']" />
{{#if payload}}
<!-- Set response defined in API manager -->
<http-transform:set-response
statusCode="#[400]">
<http-transform:body>{{{payload}}}</http-transform:body>
</http-transform:set-response>
{{else}}
<!-- Set default response -->
<http-transform:set-response
statusCode="#[400]">
<http-transform:body>#[ 'Please provide the callback url, X-CALLBACK-URL, in the request headers' ]</http-transform:body>
</http-transform:set-response>
{{/if}}
</otherwise>
</choice>
</http-policy:source>
</http-policy:proxy>
</mule>
In this guide, we looked at a use case for custom policies and implemented the logic needed. We deployed to the exchange, and our policy could be reused across multiple applications! Thank you for reading, and I hope you learned a bit and maybe can even develop your own!
Opinions expressed by DZone contributors are their own.
Comments