001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.component.http4;
018    
019    import java.net.URI;
020    import java.security.NoSuchAlgorithmException;
021    import java.util.HashMap;
022    import java.util.Map;
023    
024    import org.apache.camel.Endpoint;
025    import org.apache.camel.ResolveEndpointFailedException;
026    import org.apache.camel.impl.HeaderFilterStrategyComponent;
027    import org.apache.camel.util.CastUtils;
028    import org.apache.camel.util.IntrospectionSupport;
029    import org.apache.camel.util.URISupport;
030    import org.apache.http.auth.params.AuthParamBean;
031    import org.apache.http.client.params.ClientParamBean;
032    import org.apache.http.conn.ClientConnectionManager;
033    import org.apache.http.conn.params.ConnConnectionParamBean;
034    import org.apache.http.conn.params.ConnRouteParamBean;
035    import org.apache.http.conn.scheme.PlainSocketFactory;
036    import org.apache.http.conn.scheme.Scheme;
037    import org.apache.http.conn.scheme.SchemeRegistry;
038    import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
039    import org.apache.http.conn.ssl.SSLSocketFactory;
040    import org.apache.http.conn.ssl.X509HostnameVerifier;
041    import org.apache.http.cookie.params.CookieSpecParamBean;
042    import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
043    import org.apache.http.params.BasicHttpParams;
044    import org.apache.http.params.HttpConnectionParamBean;
045    import org.apache.http.params.HttpParams;
046    import org.apache.http.params.HttpProtocolParamBean;
047    import org.slf4j.Logger;
048    import org.slf4j.LoggerFactory;
049    
050    /**
051     * Defines the <a href="http://camel.apache.org/http.html">HTTP
052     * Component</a>
053     *
054     * @version 
055     */
056    public class HttpComponent extends HeaderFilterStrategyComponent {
057        private static final transient Logger LOG = LoggerFactory.getLogger(HttpComponent.class);
058    
059        protected HttpClientConfigurer httpClientConfigurer;
060        protected ClientConnectionManager clientConnectionManager;
061        protected HttpBinding httpBinding;
062        protected X509HostnameVerifier x509HostnameVerifier = new BrowserCompatHostnameVerifier();
063    
064        // options to the default created http connection manager
065        protected int maxTotalConnections = 200;
066        protected int connectionsPerRoute = 20;
067    
068        /**
069         * Connects the URL specified on the endpoint to the specified processor.
070         *
071         * @param consumer the consumer
072         * @throws Exception can be thrown
073         */
074        public void connect(HttpConsumer consumer) throws Exception {
075        }
076    
077        /**
078         * Disconnects the URL specified on the endpoint from the specified processor.
079         *
080         * @param consumer the consumer
081         * @throws Exception can be thrown
082         */
083        public void disconnect(HttpConsumer consumer) throws Exception {
084        }
085    
086        /**
087         * Creates the HttpClientConfigurer based on the given parameters
088         *
089         * @param parameters the map of parameters
090         * @return the configurer
091         */
092        protected HttpClientConfigurer createHttpClientConfigurer(Map<String, Object> parameters) {
093            // prefer to use endpoint configured over component configured
094            HttpClientConfigurer configurer = resolveAndRemoveReferenceParameter(parameters, "httpClientConfigurerRef", HttpClientConfigurer.class);
095            if (configurer == null) {
096                // try without ref
097                configurer = resolveAndRemoveReferenceParameter(parameters, "httpClientConfigurer", HttpClientConfigurer.class);
098                
099                if (configurer == null) {
100                    // fallback to component configured
101                    configurer = getHttpClientConfigurer();
102                }
103            }
104    
105            configurer = configureBasicAuthentication(parameters, configurer);
106            configurer = configureHttpProxy(parameters, configurer);
107    
108            return configurer;
109        }
110    
111        private HttpClientConfigurer configureBasicAuthentication(Map<String, Object> parameters, HttpClientConfigurer configurer) {
112            String username = getAndRemoveParameter(parameters, "username", String.class);
113            String password = getAndRemoveParameter(parameters, "password", String.class);
114    
115            if (username != null && password != null) {
116                String domain = getAndRemoveParameter(parameters, "domain", String.class);
117                String host = getAndRemoveParameter(parameters, "host", String.class);
118                
119                return CompositeHttpConfigurer.combineConfigurers(
120                        configurer,
121                        new BasicAuthenticationHttpClientConfigurer(username, password, domain, host));
122            }
123            
124            return configurer;
125        }
126    
127        private HttpClientConfigurer configureHttpProxy(Map<String, Object> parameters, HttpClientConfigurer configurer) {
128            String proxyHost = getAndRemoveParameter(parameters, "proxyHost", String.class);
129            Integer proxyPort = getAndRemoveParameter(parameters, "proxyPort", Integer.class);
130            
131            if (proxyHost != null && proxyPort != null) {
132                String proxyUsername = getAndRemoveParameter(parameters, "proxyUsername", String.class);
133                String proxyPassword = getAndRemoveParameter(parameters, "proxyPassword", String.class);
134                String proxyDomain = getAndRemoveParameter(parameters, "proxyDomain", String.class);
135                String proxyNtHost = getAndRemoveParameter(parameters, "proxyNtHost", String.class);
136                
137                if (proxyUsername != null && proxyPassword != null) {
138                    return CompositeHttpConfigurer.combineConfigurers(
139                            configurer, new ProxyHttpClientConfigurer(proxyHost, proxyPort, proxyUsername, proxyPassword, proxyDomain, proxyNtHost));
140                } else {
141                    return CompositeHttpConfigurer.combineConfigurers(
142                            configurer, new ProxyHttpClientConfigurer(proxyHost, proxyPort));
143                }
144            }
145            
146            return configurer;
147        }
148    
149        @Override
150        protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
151            String addressUri = uri;
152            if (!uri.startsWith("http4:") && !uri.startsWith("https4:")) {
153                addressUri = remaining;
154            }
155            Map<String, Object> httpClientParameters = new HashMap<String, Object>(parameters);
156            // http client can be configured from URI options
157            HttpParams clientParams = configureHttpParams(parameters);
158            // validate that we could resolve all httpClient. parameters as this component is lenient
159            validateParameters(uri, parameters, "httpClient.");
160            
161            HttpBinding httpBinding = resolveAndRemoveReferenceParameter(parameters, "httpBindingRef", HttpBinding.class);
162            if (httpBinding == null) {
163                httpBinding = resolveAndRemoveReferenceParameter(parameters, "httpBinding", HttpBinding.class);
164            }
165            
166            HttpClientConfigurer httpClientConfigurer = resolveAndRemoveReferenceParameter(parameters, "httpClientConfigurerRef", HttpClientConfigurer.class);
167            if (httpClientConfigurer == null) {
168                httpClientConfigurer = resolveAndRemoveReferenceParameter(parameters, "httpClientConfigurer", HttpClientConfigurer.class);
169            }
170            
171            X509HostnameVerifier x509HostnameVerifier = resolveAndRemoveReferenceParameter(parameters, "x509HostnameVerifier", X509HostnameVerifier.class);
172            if (x509HostnameVerifier == null) {
173                x509HostnameVerifier = this.x509HostnameVerifier;
174            }
175            
176            // create the configurer to use for this endpoint
177            HttpClientConfigurer configurer = createHttpClientConfigurer(parameters);
178            URI endpointUri = URISupport.createRemainingURI(new URI(addressUri), CastUtils.cast(httpClientParameters));
179            // restructure uri to be based on the parameters left as we dont want to include the Camel internal options
180            URI httpUri = URISupport.createRemainingURI(new URI(addressUri), CastUtils.cast(parameters));
181    
182            // validate http uri that end-user did not duplicate the http part that can be a common error
183            String part = httpUri.getSchemeSpecificPart();
184            if (part != null) {
185                part = part.toLowerCase();
186                if (part.startsWith("//http//") || part.startsWith("//https//") || part.startsWith("//http://") || part.startsWith("//https://")) {
187                    throw new ResolveEndpointFailedException(uri,
188                            "The uri part is not configured correctly. You have duplicated the http(s) protocol.");
189                }
190            }
191    
192            // register port on schema registry
193            boolean secure = isSecureConnection(uri);
194            int port = getPort(httpUri);
195            registerPort(secure, x509HostnameVerifier, port);
196            
197            // create the endpoint
198            HttpEndpoint endpoint = new HttpEndpoint(endpointUri.toString(), this, httpUri, clientParams, clientConnectionManager, configurer);
199            setProperties(endpoint, parameters);
200            setEndpointHeaderFilterStrategy(endpoint);
201            endpoint.setBinding(getHttpBinding());
202            if (httpBinding != null) {
203                endpoint.setHttpBinding(httpBinding);
204            }
205            if (httpClientConfigurer != null) {
206                endpoint.setHttpClientConfigurer(httpClientConfigurer);
207            }
208    
209            return endpoint;
210        }
211    
212        private static int getPort(URI uri) {
213            int port = uri.getPort();
214            if (port < 0) {
215                if ("http4".equals(uri.getScheme()) || "http".equals(uri.getScheme())) {
216                    port = 80;
217                } else if ("https4".equals(uri.getScheme()) || "https".equals(uri.getScheme())) {
218                    port = 443;
219                } else {
220                    throw new IllegalArgumentException("Unknown scheme, cannot determine port number for uri: " + uri);
221                }
222            }
223            return port;
224        }
225    
226        protected void registerPort(boolean secure, X509HostnameVerifier x509HostnameVerifier, int port) throws NoSuchAlgorithmException {
227            SchemeRegistry registry = clientConnectionManager.getSchemeRegistry();
228            if (secure) {
229                // must register both https and https4
230                SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
231                socketFactory.setHostnameVerifier(x509HostnameVerifier);
232                registry.register(new Scheme("https", port, socketFactory));
233                LOG.info("Registering SSL scheme https on port " + port);
234                
235                socketFactory = SSLSocketFactory.getSocketFactory();
236                socketFactory.setHostnameVerifier(x509HostnameVerifier);
237                registry.register(new Scheme("https4", port, socketFactory));
238                LOG.info("Registering SSL scheme https4 on port " + port);
239            } else {
240                // must register both http and http4
241                registry.register(new Scheme("http", port, new PlainSocketFactory()));
242                LOG.info("Registering PLAIN scheme http on port " + port);
243                registry.register(new Scheme("http4", port, new PlainSocketFactory()));
244                LOG.info("Registering PLAIN scheme http4 on port " + port);
245            }
246        }
247    
248        protected ClientConnectionManager createConnectionManager() {
249            SchemeRegistry schemeRegistry = new SchemeRegistry();
250    
251            ThreadSafeClientConnManager answer = new ThreadSafeClientConnManager(schemeRegistry);
252            if (getMaxTotalConnections() > 0) {
253                answer.setMaxTotal(getMaxTotalConnections());
254            }
255            if (getConnectionsPerRoute() > 0) {
256                answer.setDefaultMaxPerRoute(getConnectionsPerRoute());
257            }
258            LOG.info("Created ClientConnectionManager " + answer);
259    
260            return answer;
261        }
262    
263        protected HttpParams configureHttpParams(Map<String, Object> parameters) throws Exception {
264            HttpParams clientParams = new BasicHttpParams();
265    
266            AuthParamBean authParamBean = new AuthParamBean(clientParams);
267            IntrospectionSupport.setProperties(authParamBean, parameters, "httpClient.");
268    
269            ClientParamBean clientParamBean = new ClientParamBean(clientParams);
270            IntrospectionSupport.setProperties(clientParamBean, parameters, "httpClient.");
271    
272            ConnConnectionParamBean connConnectionParamBean = new ConnConnectionParamBean(clientParams);
273            IntrospectionSupport.setProperties(connConnectionParamBean, parameters, "httpClient.");
274    
275            ConnRouteParamBean connRouteParamBean = new ConnRouteParamBean(clientParams);
276            IntrospectionSupport.setProperties(connRouteParamBean, parameters, "httpClient.");
277    
278            CookieSpecParamBean cookieSpecParamBean = new CookieSpecParamBean(clientParams);
279            IntrospectionSupport.setProperties(cookieSpecParamBean, parameters, "httpClient.");
280    
281            HttpConnectionParamBean httpConnectionParamBean = new HttpConnectionParamBean(clientParams);
282            IntrospectionSupport.setProperties(httpConnectionParamBean, parameters, "httpClient.");
283    
284            HttpProtocolParamBean httpProtocolParamBean = new HttpProtocolParamBean(clientParams);
285            IntrospectionSupport.setProperties(httpProtocolParamBean, parameters, "httpClient.");
286    
287            return clientParams;
288        }
289    
290        private boolean isSecureConnection(String uri) {
291            return uri.startsWith("https");
292        }
293    
294        @Override
295        protected boolean useIntrospectionOnEndpoint() {
296            return false;
297        }
298    
299        public HttpClientConfigurer getHttpClientConfigurer() {
300            return httpClientConfigurer;
301        }
302    
303        public void setHttpClientConfigurer(HttpClientConfigurer httpClientConfigurer) {
304            this.httpClientConfigurer = httpClientConfigurer;
305        }
306    
307        public ClientConnectionManager getClientConnectionManager() {
308            return clientConnectionManager;
309        }
310    
311        public void setClientConnectionManager(ClientConnectionManager clientConnectionManager) {
312            this.clientConnectionManager = clientConnectionManager;
313        }
314    
315        public HttpBinding getHttpBinding() {
316            return httpBinding;
317        }
318    
319        public void setHttpBinding(HttpBinding httpBinding) {
320            this.httpBinding = httpBinding;
321        }
322    
323        public int getMaxTotalConnections() {
324            return maxTotalConnections;
325        }
326    
327        public void setMaxTotalConnections(int maxTotalConnections) {
328            this.maxTotalConnections = maxTotalConnections;
329        }
330    
331        public int getConnectionsPerRoute() {
332            return connectionsPerRoute;
333        }
334    
335        public void setConnectionsPerRoute(int connectionsPerRoute) {
336            this.connectionsPerRoute = connectionsPerRoute;
337        }
338    
339        @Override
340        public void doStart() throws Exception {
341            super.doStart();
342            if (clientConnectionManager == null) {
343                clientConnectionManager = createConnectionManager();
344            }
345        }
346    
347        @Override
348        public void doStop() throws Exception {
349            // shutdown connection manager
350            if (clientConnectionManager != null) {
351                LOG.info("Shutting down ClientConnectionManager: " + clientConnectionManager);
352                clientConnectionManager.shutdown();
353                clientConnectionManager = null;
354            }
355            super.doStop();
356        }
357    }