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