import ballerina/http;
import ballerina/log;
import ballerina/runtime;
listener http:Listener backendEP = new(8080);
http:FailoverClient foBackendEP = new({
    timeoutInMillis: 5000,
    failoverCodes: [501, 502, 503],
    intervalInMillis: 5000,
    targets: [
        { url: "http://nonexistentEP/mock1" },
        { url: "http://localhost:8080/echo" },
        { url: "http://localhost:8080/mock" }
    ]
});@http:ServiceConfig {
    basePath: "/fo"
}
service failoverDemoService on new http:Listener(9090) {
    @http:ResourceConfig {
        methods: ["GET", "POST"],
        path: "/"
    }
    resource function invokeEndpoint(http:Caller caller, http:Request request) {
        var backendResponse = foBackendEP->get("/", request);
        if (backendResponse is http:Response) {
            var responseToCaller = caller->respond(backendResponse);
            if (responseToCaller is error) {
                log:printError("Error sending response", err = responseToCaller);
            }
        } else {
            http:Response response = new;
            response.statusCode = http:STATUS_INTERNAL_SERVER_ERROR;
            response.setPayload(<string>backendResponse.detail()?.message);
            var responseToCaller = caller->respond(response);
            if (responseToCaller is error) {
                log:printError("Error sending response", err = responseToCaller);
            }
        }
    }
}
@http:ServiceConfig {
    basePath: "/echo"
}
service echo on backendEP {
    @http:ResourceConfig {
        methods: ["POST", "PUT", "GET"],
        path: "/"
    }
    resource function echoResource(http:Caller caller, http:Request req) {
        runtime:sleep(30000);        var result = caller->respond("echo Resource is invoked");
        if (result is error) {
           log:printError("Error sending response from mock service", result);
        }
    }
}
@http:ServiceConfig {
    basePath: "/mock"
}
service mock on backendEP {
    @http:ResourceConfig {
        methods: ["POST", "PUT", "GET"],
        path: "/"
    }
    resource function mockResource(http:Caller caller, http:Request req) {
        var result = caller->respond("Mock Resource is Invoked.");
        if (result is error) {
           log:printError("Error sending response from mock service", result);
        }
    }
}

Failover

Ballerina users can configure multiple HTTP clients in a given failover group. If one of the HTTP clients (dependencies) fails, Ballerina automatically fails over to another endpoint.

import ballerina/http;
import ballerina/log;
import ballerina/runtime;
listener http:Listener backendEP = new(8080);

Create an endpoint with port 8080 for the mock backend services.

http:FailoverClient foBackendEP = new({
    timeoutInMillis: 5000,
    failoverCodes: [501, 502, 503],
    intervalInMillis: 5000,

Define the failover client endpoint to call the backend services.

    targets: [
        { url: "http://nonexistentEP/mock1" },
        { url: "http://localhost:8080/echo" },
        { url: "http://localhost:8080/mock" }
    ]
});

Define a set of HTTP Clients that are targeted for failover.

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

Create a REST resource within the API.

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

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

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

@http:ServiceConfig {
    basePath: "/echo"
}
service echo on backendEP {
    @http:ResourceConfig {
        methods: ["POST", "PUT", "GET"],
        path: "/"
    }
    resource function echoResource(http:Caller caller, http:Request req) {

Define the sample service to mock connection timeouts and service outages.

        runtime:sleep(30000);

Delay the response for 30000 milliseconds to mimic network level delays.

        var result = caller->respond("echo Resource is invoked");
        if (result is error) {
           log:printError("Error sending response from mock service", result);
        }
    }
}
@http:ServiceConfig {
    basePath: "/mock"
}
service mock on backendEP {
    @http:ResourceConfig {
        methods: ["POST", "PUT", "GET"],
        path: "/"
    }
    resource function mockResource(http:Caller caller, http:Request req) {
        var result = caller->respond("Mock Resource is Invoked.");
        if (result is error) {
           log:printError("Error sending response from mock service", result);
        }
    }
}

Define the sample service to mock a healthy service.

# To start the services, navigate to the directory that contains the
# `.bal` file and use the `ballerina run` command.
$ ballerina run http_failover.bal
[ballerina/http] started HTTP/WS listener 0.0.0.0:8080
[ballerina/http] started HTTP/WS listener 0.0.0.0:9090
# To invoke the Failover Service, use following curl command.
$ curl -v http://localhost:9090/fo
# The `FailoverClient` endpoint is configured with three target services.The first two targets are configured to mimic
# failure backends. If one target service goes down, the failover client automatically calls the other targets.
# Once you invoke the Failover demo service, the Failover client fails over the request to the configured
# target endpoints. In this example, the third target is configured to give a successful response and the following
# response is given when the failover demo service is invoked.
# Server response:
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9090 (#0)
> GET /fo 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:29:37 +0530
< server: ballerina/1.0.0-beta
< content-length: 25
<
* Connection #0 to host localhost left intact
Mock Resource is Invoked.
# Invoke the Failover Service again using the curl command.
$ curl -v http://localhost:9090/fo
# At this point, Ballerina failover client already knows that leading endpoints (in this case the first two targets)
# failed in the last invocation and it got the successful response from the third target. So the failover client
# resumes the failover from the last successful target. In this case it is the third target and the client will get
# the immediate response for subsequent calls.
# Server response:
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9090 (#0)
> GET /fo 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:29:41 +0530
< server: ballerina/1.0.0-beta
< content-length: 25
<
* Connection #0 to host localhost left intact
Mock Resource is Invoked.