import ballerina/http;
import ballerina/log;
import ballerina/lang.runtime;

// Define the endpoint to the call the `mockHelloService`.
http:Client backendClientEP = check new ("http://localhost:8080", {
            // Retry configuration options.
            retryConfig: {

                // Initial retry interval in seconds.
                interval: 3,

                // Number of retry attempts before giving up.
                count: 3,

                // Multiplier of the retry interval to exponentially increase
                // the retry interval.
                backOffFactor: 2.0,

                // Upper limit of the retry interval in seconds. If
                // `interval` into `backOffFactor` value exceeded
                // `maxWaitInterval` interval value,
                // `maxWaitInterval` will be considered as the retry
                // interval.
                maxWaitInterval: 20

            },
            timeout: 2
        }
    );


service /'retry on new http:Listener(9090) {

    // Parameters include a reference to the caller and an object of the
    // request data.
    resource function 'default .(http:Caller caller, http:Request request) {

        var backendResponse = backendClientEP->forward("/hello", request);

        // If `backendResponse` is an `http:Response`, it is sent back to the
        // client. If `backendResponse` is an `http:ClientError`, an internal
        // server error is returned to the client.
        if (backendResponse is http:Response) {
            var responseToCaller = caller->respond(<@untainted>backendResponse);
            if (responseToCaller is error) {
                log:printError("Error sending response",
                                'error = responseToCaller);
            }
        } else {
            http:Response response = new;
            response.statusCode = http:STATUS_INTERNAL_SERVER_ERROR;
            response.setPayload((<@untainted error>backendResponse).message());
            var responseToCaller = caller->respond(response);
            if (responseToCaller is error) {
                log:printError("Error sending response",
                                'error = responseToCaller);
            }
        }

    }
}

int counter = 0;

// This sample service is used to mock connection timeouts and service outages.
// The service outage is mocked by stopping/starting this service.
// This should run separately from the `retryDemoService` service.
service /hello on new http:Listener(8080) {

    resource function get .(http:Caller caller, http:Request req) {
        counter = counter + 1;
        if (counter % 4 != 0) {
            log:printInfo(
                "Request received from the client to delayed service.");
            // Delay the response by 5 seconds to mimic network level delays.
            runtime:sleep(5);

            var responseToCaller = caller->respond("Hello World!!!");
            handleRespondResult(responseToCaller);
        } else {
            log:printInfo(
                "Request received from the client to healthy service.");
            var responseToCaller = caller->respond("Hello World!!!");
            handleRespondResult(responseToCaller);
        }
    }
}

function handleRespondResult(error? result) {
    if (result is http:ListenerError) {
        log:printError("Error sending response from mock service",
                        'error = result);
    }
}

Retry

The HTTP retry client tries sending over the same request to the backend service when there is a network level failure.

For more information on the underlying module, see the HTTP module.

import ballerina/http;
import ballerina/log;
import ballerina/lang.runtime;
http:Client backendClientEP = check new ("http://localhost:8080", {

Define the endpoint to the call the mockHelloService.

            retryConfig: {

Retry configuration options.

                interval: 3,

Initial retry interval in seconds.

                count: 3,

Number of retry attempts before giving up.

                backOffFactor: 2.0,

Multiplier of the retry interval to exponentially increase the retry interval.

                maxWaitInterval: 20

Upper limit of the retry interval in seconds. If interval into backOffFactor value exceeded maxWaitInterval interval value, maxWaitInterval will be considered as the retry interval.

            },
            timeout: 2
        }
    );
service /'retry on new http:Listener(9090) {
    resource function 'default .(http:Caller caller, http:Request request) {

Parameters include a reference to the caller and an object of the request data.

        var backendResponse = backendClientEP->forward("/hello", request);
        if (backendResponse is http:Response) {
            var responseToCaller = caller->respond(<@untainted>backendResponse);
            if (responseToCaller is error) {
                log:printError("Error sending response",
                                'error = responseToCaller);
            }
        } else {
            http:Response response = new;
            response.statusCode = http:STATUS_INTERNAL_SERVER_ERROR;
            response.setPayload((<@untainted error>backendResponse).message());
            var responseToCaller = caller->respond(response);
            if (responseToCaller is error) {
                log:printError("Error sending response",
                                'error = responseToCaller);
            }
        }

If backendResponse is an http:Response, it is sent back to the client. If backendResponse is an http:ClientError, an internal server error is returned to the client.

    }
}
int counter = 0;
service /hello on new http:Listener(8080) {

This sample service is used to mock connection timeouts and service outages. The service outage is mocked by stopping/starting this service. This should run separately from the retryDemoService service.

    resource function get .(http:Caller caller, http:Request req) {
        counter = counter + 1;
        if (counter % 4 != 0) {
            log:printInfo(
                "Request received from the client to delayed service.");
            runtime:sleep(5);

Delay the response by 5 seconds to mimic network level delays.

            var responseToCaller = caller->respond("Hello World!!!");
            handleRespondResult(responseToCaller);
        } else {
            log:printInfo(
                "Request received from the client to healthy service.");
            var responseToCaller = caller->respond("Hello World!!!");
            handleRespondResult(responseToCaller);
        }
    }
}
function handleRespondResult(error? result) {
    if (result is http:ListenerError) {
        log:printError("Error sending response from mock service",
                        'error = result);
    }
}
# To start the services, navigate to the directory that contains the
# `.bal` file and use the `bal run` command below.
bal run http_retry.bal.bal
[ballerina/http] started HTTP/WS listener 0.0.0.0:9090
[ballerina/http] started HTTP/WS listener 0.0.0.0:8080
time = 2021-01-21 19:00:21,374 level = INFO  module = "" message = "Request received from the client to delayed service."
time = 2021-01-21 19:00:26,379 level = INFO  module = "" message = "Request received from the client to delayed service."
time = 2021-01-21 19:00:26,446 level = ERROR module = "" message = "Error sending response from mock service" error = "Connection between remote client and host is closed"
time = 2021-01-21 19:00:31,387 level = ERROR module = "" message = "Error sending response from mock service" error = "Connection between remote client and host is closed"
time = 2021-01-21 19:00:34,402 level = INFO  module = "" message = "Request received from the client to delayed service."
time = 2021-01-21 19:00:39,409 level = ERROR module = "" message = "Error sending response from mock service" error = "Connection between remote client and host is closed"
time = 2021-01-21 19:00:48,404 level = INFO  module = "" message = "Request received from the client to healthy service."
# If the request that was sent to the `retryDemoService` fails due to an error, the client tries sending the request again.
curl -v http://localhost:9090/retry
*   Trying 127.0.0.1:9090...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 9090 (#0)
> GET /retry HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-type: text/plain
< date: Mon, 21 Sep 2020 20:20:20 +0530
< server: ballerina
< content-length: 14
< 
* Connection #0 to host localhost left intact
Hello World!!!