#HttpClient slow on first handshake, much faster on subsequent calls

83 messages ยท Page 1 of 1 (latest)

wraith dragon
#

As per title. I need multiple requests with different request headers. What is a different approach to this?

celest wrenBOT
#

โŒ› This post has been reserved for your question.

Hey @wraith dragon! Please use /close or the Close Post button above when your problem is solved. Please remember to follow the help guidelines. This post will be automatically closed after 300 minutes of inactivity.

TIP: Narrow down your issue to simple and precise questions to maximize the chance that others will reply in here.

celest wrenBOT
#

๐Ÿ’ค Post marked as dormant

This post has been inactive for over 300 minutes, thus, it has been archived.
If your question was not answered yet, feel free to re-open this post or create a new one.
In case your post is not getting any attention, you can try to use /help ping.
Warning: abusing this will result in moderative actions taken against you.

wraith dragon
#

Right now my code is something like this; how should I restructure it to reuse the client?

public static HttpUriRequest createScanRequest(String apiUrl, String apiKey, String orgId) {
        HttpGet httpGet = new HttpGet(apiUrl);

        httpGet.addHeader(HttpHeaders.ACCEPT, "application/json");
        httpGet.addHeader("X-Request-OrgId", orgId);
        httpGet.addHeader("api-key", apiKey);

        return httpGet;
    }

    public static String fetchDataFromApi(HttpUriRequest request) throws IOException {
        try(CloseableHttpClient httpClient = HttpClients.createDefault()){
            HttpResponse response = httpClient.execute(request);
            HttpEntity entity = response.getEntity();
            ...
    }

    public JsonElement fetchSingleMemberScanData(String orgId, LocalDate sDate, LocalDate eDate) throws IOException {
        validateApiKey();

        //TODO: can filter the date directly in the queryparam
        String url = "https://myurl.com/api/v2/data-management/member-scans"
                +"?from="
                +sDate.format(dateTimeFormatter).replace("-","%2F")
                +"&to="
                +eDate.format(dateTimeFormatter).replace("-","%2F");
        HttpUriRequest request = createScanRequest(url, apiKey, orgId);
        return JsonParser.parseString(fetchDataFromApi(request));
    }

    public JsonElement fetchSingleCorpScanData(String orgId,LocalDate sDate, LocalDate eDate) throws IOException {
        validateApiKey();

        //TODO: can filter the date directly in the queryparam
        String url = "https://myurl.com/api/v2/data-management/corp-scans"
                +"?from="
                +sDate.format(dateTimeFormatter).replace("-","%2F")
                +"&to="
                +eDate.format(dateTimeFormatter).replace("-","%2F");
        HttpUriRequest request = createScanRequest(url, apiKey, orgId);
        return JsonParser.parseString(fetchDataFromApi(request));
    }

hoary herald
#

It seems that you're already using Apache HttpClient for the requests, so one thing you can do since you said you only want to change the headers, but the route which you send them would be the same, what you can do is to use a custom HttpClient instead of the default one, in that way you can configure more than one connection per route, as in this example:

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.IOException;

class Scratch {
    public static void main(String[] args) throws IOException {
        try (CloseableHttpClient httpClient = HttpClients.custom()
                .setMaxConnTotal(10)
                .setMaxConnPerRoute(5)
                .build()) {
            final HttpGet httpGet = new HttpGet("https://google.com");
            for (int i = 0; i < 10; i++) {
                try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
                    System.out.println(Thread.currentThread().getName());
                    EntityUtils.consumeQuietly(response.getEntity());
                }
            }
        }
    }
}
#

Another thing you can do, is to create a pool of connections, and then get your http client from the pool instead of creating them yourself, and you can reuse the pool on other connections if you have a dependency injection container to handle the creation for you:

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;

import java.io.IOException;

class Scratch {
    public static void main(String[] args) throws IOException {
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        connectionManager.setMaxTotal(10);
        connectionManager.setDefaultMaxPerRoute(5);
        try (CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(connectionManager)
                .build()) {
            final HttpGet httpGet = new HttpGet("https://google.com");
            for (int i = 0; i < 10; i++) {
                try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
                    System.out.println(Thread.currentThread().getName());
                    EntityUtils.consumeQuietly(response.getEntity());
                }
            }
        }
    }
}

Both examples are taken from this article: https://www.mastertheboss.com/java/writing-high-performance-java-http-client-applications/

When it comes to consuming HTTP resources in Java applications, Apache HTTP Client is a popular choice for developers due to its ease of use, flexibility,

#

But when you execute those, keep in mind that even though you are re-utilizing the HttpClient from a pool, they still share the same thread (as you'll see from the execution logs). So if you want to leverage the power of distributing these connections between threads, you can follow this tutorial then: https://www.baeldung.com/httpclient-connection-management#multiple

Baeldung

How to open, manage and close connections with the Apache HttpClient 4.

wraith dragon
#

jfl this got complex fast

wraith dragon
# hoary herald It seems that you're already using Apache HttpClient for the requests, so one th...

my url also changes though

final HttpGet httpGet = new HttpGet("https://google.com") not true
see:

String url = "https://myurl.com/api/v2/data-management/member-scans"
                +"?from="
                +sDate.format(dateTimeFormatter).replace("-","%2F")
                +"&to="
                +eDate.format(dateTimeFormatter).replace("-","%2F");
String url = "https://myurl.com/api/v2/data-management/corp-scans"
                +"?from="
                +sDate.format(dateTimeFormatter).replace("-","%2F")
                +"&to="
                +eDate.format(dateTimeFormatter).replace("-","%2F");
        HttpUriRequest request = createScanRequest(url, apiKey, orgId);
hoary herald
#

Sorry about that. I would say then, that using the PoolingHttpClientConnectionManager would be a better approach in your case. Do you have HttpClient on other classes?

wraith dragon
#

just this service is in charge of api calls

hoary herald
#

Then it's fine to create the pool on the service I would say. And start from it since it's not that big of a change from what you already have, and test if it reduced the latency of the requests. If it's still not good, then you could get complex with the multi-threaded ones

wraith dragon
# hoary herald Then it's fine to create the pool on the service I would say. And start from it ...
xception in thread "main" java.lang.IllegalStateException: Connection pool shut down
    at org.apache.http.util.Asserts.check(Asserts.java:34)
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.requestConnection(PoolingHttpClientConnectionManager.java:269)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:176)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
    at Service.ApiService.fetchDataFromApi(ApiService.java:65)
    at Service.ApiService.fetchSingleCorpScanData(ApiService.java:107)
    at Util.CalculationUtil.getTotalScansByOrgId(CalculationUtil.java:14)
    at BillingRunner.main(BillingRunner.java:33)

wait

#

what happened LOL

#

hm

#

why is it even shutting down

#
        try(CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(connectionManager)
                .build()){
            HttpResponse response = httpClient.execute(request);
            HttpEntity entity = response.getEntity();
            if (entity != null) {...

        }
    }```
hoary herald
#

And how did you set the PoolingHttpClientConnectionManager?

wraith dragon
#

i setup in constructor

#
private PoolingHttpClientConnectionManager connectionManager;


public ApiService(String apiKey) {
        this.apiKey = apiKey;
        this.dateTimeFormatter= DateTimeFormatter.ofPattern("dd-MM-yyyy");
        this.connectionManager = new PoolingHttpClientConnectionManager();
        connectionManager.setMaxTotal(10);
        connectionManager.setDefaultMaxPerRoute(5);
    }
hoary herald
#

Yeah, I see no problem on this as well

wraith dragon
#

hmm

#

I am calling it like this


        ApiService apiService = new ApiService("foo");
        String[] orgIds = {"John_Org" };

        try {
            LocalDate[] desiredDate = DateUtil.getQuartileDates(2023, 4);

            Map<String,Integer> scanCountMapper = new HashMap<>();

            for(String orgId : orgIds)
                scanCountMapper.put(orgId,CalculationUtil.getTotalScansByOrgId(apiService, orgId, desiredDate));
}

it excepts on the last line

#

lemme debufg

#

oh

hoary herald
#

I think it's something with the configuration I've sent you. We have slightly different scenarios. When you debug, can you see if it closes on the second request? I saw here (https://stackoverflow.com/questions/44130464/java-lang-illegalstateexception-connection-pool-shut-down) that it might be because of this part

public String fetchDataFromApi(HttpUriRequest request) throws IOException {
        try(CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(connectionManager)
                .build()){

My guess is that it's closing after the first call finishes

wraith dragon
#

it closes after the first request

#

LOL

#
        int singleMemberScans = apiService.fetchSingleMemberScanData(orgId, desiredDate[0], desiredDate[1]).getAsJsonArray().size();

//ok

        int singleCorpScans = apiService.fetchSingleCorpScanData(orgId, desiredDate[0], desiredDate[1]).getAsJsonArray().size();

//not ok```
hoary herald
#

ok, hehe, makes sens

wraith dragon
#

i never call client.close() tho

#

why is it closing

hoary herald
#

Since you are using a pool on this class, it would make sense to close the connections only when the class is destroyed

#

You don't call it explicitly, but when you add the connection inside the try:

try(CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(connectionManager)
                .build()){

This is only possible because the CloseableHttpClient implements the Closeable interface, and once you add it to a try(), it is called try-with-resources, and after the block finishes executing, it'll will close the resource for you. It's very handy if you do it for http connections, buffers and such, so you don't end up with a connection that is never closed, but never used.

Here's a better explanation on this:
https://www.baeldung.com/java-try-with-resources

Baeldung

A quick and practical guide to how we can use the try-with-resources functionality introduced in Java 7 to auto-close resources and simplify our syntax.

wraith dragon
#

yea, i did normal non-closeable client and it works

#

where should i close it then

#

if at all

#

it seems to be double the speed when using connection pooling

#

lemme try again

#

sorry my math is dumb i mean half the time

hoary herald
#

Hehe, I got a little worried there ๐Ÿ˜†

wraith dragon
#

ya its unironically a lot faster

#

more than double the speed

hoary herald
#

And you're still using the single thread ones?

wraith dragon
#

ye

#

should i use multi

hoary herald
#

Cool

wraith dragon
#

how do i even use the multithread ones

#

actually

#

im on java 21

#

arent these using green threads

#

do i still need multithread

hoary herald
#

It depends. If the speed now pleases you, I'd say keep the single thread, it's easier to manage. For the multi-thread, you configure the pool the same way, but when you call the httpClient.execute() you need to to it in a new thread, and then manages the threads

#

I think you need to declare, for the apache http lib, that you want to make the calls on other threads, it'll be on the main by default

#

That way you don't even need this third-party lib on your code

wraith dragon
#

excellent now to rewrite everything again ๐Ÿ™‚

#

is the new default java httpclient faster

#

or just less dependencies

hoary herald
#

It's hard to say about the speed, it'll depend on more than the lib itself, but reducing a dependency to use a native class, is generally a good idea, if it suits your needs

wraith dragon
#

I'll probably rewrite it with the native class after i get everything working lol

Still need to do the CLI frontend

#

I'm still a bit confused about the threads though

#

i was told that async in java is not rly needed anymore due to project loom in Java 21

#

is this the same thing

#

or different

#

(i was also told by the same guy that async in java sucks and that loom/green threads are the only solution forward, so idk i take that with a grain of salt)

hoary herald
#

Well, afaik, project loom which is now the java 21's virtual threads, will come to facilitate how you deal with threads, and it'll mostly handle the threads differently from what the previous threads api did. However, you still need to create and manage those threads, since I don't think java does anything under the hood for you. If you don't spawn a new Thread(), or don't use some async library like project reactor, your code will be on the main thread (this needs to be checked in deep, sorry for not having more details on that)

wraith dragon
#

I'll try the thread management

should I use a library like netty

hoary herald
#

Well, threads in java are not the best thing in the world. But with virtual threads, it seems that it's closer to those lambda functions that we are used to in java 8. But as I said, most of the gains on the virtual threads will be done in how java handle the threads you create, but you still create them.
Here's a little bit for the difference between java's old platform threads, and the new virtual threads:
https://belief-driven-design.com/looking-at-java-21-virtual-threads-bd181/

One of the most significant features of Java 21 is Virtual Threads (JEP 444). These lightweight threads reduce the effort needed for writing, maintaining, and observing high-throughput concurrent applications.
As in many of my other articles, letโ€™s look first at the status quo before a new feature to understand betters what problem it tries to s...

wraith dragon
#

also is there a good rule on when to close the HttpClient now that I'm doing it this way ```

HttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.build();``` ๐Ÿ˜…

hoary herald
#

My personal opinion on the libs, its to keep it simple, and use the least you could to get stuff done

wraith dragon
#

oh it cannot be reopened once closed

#

TIL

#

ok ill just like keep it open for the entire program then

#

๐Ÿ˜•

hoary herald
#

Yeah, I don't have a simple solution for that, without using a container for managing the lifecycle of objects unfortunately

wraith dragon
#

First time I've heard of containers

They're in Spring meaning I've used them under the hood without knowing ๐Ÿ˜ฎ

hoary herald
#

You can put the connection poll creation outside of your service, and then just request a client from it, but you'll still need to create it and close somewhere else, wcich I don't have a good spot for

#

Yeah, the spring framework is a very complete solution, and one thing it provides you is with a container that manages the creation of objects that you use on your application, so you can let it create them for you, and you just put them wherever you like

#

But there's very much more to Spring still ๐Ÿ˜†