Skip to content
devlibx edited this page Dec 21, 2022 · 2 revisions

This module provides sync/async Http client. All the settings can be defined in config e.g. circuit breaker, timeouts etc

servers:
  jsonplaceholder:
    host: jsonplaceholder.typicode.com
    port: 443
    https: true
    connectTimeout: 1000
    connectionRequestTimeout: 1000

apis:
  getPosts:
    method: GET
    path: /posts/${id}
    server: jsonplaceholder
    timeout: 10000
    concurrency: 3
    waitBeforeClosingCircuitAfterError: 5000
  getPostsAsync:
    method: GET
    path: /posts/${id}
    server: jsonplaceholder
    timeout: 1000
    concurrency: 3
    async: true

Details of parameters "use port = -1": this will remove the port from url "http://something/:/abcd". With -1 it will become "http://something/abcd"

  1. timeout - timeout for the API. Your EasyHttp.call**() Api will timeout after the given time
  2. concurrency - how many parallel calls can be made to this API.
  3. rps - if you know "rps" of API call, then you should set rps e.g. rps: 100. The EasyHttp will automatically setup required threads to support concurrent calls. You don't need to set concurrency manually. For example, if timeout=20 and rps=100 then EasyHttp will set concurrency=2
  4. waitBeforeClosingCircuitAfterError = when the circuit is opened due to an error, all the calls to external service will not be done (circuit breaker will avoid calling external service).

Use of waitBeforeClosingCircuitAfterError

However, the circuit breaker has to check after some time e.g. 10sec to see if the external service is up or not. To do this it allows few calls to go to the external service to see if the external service is up. This time is configured using waitBeforeClosingCircuitAfterError (default 10sec). If you keep it too small e.g. 10-50ms; your circuit breaker will call external service frequently after an error. If you keep it large e.g. 30sec then; your circuit breaker will wait for 30 sec before calling external service. And you can see many requests are failing.

Use of rps

When you set rps then you have to consider rps from the single node i.e. how many requests this single node is going to call. For example, if you call an external API with 1000 rps; and you run 10 nodes, then a single node has rps=100


Basic Setup - Maven and lib Bootstrap

Maven Dependency

<dependency>
    <groupId>io.github.devlibx.easy</groupId>
    <artifactId>http</artifactId>
    <version>${easy.version}</version>
</dependency>

This is the basic setup which is required to use EasyHttp module. If you already use Guice then you can just add EasyHttpModule as one more module

// Setup injector (Onetime MUST setup before we call EasyHttp.setup())
Injector injector = Guice.createInjector(new EasyHttpModule());
ApplicationContext.setInjector(injector);

// Read config and setup EasyHttp
Config config = YamlUtils.readYamlCamelCase("demo_app_config.yaml", Config.class);
EasyHttp.setup(config);

Make a HTTP call (sync)

Map result = EasyHttp.callSync(
        Call.builder(Map.class)
                .withServerAndApi("jsonplaceholder", "getPosts")
                .addPathParam("id", 1)
                .build()
);

log.info("Print Result as Json String = " + JsonUtils.asJson(result));
// Result = {"userId":1,"id":1,"title":"some text ..."}

Make an Async HTTP call (async)

You can use this to make async Http calls. There are times when you want to make many Http calls in parrlel and then aggregate it, this async Http can be a good way to achieve this.

EasyHttp.callAsync(
        Call.builder(Map.class)
                .withServerAndApi("jsonplaceholder", "getPostsAsync")
                .addPathParam("id", 1)
                .build()
)
.subscribeOn(Schedulers.io())
.subscribe(
        result -> {
            log.info("Print Result as Json String = " + JsonUtils.asJson(result));
            // Result = {"userId":1,"id":1,"title":"some text ..."}
        },
        throwable -> {

        });

Full working code with all setup

package io.github.devlibx.easy.http;

import com.google.inject.Guice;
import com.google.inject.Injector;
import io.gitbub.devlibx.easy.helper.ApplicationContext;
import io.gitbub.devlibx.easy.helper.LoggingHelper;
import io.gitbub.devlibx.easy.helper.json.JsonUtils;
import io.gitbub.devlibx.easy.helper.yaml.YamlUtils;
import io.github.devlibx.easy.http.config.Config;
import io.github.devlibx.easy.http.module.EasyHttpModule;
import io.github.devlibx.easy.http.sync.SyncRequestProcessor;
import io.github.devlibx.easy.http.util.Call;
import io.github.devlibx.easy.http.util.EasyHttp;
import junit.framework.TestCase;
import lombok.extern.slf4j.Slf4j;
import org.apache.log4j.Logger;

import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import static org.apache.log4j.Level.TRACE;

@Slf4j
public class DemoApplication extends TestCase {
    private Injector injector;

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        LoggingHelper.setupLogging();
        Logger.getLogger(SyncRequestProcessor.class).setLevel(TRACE);

        // Setup injector (Onetime MUST setup before we call EasyHttp.setup())
        injector = Guice.createInjector(new EasyHttpModule());
        ApplicationContext.setInjector(injector);

        // Read config and setup EasyHttp
        Config config = YamlUtils.readYamlCamelCase("demo_app_config.yaml", Config.class);
        EasyHttp.setup(config);
    }

    public void testSyncApiCall() {
        Map result = EasyHttp.callSync(
                Call.builder(Map.class)
                        .withServerAndApi("jsonplaceholder", "getPosts")
                        .addPathParam("id", 1)
                        .build()
        );

        log.info("Print Result as Json String = " + JsonUtils.asJson(result));
        // Result = {"userId":1,"id":1,"title":"some text ..."}
    }

    public void testAsyncApiCall() throws Exception {
        CountDownLatch waitForComplete = new CountDownLatch(1);
        EasyHttp.callAsync(
                Call.builder(Map.class)
                        .withServerAndApi("jsonplaceholder", "getPostsAsync")
                        .addPathParam("id", 1)
                        .build()        
        )
        // use subscribeOn(Schedulers.io())
        .subscribe(
                result -> {
                    log.info("Print Result as Json String = " + JsonUtils.asJson(result));
                    // Result = {"userId":1,"id":1,"title":"some text ..."}
                },
                throwable -> {

                });
        waitForComplete.await(5, TimeUnit.SECONDS);
        // Or you can use blockingSubscribe(); 
    }
}