import ballerina/http;
import ballerina/log;
listener http:Listener backendEP = new(8080);
http:LoadBalanceClient lbBackendEP = new({
        targets: [
            { url: "http://localhost:8080/mock1" },
            { url: "http://localhost:8080/mock2" },
            { url: "http://localhost:8080/mock3" }
        ],
        timeoutInMillis: 5000
});
@http:ServiceConfig {
    basePath: "/lb"
}
service loadBalancerDemoService on new http:Listener (9090) {
    @http:ResourceConfig {
        path: "/"
    }
    resource function roundRobin(http:Caller caller, http:Request req) {
        json requestPayload = { "name": "Ballerina" };
        var response = lbBackendEP->post("/", requestPayload);
        if (response is http:Response) {
            var responseToCaller = caller->respond(response);
            if (responseToCaller is http:ListenerError) {
                log:printError("Error sending response", responseToCaller);
            }
        } else {
            http:Response outResponse = new;
            outResponse.statusCode = 500;
            outResponse.setPayload(<string>response.detail()?.message);
            var responseToCaller = caller->respond(outResponse);
            if (responseToCaller is http:ListenerError) {
                log:printError("Error sending response", responseToCaller);
            }
        }
    }
}
@http:ServiceConfig {
    basePath: "/mock1"
}
service mock1 on backendEP {
    @http:ResourceConfig {
        path: "/"
    }
    resource function mock1Resource(http:Caller caller, http:Request req) {
        var responseToCaller = caller->respond("Mock1 resource was invoked.");
        if (responseToCaller is http:ListenerError) {
            handleRespondResult(responseToCaller);
        }
    }
}@http:ServiceConfig {
    basePath: "/mock2"
}
service mock2 on backendEP {
    @http:ResourceConfig {
        path: "/"
    }
    resource function mock2Resource(http:Caller caller, http:Request req) {
        var responseToCaller = caller->respond("Mock2 resource was invoked.");
        if (responseToCaller is http:ListenerError) {
            handleRespondResult(responseToCaller);
        }
    }
}@http:ServiceConfig {
    basePath: "/mock3"
}
service mock3 on backendEP {
    @http:ResourceConfig {
        path: "/"
    }
    resource function mock3Resource(http:Caller caller, http:Request req) {
        var responseToCaller = caller->respond("Mock3 resource was invoked.");
        if (responseToCaller is http:ListenerError) {
            handleRespondResult(responseToCaller);
        }
    }
}
function handleRespondResult(http:ListenerError? result) {
    if (result is http:ListenerError) {
        log:printError("Error sending response from mock service", result);
    }
}

Load Balancing

An HTTP load balancing endpoint is used when the request load needs to be load balanced across a given set of target endpoints.

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

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

http:LoadBalanceClient lbBackendEP = new({

Define the load balance client endpoint to call the backend services.

        targets: [
            { url: "http://localhost:8080/mock1" },
            { url: "http://localhost:8080/mock2" },
            { url: "http://localhost:8080/mock3" }
        ],
        timeoutInMillis: 5000
});

Define the set of HTTP clients that need to be load balanced.

@http:ServiceConfig {
    basePath: "/lb"
}
service loadBalancerDemoService on new http:Listener (9090) {

Create an HTTP service bound to the endpoint (loadBalancerEP).

    @http:ResourceConfig {
        path: "/"
    }
    resource function roundRobin(http:Caller caller, http:Request req) {
        json requestPayload = { "name": "Ballerina" };
        var response = lbBackendEP->post("/", requestPayload);

Create a REST resource within the API.

        if (response is http:Response) {
            var responseToCaller = caller->respond(response);
            if (responseToCaller is http:ListenerError) {
                log:printError("Error sending response", responseToCaller);
            }
        } else {
            http:Response outResponse = new;
            outResponse.statusCode = 500;
            outResponse.setPayload(<string>response.detail()?.message);
            var responseToCaller = caller->respond(outResponse);
            if (responseToCaller is http:ListenerError) {
                log:printError("Error sending response", responseToCaller);
            }
        }
    }
}

If a response is returned, the normal process runs. If the service does not get the expected response, the error-handling logic is executed.

@http:ServiceConfig {
    basePath: "/mock1"
}
service mock1 on backendEP {
    @http:ResourceConfig {
        path: "/"
    }
    resource function mock1Resource(http:Caller caller, http:Request req) {
        var responseToCaller = caller->respond("Mock1 resource was invoked.");
        if (responseToCaller is http:ListenerError) {
            handleRespondResult(responseToCaller);
        }
    }
}

Define the mock backend services, which are called by the load balancer.

@http:ServiceConfig {
    basePath: "/mock2"
}
service mock2 on backendEP {
    @http:ResourceConfig {
        path: "/"
    }
    resource function mock2Resource(http:Caller caller, http:Request req) {
        var responseToCaller = caller->respond("Mock2 resource was invoked.");
        if (responseToCaller is http:ListenerError) {
            handleRespondResult(responseToCaller);
        }
    }
}
@http:ServiceConfig {
    basePath: "/mock3"
}
service mock3 on backendEP {
    @http:ResourceConfig {
        path: "/"
    }
    resource function mock3Resource(http:Caller caller, http:Request req) {
        var responseToCaller = caller->respond("Mock3 resource was invoked.");
        if (responseToCaller is http:ListenerError) {
            handleRespondResult(responseToCaller);
        }
    }
}
function handleRespondResult(http:ListenerError? result) {
    if (result is http:ListenerError) {
        log:printError("Error sending response from mock service", result);
    }
}

Function to handle respond results

# To start the services, navigate to the directory that contains the
# `.bal` file and use the `ballerina run` command.
$ ballerina run http_load_balancer.bal
# Service deployment
[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 Load Balancer Demo Service, use following cURL command.
$ curl -v http://localhost:9090/lb
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9090 (#0)
> GET /lb 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:36:57 +0530
< server: ballerina/1.0.0-beta
< content-length: 27
<
* Connection #0 to host localhost left intact
Mock1 resource was invoked.
# Repeat the same cURL command to invoke the Load Balance Demo Service again.
# The request will be load balanced to the second mock service.
$ curl -v http://localhost:9090/lb
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9090 (#0)
> GET /lb 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:36:58 +0530
< server: ballerina/1.0.0-beta
< content-length: 27
<
* Connection #0 to host localhost left intact
Mock2 resource was invoked.
# Invoke the Load Balancer Demo Service one more time using the same cURL command.
# The request will be load balanced to the third mock service.
$ curl -v http://localhost:9090/lb
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9090 (#0)
> GET /lb 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:36:59 +0530
< server: ballerina/1.0.0-beta
< content-length: 27
<
* Connection #0 to host localhost left intact
Mock3 resource was invoked.
# Invoke the Load Balancer Demo Service using the same cURL command for the last time.
# The request will be load balanced to the first mock service again.
$ curl -v http://localhost:9090/lb
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9090 (#0)
> GET /lb 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:37:00 +0530
< server: ballerina/1.0.0-beta
< content-length: 27
<
* Connection #0 to host localhost left intact
Mock1 resource was invoked.