38
38
import static java .util .Objects .requireNonNull ;
39
39
import java .util .Optional ;
40
40
import java .util .ServiceLoader ;
41
+ import java .util .Set ;
41
42
import java .util .concurrent .TimeUnit ;
43
+ import java .util .concurrent .TimeoutException ;
44
+ import org .apache .commons .lang3 .exception .UncheckedException ;
42
45
import org .apache .commons .lang3 .tuple .ImmutablePair ;
43
46
import org .apache .commons .lang3 .tuple .Pair ;
44
47
import org .eclipse .aether .ConfigurationProperties ;
69
72
import org .eclipse .jetty .http .PreEncodedHttpField ;
70
73
import org .eclipse .jetty .http3 .client .HTTP3Client ;
71
74
import org .eclipse .jetty .http3 .client .transport .HttpClientTransportOverHTTP3 ;
75
+ import org .eclipse .jetty .util .ssl .SslContextFactory ;
72
76
73
77
/**
74
78
* A transporter for HTTP/HTTPS.
75
79
*/
76
80
final class HttpTransporter extends AbstractTransporter {
77
81
82
+ private final static Set <String > CENTRAL = Set .of (
83
+ "repo.maven.apache.org" ,
84
+ "oss.sonatype.org" ,
85
+ "packages.atlassian.com"
86
+ );
87
+
78
88
private final Map <String , ChecksumExtractor > checksumExtractors ;
79
89
80
90
private final AuthenticationContext repoAuthContext ;
@@ -83,8 +93,11 @@ final class HttpTransporter extends AbstractTransporter {
83
93
84
94
private final URI baseUri ;
85
95
86
- private final HttpClient client ;
96
+ private HttpClient http3Client ;
97
+ private HttpClient httpClient = null ;
98
+
87
99
private final int connectTimeout ;
100
+ private final String httpsSecurityMode ;
88
101
89
102
private String [] authInfo = null ;
90
103
@@ -123,7 +136,7 @@ final class HttpTransporter extends AbstractTransporter {
123
136
};
124
137
}
125
138
126
- String httpsSecurityMode = ConfigUtils .getString (
139
+ httpsSecurityMode = ConfigUtils .getString (
127
140
session ,
128
141
ConfigurationProperties .HTTPS_SECURITY_MODE_DEFAULT ,
129
142
ConfigurationProperties .HTTPS_SECURITY_MODE + "." + repository .getId (),
@@ -135,16 +148,43 @@ final class HttpTransporter extends AbstractTransporter {
135
148
ConfigurationProperties .CONNECT_TIMEOUT + "." + repository .getId (),
136
149
ConfigurationProperties .CONNECT_TIMEOUT
137
150
);
151
+ this .chooseClient ();
152
+ }
138
153
139
- HTTP3Client h3Client = new HTTP3Client ();
140
- HttpClientTransportOverHTTP3 transport = new HttpClientTransportOverHTTP3 (h3Client );
141
- this .client = new HttpClient (transport );
142
- this .client .setFollowRedirects (true );
143
- this .client .setConnectTimeout (connectTimeout );
144
- this .client .start ();
145
- h3Client .getClientConnector ().getSslContextFactory ().setTrustAll (
146
- httpsSecurityMode .equals (ConfigurationProperties .HTTPS_SECURITY_MODE_INSECURE )
147
- );
154
+ private HttpClient initOrGetHttpClient () {
155
+ if (this .httpClient == null ) {
156
+ this .httpClient = new HttpClient ();
157
+ this .httpClient .setFollowRedirects (true );
158
+ this .httpClient .setConnectTimeout (connectTimeout );
159
+ SslContextFactory .Client sslContextFactory = new SslContextFactory .Client ();
160
+ sslContextFactory .setTrustAll (httpsSecurityMode .equals (ConfigurationProperties .HTTPS_SECURITY_MODE_INSECURE ));
161
+ httpClient .setSslContextFactory (sslContextFactory );
162
+ try {
163
+ this .httpClient .start ();
164
+ } catch (Exception e ) {
165
+ throw new UncheckedException (e );
166
+ }
167
+ }
168
+ return this .httpClient ;
169
+ }
170
+
171
+ private HttpClient initOrGetHttp3Client () {
172
+ if (this .http3Client == null ) {
173
+ HTTP3Client h3Client = new HTTP3Client ();
174
+ HttpClientTransportOverHTTP3 transport = new HttpClientTransportOverHTTP3 (h3Client );
175
+ this .http3Client = new HttpClient (transport );
176
+ this .http3Client .setFollowRedirects (true );
177
+ this .http3Client .setConnectTimeout (connectTimeout );
178
+ try {
179
+ this .http3Client .start ();
180
+ h3Client .getClientConnector ().getSslContextFactory ().setTrustAll (
181
+ httpsSecurityMode .equals (ConfigurationProperties .HTTPS_SECURITY_MODE_INSECURE )
182
+ );
183
+ } catch (Exception e ) {
184
+ throw new UncheckedException (e );
185
+ }
186
+ }
187
+ return this .http3Client ;
148
188
}
149
189
150
190
@ Override
@@ -157,12 +197,13 @@ public int classify(Throwable error) {
157
197
158
198
@ Override
159
199
protected void implPeek (PeekTask task ) throws Exception {
160
- this .makeRequest (HttpMethod .HEAD , task , null );
200
+ this .makeRequest (HttpMethod .HEAD , task , null , this . chooseClient () );
161
201
}
162
202
163
203
@ Override
164
204
protected void implGet (GetTask task ) throws Exception {
165
- final Pair <InputStream , HttpFields > response = this .makeRequest (HttpMethod .GET , task , null );
205
+ final Pair <InputStream , HttpFields > response =
206
+ this .makeRequest (HttpMethod .GET , task , null , this .chooseClient ());
166
207
final boolean resume = false ;
167
208
final File dataFile = task .getDataFile ();
168
209
long length = Long .parseLong (
@@ -202,16 +243,21 @@ protected void implGet(GetTask task) throws Exception {
202
243
protected void implPut (PutTask task ) throws Exception {
203
244
try (final InputStream stream = task .newInputStream ()) {
204
245
this .makeRequest (HttpMethod .PUT , task ,
205
- new InputStreamRequestContent (stream )
206
- );
246
+ new InputStreamRequestContent (stream ), this .chooseClient ());
207
247
}
208
248
}
209
249
210
250
@ Override
211
251
protected void implClose () {
212
252
try {
213
- client .stop ();
214
- client .destroy ();
253
+ if (this .http3Client != null ) {
254
+ http3Client .stop ();
255
+ http3Client .destroy ();
256
+ }
257
+ if (this .httpClient != null ) {
258
+ this .httpClient .stop ();
259
+ this .httpClient .destroy ();
260
+ }
215
261
} catch (Exception e ) {
216
262
throw new UncheckedIOException (new IOException (e ));
217
263
}
@@ -220,17 +266,20 @@ protected void implClose() {
220
266
}
221
267
222
268
private Pair <InputStream , HttpFields > makeRequest (
223
- HttpMethod method , TransportTask task , Request .Content bodyContent
269
+ HttpMethod method , TransportTask task , Request .Content bodyContent , HttpClient client
224
270
) {
225
271
final String url = this .baseUri .resolve (task .getLocation ()).toString ();
226
272
if (this .authInfo != null ) {
227
- this . client .getAuthenticationStore ().addAuthenticationResult (
273
+ client .getAuthenticationStore ().addAuthenticationResult (
228
274
new BasicAuthentication .BasicResult (this .baseUri , this .authInfo [0 ], this .authInfo [1 ])
229
275
);
230
276
}
277
+ Request request = null ;
278
+ final HttpVersion version = this .httpVersion (client );
231
279
try {
232
280
InputStreamResponseListener listener = new InputStreamResponseListener ();
233
- this .client .newRequest (url ).method (method ).headers (
281
+ request = client .newRequest (url );
282
+ request .method (method ).headers (
234
283
httpFields -> {
235
284
if (bodyContent != null ) {
236
285
httpFields .add (HttpHeader .CONTENT_TYPE , bodyContent .getContentType ());
@@ -245,22 +294,26 @@ private Pair<InputStream, HttpFields> makeRequest(
245
294
final Response response = listener .get (this .connectTimeout , TimeUnit .MILLISECONDS );
246
295
if (response .getStatus () >= 300 ) {
247
296
System .err .printf (
248
- "Request over HTTP3 error status %s, method=%s, url=%s%n" ,
249
- response .getStatus (), method , url
297
+ "Request over %s error status %s, method=%s, url=%s%n" ,
298
+ version , response .getStatus (), method , url
250
299
);
251
300
throw new HttpResponseException (Integer .toString (response .getStatus ()), response );
252
301
}
253
302
System .err .printf (
254
- "Request over HTTP3 done, method=%s, resp status=%s, url=%s%n" ,
255
- method , response .getStatus (), url
303
+ "Request over %s done, method=%s, resp status=%s, url=%s%n" ,
304
+ version , method , response .getStatus (), url
256
305
);
257
306
return new ImmutablePair <>(listener .getInputStream (), response .getHeaders ());
258
307
} catch (Exception ex ) {
259
308
System .err .printf (
260
- "Request over HTTP3 error=%s: %s, method=%s, url=%s%n" ,
309
+ "Request over %s error=%s: %s, method=%s, url=%s%n" , version ,
261
310
ex .getClass (), ex .getMessage (), method , url
262
311
);
263
- throw new HttpRequestException (ex .getMessage (), this .client .newRequest (url ));
312
+ if (version == HttpVersion .HTTP_3 && ex instanceof TimeoutException ) {
313
+ System .err .printf ("Repeat request over HTTP/1.1 method=%s, url=%s%n" , method , url );
314
+ return this .makeRequest (method , task , bodyContent , this .initOrGetHttpClient ());
315
+ }
316
+ throw new HttpRequestException (ex .getMessage (), request );
264
317
}
265
318
}
266
319
@@ -274,6 +327,24 @@ private void extractChecksums(HttpFields response, GetTask task) {
274
327
}
275
328
}
276
329
330
+ /**
331
+ * Choose http client to initialize and perform request with: if host is present in known
332
+ * central's hosts {@link HttpTransporter#CENTRAL}, http 1.1 client is used, otherwise we use http3 client.
333
+ */
334
+ private HttpClient chooseClient () {
335
+ final HttpClient res ;
336
+ if (CENTRAL .contains (this .baseUri .getHost ())) {
337
+ res = Optional .ofNullable (this .httpClient ).orElseGet (this ::initOrGetHttpClient );
338
+ } else {
339
+ res = Optional .ofNullable (this .http3Client ).orElseGet (this ::initOrGetHttp3Client );
340
+ }
341
+ return res ;
342
+ }
343
+
344
+ private HttpVersion httpVersion (final HttpClient client ) {
345
+ return client .getTransport () instanceof HttpClientTransportOverHTTP3 ? HttpVersion .HTTP_3 : HttpVersion .HTTP_1_1 ;
346
+ }
347
+
277
348
/**
278
349
* TOOD: For unknown reason when running inside Maven, HttpFieldPreEncoder for HTTP3 is missing.
279
350
* It is not available in Jetty static initializer when that library is loaded by Maven.
0 commit comments