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  
31  import org.apache.http.HttpHost;
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.PoolingHttpClientConnectionManager;
39  import org.apache.http.ssl.SSLContextBuilder;
40  import org.apache.http.ssl.SSLInitializationException;
41  import org.eclipse.aether.ConfigurationProperties;
42  import org.eclipse.aether.RepositoryCache;
43  import org.eclipse.aether.RepositorySystemSession;
44  import org.eclipse.aether.util.ConfigUtils;
45  
46  /**
47   * Container for HTTP-related state that can be shared across incarnations of the transporter to optimize the
48   * communication with servers.
49   */
50  final class GlobalState implements Closeable {
51  
52      static class CompoundKey {
53  
54          private final Object[] keys;
55  
56          CompoundKey(Object... keys) {
57              this.keys = keys;
58          }
59  
60          @Override
61          public boolean equals(Object obj) {
62              if (this == obj) {
63                  return true;
64              }
65              if (obj == null || !getClass().equals(obj.getClass())) {
66                  return false;
67              }
68              CompoundKey that = (CompoundKey) obj;
69              return Arrays.equals(keys, that.keys);
70          }
71  
72          @Override
73          public int hashCode() {
74              int hash = 17;
75              hash = hash * 31 + Arrays.hashCode(keys);
76              return hash;
77          }
78  
79          @Override
80          public String toString() {
81              return Arrays.toString(keys);
82          }
83      }
84  
85      private static final String KEY = GlobalState.class.getName();
86  
87      private static final String CONFIG_PROP_CACHE_STATE = "aether.connector.http.cacheState";
88  
89      private final ConcurrentMap<SslConfig, HttpClientConnectionManager> connectionManagers;
90  
91      private final ConcurrentMap<CompoundKey, Object> userTokens;
92  
93      private final ConcurrentMap<HttpHost, AuthSchemePool> authSchemePools;
94  
95      private final ConcurrentMap<CompoundKey, Boolean> expectContinues;
96  
97      public static GlobalState get(RepositorySystemSession session) {
98          GlobalState cache;
99          RepositoryCache repoCache = session.getCache();
100         if (repoCache == null || !ConfigUtils.getBoolean(session, true, CONFIG_PROP_CACHE_STATE)) {
101             cache = null;
102         } else {
103             Object tmp = repoCache.get(session, KEY);
104             if (tmp instanceof GlobalState) {
105                 cache = (GlobalState) tmp;
106             } else {
107                 synchronized (GlobalState.class) {
108                     tmp = repoCache.get(session, KEY);
109                     if (tmp instanceof GlobalState) {
110                         cache = (GlobalState) tmp;
111                     } else {
112                         cache = new GlobalState();
113                         repoCache.put(session, KEY, cache);
114                     }
115                 }
116             }
117         }
118         return cache;
119     }
120 
121     private GlobalState() {
122         connectionManagers = new ConcurrentHashMap<>();
123         userTokens = new ConcurrentHashMap<>();
124         authSchemePools = new ConcurrentHashMap<>();
125         expectContinues = new ConcurrentHashMap<>();
126     }
127 
128     @Override
129     public void close() {
130         for (Iterator<Map.Entry<SslConfig, HttpClientConnectionManager>> it =
131                         connectionManagers.entrySet().iterator();
132                 it.hasNext(); ) {
133             HttpClientConnectionManager connMgr = it.next().getValue();
134             it.remove();
135             connMgr.shutdown();
136         }
137     }
138 
139     public HttpClientConnectionManager getConnectionManager(SslConfig config) {
140         HttpClientConnectionManager manager = connectionManagers.get(config);
141         if (manager == null) {
142             HttpClientConnectionManager connMgr = newConnectionManager(config);
143             manager = connectionManagers.putIfAbsent(config, connMgr);
144             if (manager != null) {
145                 connMgr.shutdown();
146             } else {
147                 manager = connMgr;
148             }
149         }
150         return manager;
151     }
152 
153     @SuppressWarnings("checkstyle:magicnumber")
154     public static HttpClientConnectionManager newConnectionManager(SslConfig sslConfig) {
155         RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder.<ConnectionSocketFactory>create()
156                 .register("http", PlainConnectionSocketFactory.getSocketFactory());
157 
158         if (sslConfig == null) {
159             registryBuilder.register("https", SSLConnectionSocketFactory.getSystemSocketFactory());
160         } else {
161             // config present: use provided, if any, or create (depending on httpsSecurityMode)
162             SSLSocketFactory sslSocketFactory = sslConfig.context != null ? sslConfig.context.getSocketFactory() : null;
163             HostnameVerifier hostnameVerifier = sslConfig.verifier;
164             if (ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT.equals(sslConfig.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(sslConfig.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 '" + sslConfig.httpsSecurityMode + "' HTTPS security mode", e);
181                     }
182                 }
183                 if (hostnameVerifier == null) {
184                     hostnameVerifier = NoopHostnameVerifier.INSTANCE;
185                 }
186             } else {
187                 throw new IllegalArgumentException(
188                         "Unsupported '" + sslConfig.httpsSecurityMode + "' HTTPS security mode.");
189             }
190 
191             registryBuilder.register(
192                     "https",
193                     new SSLConnectionSocketFactory(
194                             sslSocketFactory, sslConfig.protocols, sslConfig.cipherSuites, hostnameVerifier));
195         }
196 
197         PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(registryBuilder.build());
198         connMgr.setMaxTotal(100);
199         connMgr.setDefaultMaxPerRoute(50);
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 ConcurrentMap<HttpHost, AuthSchemePool> getAuthSchemePools() {
216         return authSchemePools;
217     }
218 
219     public Boolean getExpectContinue(CompoundKey key) {
220         return expectContinues.get(key);
221     }
222 
223     public void setExpectContinue(CompoundKey key, boolean enabled) {
224         expectContinues.put(key, enabled);
225     }
226 }