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     @SuppressWarnings("checkstyle:magicnumber")
149     public static HttpClientConnectionManager newConnectionManager(ConnMgrConfig connMgrConfig) {
150         RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder.<ConnectionSocketFactory>create()
151                 .register("http", PlainConnectionSocketFactory.getSocketFactory());
152         int connectionMaxTtlSeconds = ConfigurationProperties.DEFAULT_HTTP_CONNECTION_MAX_TTL;
153         int maxConnectionsPerRoute = ConfigurationProperties.DEFAULT_HTTP_MAX_CONNECTIONS_PER_ROUTE;
154 
155         if (connMgrConfig == null) {
156             registryBuilder.register("https", SSLConnectionSocketFactory.getSystemSocketFactory());
157         } else {
158             // config present: use provided, if any, or create (depending on httpsSecurityMode)
159             connectionMaxTtlSeconds = connMgrConfig.connectionMaxTtlSeconds;
160             maxConnectionsPerRoute = connMgrConfig.maxConnectionsPerRoute;
161             SSLSocketFactory sslSocketFactory =
162                     connMgrConfig.context != null ? connMgrConfig.context.getSocketFactory() : null;
163             HostnameVerifier hostnameVerifier = connMgrConfig.verifier;
164             if (ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT.equals(connMgrConfig.httpsSecurityMode)) {
165                 if (sslSocketFactory == null) {
166                     sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
167                 }
168                 if (hostnameVerifier == null) {
169                     hostnameVerifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier();
170                 }
171             } else if (ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE.equals(connMgrConfig.httpsSecurityMode)) {
172                 if (sslSocketFactory == null) {
173                     try {
174                         sslSocketFactory = new SSLContextBuilder()
175                                 .loadTrustMaterial(null, (chain, auth) -> true)
176                                 .build()
177                                 .getSocketFactory();
178                     } catch (Exception e) {
179                         throw new SSLInitializationException(
180                                 "Could not configure '" + connMgrConfig.httpsSecurityMode + "' HTTPS security mode", e);
181                     }
182                 }
183                 if (hostnameVerifier == null) {
184                     hostnameVerifier = NoopHostnameVerifier.INSTANCE;
185                 }
186             } else {
187                 throw new IllegalArgumentException(
188                         "Unsupported '" + connMgrConfig.httpsSecurityMode + "' HTTPS security mode.");
189             }
190 
191             registryBuilder.register(
192                     "https",
193                     new SSLConnectionSocketFactory(
194                             sslSocketFactory, connMgrConfig.protocols, connMgrConfig.cipherSuites, hostnameVerifier));
195         }
196 
197         PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(
198                 new DefaultHttpClientConnectionOperator(
199                         registryBuilder.build(), DefaultSchemePortResolver.INSTANCE, SystemDefaultDnsResolver.INSTANCE),
200                 ManagedHttpClientConnectionFactory.INSTANCE,
201                 connectionMaxTtlSeconds,
202                 TimeUnit.SECONDS);
203         connMgr.setMaxTotal(maxConnectionsPerRoute * 2);
204         connMgr.setDefaultMaxPerRoute(maxConnectionsPerRoute);
205         return connMgr;
206     }
207 
208     public Object getUserToken(CompoundKey key) {
209         return userTokens.get(key);
210     }
211 
212     public void setUserToken(CompoundKey key, Object userToken) {
213         if (userToken != null) {
214             userTokens.put(key, userToken);
215         } else {
216             userTokens.remove(key);
217         }
218     }
219 
220     public ConcurrentMap<HttpHost, AuthSchemePool> getAuthSchemePools() {
221         return authSchemePools;
222     }
223 
224     public Boolean getExpectContinue(CompoundKey key) {
225         return expectContinues.get(key);
226     }
227 
228     public void setExpectContinue(CompoundKey key, boolean enabled) {
229         expectContinues.put(key, enabled);
230     }
231 }