In een vorige blog hebben we gekeken naar het afhandelen van fouten in een API als een eindpunt niet beschikbaar is. Kort samengevat hebben we in de volledige sequence van de API twee elementen toegevoegd. Als eerste de foutcode die als foutbericht op de console getoond wordt en als tweede een element dat een speciaal bericht meegaf en de error verving met HTTP 404 (not found). In deze blog gaan we WireMock gebruiken om een eindpunt te creëren.
De reden voor het gebruik van een fault sequence is dat dit de sequence is die getriggerd wordt als er iets fout gaat. Maar dat gebeurt niet alleen als er geen eindpunt gevonden kan worden. Er zijn namelijk ook andere situaties die de fault sequence triggeren. Een voorbeeld hiervan is een eindpunt dat niet op tijd reageert. Om dit te testen gaan we WireMock gebruiken om een eindpunt te creëren dat altijd een paar seconden later reageert dan de timeout die we instellen op het eindpunt.
Een eindpunt definiëren
Als je een eindpunt definieert kun je meer dan alleen de URL of het adres opgeven. De definitie van het eindpunt is alleen het URI-sjabloon en de methodes (in het geval van een HTTP-eindpunt). Maar als je inzoomt, kun je zien dat er foutverwerking in het eindpunt mogelijk is met zaken zoals timeouts, suspend codes enzovoort.
We laten de eindpuntconfiguratie op dit moment voor wat het is en maken eerst een eindpunt aan zoals hierboven te zien is. In WireMock definiëren we een nieuwe service met de naam servicedelay.json en sturen die naar de mappings-map van WireMock.
{
"request": {
"method": "GET",
"url": "/yenlo/api/testdelay"
},
"response": {
"status": 200,
"fixedDelayMilliseconds": 31000,
"body": "Service is responding"
}
}
We testen het eindpunt door SoapUI te gebruiken en na 31 seconden krijgen we bijna hetzelfde bericht als bij de vorige service. Wat we dus gaan doen is een nieuwe API maken die dit specifieke eindpunt aanroept en dan bekijkt wat de respons wordt. We maken een letterlijke kopie van de vorige API en veranderen alleen het eindpunt en geven het een andere naam, dat is alles.
Voeg de API toe aan de Composite Application en deploy het .car-bestand naar de Enterprise Integrator die natuurlijk al hoort te runnen.
Controleer met de management UI of de API goed is geplaatst in de Enterprise Integrator.
Test de API door de SoapUI te gebruiken met de API invocation URL die je kunt zien in het overzicht met geplaatste API’s. Na 31 seconden krijgen we inderdaad een respons van de service: een 200-code en het bericht.
Wat we nu moeten doen is de eindpuntconfiguratie aanpassen, zodat het werkelijk een timeout oplevert als een respons niet binnen de opgegeven tijd terugkomt. We doen dit door twee belangrijke configuraties te definiëren: ‘endpoints timeout state’ en ‘timeout configuration’.
Als we geen respons krijgen, dan vertellen we de enterprise integrator om drie keer verbinding te maken met een vertraging van 500 ms tussen iedere poging. Voordat we onderbreken hebben we een periode van 10.000 ms en bovendien wordt in geval van onderbreking de hele sequence uitgevoerd.
In de broncode ziet het er ongeveer zo uit (we laten hier alleen de eindpuntconfiguratie zien).
<send>
<endpoint>
<http method="get" uri-template="http://localhost:8080/yenlo/api/testdelay">
<timeout>
<duration>10000</duration>
<responseAction>fault</responseAction>
</timeout>
<markForSuspension>
<retriesBeforeSuspension>3</retriesBeforeSuspension>
<retryDelay>500</retryDelay>
</markForSuspension>
</http>
<description/>
</endpoint>
</send>
Het resultaat, wanneer we de API aanroepen met deze configuratie, is dat het een aantal pogingen doet en dan pas de FaultSequence triggert.
[2019-05-10 08:50:22,758] [EI-Core] WARN - EndpointContext Endpoint : AnonymousEndpoint with address http://localhost:8080/yenlo/api/testdelay is marked as TIMEOUT and will be retried : 2 more time/s after : Fri May 10 08:50:23 CEST 2019 until its marked SUSPENDED for failure
[2019-05-10 08:50:22,759] [EI-Core] INFO - LogMediator Error = 101504, MSG = Send timeout, Trace = null, exception = null
[2019-05-10 08:50:22,773] [EI-Core] WARN - TimeoutHandler Expiring message ID : urn:uuid:073c955c-a893-4cf3-b0fd-2f8c076c3e27; dropping message after ENDPOINT_TIMEOUT of : 10 seconds for AnonymousEndpoint, URI : http://localhost:8080/yenlo/api/testdelay, Received through API : EPTest2
[2019-05-10 08:50:30,822] [EI-Core] WARN - SynapseCallbackReceiver Synapse received a response for the request with message Id : urn:uuid:073c955c-a893-4cf3-b0fd-2f8c076c3e27 But a callback is not registered (anymore) to process this response
Zoals je ziet wordt de volledige sequence getriggerd en de foutcode 101504 getoond. Als we deze code opzoeken, zien we dat dit verwijst naar: ‘Connection timed out (no input was detected on this connection over the maximum period of inactivity)’
We hadden de FaultSequence geprogrammeerd voor een situatie waarin een ‘404 not found’ voorkomt, maar zoals we nu zien wordt de volledige sequence ook getriggerd als we een timeout hebben.
HTTP/1.1 404 Not Found
Host: localhost:8280
Accept-Encoding: gzip,deflate
Content-Type: application/json; charset=UTF-8
Date: Fri, 10 May 2019 06:50:22 GMT
Transfer-Encoding: chunked
Connection: Keep-Alive
{ 'Error':'Connection cannot be made',
'Error Code' : 101504,
'Error Message' : ,
'Error Detail' : Send timeout
}
De oplossing vinden we natuurlijk door te kijken naar de foutcode en dan te bepalen wat het juiste foutbericht is dat bij de respons past.
Switch Mediator
De beste manier om dit te doen is door een switch mediator te gebruiken in de FaultSequence. De switch mediator geeft je de mogelijkheid om op basis van de foutcode af te takken naar een speciaal deel en daar de zowel de respons als de statuscode in te stellen.
Laten we deze veranderingen doorvoeren in de fault sequence. De switch mediator gebruikt gevallen die overeenkomen met een foutcode en heeft ook een standaardroute die gebruikt wordt als geen van de specifieke gevallen matcht. Dit is een makkelijke manier om een generieke fout te krijgen, zonder dat je alle foutcodes hoeft te specificeren. We beginnen bij het definiëren van twee gevallen: één voor de situatie waarin het eindpunt niet gevonden kan worden en één wanneer we een error bij onderbreking krijgen.
We kopiëren de volledige configuratie in de statements van beide specifieke gevallen en nog een keer in voor de generieke fout als standaard uitkomst.
Dit resulteert echter in aardig wat dubbele code. Dus wat we gaan doen is dit: we verwijderen de elementen die in ieder geval exact hetzelfde zijn en plaatsen die buiten de switch mediator. We hebben het natuurlijk over de log mediator die de foutcode aan de console doorgeeft. Hierdoor besparen we een flink stuk code, maar er is nog meer mogelijk! De HTTP_SC is beschikbaar, dus dat kunnen we in de switch mediator opnemen. Onze zelf gedefinieerde ‘fout’-meldingen zijn nu hardcoded, maar dat kunnen we veranderen naar een variabele en die variabele opnemen in de switch mediator. Als we alle verandering maken, dan is dit code die overblijft:
<faultSequence>
<log description="" level="custom">
<property expression="get-property('ERROR_CODE')" name="Error"/>
<property expression="get-property('ERROR_MESSAGE')" name="MSG"/>
<property expression="get-property('ERROR_DETAIL')" name="Trace"/>
<property expression="$ctx:ERROR_EXCEPTION" name="exception"/>
</log>
<switch description="" source="get-property('ERROR_CODE')">
<case regex="101503">
<property name="HTTP_SC" scope="axis2" type="STRING" value="404"/>
<property name="OUR_ERROR" scope="axis2" type="STRING" value="Error':'Connection cannot be made"/>
</case>
<case regex="101504">
<property name="HTTP_SC" scope="axis2" type="STRING" value="408"/>
<property name="OUR_ERROR" scope="axis2" type="STRING" value="Error':'Connection timed out into suspension"/>
</case>
<default>
<property name="HTTP_SC" scope="axis2" type="STRING" value="500"/>
<property name="OUR_ERROR" scope="axis2" type="STRING" value="Error': Generic fault handler, code not specified"/>
</default>
</switch>
<payloadFactory media-type="json">
<format>{ 'Error':$1,
'Error Code' : $2,
'Error Message' : $3,
'Error Detail' : $4}</format>
<args>
<arg evaluator="xml" expression="get-property('ERROR_CODE')"/>
<arg evaluator="xml" expression="get-property('ERROR_EXCEPTION')"/>
<arg evaluator="xml" expression="get-property('ERROR_MESSAGE')"/>
</args>
</payloadFactory>
<respond/>
</faultSequence>
We hebben nu een flexibele FaultSequence die twee specifieke errors kan afvangen, 404 en 408, en daarnaast de generieke error die we de code 500 gegeven hebben.
Als we dit uitproberen in SoapUI krijgen we dit bericht:
Ik hoop dat deze blog je geholpen heeft. Laat een commentaar achter als je vragen of feedback hebt. Zou je meer willen leren over bijvoorbeeld de WSO2 Enterprise Integrator of WSO2 API Manager? Neem dan deel aan één van onze WSO2 trainingen.