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.http;
20  
21  import javax.net.ssl.HostnameVerifier;
22  import javax.net.ssl.SSLSocketFactory;
23  
24  import java.io.Closeable;
25  import java.util.Arrays;
26  import java.util.Iterator;
27  import java.util.Map;
28  import java.util.concurrent.ConcurrentHashMap;
29  import java.util.concurrent.ConcurrentMap;
30  import java.util.concurrent.TimeUnit;
31  
32  import org.apache.http.HttpHost;
33  import org.apache.http.config.RegistryBuilder;
34  import org.apache.http.conn.HttpClientConnectionManager;
35  import org.apache.http.conn.socket.ConnectionSocketFactory;
36  import org.apache.http.conn.socket.PlainConnectionSocketFactory;
37  import org.apache.http.conn.ssl.NoopHostnameVerifier;
38  import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
39  import org.apache.http.impl.conn.DefaultHttpClientConnectionOperator;
40  import org.apache.http.impl.conn.DefaultSchemePortResolver;
41  import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory;
42  import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
43  import org.apache.http.impl.conn.SystemDefaultDnsResolver;
44  import org.apache.http.ssl.SSLContextBuilder;
45  import org.apache.http.ssl.SSLInitializationException;
46  import org.eclipse.aether.ConfigurationProperties;
47  import org.eclipse.aether.RepositoryCache;
48  import org.eclipse.aether.RepositorySystemSession;
49  import org.eclipse.aether.util.ConfigUtils;
50  
51  /**
52   * Container for HTTP-related state that can be shared across incarnations of the transporter to optimize the
53   * communication with servers.
54   */
55  final class GlobalState implements Closeable {
56  
57      static class CompoundKey {
58  
59          private final Object[] keys;
60  
61          CompoundKey(Object... keys) {
62              this.keys = keys;
63          }
64  
65          @Override
66          public boolean equals(Object obj) {
67              if (this == obj) {
68                  return true;
69              }
70              if (obj == null || !getClass().equals(obj.getClass())) {
71                  return false;
72              }
73              CompoundKey that = (CompoundKey) obj;
74              return Arrays.equals(keys, that.keys);
75          }
76  
77          @Override
78          public int hashCode() {
79              int hash = 17;
80              hash = hash * 31 + Arrays.hashCode(keys);
81              return hash;
82          }
83  
84          @Override
85          public String toString() {
86              return Arrays.toString(keys);
87          }
88      }
89  
90      private static final String KEY = GlobalState.class.getName();
91  
92      private static final String CONFIG_PROP_CACHE_STATE = "aether.connector.http.cacheState";
93  
94      private final ConcurrentMap<ConnMgrConfig, HttpClientConnectionManager> connectionManagers;
95  
96      private final ConcurrentMap<CompoundKey, Object> userTokens;
97  
98      private final ConcurrentMap<HttpHost, AuthSchemePool> authSchemePools;
99  
100     private final ConcurrentMap<CompoundKey, Boolean> expectContinues;
101 
102     public static GlobalState get(RepositorySystemSession session) {
103         GlobalState cache;
104         RepositoryCache repoCache = session.getCache();
105         if (repoCache == null || !ConfigUtils.getBoolean(session, true, CONFIG_PROP_CACHE_STATE)) {
106             cache = null;
107         } else {
108             Object tmp = repoCache.get(session, KEY);
109             if (tmp instanceof GlobalState) {
110                 cache = (GlobalState) tmp;
111             } else {
112                 synchronized (GlobalState.class) {
113                     tmp = repoCache.get(session, KEY);
114                     if (tmp instanceof GlobalState) {
115                         cache = (GlobalState) tmp;
116                     } else {
117                         cache = new GlobalState();
118                         repoCache.put(session, KEY, cache);
119                     }
120                 }
121             }
122         }
123         return cache;
124     }
125 
126     private GlobalState() {
127         connectionManagers = new ConcurrentHashMap<>();
128         userTokens = new ConcurrentHashMap<>();
129         authSchemePools = new ConcurrentHashMap<>();
130         expectContinues = new ConcurrentHashMap<>();
131     }
132 
133     @Override
134     public void close() {
135         for (Iterator<Map.Entry<ConnMgrConfig, HttpClientConnectionManager>> it =
136                         connectionManagers.entrySet().iterator();
137                 it.hasNext(); ) {
138             HttpClientConnectionManager connMgr = it.next().getValue();
139             it.remove();
140             connMgr.shutdown();
141         }
142     }
143 
144     public HttpClientConnectionManager getConnectionManager(ConnMgrConfig config) {
145         return connectionManagers.computeIfAbsent(config, GlobalState::newConnectionManager);
146     }
147 
148     public static HttpClientConnectionManager newConnectionManager(ConnMgrConfig connMgrConfig) {
149         RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder.<ConnectionSocketFactory>create()
150                 .register("http", PlainConnectionSocketFactory.getSocketFactory());
151         int connectionMaxTtlSeconds = ConfigurationProperties.DEFAULT_HTTP_CONNECTION_MAX_TTL;
152         int maxConnectionsPerRoute = ConfigurationProperties.DEFAULT_HTTP_MAX_CONNECTIONS_PER_ROUTE;
153 
154         if (connMgrConfig == null) {
155             registryBuilder.register("https", SSLConnectionSocketFactory.getSystemSocketFactory());
156         } else {
157             // config present: use provided, if any, or create (depending on httpsSecurityMode)
158             connectionMaxTtlSeconds = connMgrConfig.connectionMaxTtlSeconds;
159             maxConnectionsPerRoute = connMgrConfig.maxConnectionsPerRoute;
160             SSLSocketFactory sslSocketFactory =
161                     connMgrConfig.context != null ? connMgrConfig.context.getSocketFactory() : null;
162             HostnameVerifier hostnameVerifier = connMgrConfig.verifier;
163             if (ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT.equals(connMgrConfig.httpsSecurityMode)) {
164                 if (sslSocketFactory == null) {
165                     sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
166                 }
167                 if (hostnameVerifier == null) {
168                     hostnameVerifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier();
169                 }
170             } else if (ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE.equals(connMgrConfig.httpsSecurityMode)) {
171                 if (sslSocketFactory == null) {
172                     try {
173                         sslSocketFactory = new SSLContextBuilder()
174                                 .loadTrustMaterial(null, (chain, auth) -> true)
175                                 .build()
176                                 .getSocketFactory();
177                     } catch (Exception e) {
178                         throw new SSLInitializationException(
179                                 "Could not configure '" + connMgrConfig.httpsSecurityMode + "' HTTPS security mode", e);
180                     }
181                 }
182                 if (hostnameVerifier == null) {
183                     hostnameVerifier = NoopHostnameVerifier.INSTANCE;
184                 }
185             } else {
186                 throw new IllegalArgumentException(
187                         "Unsupported '" + connMgrConfig.httpsSecurityMode + "' HTTPS security mode.");
188             }
189 
190             registryBuilder.register(
191                     "https",
192                     new SSLConnectionSocketFactory(
193                             sslSocketFactory, connMgrConfig.protocols, connMgrConfig.cipherSuites, hostnameVerifier));
194         }
195 
196         PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(
197                 new DefaultHttpClientConnectionOperator(
198                         registryBuilder.build(), DefaultSchemePortResolver.INSTANCE, SystemDefaultDnsResolver.INSTANCE),
199                 ManagedHttpClientConnectionFactory.INSTANCE,
200                 connectionMaxTtlSeconds,
201                 TimeUnit.SECONDS);
202         connMgr.setMaxTotal(maxConnectionsPerRoute * 2);
203         connMgr.setDefaultMaxPerRoute(maxConnectionsPerRoute);
204         return connMgr;
205     }
206 
207     public Object getUserToken(CompoundKey key) {
208         return userTokens.get(key);
209     }
210 
211     public void setUserToken(CompoundKey key, Object userToken) {
212         if (userToken != null) {
213             userTokens.put(key, userToken);
214         } else {
215             userTokens.remove(key);
216         }
217     }
218 
219     public ConcurrentMap<HttpHost, AuthSchemePool> getAuthSchemePools() {
220         return authSchemePools;
221     }
222 
223     public Boolean getExpectContinue(CompoundKey key) {
224         return expectContinues.get(key);
225     }
226 
227     public void setExpectContinue(CompoundKey key, boolean enabled) {
228         expectContinues.put(key, enabled);
229     }
230 }