View Javadoc
1   /*
2    * Copyright (c) 2002-2021, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.util.httpaccess;
35  
36  
37  import org.apache.commons.lang3.StringUtils;
38  import org.apache.hc.client5.http.config.RequestConfig;
39  import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
40  import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
41  import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
42  import org.apache.hc.client5.http.impl.classic.HttpClients;
43  import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
44  import org.apache.hc.core5.http.HttpHost;
45  import org.apache.hc.core5.util.Timeout;
46  
47  import fr.paris.lutece.portal.service.util.AppLogService;
48  import fr.paris.lutece.portal.service.util.AppPropertiesService;
49  
50  
51  /**
52   * HttpAccessService.
53   */
54  public class HttpAccessService implements ResponseStatusValidator
55  {
56      /** The Constant DEFAULT_RESPONSE_CODE_AUTHORIZED. */
57      private static final String DEFAULT_RESPONSE_CODE_AUTHORIZED = "200,201,202";
58  
59      /** The Constant PROPERTY_PROXY_HOST. */
60      private static final String PROPERTY_PROXY_HOST = "httpAccess.proxyHost";
61  
62      /** The Constant PROPERTY_PROXY_PORT. */
63      private static final String PROPERTY_PROXY_PORT = "httpAccess.proxyPort";
64  
65      /** The Constant PROPERTY_PROXY_USERNAME. */
66      private static final String PROPERTY_PROXY_USERNAME = "httpAccess.proxyUserName";
67  
68      /** The Constant PROPERTY_PROXY_PASSWORD. */
69      private static final String PROPERTY_PROXY_PASSWORD = "httpAccess.proxyPassword";
70  
71      /** The Constant PROPERTY_HOST_NAME. */
72      private static final String PROPERTY_HOST_NAME = "httpAccess.hostName";
73  
74      /** The Constant PROPERTY_DOMAIN_NAME. */
75      private static final String PROPERTY_DOMAIN_NAME = "httpAccess.domainName";
76  
77      /** The Constant PROPERTY_REALM. */
78      private static final String PROPERTY_REALM = "httpAccess.realm";
79  
80      /** The Constant PROPERTY_NO_PROXY_FOR. */
81      private static final String PROPERTY_NO_PROXY_FOR = "httpAccess.noProxyFor";
82  
83      /** The Constant PROPERTY_CONTENT_CHARSET. */
84      private static final String PROPERTY_CONTENT_CHARSET = "httpAccess.contentCharset";
85  
86      /** The Constant PROPERTY_ELEMENT_CHARSET. */
87      private static final String PROPERTY_ELEMENT_CHARSET = "httpAccess.elementCharset";
88  
89      /** The Constant PROPERTY_SOCKET_TIMEOUT. */
90      private static final String PROPERTY_SOCKET_TIMEOUT = "httpAccess.socketTimeout";
91  
92      /** The Constant PROPERTY_CONNECTION_TIMEOUT. */
93      private static final String PROPERTY_CONNECTION_TIMEOUT = "httpAccess.connectionTimeout";
94  
95      /** The Constant PROPERTY_CONNECTION_POOL_ENABLED. */
96      private static final String PROPERTY_CONNECTION_POOL_ENABLED = "httpAccess.connectionPoolEnabled";
97  
98      /** The Constant PROPERTY_CONNECTION_POOL_MAX_TOTAL_CONNECTION. */
99      private static final String PROPERTY_CONNECTION_POOL_MAX_TOTAL_CONNECTION = "httpAccess.connectionPoolMaxTotalConnections";
100 
101     /** The Constant PROPERTY_CONNECTION_POOL_MAX_TOTAL_CONNECTION_PER_HOST. */
102     private static final String PROPERTY_CONNECTION_POOL_MAX_TOTAL_CONNECTION_PER_HOST = "httpAccess.connectionPoolMaxConnectionsPerHost";
103 
104     /** The Constant PROPERTY_HTTP_RESPONSES_CODE_AUTHORIZED. */
105     private static final String PROPERTY_HTTP_RESPONSES_CODE_AUTHORIZED = "httpAccess.responsesCodeAuthorized";
106 
107     /** The Constant PROPERTY_HTTP_PROTOCOLE_CONTENT_CHARSET. */
108     private static final String PROPERTY_HTTP_PROTOCOLE_CONTENT_CHARSET = "http.protocol.content-charset";
109 
110     /** The Constant PROPERTY_HTTP_PROTOCOLE_ELEMENT_CHARSET. */
111     private static final String PROPERTY_HTTP_PROTOCOLE_ELEMENT_CHARSET = "http.protocol.element-charset";
112 
113     /** The _singleton. */
114     private static HttpAccessService _singleton;
115 
116     private HttpClientConfiguration _httpClientConfiguration;
117     
118     
119     private  PoolingHttpClientConnectionManager _connectionManager;
120     
121     private  CloseableHttpClient _httpClient;
122    
123     
124     public HttpClientConfiguration getHttpClientConfiguration() {
125 		return _httpClientConfiguration;
126 	}
127 
128 
129 
130 	/** The Constant SEPARATOR. */
131     private static final String SEPARATOR = ",";
132 
133     private ResponseStatusValidator _responseValidator;
134 
135     /**
136      * Gets the single instance of HttpAccessService.
137      *
138      * @return single instance of HttpAccessService
139      */
140     public static HttpAccessService getInstance( )
141     {
142         if ( _singleton == null )
143         {
144             _singleton = new HttpAccessService( );
145             // WARNING if .init( ) throw exception, the singleton is already defined
146             _singleton.init( );
147             _singleton._responseValidator = SimpleResponseValidator.loadFromProperty( PROPERTY_HTTP_RESPONSES_CODE_AUTHORIZED, DEFAULT_RESPONSE_CODE_AUTHORIZED );
148         }
149 
150         return _singleton;
151     }
152     /**
153      * create new specific Instance of HttpAccessService 
154      * @param httpClienConfiguration the httpClienConfiguration
155      */
156     public HttpAccessService(HttpClientConfiguration httpClienConfiguration)
157     {
158     	super();
159     	_httpClientConfiguration=httpClienConfiguration;
160     	
161     }
162     
163     private HttpAccessService( )
164     {
165     	
166     }
167     
168 
169     /**
170      * get an HTTP client object using current configuration.
171      *
172      * @param method
173      *            The method
174      * @return An HTTP client authenticated
175      */
176     public synchronized CloseableHttpClient  getHttpClient(  String strTargetHost)
177     {
178     	
179     	
180 			HttpClientBuilder clientBuilder = HttpClients.custom();
181 			
182 		
183 		    // bNoProxy will be true when we would normally be using a proxy but matched on the NoProxyFor list
184 		  
185 		    if ( StringUtils.isNotBlank( _httpClientConfiguration.getProxyHost() ) )
186 		    {
187 		    	    boolean bNoProxy = ( StringUtils.isNotBlank( _httpClientConfiguration.getNoProxyFor() ) && matchesList( _httpClientConfiguration.getNoProxyFor().split( SEPARATOR ), strTargetHost) );
188 		            if(!bNoProxy && StringUtils.isNotBlank( _httpClientConfiguration.getProxyPort() ) && StringUtils.isNumeric( _httpClientConfiguration.getProxyPort() ))
189 		            {
190 		            	final HttpHost proxy = new HttpHost("http", _httpClientConfiguration.getProxyHost(),Integer.parseInt( _httpClientConfiguration.getProxyPort()));
191 		            	clientBuilder.setProxy(proxy);
192 		            }
193 		        
194 		    }
195 		
196 		    if ( _httpClientConfiguration.isConnectionPoolEnabled() )
197 		    {
198 		    	
199 		    	  if(_connectionManager==null)
200 		    	  {
201 		    		  _connectionManager= new PoolingHttpClientConnectionManager();
202 		        	
203 		                if (_httpClientConfiguration.getConnectionPoolMaxConnectionPerHost() !=null )
204 		                {
205 		                	_connectionManager.setDefaultMaxPerRoute(_httpClientConfiguration.getConnectionPoolMaxConnectionPerHost());
206 		                	
207 		                
208 		                }
209 		
210 		                if (  _httpClientConfiguration.getConnectionPoolMaxTotalConnection() !=null )
211 		                {
212 		                	_connectionManager.setMaxTotal(   _httpClientConfiguration.getConnectionPoolMaxTotalConnection()  );
213 		                }
214 		               
215 		    	  }
216 		            clientBuilder.setConnectionManager(_connectionManager);
217 		            clientBuilder.setConnectionManagerShared(true);   
218 		        
219 		     }
220 
221 		    if (  _httpClientConfiguration.getSocketTimeout() != null  ||    _httpClientConfiguration.getConnectionTimeout() != null  )
222 		    {
223 		    	RequestConfig.Builder requestConfiguilder = RequestConfig.custom();
224 		    	 if(  _httpClientConfiguration.getConnectionTimeout()!=null)
225 		    	 {
226 		    		 requestConfiguilder.setConnectTimeout(Timeout.ofMilliseconds(  _httpClientConfiguration.getConnectionTimeout() ));
227 		    		 
228 		    	 }
229 		    	 
230 		    	 if( _httpClientConfiguration.getSocketTimeout() != null )
231 		    	 {
232 		    		
233 		    		 requestConfiguilder.setResponseTimeout(Timeout.ofMilliseconds( _httpClientConfiguration.getSocketTimeout() ));
234 		    		
235 		    		 
236 		    	 }
237 		    	 clientBuilder.setDefaultRequestConfig(requestConfiguilder.build());
238 		    	 //follow redirect
239 		         clientBuilder.setRedirectStrategy(DefaultRedirectStrategy.INSTANCE);
240         }
241 
242        
243 
244         _httpClient =clientBuilder.build();
245     
246     	
247         return _httpClient;
248     }
249 
250     /**
251      * heck if the text matches one of the pattern of the list.
252      *
253      * @param listPatterns
254      *            the list of patterns
255      * @param strText
256      *            the text
257      * @return true if the text matches one of the pattern, false otherwise
258      */
259     private boolean matchesList( String [ ] listPatterns, String strText )
260     {
261         if ( listPatterns == null )
262         {
263             return false;
264         }
265 
266         for ( String pattern : listPatterns )
267         {
268             if ( matches( pattern, strText ) )
269             {
270                 return true;
271             }
272         }
273 
274         return false;
275     }
276 
277     /**
278      * Check if the pattern match the text. It also deals with special characters like * or ?
279      * 
280      * @param strPattern
281      *            the pattern
282      * @param strText
283      *            the text
284      * @return true if the text matches the pattern, false otherwise
285      */
286     private static boolean matches( String strPattern, String strText )
287     {
288         String strTextTmp = strText + '\0';
289         String strPatternTmp = strPattern + '\0';
290 
291         int nLength = strPatternTmp.length( );
292 
293         boolean [ ] states = new boolean [ nLength + 1];
294         boolean [ ] old = new boolean [ nLength + 1];
295         old [0] = true;
296 
297         for ( int i = 0; i < strTextTmp.length( ); i++ )
298         {
299             char c = strTextTmp.charAt( i );
300             states = new boolean [ nLength + 1];
301 
302             for ( int j = 0; j < nLength; j++ )
303             {
304                 char p = strPatternTmp.charAt( j );
305 
306                 if ( old [j] && ( p == '*' ) )
307                 {
308                     old [j + 1] = true;
309                 }
310 
311                 if ( old [j] && ( p == c ) )
312                 {
313                     states [j + 1] = true;
314                 }
315 
316                 if ( old [j] && ( p == '?' ) )
317                 {
318                     states [j + 1] = true;
319                 }
320 
321                 if ( old [j] && ( p == '*' ) )
322                 {
323                     states [j] = true;
324                 }
325 
326                 if ( old [j] && ( p == '*' ) )
327                 {
328                     states [j + 1] = true;
329                 }
330             }
331 
332             old = states;
333         }
334 
335         return states [nLength];
336     }
337 
338     /**
339      * init properties.
340      */
341     private void init( )
342     {
343     	_httpClientConfiguration=new HttpClientConfiguration();
344         _httpClientConfiguration.setProxyHost(AppPropertiesService.getProperty( PROPERTY_PROXY_HOST ));
345         _httpClientConfiguration.setProxyPort (AppPropertiesService.getProperty( PROPERTY_PROXY_PORT ));
346         _httpClientConfiguration.setProxyUserName (AppPropertiesService.getProperty( PROPERTY_PROXY_USERNAME ));
347         _httpClientConfiguration.setProxyPassword( AppPropertiesService.getProperty( PROPERTY_PROXY_PASSWORD ));
348         _httpClientConfiguration.setHostName (AppPropertiesService.getProperty( PROPERTY_HOST_NAME ));
349         _httpClientConfiguration.setDomainName ( AppPropertiesService.getProperty( PROPERTY_DOMAIN_NAME ));
350         _httpClientConfiguration.setRealm( AppPropertiesService.getProperty( PROPERTY_REALM ));
351         _httpClientConfiguration.setNoProxyFor (AppPropertiesService.getProperty( PROPERTY_NO_PROXY_FOR ));
352         _httpClientConfiguration.setContentCharset (AppPropertiesService.getProperty( PROPERTY_CONTENT_CHARSET ));
353         _httpClientConfiguration.setElementCharset ( AppPropertiesService.getProperty( PROPERTY_ELEMENT_CHARSET ));
354         _httpClientConfiguration.setConnectionPoolEnabled ( AppPropertiesService.getPropertyBoolean( PROPERTY_CONNECTION_POOL_ENABLED, false ));
355         
356 		try {
357 			_httpClientConfiguration.setSocketTimeout( StringUtils.isNotEmpty( AppPropertiesService.getProperty(PROPERTY_SOCKET_TIMEOUT) ) 	? Integer.parseInt(AppPropertiesService.getProperty(PROPERTY_SOCKET_TIMEOUT))
358 					: null);
359 
360 		} catch (NumberFormatException e) {
361 			AppLogService.error("Error during initialisation of socket timeout ", e);
362 		}
363 
364 		try {
365 			_httpClientConfiguration
366 					.setConnectionTimeout( StringUtils.isNotEmpty(  AppPropertiesService.getProperty(PROPERTY_CONNECTION_TIMEOUT) )
367 							? Integer.parseInt(AppPropertiesService.getProperty(PROPERTY_CONNECTION_TIMEOUT))
368 							: null);
369 
370 		} catch (NumberFormatException e) {
371 			AppLogService.error("Error during initialisation of connection timeout ", e);
372 		}
373 		try {
374 			_httpClientConfiguration.setConnectionPoolMaxTotalConnection(
375 					StringUtils.isNotEmpty( AppPropertiesService.getProperty(PROPERTY_CONNECTION_POOL_MAX_TOTAL_CONNECTION) )
376 							? Integer.parseInt(
377 									AppPropertiesService.getProperty(PROPERTY_CONNECTION_POOL_MAX_TOTAL_CONNECTION))
378 							: null);
379 
380 		} catch (NumberFormatException e) {
381 			AppLogService.error("Error during initialisation of Connection Pool Maxt Total Connection ", e);
382 		}
383 		try {
384 			_httpClientConfiguration.setConnectionPoolMaxConnectionPerHost(
385 					StringUtils.isNotEmpty(  AppPropertiesService.getProperty(PROPERTY_CONNECTION_POOL_MAX_TOTAL_CONNECTION_PER_HOST) ) 
386 							? Integer.parseInt(AppPropertiesService
387 									.getProperty(PROPERTY_CONNECTION_POOL_MAX_TOTAL_CONNECTION_PER_HOST))
388 							: null);
389 
390 		} catch (NumberFormatException e) {
391 			AppLogService.error("Error during initialisation of Connection Pool Maxt Total Connection Per Host ", e);
392 		}
393        
394         
395         }
396 
397     /**
398      * Default Response status Validation
399      * 
400      * @param nStatus
401      *            The status
402      * @return true if Response code is authorized
403      */
404     @Override
405     public boolean validate( int nStatus )
406     {
407         return _responseValidator.validate( nStatus );
408     }
409 
410    
411 }