Skip to content

Commit debc417

Browse files
authored
Merge pull request #1006 from commercetools/DEVX-658-Update-dependency-com.squareup.okhttp-to-v5
DEVX-658: updating okhttp to v5
2 parents 00ce576 + cf82884 commit debc417

File tree

16 files changed

+798
-0
lines changed

16 files changed

+798
-0
lines changed

.git-blame-ignore-revs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ af9205fa592e4ae32ceca951c560a48b512b7744
1717
f58dd73747791edcd45722a4903da44da1b9b2d3
1818
fcdaa3d6735d2382a44d066a6ce051a2c5e6469f
1919
9bbe16b7d1600fc96100f54cb25869cbdf425521
20+
3addaca346bcdfd555fca0340e40c7e5518c5185
2021
165a4da6a015267909adf7e7d5f3edc16a56dda6
2122
c4e2bf407aa0b7a3d34605134791013ba8a2d376

allowed-licenses.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
"moduleLicense": null,
2121
"moduleName": "com.squareup.okio:okio"
2222
},
23+
{
24+
"moduleLicense": null,
25+
"moduleName": "com.squareup.okhttp3:okhttp"
26+
},
2327
{
2428
"moduleLicense": null,
2529
"moduleName": "com.netflix.graphql.dgs:graphql-dgs-platform"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
apply plugin: "me.champeau.jmh"
2+
3+
jmh {
4+
iterations = 5
5+
benchmarkMode = ['thrpt']
6+
threads = 25
7+
fork = 3
8+
timeOnIteration = '1s'
9+
profilers = ['gc']
10+
}
11+
12+
dependencies {
13+
api project(":rmf:rmf-java-base")
14+
api "com.squareup.okhttp3:okhttp:5.3.2" version {
15+
strictly '[5.0,5.99999]'
16+
prefer "5.3.2"
17+
}
18+
implementation "com.squareup.okio:okio:3.16.2"
19+
20+
implementation javax.validation
21+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
2+
package com.commercetools.http.okhttp5;
3+
4+
import java.io.ByteArrayInputStream;
5+
import java.io.ByteArrayOutputStream;
6+
import java.io.IOException;
7+
import java.nio.charset.StandardCharsets;
8+
import java.util.zip.GZIPOutputStream;
9+
10+
import io.vrap.rmf.base.client.ApiHttpResponse;
11+
12+
import org.assertj.core.api.Assertions;
13+
import org.openjdk.jmh.annotations.*;
14+
15+
import okhttp3.*;
16+
import okio.Okio;
17+
18+
public class UnzipBenchmark {
19+
20+
@State(Scope.Benchmark)
21+
public static class InterceptorState {
22+
private CtOkHttp5Client.UnzippingInterceptor interceptor;
23+
24+
@Setup(Level.Trial)
25+
public void init() {
26+
interceptor = new CtOkHttp5Client.UnzippingInterceptor();
27+
printUsedMemory();
28+
29+
}
30+
31+
@TearDown(Level.Trial)
32+
public void tearDown() {
33+
printUsedMemory();
34+
}
35+
}
36+
@Benchmark
37+
public void unzip(InterceptorState state) throws IOException {
38+
39+
ByteArrayOutputStream os = new ByteArrayOutputStream();
40+
GZIPOutputStream gzipOs = new GZIPOutputStream(os);
41+
byte[] buffer = "Sample Text".getBytes();
42+
gzipOs.write(buffer, 0, buffer.length);
43+
gzipOs.close();
44+
ByteArrayInputStream inputStream = new ByteArrayInputStream(os.toByteArray());
45+
46+
Response gzipped = new Response.Builder().request(new Request.Builder().url("http://localhost").build())
47+
.protocol(Protocol.HTTP_1_1)
48+
.addHeader("content-encoding", "gzip")
49+
.addHeader("content-type", "application/json")
50+
.code(200)
51+
.message("Ok")
52+
.body(ResponseBody.create(MediaType.parse("application/json"), -1L,
53+
Okio.buffer(Okio.source(inputStream))))
54+
.build();
55+
Assertions.assertThat(gzipped.body().source().isOpen()).isTrue();
56+
57+
Response unzipped = state.interceptor.unzip(gzipped);
58+
59+
Assertions.assertThat(gzipped.body().source().isOpen()).isTrue();
60+
Assertions.assertThat(unzipped.body().source().isOpen()).isTrue();
61+
Assertions.assertThat(inputStream.available()).isEqualTo(31);
62+
63+
ApiHttpResponse<byte[]> response = CtOkHttp5Client.toResponse(unzipped);
64+
65+
Assertions.assertThat(gzipped.body().source().isOpen()).isFalse();
66+
Assertions.assertThat(unzipped.body().source().isOpen()).isFalse();
67+
Assertions.assertThat(inputStream.available()).isEqualTo(0);
68+
69+
String text = new String(response.getBody(), StandardCharsets.UTF_8);
70+
Assertions.assertThat(text).isEqualTo("Sample Text");
71+
}
72+
73+
public static void printUsedMemory() {
74+
long _usedMem;
75+
long _total;
76+
long _total2;
77+
long _count = -1;
78+
System.out.flush();
79+
// loop to get a stable reading, since memory may be resized between the method calls
80+
do {
81+
_count++;
82+
_total = Runtime.getRuntime().totalMemory();
83+
try {
84+
Thread.sleep(12);
85+
}
86+
catch (Exception ignore) {
87+
}
88+
long _free = Runtime.getRuntime().freeMemory();
89+
_total2 = Runtime.getRuntime().totalMemory();
90+
_usedMem = _total - _free;
91+
} while (_total != _total2);
92+
System.out.println("before GC: used=" + _usedMem + ", loopCount=" + _count + ", total=" + _total);
93+
try {
94+
Runtime.getRuntime().gc();
95+
Thread.sleep(55);
96+
Runtime.getRuntime().gc();
97+
Thread.sleep(55);
98+
Runtime.getRuntime().gc();
99+
Thread.sleep(55);
100+
Runtime.getRuntime().gc();
101+
Thread.sleep(55);
102+
}
103+
catch (Exception ignore) {
104+
}
105+
// loop to get a stable reading, since memory may be resized between the method calls
106+
do {
107+
_count++;
108+
_total = Runtime.getRuntime().totalMemory();
109+
try {
110+
Thread.sleep(12);
111+
}
112+
catch (Exception ignore) {
113+
}
114+
long _free = Runtime.getRuntime().freeMemory();
115+
_total2 = Runtime.getRuntime().totalMemory();
116+
_usedMem = _total - _free;
117+
} while (_total != _total2);
118+
System.out.println("after GC: used=" + _usedMem + ", loopCount=" + _count + ", total=" + _total);
119+
}
120+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
package com.commercetools.http.okhttp5;
3+
4+
import okhttp3.OkHttpClient;
5+
6+
@FunctionalInterface
7+
public interface BuilderOptions {
8+
OkHttpClient.Builder plus(OkHttpClient.Builder builder);
9+
}
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
2+
package com.commercetools.http.okhttp5;
3+
4+
import java.io.IOException;
5+
import java.util.*;
6+
import java.util.concurrent.CompletableFuture;
7+
import java.util.concurrent.ExecutorService;
8+
import java.util.concurrent.TimeUnit;
9+
import java.util.function.Supplier;
10+
import java.util.stream.Collectors;
11+
12+
import io.vrap.rmf.base.client.*;
13+
import io.vrap.rmf.base.client.utils.Utils;
14+
15+
import jakarta.validation.constraints.NotNull;
16+
import okhttp3.OkHttpClient;
17+
import okio.GzipSource;
18+
import okio.Okio;
19+
20+
public class CtOkHttp5Client extends HttpClientBase {
21+
22+
public static final int MAX_REQUESTS = 64;
23+
private final Supplier<OkHttpClient.Builder> clientBuilder = () -> new OkHttpClient.Builder()
24+
.connectTimeout(120, TimeUnit.SECONDS)
25+
.writeTimeout(120, TimeUnit.SECONDS)
26+
.readTimeout(120, TimeUnit.SECONDS)
27+
.protocols(Collections.singletonList(okhttp3.Protocol.HTTP_1_1))
28+
.addInterceptor(new UnzippingInterceptor());
29+
30+
private final OkHttpClient okHttpClient;
31+
32+
public CtOkHttp5Client() {
33+
super();
34+
okHttpClient = clientBuilder.get().dispatcher(createDispatcher(MAX_REQUESTS, MAX_REQUESTS)).build();
35+
}
36+
37+
public CtOkHttp5Client(final BuilderOptions options) {
38+
super();
39+
okHttpClient = options.plus(clientBuilder.get().dispatcher(createDispatcher(MAX_REQUESTS, MAX_REQUESTS)))
40+
.build();
41+
}
42+
43+
public CtOkHttp5Client(final Supplier<OkHttpClient.Builder> builderSupplier) {
44+
super();
45+
okHttpClient = builderSupplier.get().build();
46+
}
47+
48+
public CtOkHttp5Client(final int maxRequests, final int maxRequestsPerHost) {
49+
super();
50+
okHttpClient = clientBuilder.get().dispatcher(createDispatcher(maxRequests, maxRequestsPerHost)).build();
51+
}
52+
53+
public CtOkHttp5Client(final int maxRequests, final int maxRequestsPerHost, final BuilderOptions options) {
54+
super();
55+
okHttpClient = options.plus(clientBuilder.get().dispatcher(createDispatcher(maxRequests, maxRequestsPerHost)))
56+
.build();
57+
}
58+
59+
public CtOkHttp5Client(final ExecutorService executor) {
60+
super(executor);
61+
okHttpClient = clientBuilder.get().dispatcher(createDispatcher(executor, MAX_REQUESTS, MAX_REQUESTS)).build();
62+
}
63+
64+
public CtOkHttp5Client(final ExecutorService executor, final BuilderOptions options) {
65+
super(executor);
66+
okHttpClient = options.plus(clientBuilder.get().dispatcher(createDispatcher(MAX_REQUESTS, MAX_REQUESTS)))
67+
.build();
68+
}
69+
70+
public CtOkHttp5Client(final ExecutorService executor, final int maxRequests, final int maxRequestsPerHost) {
71+
super(executor);
72+
okHttpClient = clientBuilder.get()
73+
.dispatcher(createDispatcher(executor, maxRequests, maxRequestsPerHost))
74+
.build();
75+
}
76+
77+
public CtOkHttp5Client(final ExecutorService executor, final int maxRequests, final int maxRequestsPerHost,
78+
final BuilderOptions options) {
79+
super(executor);
80+
okHttpClient = options
81+
.plus(clientBuilder.get().dispatcher(createDispatcher(executor, maxRequests, maxRequestsPerHost)))
82+
.build();
83+
}
84+
85+
public okhttp3.Dispatcher createDispatcher(final int maxRequests, final int maxRequestsPerHost) {
86+
final okhttp3.Dispatcher dispatcher = new okhttp3.Dispatcher();
87+
dispatcher.setMaxRequests(maxRequests);
88+
dispatcher.setMaxRequestsPerHost(maxRequestsPerHost);
89+
return dispatcher;
90+
}
91+
92+
public okhttp3.Dispatcher createDispatcher(final ExecutorService executor, final int maxRequests,
93+
final int maxRequestsPerHost) {
94+
final okhttp3.Dispatcher dispatcher = new okhttp3.Dispatcher(executor);
95+
dispatcher.setMaxRequests(maxRequests);
96+
dispatcher.setMaxRequestsPerHost(maxRequestsPerHost);
97+
return dispatcher;
98+
}
99+
100+
private static final String CONTENT_TYPE = "Content-Type";
101+
private static final okhttp3.MediaType JSON = okhttp3.MediaType.get("application/json; charset=utf-8");
102+
private static final byte[] emptyBody = new byte[0];
103+
104+
@Override
105+
public CompletableFuture<ApiHttpResponse<byte[]>> execute(final ApiHttpRequest request) {
106+
return makeRequest(okHttpClient, toRequest(request)).thenApplyAsync(CtOkHttp5Client::toResponse, executor());
107+
108+
}
109+
110+
static ApiHttpResponse<byte[]> toResponse(final okhttp3.Response response) {
111+
final ApiHttpHeaders apiHttpHeaders = new ApiHttpHeaders(response.headers()
112+
.toMultimap()
113+
.entrySet()
114+
.stream()
115+
.flatMap(e -> e.getValue().stream().map(value -> ApiHttpHeaders.headerEntry(e.getKey(), value)))
116+
.collect(Collectors.toList()));
117+
118+
final ApiHttpResponse<byte[]> apiHttpResponse = new ApiHttpResponse<>(response.code(), apiHttpHeaders,
119+
Optional.ofNullable(response.body())
120+
.map(Utils.wrapToCompletionException(okhttp3.ResponseBody::bytes))
121+
.orElse(null),
122+
response.message());
123+
if (response.body() != null) {
124+
response.close();
125+
}
126+
return apiHttpResponse;
127+
}
128+
129+
private static okhttp3.Request toRequest(final ApiHttpRequest apiHttpRequest) {
130+
131+
okhttp3.Request.Builder httpRequestBuilder = new okhttp3.Request.Builder().url(apiHttpRequest.getUrl());
132+
133+
//set headers
134+
for (Map.Entry<String, String> entry : apiHttpRequest.getHeaders().getHeaders()) {
135+
httpRequestBuilder = httpRequestBuilder.header(entry.getKey(), entry.getValue());
136+
}
137+
138+
if (apiHttpRequest.getMethod() == null) {
139+
throw new IllegalStateException("apiHttpRequest method should be non null");
140+
}
141+
142+
//default media type is JSON, if other media type is set as a header, use it
143+
okhttp3.MediaType mediaType = JSON;
144+
if (apiHttpRequest.getHeaders()
145+
.getHeaders()
146+
.stream()
147+
.anyMatch(s -> s.getKey().equalsIgnoreCase(CONTENT_TYPE))) {
148+
mediaType = okhttp3.MediaType
149+
.get(Objects.requireNonNull(apiHttpRequest.getHeaders().getFirst(ApiHttpHeaders.CONTENT_TYPE)));
150+
}
151+
152+
try {
153+
final okhttp3.RequestBody body = apiHttpRequest.getBody() == null ? null
154+
: okhttp3.RequestBody.create(apiHttpRequest.getBody(), mediaType);
155+
httpRequestBuilder.method(apiHttpRequest.getMethod().name(), body);
156+
}
157+
catch (NoSuchMethodError error) {
158+
throw new IllegalStateException(
159+
"Request class is not compatible with this HTTP client implementation. Probably a wrong http client package is used. Please try \"commercetools-okhttp-client3\" instead");
160+
}
161+
return httpRequestBuilder.build();
162+
}
163+
164+
private CompletableFuture<okhttp3.Response> makeRequest(final OkHttpClient client, final okhttp3.Request request) {
165+
final okhttp3.Call call = client.newCall(request);
166+
final OkHttpResponseFuture result = new OkHttpResponseFuture();
167+
call.enqueue(result);
168+
return result.future;
169+
}
170+
171+
private static class OkHttpResponseFuture implements okhttp3.Callback {
172+
public final CompletableFuture<okhttp3.Response> future = new CompletableFuture<>();
173+
174+
public OkHttpResponseFuture() {
175+
}
176+
177+
@Override
178+
public void onFailure(final okhttp3.Call call, final IOException e) {
179+
future.completeExceptionally(e);
180+
}
181+
182+
@Override
183+
public void onResponse(final okhttp3.Call call, final okhttp3.Response response) throws IOException {
184+
future.complete(response);
185+
}
186+
}
187+
188+
@Override
189+
public void closeDelegate() throws IOException {
190+
okHttpClient.dispatcher().executorService().shutdown();
191+
okHttpClient.connectionPool().evictAll();
192+
if (okHttpClient.cache() != null)
193+
Objects.requireNonNull(okHttpClient.cache()).close();
194+
}
195+
196+
public static class UnzippingInterceptor implements okhttp3.Interceptor {
197+
@Override
198+
@NotNull
199+
public okhttp3.Response intercept(Chain chain) throws IOException {
200+
okhttp3.Response response = chain.proceed(chain.request());
201+
return unzip(response);
202+
}
203+
204+
okhttp3.Response unzip(final okhttp3.Response response) {
205+
if (!"gzip".equalsIgnoreCase(response.header("Content-Encoding"))) {
206+
return response;
207+
}
208+
209+
if (response.body() == null) {
210+
return response;
211+
}
212+
213+
okhttp3.Headers strippedHeaders = response.headers()
214+
.newBuilder()
215+
.removeAll("Content-Encoding")
216+
.removeAll("Content-Length")
217+
.build();
218+
String contentType = response.header("Content-Type");
219+
return response.newBuilder()
220+
.headers(strippedHeaders)
221+
.body(okhttp3.ResponseBody.create(Okio.buffer(new GzipSource(response.body().source())),
222+
okhttp3.MediaType.get(contentType), -1L))
223+
.build();
224+
}
225+
}
226+
}

0 commit comments

Comments
 (0)