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.util.HashMap;
021    import java.util.Map;
022    
023    import org.apache.camel.Endpoint;
024    import org.apache.camel.ResolveEndpointFailedException;
025    import org.apache.camel.impl.HeaderFilterStrategyComponent;
026    import org.apache.camel.util.CastUtils;
027    import org.apache.camel.util.IntrospectionSupport;
028    import org.apache.camel.util.URISupport;
029    import org.apache.commons.logging.Log;
030    import org.apache.commons.logging.LogFactory;
031    import org.apache.http.auth.params.AuthParamBean;
032    import org.apache.http.client.params.ClientParamBean;
033    import org.apache.http.conn.ClientConnectionManager;
034    import org.apache.http.conn.params.ConnConnectionParamBean;
035    import org.apache.http.conn.params.ConnManagerParamBean;
036    import org.apache.http.conn.params.ConnPerRouteBean;
037    import org.apache.http.conn.params.ConnRouteParamBean;
038    import org.apache.http.conn.scheme.PlainSocketFactory;
039    import org.apache.http.conn.scheme.Scheme;
040    import org.apache.http.conn.scheme.SchemeRegistry;
041    import org.apache.http.conn.ssl.SSLSocketFactory;
042    import org.apache.http.cookie.params.CookieSpecParamBean;
043    import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
044    import org.apache.http.params.BasicHttpParams;
045    import org.apache.http.params.HttpConnectionParamBean;
046    import org.apache.http.params.HttpParams;
047    import org.apache.http.params.HttpProtocolParamBean;
048    
049    /**
050     * Defines the <a href="http://camel.apache.org/http.html">HTTP
051     * Component</a>
052     *
053     * @version $Revision: 1053033 $
054     */
055    public class HttpComponent extends HeaderFilterStrategyComponent {
056        private static final transient Log LOG = LogFactory.getLog(HttpComponent.class);
057    
058        protected HttpClientConfigurer httpClientConfigurer;
059        protected ClientConnectionManager clientConnectionManager;
060        protected HttpBinding httpBinding;
061    
062        // options to the default created http connection manager
063        protected int maxTotalConnections = 200;
064        protected int connectionsPerRoute = 20;
065    
066        /**
067         * Connects the URL specified on the endpoint to the specified processor.
068         *
069         * @param consumer the consumer
070         * @throws Exception can be thrown
071         */
072        public void connect(HttpConsumer consumer) throws Exception {
073        }
074    
075        /**
076         * Disconnects the URL specified on the endpoint from the specified processor.
077         *
078         * @param consumer the consumer
079         * @throws Exception can be thrown
080         */
081        public void disconnect(HttpConsumer consumer) throws Exception {
082        }
083    
084        /**
085         * Creates the HttpClientConfigurer based on the given parameters
086         *
087         * @param parameters the map of parameters
088         * @return the configurer
089         */
090        protected HttpClientConfigurer createHttpClientConfigurer(Map<String, Object> parameters) {
091            // prefer to use endpoint configured over component configured
092            HttpClientConfigurer configurer = resolveAndRemoveReferenceParameter(parameters, "httpClientConfigurerRef", HttpClientConfigurer.class);
093            if (configurer == null) {
094                // try without ref
095                configurer = resolveAndRemoveReferenceParameter(parameters, "httpClientConfigurer", HttpClientConfigurer.class);
096            }
097            if (configurer == null) {
098                // fallback to component configured
099                configurer = getHttpClientConfigurer();
100            }
101    
102            // check the user name and password for basic authentication
103            String username = getAndRemoveParameter(parameters, "username", String.class);
104            String password = getAndRemoveParameter(parameters, "password", String.class);
105            String domain = getAndRemoveParameter(parameters, "domain", String.class);
106            String host = getAndRemoveParameter(parameters, "host", String.class);
107            if (username != null && password != null) {
108                configurer = CompositeHttpConfigurer.combineConfigurers(
109                        configurer,
110                        new BasicAuthenticationHttpClientConfigurer(username, password, domain, host));
111            }
112    
113            // check the proxy details for proxy configuration
114            String proxyHost = getAndRemoveParameter(parameters, "proxyHost", String.class);
115            Integer proxyPort = getAndRemoveParameter(parameters, "proxyPort", Integer.class);
116            if (proxyHost != null && proxyPort != null) {
117                String proxyUsername = getAndRemoveParameter(parameters, "proxyUsername", String.class);
118                String proxyPassword = getAndRemoveParameter(parameters, "proxyPassword", String.class);
119                String proxyDomain = getAndRemoveParameter(parameters, "proxyDomain", String.class);
120                String proxyNtHost = getAndRemoveParameter(parameters, "proxyNtHost", String.class);
121                if (proxyUsername != null && proxyPassword != null) {
122                    configurer = CompositeHttpConfigurer.combineConfigurers(
123                            configurer, new ProxyHttpClientConfigurer(proxyHost, proxyPort, proxyUsername, proxyPassword, proxyDomain, proxyNtHost));
124                } else {
125                    configurer = CompositeHttpConfigurer.combineConfigurers(
126                            configurer, new ProxyHttpClientConfigurer(proxyHost, proxyPort));
127                }
128            }
129    
130            return configurer;
131        }
132    
133        @Override
134        protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
135            String addressUri = uri;
136            if (!uri.startsWith("http4:") && !uri.startsWith("https4:")) {
137                addressUri = remaining;
138            }
139            Map<String, Object> httpClientParameters = new HashMap<String, Object>(parameters);
140            // http client can be configured from URI options
141            HttpParams clientParams = configureHttpParams(parameters);
142    
143            // must extract well known parameters before we create the endpoint
144            HttpBinding binding = resolveAndRemoveReferenceParameter(parameters, "httpBindingRef", HttpBinding.class);
145            if (binding == null) {
146                // try without ref
147                binding = resolveAndRemoveReferenceParameter(parameters, "httpBinding", HttpBinding.class);
148            }
149            Boolean throwExceptionOnFailure = getAndRemoveParameter(parameters, "throwExceptionOnFailure", Boolean.class);
150            Boolean transferException = getAndRemoveParameter(parameters, "transferException", Boolean.class);
151            Boolean bridgeEndpoint = getAndRemoveParameter(parameters, "bridgeEndpoint", Boolean.class);
152            Boolean matchOnUriPrefix = getAndRemoveParameter(parameters, "matchOnUriPrefix", Boolean.class);
153            Boolean disableStreamCache = getAndRemoveParameter(parameters, "disableStreamCache", Boolean.class);
154    
155            // validate that we could resolve all httpClient. parameters as this component is lenient
156            validateParameters(uri, parameters, "httpClient.");
157            // create the configurer to use for this endpoint
158            HttpClientConfigurer configurer = createHttpClientConfigurer(parameters);
159            URI endpointUri = URISupport.createRemainingURI(new URI(addressUri), CastUtils.cast(httpClientParameters));
160            // restructure uri to be based on the parameters left as we dont want to include the Camel internal options
161            URI httpUri = URISupport.createRemainingURI(new URI(addressUri), CastUtils.cast(parameters));
162    
163            // validate http uri that end-user did not duplicate the http part that can be a common error
164            String part = httpUri.getSchemeSpecificPart();
165            if (part != null) {
166                part = part.toLowerCase();
167                if (part.startsWith("//http//") || part.startsWith("//https//")) {
168                    throw new ResolveEndpointFailedException(uri,
169                            "The uri part is not configured correctly. You have duplicated the http(s) protocol.");
170                }
171            }
172    
173            // register port on schema registry
174            boolean secure = isSecureConnection(uri);
175            int port = getPort(httpUri);
176            registerPort(secure, port);
177    
178            // create the endpoint
179            HttpEndpoint endpoint = new HttpEndpoint(endpointUri.toString(), this, httpUri, clientParams, clientConnectionManager, configurer);
180            setEndpointHeaderFilterStrategy(endpoint);
181    
182            // prefer to use endpoint configured over component configured
183            if (binding == null) {
184                // fallback to component configured
185                binding = getHttpBinding();
186            }
187            if (binding != null) {
188                endpoint.setBinding(binding);
189            }
190            // should we use an exception for failed error codes?
191            if (throwExceptionOnFailure != null) {
192                endpoint.setThrowExceptionOnFailure(throwExceptionOnFailure);
193            }
194            // should we transfer exception as serialized object
195            if (transferException != null) {
196                endpoint.setTransferException(transferException);
197            }
198            if (bridgeEndpoint != null) {
199                endpoint.setBridgeEndpoint(bridgeEndpoint);
200            }
201            if (matchOnUriPrefix != null) {
202                endpoint.setMatchOnUriPrefix(matchOnUriPrefix);
203            }
204            if (disableStreamCache != null) {
205                endpoint.setDisableStreamCache(disableStreamCache);
206            }
207    
208            setProperties(endpoint, parameters);
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, int port) {
227            SchemeRegistry registry = clientConnectionManager.getSchemeRegistry();
228            if (secure) {
229                // must register both https and https4
230                registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), port));
231                LOG.info("Registering SSL scheme https on port " + port);
232                registry.register(new Scheme("https4", SSLSocketFactory.getSocketFactory(), port));
233                LOG.info("Registering SSL scheme https4 on port " + port);
234            } else {
235                // must register both http and http4
236                registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), port));
237                LOG.info("Registering PLAIN scheme http on port " + port);
238                registry.register(new Scheme("http4", PlainSocketFactory.getSocketFactory(), port));
239                LOG.info("Registering PLAIN scheme http4 on port " + port);
240            }
241        }
242    
243        protected ClientConnectionManager createConnectionManager() {
244            SchemeRegistry schemeRegistry = new SchemeRegistry();
245    
246            // configure additional configurations
247            HttpParams params = new BasicHttpParams();
248            ConnManagerParamBean param = new ConnManagerParamBean(params);
249            if (getMaxTotalConnections() > 0) {
250                param.setMaxTotalConnections(getMaxTotalConnections());
251            }
252            if (getConnectionsPerRoute() > 0) {
253                param.setConnectionsPerRoute(new ConnPerRouteBean(getConnectionsPerRoute()));
254            }
255    
256            ThreadSafeClientConnManager answer = new ThreadSafeClientConnManager(params, schemeRegistry);
257            LOG.info("Created ClientConnectionManager " + answer);
258    
259            return answer;
260        }
261    
262        protected HttpParams configureHttpParams(Map<String, Object> parameters) throws Exception {
263            HttpParams clientParams = new BasicHttpParams();
264    
265            AuthParamBean authParamBean = new AuthParamBean(clientParams);
266            IntrospectionSupport.setProperties(authParamBean, parameters, "httpClient.");
267    
268            ClientParamBean clientParamBean = new ClientParamBean(clientParams);
269            IntrospectionSupport.setProperties(clientParamBean, parameters, "httpClient.");
270    
271            ConnConnectionParamBean connConnectionParamBean = new ConnConnectionParamBean(clientParams);
272            IntrospectionSupport.setProperties(connConnectionParamBean, parameters, "httpClient.");
273    
274            ConnManagerParamBean connManagerParamBean = new ConnManagerParamBean(clientParams);
275            IntrospectionSupport.setProperties(connManagerParamBean, parameters, "httpClient.");
276    
277            ConnRouteParamBean connRouteParamBean = new ConnRouteParamBean(clientParams);
278            IntrospectionSupport.setProperties(connRouteParamBean, parameters, "httpClient.");
279    
280            CookieSpecParamBean cookieSpecParamBean = new CookieSpecParamBean(clientParams);
281            IntrospectionSupport.setProperties(cookieSpecParamBean, parameters, "httpClient.");
282    
283            HttpConnectionParamBean httpConnectionParamBean = new HttpConnectionParamBean(clientParams);
284            IntrospectionSupport.setProperties(httpConnectionParamBean, parameters, "httpClient.");
285    
286            HttpProtocolParamBean httpProtocolParamBean = new HttpProtocolParamBean(clientParams);
287            IntrospectionSupport.setProperties(httpProtocolParamBean, parameters, "httpClient.");
288    
289            return clientParams;
290        }
291    
292        private boolean isSecureConnection(String uri) {
293            return uri.startsWith("https");
294        }
295    
296        @Override
297        protected boolean useIntrospectionOnEndpoint() {
298            return false;
299        }
300    
301        public HttpClientConfigurer getHttpClientConfigurer() {
302            return httpClientConfigurer;
303        }
304    
305        public void setHttpClientConfigurer(HttpClientConfigurer httpClientConfigurer) {
306            this.httpClientConfigurer = httpClientConfigurer;
307        }
308    
309        public ClientConnectionManager getClientConnectionManager() {
310            return clientConnectionManager;
311        }
312    
313        public void setClientConnectionManager(ClientConnectionManager clientConnectionManager) {
314            this.clientConnectionManager = clientConnectionManager;
315        }
316    
317        public HttpBinding getHttpBinding() {
318            return httpBinding;
319        }
320    
321        public void setHttpBinding(HttpBinding httpBinding) {
322            this.httpBinding = httpBinding;
323        }
324    
325        public int getMaxTotalConnections() {
326            return maxTotalConnections;
327        }
328    
329        public void setMaxTotalConnections(int maxTotalConnections) {
330            this.maxTotalConnections = maxTotalConnections;
331        }
332    
333        public int getConnectionsPerRoute() {
334            return connectionsPerRoute;
335        }
336    
337        public void setConnectionsPerRoute(int connectionsPerRoute) {
338            this.connectionsPerRoute = connectionsPerRoute;
339        }
340    
341        @Override
342        public void start() throws Exception {
343            super.start();
344            if (clientConnectionManager == null) {
345                clientConnectionManager = createConnectionManager();
346            }
347        }
348    
349        @Override
350        public void stop() throws Exception {
351            // shutdown connection manager
352            if (clientConnectionManager != null) {
353                LOG.info("Shutting down ClientConnectionManager: " + clientConnectionManager);
354                clientConnectionManager.shutdown();
355                clientConnectionManager = null;
356            }
357            super.stop();
358        }
359    }