View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.transport.jetty;
20  
21  import javax.net.ssl.SSLContext;
22  
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.net.URI;
26  import java.net.URISyntaxException;
27  import java.nio.file.Files;
28  import java.nio.file.Path;
29  import java.nio.file.StandardCopyOption;
30  import java.security.NoSuchAlgorithmException;
31  import java.util.ArrayList;
32  import java.util.Collection;
33  import java.util.HashMap;
34  import java.util.Map;
35  import java.util.concurrent.ExecutionException;
36  import java.util.concurrent.TimeUnit;
37  import java.util.concurrent.atomic.AtomicBoolean;
38  import java.util.concurrent.atomic.AtomicReference;
39  import java.util.function.Function;
40  import java.util.regex.Matcher;
41  
42  import org.eclipse.aether.ConfigurationProperties;
43  import org.eclipse.aether.ConfigurationProperties.HttpVersion;
44  import org.eclipse.aether.RepositorySystemSession;
45  import org.eclipse.aether.repository.AuthenticationContext;
46  import org.eclipse.aether.repository.RemoteRepository;
47  import org.eclipse.aether.spi.connector.transport.AbstractTransporter;
48  import org.eclipse.aether.spi.connector.transport.GetTask;
49  import org.eclipse.aether.spi.connector.transport.PeekTask;
50  import org.eclipse.aether.spi.connector.transport.PutTask;
51  import org.eclipse.aether.spi.connector.transport.TransportTask;
52  import org.eclipse.aether.spi.connector.transport.http.ChecksumExtractor;
53  import org.eclipse.aether.spi.connector.transport.http.HttpTransportPropertiesBuilder;
54  import org.eclipse.aether.spi.connector.transport.http.HttpTransporter;
55  import org.eclipse.aether.spi.connector.transport.http.HttpTransporterException;
56  import org.eclipse.aether.spi.io.PathProcessor;
57  import org.eclipse.aether.transfer.HttpTransportProperty;
58  import org.eclipse.aether.transfer.NoTransporterException;
59  import org.eclipse.aether.transfer.TransferCancelledException;
60  import org.eclipse.aether.transfer.TransferEvent;
61  import org.eclipse.aether.util.ConfigUtils;
62  import org.eclipse.aether.util.connector.transport.http.HttpTransporterUtils;
63  import org.eclipse.jetty.client.Authentication;
64  import org.eclipse.jetty.client.BasicAuthentication;
65  import org.eclipse.jetty.client.HttpClient;
66  import org.eclipse.jetty.client.HttpProxy;
67  import org.eclipse.jetty.client.InputStreamResponseListener;
68  import org.eclipse.jetty.client.Request;
69  import org.eclipse.jetty.client.Response;
70  import org.eclipse.jetty.client.transport.HttpClientConnectionFactory;
71  import org.eclipse.jetty.client.transport.HttpClientTransportDynamic;
72  import org.eclipse.jetty.http.HttpField;
73  import org.eclipse.jetty.http.HttpHeader;
74  import org.eclipse.jetty.http2.client.HTTP2Client;
75  import org.eclipse.jetty.http2.client.transport.ClientConnectionFactoryOverHTTP2;
76  import org.eclipse.jetty.http3.client.HTTP3Client;
77  import org.eclipse.jetty.http3.client.HTTP3ClientQuicConfiguration;
78  import org.eclipse.jetty.http3.client.transport.ClientConnectionFactoryOverHTTP3;
79  import org.eclipse.jetty.io.ClientConnectionFactory;
80  import org.eclipse.jetty.io.ClientConnector;
81  import org.eclipse.jetty.io.EndPoint.SslSessionData;
82  import org.eclipse.jetty.quic.quiche.client.QuicheClientQuicConfiguration;
83  import org.eclipse.jetty.quic.quiche.client.QuicheTransport;
84  import org.eclipse.jetty.util.ssl.SslContextFactory;
85  
86  import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.ACCEPT_ENCODING;
87  import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.CONTENT_LENGTH;
88  import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.CONTENT_RANGE;
89  import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.CONTENT_RANGE_PATTERN;
90  import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.IF_UNMODIFIED_SINCE;
91  import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.LAST_MODIFIED;
92  import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.MULTIPLE_CHOICES;
93  import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.PRECONDITION_FAILED;
94  import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.RANGE;
95  import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.USER_AGENT;
96  
97  /**
98   * A transporter for HTTP/HTTPS.
99   *
100  * @since 2.0.0
101  */
102 final class JettyTransporter extends AbstractTransporter implements HttpTransporter {
103     private static final long MODIFICATION_THRESHOLD = 60L * 1000L;
104 
105     private final RepositorySystemSession session;
106 
107     private final RemoteRepository repository;
108 
109     private final ChecksumExtractor checksumExtractor;
110 
111     private final PathProcessor pathProcessor;
112 
113     private final URI baseUri;
114 
115     private final HttpClient client;
116 
117     private final int connectTimeout;
118 
119     private final int requestTimeout;
120 
121     private final Map<String, String> headers;
122 
123     private final boolean preemptiveAuth;
124 
125     private final boolean preemptivePutAuth;
126 
127     private final boolean sendRfc9457Accept;
128 
129     private final boolean insecure;
130 
131     private final AtomicReference<BasicAuthentication.BasicResult> basicServerAuthenticationResult;
132 
133     private final AtomicReference<BasicAuthentication.BasicResult> basicProxyAuthenticationResult;
134 
135     JettyTransporter(
136             RepositorySystemSession session,
137             RemoteRepository repository,
138             ChecksumExtractor checksumExtractor,
139             PathProcessor pathProcessor)
140             throws NoTransporterException {
141         this.session = session;
142         this.repository = repository;
143         this.checksumExtractor = checksumExtractor;
144         this.pathProcessor = pathProcessor;
145         try {
146             this.baseUri = HttpTransporterUtils.getBaseUri(repository);
147         } catch (URISyntaxException e) {
148             throw new NoTransporterException(repository, e.getMessage(), e);
149         }
150 
151         HashMap<String, String> headers = new HashMap<>();
152         String userAgent = HttpTransporterUtils.getUserAgent(session, repository);
153         if (userAgent != null) {
154             headers.put(USER_AGENT, userAgent);
155         }
156         Map<String, String> configuredHeaders = HttpTransporterUtils.getHttpHeaders(session, repository);
157         if (configuredHeaders != null) {
158             headers.putAll(configuredHeaders);
159         }
160 
161         this.headers = headers;
162 
163         this.connectTimeout = HttpTransporterUtils.getHttpConnectTimeout(session, repository);
164         this.requestTimeout = HttpTransporterUtils.getHttpRequestTimeout(session, repository);
165         this.preemptiveAuth = HttpTransporterUtils.isHttpPreemptiveAuth(session, repository);
166         this.preemptivePutAuth = HttpTransporterUtils.isHttpPreemptivePutAuth(session, repository);
167         this.sendRfc9457Accept = HttpTransporterUtils.isHttpSendRfc9457Accept(session, repository);
168         final String httpsSecurityMode = HttpTransporterUtils.getHttpsSecurityMode(session, repository);
169         this.insecure = ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE.equals(httpsSecurityMode);
170 
171         this.basicServerAuthenticationResult = new AtomicReference<>(null);
172         this.basicProxyAuthenticationResult = new AtomicReference<>(null);
173         this.client = createClient();
174     }
175 
176     private void mayApplyPreemptiveAuth(Request request) {
177         if (basicServerAuthenticationResult.get() != null) {
178             basicServerAuthenticationResult.get().apply(request);
179         }
180         if (basicProxyAuthenticationResult.get() != null) {
181             basicProxyAuthenticationResult.get().apply(request);
182         }
183     }
184 
185     private URI resolve(TransportTask task) {
186         return baseUri.resolve(task.getLocation());
187     }
188 
189     @Override
190     protected void implPeek(PeekTask task) throws Exception {
191         Request request = client.newRequest(resolve(task)).method("HEAD");
192         request.headers(m -> headers.forEach(m::add));
193         if (preemptiveAuth) {
194             mayApplyPreemptiveAuth(request);
195         }
196         // capture raw response headers as described in https://github.com/jetty/jetty.project/discussions/14404
197         Map<String, HttpField> rawResponseHeaders = new HashMap<>();
198         request.onResponseHeader((r, field) -> {
199             rawResponseHeaders.put(field.getLowerCaseName(), field);
200             return true; // continue processing
201         });
202         Response response = request.send();
203         Map<TransferEvent.TransportPropertyKey, Object> transportProperties =
204                 createTransportProperties(request, rawResponseHeaders);
205         task.getListener().transportPropertiesAvailable(transportProperties);
206         if (response.getStatus() >= MULTIPLE_CHOICES) {
207             throw new HttpTransporterException(response.getStatus());
208         }
209     }
210 
211     @Override
212     protected void implGet(GetTask task) throws Exception {
213         boolean resume = task.getResumeOffset() > 0L && task.getDataPath() != null;
214         Response response;
215         InputStreamResponseListener listener;
216 
217         while (true) {
218             Request request = client.newRequest(resolve(task)).method("GET");
219             request.headers(m -> headers.forEach(m::add));
220             if (preemptiveAuth) {
221                 mayApplyPreemptiveAuth(request);
222             }
223             if (sendRfc9457Accept) {
224                 JettyRFC9457Reporter.INSTANCE.prepareRequest(request);
225             }
226             if (resume) {
227                 long resumeOffset = task.getResumeOffset();
228                 long lastModified =
229                         Files.getLastModifiedTime(task.getDataPath()).toMillis();
230                 request.headers(h -> {
231                     h.add(RANGE, "bytes=" + resumeOffset + '-');
232                     h.addDateField(IF_UNMODIFIED_SINCE, lastModified - MODIFICATION_THRESHOLD);
233                     h.remove(HttpHeader.ACCEPT_ENCODING);
234                     h.add(ACCEPT_ENCODING, "identity");
235                 });
236             }
237 
238             // capture raw response headers as described in https://github.com/jetty/jetty.project/discussions/14404
239             Map<String, HttpField> rawResponseHeaders = new HashMap<>();
240             request.onResponseHeader((r, field) -> {
241                 rawResponseHeaders.put(field.getLowerCaseName(), field);
242                 return true; // continue processing
243             });
244             listener = new InputStreamResponseListener();
245             request.send(listener);
246             try {
247                 response = listener.get(requestTimeout, TimeUnit.MILLISECONDS);
248             } catch (ExecutionException e) {
249                 Throwable t = e.getCause();
250                 if (t instanceof Exception) {
251                     throw (Exception) t;
252                 } else {
253                     throw new RuntimeException(t);
254                 }
255             }
256             Map<TransferEvent.TransportPropertyKey, Object> transportProperties =
257                     createTransportProperties(request, rawResponseHeaders);
258             task.getListener().transportPropertiesAvailable(transportProperties);
259             if (response.getStatus() >= MULTIPLE_CHOICES) {
260                 if (resume && response.getStatus() == PRECONDITION_FAILED) {
261                     resume = false;
262                     continue;
263                 }
264                 JettyRFC9457Reporter.INSTANCE.generateException(listener, (statusCode, reasonPhrase) -> {
265                     throw new HttpTransporterException(statusCode);
266                 });
267             }
268             break;
269         }
270 
271         long offset = 0L, length = response.getHeaders().getLongField(CONTENT_LENGTH);
272         if (resume) {
273             String range = response.getHeaders().get(CONTENT_RANGE);
274             if (range != null) {
275                 Matcher m = CONTENT_RANGE_PATTERN.matcher(range);
276                 if (!m.matches()) {
277                     throw new IOException("Invalid Content-Range header for partial download: " + range);
278                 }
279                 offset = Long.parseLong(m.group(1));
280                 length = Long.parseLong(m.group(2)) + 1L;
281                 if (offset < 0L || offset >= length || (offset > 0L && offset != task.getResumeOffset())) {
282                     throw new IOException("Invalid Content-Range header for partial download from offset "
283                             + task.getResumeOffset() + ": " + range);
284                 }
285             }
286         }
287 
288         final boolean downloadResumed = offset > 0L;
289         final Path dataFile = task.getDataPath();
290         if (dataFile == null) {
291             try (InputStream is = listener.getInputStream()) {
292                 utilGet(task, is, true, length, downloadResumed);
293             }
294         } else {
295             try (PathProcessor.CollocatedTempFile tempFile = pathProcessor.newTempFile(dataFile)) {
296                 task.setDataPath(tempFile.getPath(), downloadResumed);
297                 if (downloadResumed && Files.isRegularFile(dataFile)) {
298                     try (InputStream inputStream = Files.newInputStream(dataFile)) {
299                         Files.copy(inputStream, tempFile.getPath(), StandardCopyOption.REPLACE_EXISTING);
300                     }
301                 }
302                 try (InputStream is = listener.getInputStream()) {
303                     utilGet(task, is, true, length, downloadResumed);
304                 }
305                 tempFile.move();
306             } finally {
307                 task.setDataPath(dataFile);
308             }
309         }
310         if (task.getDataPath() != null && response.getHeaders().getDateField(LAST_MODIFIED) != -1) {
311             long lastModified =
312                     response.getHeaders().getDateField(LAST_MODIFIED); // note: Wagon also does first not last
313             if (lastModified != -1) {
314                 pathProcessor.setLastModified(task.getDataPath(), lastModified);
315             }
316         }
317         Map<String, String> checksums = checksumExtractor.extractChecksums(headerGetter(response));
318         if (checksums != null && !checksums.isEmpty()) {
319             checksums.forEach(task::setChecksum);
320         }
321     }
322 
323     private Map<TransferEvent.TransportPropertyKey, Object> createTransportProperties(
324             Request request, Map<String, HttpField> rawResponseHeaders) {
325         HttpTransportPropertiesBuilder builder =
326                 new HttpTransportPropertiesBuilder(toHttpVersion(request.getVersion()));
327         SslSessionData sslSessionData = request.getConnection().getSslSessionData();
328         if (sslSessionData != null && sslSessionData.sslSession() != null) {
329             builder.withSslProtocol(sslSessionData.sslSession().getProtocol());
330             builder.withSslCipherSuite(sslSessionData.sslSession().getCipherSuite());
331         }
332         if (rawResponseHeaders.containsKey("content-encoding")) {
333             builder.withContentCoding(rawResponseHeaders.get("content-encoding").getValue());
334         }
335         return builder.build();
336     }
337 
338     static HttpTransportProperty.HttpVersion toHttpVersion(org.eclipse.jetty.http.HttpVersion version) {
339         switch (version) {
340             case HTTP_1_0:
341                 return HttpTransportProperty.HttpVersion.HTTP_1_0;
342             case HTTP_1_1:
343                 return HttpTransportProperty.HttpVersion.HTTP_1_1;
344             case HTTP_2:
345                 return HttpTransportProperty.HttpVersion.HTTP_2;
346             case HTTP_3:
347                 return HttpTransportProperty.HttpVersion.HTTP_3;
348             default:
349                 throw new IllegalArgumentException("Unknown version " + version.toString());
350         }
351     }
352 
353     private static Function<String, String> headerGetter(Response response) {
354         return s -> response.getHeaders().get(s);
355     }
356 
357     @Override
358     protected void implPut(PutTask task) throws Exception {
359         Request request = client.newRequest(resolve(task)).method("PUT");
360         request.headers(m -> headers.forEach(m::add));
361         if (sendRfc9457Accept) {
362             JettyRFC9457Reporter.INSTANCE.prepareRequest(request);
363         }
364         if (preemptiveAuth || preemptivePutAuth) {
365             mayApplyPreemptiveAuth(request);
366         }
367         request.body(PutTaskRequestContent.from(task));
368         // capture raw response headers as described in https://github.com/jetty/jetty.project/discussions/14404
369         Map<String, HttpField> rawResponseHeaders = new HashMap<>();
370         request.onResponseHeader((r, field) -> {
371             rawResponseHeaders.put(field.getLowerCaseName(), field);
372             return true; // continue processing
373         });
374         AtomicBoolean started = new AtomicBoolean(false);
375         Response response;
376         try (InputStreamResponseListener listener = new InputStreamResponseListener()) {
377             request.onRequestCommit(r -> {
378                         if (task.getDataLength() == 0) {
379                             if (started.compareAndSet(false, true)) {
380                                 try {
381                                     task.getListener().transportStarted(0, task.getDataLength());
382                                 } catch (TransferCancelledException e) {
383                                     r.abort(e);
384                                 }
385                             }
386                         }
387                     })
388                     .onRequestContent((r, b) -> {
389                         if (started.compareAndSet(false, true)) {
390                             try {
391                                 task.getListener().transportStarted(0, task.getDataLength());
392                             } catch (TransferCancelledException e) {
393                                 r.abort(e);
394                                 return;
395                             }
396                         }
397                         try {
398                             task.getListener().transportProgressed(b);
399                         } catch (TransferCancelledException e) {
400                             r.abort(e);
401                         }
402                     })
403                     .send(listener);
404             response = listener.get(requestTimeout, TimeUnit.MILLISECONDS);
405             task.getListener().transportPropertiesAvailable(createTransportProperties(request, rawResponseHeaders));
406             if (response.getStatus() >= MULTIPLE_CHOICES) {
407                 JettyRFC9457Reporter.INSTANCE.generateException(listener, (statusCode, reasonPhrase) -> {
408                     throw new HttpTransporterException(statusCode);
409                 });
410             }
411         } catch (ExecutionException e) {
412             Throwable t = e.getCause();
413             if (t instanceof IOException ioex) {
414                 if (ioex.getCause() instanceof TransferCancelledException) {
415                     throw (TransferCancelledException) ioex.getCause();
416                 } else {
417                     throw ioex;
418                 }
419             } else if (t instanceof Exception) {
420                 throw (Exception) t;
421             } else {
422                 throw new RuntimeException(t);
423             }
424         }
425     }
426 
427     @Override
428     protected void implClose() {
429         try {
430             this.client.stop();
431         } catch (Exception e) {
432             throw new RuntimeException(e);
433         }
434     }
435 
436     @SuppressWarnings("checkstyle:methodlength")
437     private HttpClient createClient() throws RuntimeException {
438         BasicAuthentication.BasicResult serverAuth = null;
439         BasicAuthentication.BasicResult proxyAuth = null;
440         SSLContext sslContext = null;
441         BasicAuthentication basicAuthentication = null;
442         try (AuthenticationContext repoAuthContext = AuthenticationContext.forRepository(session, repository)) {
443             if (repoAuthContext != null) {
444                 sslContext = repoAuthContext.get(AuthenticationContext.SSL_CONTEXT, SSLContext.class);
445 
446                 String username = repoAuthContext.get(AuthenticationContext.USERNAME);
447                 String password = repoAuthContext.get(AuthenticationContext.PASSWORD);
448 
449                 URI uri = URI.create(repository.getUrl());
450                 basicAuthentication = new BasicAuthentication(uri, Authentication.ANY_REALM, username, password);
451                 if (preemptiveAuth || preemptivePutAuth) {
452                     serverAuth = new BasicAuthentication.BasicResult(uri, HttpHeader.AUTHORIZATION, username, password);
453                 }
454             }
455         }
456 
457         SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
458         if (insecure) {
459             // this is also passed on to Quiche for HTTP/3
460             sslContextFactory.setEndpointIdentificationAlgorithm(null);
461             sslContextFactory.setHostnameVerifier((name, context) -> true);
462             sslContextFactory.setTrustAll(true);
463         } else {
464             try {
465                 if (sslContext == null) {
466                     // use the JVM's default SSL context (potentially with custom keystores/truststores)
467                     // https://github.com/jetty/jetty.project/issues/15378
468                     sslContext = SSLContext.getDefault();
469                 }
470             } catch (NoSuchAlgorithmException e) {
471                 throw new IllegalStateException("SSL Context setup failure", e);
472             }
473         }
474 
475         if (sslContext != null) {
476             // not properly supported by Quiche for HTTP/3, but Jetty will use it for HTTP/2 and HTTP/1.1
477             sslContextFactory.setSslContext(sslContext);
478         }
479 
480         ClientConnector clientConnector = new ClientConnector();
481         clientConnector.setSslContextFactory(sslContextFactory);
482 
483         Collection<ClientConnectionFactory.Info> connectors = new ArrayList<>();
484         if ("https".equalsIgnoreCase(repository.getProtocol())) {
485             HttpVersion httpVersion = HttpTransporterUtils.getHttpVersion(session, repository);
486             switch (httpVersion) {
487                 case MAXIMUM:
488                 case HTTP_3:
489                     QuicheClientQuicConfiguration clientQuicConfig =
490                             HTTP3ClientQuicConfiguration.configure(new QuicheClientQuicConfiguration());
491                     HTTP3Client http3Client = new HTTP3Client(clientQuicConfig, clientConnector);
492                     QuicheTransport transport = new QuicheTransport(clientQuicConfig);
493                     ClientConnectionFactoryOverHTTP3.HTTP3 http3 =
494                             new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client, transport);
495                     connectors.add(http3);
496                     break; // fallback to HTTP/2 not supported yet (https://github.com/jetty/jetty.project/issues/15423)
497                 case HTTP_2:
498                 case DEFAULT:
499                     HTTP2Client http2Client = new HTTP2Client(clientConnector);
500                     ClientConnectionFactoryOverHTTP2.HTTP2 http2 =
501                             new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
502                     connectors.add(http2);
503                     break;
504                 default:
505                     break;
506             }
507         }
508         connectors.add(HttpClientConnectionFactory.HTTP11); // HTTP/1.1, always supported but has least priority
509 
510         HttpClientTransportDynamic dynamicTransport = new HttpClientTransportDynamic(
511                 clientConnector, connectors.toArray(new ClientConnectionFactory.Info[0]));
512         HttpClient httpClient = new HttpClient(dynamicTransport);
513         httpClient.setConnectTimeout(connectTimeout);
514         httpClient.setIdleTimeout(requestTimeout);
515         httpClient.setFollowRedirects(ConfigUtils.getBoolean(
516                 session,
517                 JettyTransporterConfigurationKeys.DEFAULT_FOLLOW_REDIRECTS,
518                 JettyTransporterConfigurationKeys.CONFIG_PROP_FOLLOW_REDIRECTS));
519         httpClient.setMaxRedirects(ConfigUtils.getInteger(
520                 session,
521                 JettyTransporterConfigurationKeys.DEFAULT_MAX_REDIRECTS,
522                 JettyTransporterConfigurationKeys.CONFIG_PROP_MAX_REDIRECTS));
523 
524         httpClient.setUserAgentField(null); // we manage it
525 
526         if (basicAuthentication != null) {
527             httpClient.getAuthenticationStore().addAuthentication(basicAuthentication);
528         }
529 
530         if (repository.getProxy() != null) {
531             HttpProxy proxy = new HttpProxy(
532                     repository.getProxy().getHost(), repository.getProxy().getPort());
533 
534             httpClient.getProxyConfiguration().addProxy(proxy);
535             try (AuthenticationContext proxyAuthContext = AuthenticationContext.forProxy(session, repository)) {
536                 if (proxyAuthContext != null) {
537                     String username = proxyAuthContext.get(AuthenticationContext.USERNAME);
538                     String password = proxyAuthContext.get(AuthenticationContext.PASSWORD);
539 
540                     BasicAuthentication proxyAuthentication =
541                             new BasicAuthentication(proxy.getURI(), Authentication.ANY_REALM, username, password);
542 
543                     httpClient.getAuthenticationStore().addAuthentication(proxyAuthentication);
544                     if (preemptiveAuth || preemptivePutAuth) {
545                         proxyAuth = new BasicAuthentication.BasicResult(
546                                 proxy.getURI(), HttpHeader.PROXY_AUTHORIZATION, username, password);
547                     }
548                 }
549             }
550         }
551         if (serverAuth != null) {
552             this.basicServerAuthenticationResult.set(serverAuth);
553         }
554         if (proxyAuth != null) {
555             this.basicProxyAuthenticationResult.set(proxyAuth);
556         }
557 
558         try {
559             httpClient.start();
560             return httpClient;
561         } catch (Exception e) {
562             if (e instanceof RuntimeException) {
563                 throw (RuntimeException) e;
564             } else {
565                 throw new IllegalStateException("Jetty client start failure", e);
566             }
567         }
568     }
569 }