Skip to content

Commit ea8970d

Browse files
MCPODS-7035 Ffw storage http client reuse (#215)
1 parent 91fd92b commit ea8970d

File tree

4 files changed

+358
-33
lines changed

4 files changed

+358
-33
lines changed

here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorage.java

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@
1818
*/
1919
package com.here.naksha.storage.http;
2020

21+
import static com.here.naksha.storage.http.RequestSender.KeyProperties;
22+
2123
import com.here.naksha.lib.core.NakshaContext;
2224
import com.here.naksha.lib.core.lambdas.Fe1;
2325
import com.here.naksha.lib.core.models.naksha.Storage;
2426
import com.here.naksha.lib.core.storage.IReadSession;
2527
import com.here.naksha.lib.core.storage.IStorage;
2628
import com.here.naksha.lib.core.util.json.JsonSerializable;
27-
import java.net.http.HttpClient;
28-
import java.time.Duration;
29+
import com.here.naksha.storage.http.cache.RequestSenderCache;
2930
import java.util.concurrent.Future;
3031
import java.util.concurrent.FutureTask;
3132
import org.jetbrains.annotations.NotNull;
@@ -41,15 +42,13 @@ public class HttpStorage implements IStorage {
4142

4243
public HttpStorage(@NotNull Storage storage) {
4344
HttpStorageProperties properties = HttpStorage.getProperties(storage);
44-
HttpClient httpStorageClient = HttpClient.newBuilder()
45-
.connectTimeout(Duration.ofSeconds(properties.getConnectTimeout()))
46-
.build();
47-
requestSender = new RequestSender(
48-
storage.getId(),
49-
properties.getUrl(),
50-
properties.getHeaders(),
51-
httpStorageClient,
52-
properties.getSocketTimeout());
45+
requestSender = RequestSenderCache.getInstance()
46+
.getSenderWith(new KeyProperties(
47+
storage.getId(),
48+
properties.getUrl(),
49+
properties.getHeaders(),
50+
properties.getConnectTimeout(),
51+
properties.getSocketTimeout()));
5352
}
5453

5554
@Override

here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/RequestSender.java

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,26 +34,21 @@
3434
import org.slf4j.Logger;
3535
import org.slf4j.LoggerFactory;
3636

37-
class RequestSender {
37+
public class RequestSender {
3838

3939
private static final Logger log = LoggerFactory.getLogger(RequestSender.class);
40-
private final String name;
41-
private final String hostUrl;
42-
private final Map<String, String> defaultHeaders;
40+
41+
@NotNull
4342
private final HttpClient httpClient;
44-
private final long socketTimeoutSec;
4543

46-
public RequestSender(
47-
final String name,
48-
String hostUrl,
49-
Map<String, String> defaultHeaders,
50-
HttpClient httpClient,
51-
long socketTimeoutSec) {
52-
this.name = name;
53-
this.httpClient = httpClient;
54-
this.hostUrl = hostUrl;
55-
this.defaultHeaders = defaultHeaders;
56-
this.socketTimeoutSec = socketTimeoutSec;
44+
@NotNull
45+
private final RequestSender.KeyProperties keyProps;
46+
47+
public RequestSender(@NotNull RequestSender.KeyProperties keyProps) {
48+
this.keyProps = keyProps;
49+
this.httpClient = HttpClient.newBuilder()
50+
.connectTimeout(Duration.ofSeconds(keyProps.connectionTimeoutSec))
51+
.build();
5752
}
5853

5954
/**
@@ -71,10 +66,10 @@ HttpResponse<String> sendRequest(
7166
@Nullable Map<String, String> headers,
7267
@Nullable String httpMethod,
7368
@Nullable String body) {
74-
URI uri = URI.create(hostUrl + endpoint);
75-
HttpRequest.Builder builder = newBuilder().uri(uri).timeout(Duration.ofSeconds(socketTimeoutSec));
69+
URI uri = URI.create(keyProps.hostUrl + endpoint);
70+
HttpRequest.Builder builder = newBuilder().uri(uri).timeout(Duration.ofSeconds(keyProps.socketTimeoutSec));
7671

77-
if (keepDefHeaders) defaultHeaders.forEach(builder::header);
72+
if (keepDefHeaders) keyProps.defaultHeaders.forEach(builder::header);
7873
if (headers != null) headers.forEach(builder::header);
7974

8075
HttpRequest.BodyPublisher bodyPublisher =
@@ -91,7 +86,7 @@ private HttpResponse<String> sendRequest(HttpRequest request) {
9186
try {
9287
CompletableFuture<HttpResponse<String>> futureResponse =
9388
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString());
94-
response = futureResponse.get(socketTimeoutSec, TimeUnit.SECONDS);
89+
response = futureResponse.get(keyProps.socketTimeoutSec, TimeUnit.SECONDS);
9590
return response;
9691
} catch (Exception e) {
9792
log.warn("We got exception while executing Http request against remote server.", e);
@@ -101,13 +96,29 @@ private HttpResponse<String> sendRequest(HttpRequest request) {
10196
log.info(
10297
"[Storage API stats => type,storageId,host,method,path,status,timeTakenMs,resSize] - StorageAPIStats {} {} {} {} {} {} {} {}",
10398
"HttpStorage",
104-
this.name,
105-
this.hostUrl,
99+
keyProps.name,
100+
keyProps.hostUrl,
106101
request.method(),
107102
request.uri(),
108103
(response == null) ? "-" : response.statusCode(),
109104
executionTime,
110105
(response == null) ? 0 : response.body().length());
111106
}
112107
}
108+
109+
public boolean hasKeyProps(KeyProperties thatKeyProps) {
110+
return this.keyProps.equals(thatKeyProps);
111+
}
112+
113+
/**
114+
* Set of properties that are just enough to construct the sender
115+
* and distinguish unambiguously between objects
116+
* in terms of their effective configuration
117+
*/
118+
public record KeyProperties(
119+
@NotNull String name,
120+
@NotNull String hostUrl,
121+
@NotNull Map<String, String> defaultHeaders,
122+
long connectionTimeoutSec,
123+
long socketTimeoutSec) {}
113124
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright (C) 2017-2023 HERE Europe B.V.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* License-Filename: LICENSE
18+
*/
19+
package com.here.naksha.storage.http.cache;
20+
21+
import static com.here.naksha.storage.http.RequestSender.KeyProperties;
22+
23+
import com.here.naksha.storage.http.RequestSender;
24+
import java.util.concurrent.*;
25+
import org.jetbrains.annotations.NotNull;
26+
import org.jetbrains.annotations.Nullable;
27+
28+
public class RequestSenderCache {
29+
30+
public static final int CLEANER_PERIOD_HOURS = 8;
31+
private final ConcurrentMap<String, RequestSender> requestSenders;
32+
33+
private RequestSenderCache() {
34+
this(new ConcurrentHashMap<>(), CLEANER_PERIOD_HOURS, TimeUnit.HOURS);
35+
}
36+
37+
RequestSenderCache(ConcurrentMap<String, RequestSender> requestSenders, int cleanPeriod, TimeUnit cleanPeriodUnit) {
38+
this.requestSenders = requestSenders;
39+
Executors.newSingleThreadScheduledExecutor()
40+
.scheduleAtFixedRate(requestSenders::clear, cleanPeriod, cleanPeriod, cleanPeriodUnit);
41+
}
42+
43+
@NotNull
44+
public static RequestSenderCache getInstance() {
45+
return InstanceHolder.instance;
46+
}
47+
48+
@NotNull
49+
public RequestSender getSenderWith(KeyProperties keyProperties) {
50+
return requestSenders.compute(
51+
keyProperties.name(), (__, cachedSender) -> getUpdated(cachedSender, keyProperties));
52+
}
53+
54+
private @NotNull RequestSender getUpdated(
55+
@Nullable RequestSender cachedSender, @NotNull KeyProperties keyProperties) {
56+
if (cachedSender != null && cachedSender.hasKeyProps(keyProperties)) return cachedSender;
57+
else return new RequestSender(keyProperties);
58+
}
59+
60+
private static final class InstanceHolder {
61+
private static final RequestSenderCache instance = new RequestSenderCache();
62+
}
63+
}

0 commit comments

Comments
 (0)