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: 964673 $
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 bridgeEndpoint = getAndRemoveParameter(parameters, "bridgeEndpoint", Boolean.class);
151            Boolean matchOnUriPrefix = getAndRemoveParameter(parameters, "matchOnUriPrefix", Boolean.class);
152            Boolean disableStreamCache = getAndRemoveParameter(parameters, "disableStreamCache", Boolean.class);
153    
154            // validate that we could resolve all httpClient. parameters as this component is lenient
155            validateParameters(uri, parameters, "httpClient.");
156            // create the configurer to use for this endpoint
157            HttpClientConfigurer configurer = createHttpClientConfigurer(parameters);
158            URI endpointUri = URISupport.createRemainingURI(new URI(addressUri), CastUtils.cast(httpClientParameters));
159            // restructure uri to be based on the parameters left as we dont want to include the Camel internal options
160            URI httpUri = URISupport.createRemainingURI(new URI(addressUri), CastUtils.cast(parameters));
161            
162            // validate http uri that end-user did not duplicate the http part that can be a common error
163            String part = httpUri.getSchemeSpecificPart();
164            if (part != null) {
165                part = part.toLowerCase();
166                if (part.startsWith("//http//") || part.startsWith("//https//")) {
167                    throw new ResolveEndpointFailedException(uri,
168                            "The uri part is not configured correctly. You have duplicated the http(s) protocol.");
169                }
170            }
171    
172            // register port on schema registry
173            boolean secure = isSecureConnection(uri);
174            int port = getPort(httpUri);
175            registerPort(secure, port);
176    
177            // create the endpoint
178            HttpEndpoint endpoint = new HttpEndpoint(endpointUri.toString(), this, httpUri, clientParams, clientConnectionManager, configurer);
179            setEndpointHeaderFilterStrategy(endpoint);
180    
181            // prefer to use endpoint configured over component configured
182            if (binding == null) {
183                // fallback to component configured
184                binding = getHttpBinding();
185            }
186            if (binding != null) {
187                endpoint.setBinding(binding);
188            }
189            // should we use an exception for failed error codes?
190            if (throwExceptionOnFailure != null) {
191                endpoint.setThrowExceptionOnFailure(throwExceptionOnFailure);
192            }
193            if (bridgeEndpoint != null) {
194                endpoint.setBridgeEndpoint(bridgeEndpoint);
195            }
196            if (matchOnUriPrefix != null) {
197                endpoint.setMatchOnUriPrefix(matchOnUriPrefix);
198            }
199            if (disableStreamCache != null) {
200                endpoint.setDisableStreamCache(disableStreamCache);
201            }
202    
203            setProperties(endpoint, parameters);
204            return endpoint;
205        }
206    
207        private static int getPort(URI uri) {
208            int port = uri.getPort();
209            if (port < 0) {
210                if ("http4".equals(uri.getScheme()) || "http".equals(uri.getScheme())) {
211                    port = 80;
212                } else if ("https4".equals(uri.getScheme()) || "https".equals(uri.getScheme())) {
213                    port = 443;
214                } else {
215                    throw new IllegalArgumentException("Unknown scheme, cannot determine port number for uri: " + uri);
216                }
217            }
218            return port;
219        }
220    
221        protected void registerPort(boolean secure, int port) {
222            SchemeRegistry registry = clientConnectionManager.getSchemeRegistry();
223            if (secure) {
224                // must register both https and https4
225                registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), port));
226                LOG.info("Registering SSL scheme https on port " + port);
227                registry.register(new Scheme("https4", SSLSocketFactory.getSocketFactory(), port));
228                LOG.info("Registering SSL scheme https4 on port " + port);
229            } else {
230                // must register both http and http4
231                registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), port));
232                LOG.info("Registering PLAIN scheme http on port " + port);
233                registry.register(new Scheme("http4", PlainSocketFactory.getSocketFactory(), port));
234                LOG.info("Registering PLAIN scheme http4 on port " + port);
235            }
236        }
237    
238        protected ClientConnectionManager createConnectionManager() {
239            SchemeRegistry schemeRegistry = new SchemeRegistry();
240    
241            // configure additional configurations
242            HttpParams params = new BasicHttpParams();
243            ConnManagerParamBean param = new ConnManagerParamBean(params);
244            if (getMaxTotalConnections() > 0) {
245                param.setMaxTotalConnections(getMaxTotalConnections());
246            }
247            if (getConnectionsPerRoute() > 0) {
248                param.setConnectionsPerRoute(new ConnPerRouteBean(getConnectionsPerRoute()));
249            }
250    
251            ThreadSafeClientConnManager answer = new ThreadSafeClientConnManager(params, schemeRegistry);
252            LOG.info("Created ClientConnectionManager " + answer);
253    
254            return answer;
255        }
256    
257        protected HttpParams configureHttpParams(Map<String, Object> parameters) throws Exception {
258            HttpParams clientParams = new BasicHttpParams();
259    
260            AuthParamBean authParamBean = new AuthParamBean(clientParams);
261            IntrospectionSupport.setProperties(authParamBean, parameters, "httpClient.");
262    
263            ClientParamBean clientParamBean = new ClientParamBean(clientParams);
264            IntrospectionSupport.setProperties(clientParamBean, parameters, "httpClient.");
265    
266            ConnConnectionParamBean connConnectionParamBean = new ConnConnectionParamBean(clientParams);
267            IntrospectionSupport.setProperties(connConnectionParamBean, parameters, "httpClient.");
268    
269            ConnManagerParamBean connManagerParamBean = new ConnManagerParamBean(clientParams);
270            IntrospectionSupport.setProperties(connManagerParamBean, parameters, "httpClient.");
271    
272            ConnRouteParamBean connRouteParamBean = new ConnRouteParamBean(clientParams);
273            IntrospectionSupport.setProperties(connRouteParamBean, parameters, "httpClient.");
274    
275            CookieSpecParamBean cookieSpecParamBean = new CookieSpecParamBean(clientParams);
276            IntrospectionSupport.setProperties(cookieSpecParamBean, parameters, "httpClient.");
277    
278            HttpConnectionParamBean httpConnectionParamBean = new HttpConnectionParamBean(clientParams);
279            IntrospectionSupport.setProperties(httpConnectionParamBean, parameters, "httpClient.");
280    
281            HttpProtocolParamBean httpProtocolParamBean = new HttpProtocolParamBean(clientParams);
282            IntrospectionSupport.setProperties(httpProtocolParamBean, parameters, "httpClient.");
283    
284            return clientParams;
285        }
286    
287        private boolean isSecureConnection(String uri) {
288            return uri.startsWith("https");
289        }
290    
291        @Override
292        protected boolean useIntrospectionOnEndpoint() {
293            return false;
294        }
295    
296        public HttpClientConfigurer getHttpClientConfigurer() {
297            return httpClientConfigurer;
298        }
299    
300        public void setHttpClientConfigurer(HttpClientConfigurer httpClientConfigurer) {
301            this.httpClientConfigurer = httpClientConfigurer;
302        }
303    
304        public ClientConnectionManager getClientConnectionManager() {
305            return clientConnectionManager;
306        }
307    
308        public void setClientConnectionManager(ClientConnectionManager clientConnectionManager) {
309            this.clientConnectionManager = clientConnectionManager;
310        }
311    
312        public HttpBinding getHttpBinding() {
313            return httpBinding;
314        }
315    
316        public void setHttpBinding(HttpBinding httpBinding) {
317            this.httpBinding = httpBinding;
318        }
319    
320        public int getMaxTotalConnections() {
321            return maxTotalConnections;
322        }
323    
324        public void setMaxTotalConnections(int maxTotalConnections) {
325            this.maxTotalConnections = maxTotalConnections;
326        }
327    
328        public int getConnectionsPerRoute() {
329            return connectionsPerRoute;
330        }
331    
332        public void setConnectionsPerRoute(int connectionsPerRoute) {
333            this.connectionsPerRoute = connectionsPerRoute;
334        }
335    
336        @Override
337        public void start() throws Exception {
338            super.start();
339            if (clientConnectionManager == null) {
340                clientConnectionManager = createConnectionManager();
341            }
342        }
343    
344        @Override
345        public void stop() throws Exception {
346            // shutdown connection manager
347            if (clientConnectionManager != null) {
348                LOG.info("Shutting down ClientConnectionManager: " + clientConnectionManager);
349                clientConnectionManager.shutdown();
350                clientConnectionManager = null;
351            }
352            super.stop();
353        }
354    }