In an ideal world, there are no problems, no errors. We however, live in a real world, where not everything we do always works as we planned or hoped it would. In WSO2 Enterprise Integrator, the main area where we must handle errors are proxies and APIs. Error handling is key for a proper developer-experience for your services and APIs. There are few things more frustrating than a nonsensical error message like ‘Something went wrong’. It is not only for others that you develop proper error handling, you also do it for yourself. Simply because half a year later, you will thank yourself for an error message like ‘Could not connect to FinanceWorld Invoicing service due to a connection timeout’ rather than ‘Something went wrong’.
In this blog, I will talk about error handling and the way you can configure it in the WSO2 Enterprise Integrator.
Mechanisms for error handling
Some errors occur outside of mediation. If you, for instance, try to start the Enterprise Integrator twice, you will get a stack trace that will, for instance, indicate that a file is locked.
Another error, for instance out of memory, can kill the entire Java Virtual Machine. Again, little to be done other than increase the amount of memory and/or removing other programs that run simultaneously.
What I am talking about is error handling with regard to the integration process and this involves errors that are caused by endpoint, message payloads et cetera.
Fault Sequence in Integration Studio
When you create a proxy or API and you are using its design-view in Integration Studio, you might have seen a red rectangular box. This is the fault sequence. The fault sequence is executed when an ERROR in EI-components occurred, not to be confused with an error returned by the back-end. Errors from the back-end are handled in the in- or outSequence but do not, by default, get sent to the fault sequence!
So, one of the things that we want is to capture the unavailability of an endpoint. To this end, I’ve created an endpoint that is pointing at the echo service that is included with the Enterprise Integrator. I switch off the endpoint. So that, ideally, it triggers an error message. I’ve furthermore copied the configuration of default ‘fault’ sequence and made some changes. So that it is clear that it is the custom fault sequence within the proxy that I’m creating, that is pushing the information onto the console.
<?xml version="1.0" encoding="UTF-8"?>
<proxy name="ErrorProxy" startOnLoad="true" transports="http https" xmlns="http://ws.apache.org/ns/synapse">
<target>
<inSequence>
<send>
<endpoint key="Test"/>
</send>
</inSequence>
<outSequence>
<send/>
</outSequence>
<faultSequence>
<log level="custom">
<property name="MESSAGE" value="Executing Proxy 'fault' sequence"/>
<property expression="get-property('ERROR_CODE')" name="ERROR_CODE"/>
<property expression="get-property('ERROR_MESSAGE')" name="ERROR_MESSAGE"/>
</log>
<payloadFactory media-type="xml">
<format>
<Error>$1</Error>
</format>
<args>
<arg evaluator="xml" expression="get-property('ERROR_CODE')"/>
</args>
</payloadFactory>
<respond/>
</faultSequence>
</target>
</proxy
This is the console response:
[2021-06-15 16:41:50,953] INFO
{org.apache.synapse.mediators.builtin.LogMediator} - To: /services/ErrorProxy.ErrorProxyHttpSoap12Endpoint, WSAction: urn:mediate, SOAPAction: urn:mediate, MessageID: urn:uuid:cb296ee0-e738-478c-9ac4-008dcc9c4c5a, Direction: request, MESSAGE = Executing Proxy 'fault' sequence, ERROR_CODE = 303001, ERROR_MESSAGE = Currently , Address endpoint : [ Name : Test ] [ State : MAINTNENCE ], Envelope: <?xml version='1.0' encoding='utf-8'?><soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope"><soapenv:Body/></soapenv:Envelope>
An improvement over no message but still a long way off. We also have a response in our client.
Let’s put it in a sequence in the next paragraph.
Make it a generic Error sequence
But we can do more! And we should do more for proper error handling. The same error handling can be moved to a sequence entirely. What we should include in such handling are things that will help to identify what has gone wrong. You can think of elements like:
- uuid (unique message ID)
- datetime (identify when it occurred)
- own error code (translating wso2 codes to your own codes)
Furthermore, we also need to give back a message to the client that will indicate something went wrong. The nontechnical errors (see Empty Response later on) should also be included so that there is one error sequence that is called.
Error in a sequence
When you’re defining a sequence, you can also define the on-error sequence that is associated with it. So, if something goes wrong within a sequence, for instance: you call an endpoint, you are still able to get an error message and do proper error handling. So, instead of a proxy where the error message is triggered, I’m going to do a proxy which causes a sequence, and which then tries to call the endpoint that is switched off. This is the same endpoint as before. In that case, we should see the same error message. But again, I’m making a change within the error sequence to indicate that this is not the fault sequence with regard to the proxy. This is the fault sequence that was created and is associated with the sequence that we’re just calling.
<?xml version="1.0" encoding="UTF-8"?>
<sequence name="callEndpoint" onError="callEndPointError" trace="disable" xmlns="http://ws.apache.org/ns/synapse">
<call>
<endpoint key="Test"/>
</call>
</sequence>
Sequence error
<?xml version="1.0" encoding="UTF-8"?>
<sequence name="callEndPointError" trace="disable" xmlns="http://ws.apache.org/ns/synapse">
<log level="custom">
<property name="MESSAGE" value="Executing Sequence 'fault' sequence"/>
<property expression="get-property('ERROR_CODE')" name="ERROR_CODE"/>
<property expression="get-property('ERROR_MESSAGE')" name="ERROR_MESSAGE"/>
</log>
<payloadFactory media-type="xml">
<format>
<Error>$1</Error>
</format>
<args>
<arg evaluator="xml" expression="get-property('ERROR_CODE')"/>
</args>
</payloadFactory>
<respond/>
</sequence>
The sequence above can be adapted to also point at a generic error sequence.
Error Proxy2
A second proxy calls the sequence and responds.
<?xml version="1.0" encoding="UTF-8"?>
<proxy name="ErrorProxy2" startOnLoad="true" transports="http https" xmlns="http://ws.apache.org/ns/synapse">
<target>
<inSequence>
<sequence key="callEndpoint"/>
<respond/>
</inSequence>
<outSequence/>
<faultSequence>
<log level="custom">
<property name="MESSAGE" value="Executing Proxy 'fault' sequence"/>
<property expression="get-property('ERROR_CODE')" name="ERROR_CODE"/>
<property expression="get-property('ERROR_MESSAGE')" name="ERROR_MESSAGE"/>
</log>
<payloadFactory media-type="xml">
<format>
<Error>$1</Error>
</format>
<args>
<arg evaluator="xml" expression="get-property('ERROR_CODE')"/>
</args>
</payloadFactory>
<respond/>
</faultSequence>
</target>
</proxy>
Even if I include the sequence in the fault sequence of the proxy, it still triggers the sequence fault since it occurred there.
[2021-06-15 16:44:16,818] INFO
{org.apache.synapse.mediators.builtin.LogMediator} - To: /services/ErrorProxy2.ErrorProxy2HttpSoap12Endpoint, WSAction: urn:mediate, SOAPAction: urn:mediate, MessageID: urn:uuid:a25b79ab-12ad-40d7-9817-2ea2995ad1a7, Direction: request, MESSAGE = Executing Sequence 'fault' sequence, ERROR_CODE = 303001, ERROR_MESSAGE = Currently , Address endpoint : [ Name : Test ] [ State : MAINTNENCE ], Envelope: <?xml version='1.0' encoding='utf-8'?><soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope"><soapenv:Body/></soapenv:Envelope>
Test endpoint with an error code 101504 defined (Connection timed out (no input was detected on this connection over the maximum period of inactivity)).
<?xml version="1.0" encoding="UTF-8"?>
<endpoint name="Test" xmlns="http://ws.apache.org/ns/synapse">
<address uri="http://localhost:8280/services/echo">
<suspendOnFailure>
<errorCodes>101504</errorCodes>
<initialDuration>-1</initialDuration>
<progressionFactor>1.0</progressionFactor>
</suspendOnFailure>
<markForSuspension>
<retriesBeforeSuspension>0</retriesBeforeSuspension>
</markForSuspension>
</address>
</endpoint>
As you can see, from the response, I’m now getting a couple of log mediators that will indicate the error at hand. The generic sequence should translate the WSO2 error codes to a set of custom error codes like ‘EP_001- Endpoint not available’ or something similar.
Getting an empty response
But how about an empty response where you try to call a proxy or an API, but the response is empty because the value could not be found?
How do you manage that? Well, let’s try it. We are going to do a simple proxy. We’ll look at the payload. And if the payload has the word GREEN in it, it will echo that back to the client. If it doesn’t have the word GREEN in it, it is going to be an empty payload. In the ErrorProxy3 we will create a simple error message ‘No Color Found’.
<?xml version="1.0" encoding="UTF-8"?>
<proxy name="ErrorProxy3" startOnLoad="true" transports="http https" xmlns="http://ws.apache.org/ns/synapse">
<target>
<inSequence>
<call>
<endpoint>
<address uri="http://localhost:8280/services/Responseproxy">
<suspendOnFailure>
<initialDuration>-1</initialDuration>
<progressionFactor>-1</progressionFactor>
<maximumDuration>0</maximumDuration>
</suspendOnFailure>
<markForSuspension>
<retriesBeforeSuspension>0</retriesBeforeSuspension>
</markForSuspension>
</address>
</endpoint>
</call>
<log level="full"/>
<filter xpath="$body/color!='beautiful'">
<then>
<payloadFactory media-type="xml">
<format>
<error xmlns="">No Color Found</error>
</format>
<args/>
</payloadFactory>
</then>
</filter>
<respond/>
</inSequence>
<outSequence/>
<faultSequence/>
</target>
</proxy>
<?xml version="1.0" encoding="UTF-8"?>
<proxy name="Responseproxy" startOnLoad="true" transports="http https" xmlns="http://ws.apache.org/ns/synapse">
<target>
<inSequence>
<filter xpath="$body/color='GREEN'">
<then>
<property name="result" value="beautiful"/>
</then>
<else>
<property name="result" value=""/>
</else>
</filter>
<payloadFactory media-type="xml">
<format>
<color xmlns="">$1</color>
</format>
<args>
<arg evaluator="xml" expression="$ctx:result"/>
</args>
</payloadFactory>
<respond/>
</inSequence>
<outSequence/>
<faultSequence/>
</target>
</proxy>
Conclusion
These are simple examples that show how error handling works.
However, a better approach is to create a generic fault handling sequence that enables a homogenic fault response for both back-end errors and errors from the fault sequence. Such a sequence can be defined for a number of services or a specific service group that will fill the standard error fields with specific values. Thinking error handling through requires a broad view on error types (fault, back-end, major errors that trigger the generic fault sequence) but also on the kind of information that is available and what is needed to be able to correct the errors.
Depending on the types of messages, sort of calls and other parameters you can identify the information needed and define core information and optional information e.g. specific connection info.