import ballerina/http;
import ballerina/runtime;
import ballerina/io;endpoint http:Listener passthruEP {
    port:9090
};endpoint http:Listener backendEP {
    port:8080
};endpoint http:Client backendClientEP {
    circuitBreaker: {
        rollingWindow: {
                            timeWindow:10000,
                            bucketSize:2000
                       },
        failureThreshold:0.2,
        resetTimeMillies:10000,
        statusCodes:[400, 404, 500]
    },
    targets: [
                 {
                     url: "http://localhost:8080"
                 }
             ],
    timeoutMillis:2000
};@http:ServiceConfig {
    basePath:"/cb"
}
service<http:Service> circuitbreaker bind passthruEP {    @http:ResourceConfig {
        methods:["GET"],
        path:"/"
    }
    passthru (endpoint client, http:Request request) {
        var backendRes = backendClientEP -> forward("/hello", request);
        match backendRes {
            http:Response res => {
            _ = client -> respond(res);}
            http:HttpConnectorError httpConnectorError => {
            http:Response response = new;
            response.statusCode = 500;
            response.setStringPayload(httpConnectorError.message);
            _ = client -> respond(response);}
        }
    }
}public  int counter = 1;
@http:ServiceConfig {basePath:"/hello"}
service<http:Service> helloWorld bind backendEP {
    @http:ResourceConfig {
        methods:["GET"],
        path:"/"
    }
    sayHello (endpoint client, http:Request req) {
        if (counter % 5 == 0) {
            runtime:sleepCurrentWorker(5000);
            counter = counter + 1;
            http:Response res = new;
            res.setStringPayload("Hello World!!!");
            _ = client -> respond(res);
        } else if (counter % 5 == 3) {
            counter = counter + 1;
            http:Response res = new;
            res.statusCode = 500;
            res.setStringPayload("Internal error occurred while processing the request.");
            _ = client -> respond(res);
        } else {
            counter = counter + 1;
            http:Response res = new;
            res.setStringPayload("Hello World!!!");
            _ = client -> respond(res);
        }
    }
}

HTTP Circuit Breaker

The Circuit Breaker can be used to gracefully handle errors (network related) which occur when using the HTTP Client.

import ballerina/http;
import ballerina/runtime;
import ballerina/io;
endpoint http:Listener passthruEP {
    port:9090
};
endpoint http:Listener backendEP {
    port:8080
};
endpoint http:Client backendClientEP {
    circuitBreaker: {
        rollingWindow: {
                            timeWindow:10000,
                            bucketSize:2000
                       },
        failureThreshold:0.2,
        resetTimeMillies:10000,
        statusCodes:[400, 404, 500]
    },
    targets: [
                 {
                     url: "http://localhost:8080"
                 }
             ],
    timeoutMillis:2000
};
@http:ServiceConfig {
    basePath:"/cb"
}
service<http:Service> circuitbreaker bind passthruEP {
    @http:ResourceConfig {
        methods:["GET"],
        path:"/"
    }
    passthru (endpoint client, http:Request request) {
        var backendRes = backendClientEP -> forward("/hello", request);
        match backendRes {
            http:Response res => {
            _ = client -> respond(res);}
            http:HttpConnectorError httpConnectorError => {
            http:Response response = new;
            response.statusCode = 500;
            response.setStringPayload(httpConnectorError.message);
            _ = client -> respond(response);}
        }
    }
}
public  int counter = 1;
@http:ServiceConfig {basePath:"/hello"}
service<http:Service> helloWorld bind backendEP {
    @http:ResourceConfig {
        methods:["GET"],
        path:"/"
    }
    sayHello (endpoint client, http:Request req) {
        if (counter % 5 == 0) {
            runtime:sleepCurrentWorker(5000);
            counter = counter + 1;
            http:Response res = new;
            res.setStringPayload("Hello World!!!");
            _ = client -> respond(res);
        } else if (counter % 5 == 3) {
            counter = counter + 1;
            http:Response res = new;
            res.statusCode = 500;
            res.setStringPayload("Internal error occurred while processing the request.");
            _ = client -> respond(res);
        } else {
            counter = counter + 1;
            http:Response res = new;
            res.setStringPayload("Hello World!!!");
            _ = client -> respond(res);
        }
    }
}

This sample service can be used to mock connection timeouts and service outages. Service outage can be mocked by stopping/starting this service. This should be run separately from the circuitBreakerDemo service.

$ ballerina run http-circuit-breaker.bal
ballerina: initiating service(s) in 'http-circuit-breaker.bal'
ballerina: started HTTP/WS server connector 0.0.0.0:9090
ballerina: started HTTP/WS server connector 0.0.0.0:8080
2018-03-23 07:34:10,474 INFO  [ballerina.net.http] - CircuitBreaker failure threshold exceeded. Circuit tripped from CLOSE to OPEN state.
2018-03-23 07:34:24,624 INFO  [ballerina.net.http] - CircuitBreaker reset timeout reached. Circuit switched from OPEN to HALF_OPEN state.
2018-03-23 07:34:29,071 INFO  [ballerina.net.http] - CircuitBreaker trial run  was successful. Circuit switched from HALF_OPEN to CLOSE state.
2018-03-23 07:34:38,072 INFO  [ballerina.net.http] - CircuitBreaker failure threshold exceeded. Circuit tripped from CLOSE to OPEN state.
2018-03-23 07:34:51,155 INFO  [ballerina.net.http] - CircuitBreaker reset timeout reached. Circuit switched from OPEN to HALF_OPEN state.
2018-03-23 07:34:53,061 INFO  [ballerina.net.http] - CircuitBreaker trial run  was successful. Circuit switched from HALF_OPEN to CLOSE state.
$ curl -v http://localhost:9090/cb
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9090 (#0)
> GET /cb HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Length: 14
< Content-Type: text/plain
<
* Connection #0 to host localhost left intact
Hello World!!!

The first 2 requests complete without any issue.

$ curl -v http://localhost:9090/cb
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9090 (#0)
> GET /cb HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Length: 14
< Content-Type: text/plain
<
* Connection #0 to host localhost left intact
Hello World!!!
$ curl -v http://localhost:9090/cb
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9090 (#0)
> GET /cb HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 500 Internal Server Error
< Connection: Keep-Alive
< Content-Type: text/plain
< Content-Length: 53
<
* Connection #0 to host localhost left intact
Internal error occurred while processing the request.

The third one responds with 500 Internal Server Error, since our mock service send 500 http status code when responding to every third request.

$ curl -v http://localhost:9090/cb
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9090 (#0)
> GET /cb HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 500 Internal Server Error
< Connection: Keep-Alive
< Content-Type: text/plain
< Content-Length: 100
<
* Connection #0 to host localhost left intact
Upstream service unavailable. Requests to upstream service will be suspended for 14061 milliseconds

Subsequent requests fail immediately since the reset timeout period has not elapsed.

$ curl -v http://localhost:9090/cb
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9090 (#0)
> GET /cb HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 500 Internal Server Error
< Connection: Keep-Alive
< Content-Type: text/plain
< Content-Length: 99
<
* Connection #0 to host localhost left intact
Upstream service unavailable. Requests to upstream service will be suspended for 5398 milliseconds
$ curl -v http://localhost:9090/cb
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9090 (#0)
> GET /cb HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 500 Internal Server Error
< Connection: Keep-Alive
< Content-Type: text/plain
< Content-Length: 99
<
* Connection #0 to host localhost left intact
Upstream service unavailable. Requests to upstream service will be suspended for 1753 milliseconds
$ curl -v http://localhost:9090/cb
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9090 (#0)
> GET /cb HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Length: 14
< Content-Type: text/plain
<
* Connection #0 to host localhost left intact
Hello World!!!

The request sent immediately after the timeout period expires is the trial request, to see if the backend service is back to normal. If this succeeds, the circuit is set to ‘close’ and normal functionality resumes.

$ curl -v http://localhost:9090/cb
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9090 (#0)
> GET /cb HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 500 Internal Server Error
< Connection: Keep-Alive
< Content-Type: text/plain
< Content-Length: 54
<
* Connection #0 to host localhost left intact
Idle timeout triggered before reading inbound response

The fifth one times out, since our mock service times out when responding to every fifth request.

$ curl -v http://localhost:9090/cb
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9090 (#0)
> GET /cb HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 500 Internal Server Error
< Connection: Keep-Alive
< Content-Type: text/plain
< Content-Length: 100
<
* Connection #0 to host localhost left intact
Upstream service unavailable. Requests to upstream service will be suspended for 14061 milliseconds

Subsequent requests fail immediately since the reset timeout period has not elapsed.

$ curl -v http://localhost:9090/cb
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9090 (#0)
> GET /cb HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Length: 14
< Content-Type: text/plain
<
* Connection #0 to host localhost left intact
Hello World!!!

The request sent immediately after the timeout period expires is the trial request, to see if the backend service is back to normal. If this succeeds, the circuit is set to ‘close’ and normal functionality resumes.

$ curl -v http://localhost:9090/cb
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9090 (#0)
> GET /cb HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Length: 14
< Content-Type: text/plain
<
* Connection #0 to host localhost left intact
Hello World!!!

Since the immediate request after the timeout expired was successful, the requests after that complete normally.