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