import ballerina/http;
import ballerina/log;
import ballerina/runtime;
http:Client backendClientEP = new ("http://localhost:8080", {
        retryConfig: {
            intervalInMillis: 3000,
            count: 3,
            backOffFactor: 2.0,
            maxWaitIntervalInMillis: 20000
        },
        timeoutInMillis: 2000
    }
);@http:ServiceConfig {
    basePath: "/retry"
}
service retryDemoService on new http:Listener(9090) {
    @http:ResourceConfig {
        methods: ["GET"],
        path: "/"
    }
    resource function invokeEndpoint(http:Caller caller, http:Request request) {
        var backendResponse = backendClientEP->forward("/hello", request);
        if (backendResponse is http:Response) {
            var responseToCaller = caller->respond(backendResponse);
            if (responseToCaller is error) {
                log:printError("Error sending response", responseToCaller);
            }
        } else {
            http:Response response = new;
            response.statusCode = http:STATUS_INTERNAL_SERVER_ERROR;
            string errCause = <string>backendResponse.detail()?.message;
            response.setPayload(errCause);
            var responseToCaller = caller->respond(response);
            if (responseToCaller is error) {
                log:printError("Error sending response", responseToCaller);
            }
        }
    }
}int counter = 0;
@http:ServiceConfig {
    basePath: "/hello"
}
service mockHelloService on new http:Listener(8080) {    @http:ResourceConfig {
        methods: ["GET", "POST"],
        path: "/"
    }
    resource function sayHello(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(5000);
            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", result);
    }
}

Retry

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

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

Define the endpoint to the call the mockHelloService.

        retryConfig: {

Retry configuration options.

            intervalInMillis: 3000,

Initial retry interval in milliseconds.

            count: 3,

Number of retry attempts before giving up.

            backOffFactor: 2.0,

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

            maxWaitIntervalInMillis: 20000
        },
        timeoutInMillis: 2000
    }
);

Upper limit of the retry interval in milliseconds. If intervalInMillis into backOffFactor value exceeded maxWaitIntervalInMillis interval value. maxWaitIntervalInMillis will be considered as the retry interval.

@http:ServiceConfig {
    basePath: "/retry"
}
service retryDemoService on new http:Listener(9090) {
    @http:ResourceConfig {
        methods: ["GET"],
        path: "/"
    }

Create a REST resource within the API.

    resource function invokeEndpoint(http:Caller caller, http:Request request) {
        var backendResponse = backendClientEP->forward("/hello", request);

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

        if (backendResponse is http:Response) {
            var responseToCaller = caller->respond(backendResponse);
            if (responseToCaller is error) {
                log:printError("Error sending response", responseToCaller);
            }
        } else {
            http:Response response = new;
            response.statusCode = http:STATUS_INTERNAL_SERVER_ERROR;
            string errCause = <string>backendResponse.detail()?.message;
            response.setPayload(errCause);
            var responseToCaller = caller->respond(response);
            if (responseToCaller is error) {
                log:printError("Error sending response", 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;
@http:ServiceConfig {
    basePath: "/hello"
}
service mockHelloService 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.

    @http:ResourceConfig {
        methods: ["GET", "POST"],
        path: "/"
    }
    resource function sayHello(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(5000);
            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);
        }
    }
}

Delay the response by 5000 milliseconds to mimic network level delays.

function handleRespondResult(error? result) {
    if (result is http:ListenerError) {
        log:printError("Error sending response from mock service", result);
    }
}
# To start the services, navigate to the directory that contains the
# `.bal` file and use the `ballerina run` command.
$ ballerina 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
2019-09-04 15:47:25,980 INFO  [] - Request received from the client to delayed service.
2019-09-04 15:47:30,998 INFO  [] - Request received from the client to delayed service.
2019-09-04 15:47:31,012 ERROR [] - Error sending response from mock service : error {ballerina/http}GenericListenerError message=Connection between remote client and host is closed
2019-09-04 15:47:36,004 ERROR [] - Error sending response from mock service : error {ballerina/http}GenericListenerError message=Connection between remote client and host is closed
2019-09-04 15:47:39,005 INFO  [] - Request received from the client to delayed service.
2019-09-04 15:47:44,007 ERROR [] - Error sending response from mock service : error {ballerina/http}GenericListenerError message=Connection between remote client and host is closed
2019-09-04 15:47:53,016 INFO  [] - 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 ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9090 (#0)
> GET /retry HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: text/plain
< date: Wed, 4 Sep 2019 15:47:53 +0530
< server: ballerina/1.0.0-beta
< content-length: 14
<
* Connection #0 to host localhost left intact
Hello World!!!