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.http;
018    
019    import java.net.URI;
020    import java.util.HashMap;
021    import java.util.LinkedHashSet;
022    import java.util.Map;
023    import java.util.Set;
024    
025    import org.apache.camel.Endpoint;
026    import org.apache.camel.ResolveEndpointFailedException;
027    import org.apache.camel.impl.HeaderFilterStrategyComponent;
028    import org.apache.camel.util.CastUtils;
029    import org.apache.camel.util.CollectionHelper;
030    import org.apache.camel.util.IntrospectionSupport;
031    import org.apache.camel.util.ObjectHelper;
032    import org.apache.camel.util.URISupport;
033    import org.apache.commons.httpclient.HttpConnectionManager;
034    import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
035    import org.apache.commons.httpclient.params.HttpClientParams;
036    
037    /**
038     * Defines the <a href="http://camel.apache.org/http.html">HTTP
039     * Component</a>
040     *
041     * @version 
042     */
043    public class HttpComponent extends HeaderFilterStrategyComponent {
044        protected HttpClientConfigurer httpClientConfigurer;
045        protected HttpConnectionManager httpConnectionManager = new MultiThreadedHttpConnectionManager();
046        protected HttpBinding httpBinding;
047        protected HttpConfiguration httpConfiguration;
048    
049        /**
050         * Connects the URL specified on the endpoint to the specified processor.
051         *
052         * @param consumer the consumer
053         * @throws Exception can be thrown
054         */
055        public void connect(HttpConsumer consumer) throws Exception {
056        }
057    
058        /**
059         * Disconnects the URL specified on the endpoint from the specified processor.
060         *
061         * @param consumer the consumer
062         * @throws Exception can be thrown
063         */
064        public void disconnect(HttpConsumer consumer) throws Exception {
065        }
066    
067        /** 
068         * Creates the HttpClientConfigurer based on the given parameters
069         * 
070         * @param parameters the map of parameters 
071         * @return the configurer
072         */
073        protected HttpClientConfigurer createHttpClientConfigurer(Map<String, Object> parameters, Set<AuthMethod> authMethods) {
074            // prefer to use endpoint configured over component configured
075            HttpClientConfigurer configurer = resolveAndRemoveReferenceParameter(parameters, "httpClientConfigurerRef", HttpClientConfigurer.class);
076            if (configurer == null) {
077                // try without ref
078                configurer = resolveAndRemoveReferenceParameter(parameters, "httpClientConfigurer", HttpClientConfigurer.class);
079            }
080            if (configurer == null) {
081                // fallback to component configured
082                configurer = getHttpClientConfigurer();
083            }
084    
085            // authentication can be endpoint configured
086            String authUsername = getAndRemoveParameter(parameters, "authUsername", String.class);
087            AuthMethod authMethod = getAndRemoveParameter(parameters, "authMethod", AuthMethod.class);
088            // validate that if auth username is given then the auth method is also provided
089            if (authUsername != null && authMethod == null) {
090                throw new IllegalArgumentException("Option authMethod must be provided to use authentication");
091            }
092            if (authMethod != null) {
093                String authPassword = getAndRemoveParameter(parameters, "authPassword", String.class);
094                String authDomain = getAndRemoveParameter(parameters, "authDomain", String.class);
095                String authHost = getAndRemoveParameter(parameters, "authHost", String.class);
096                configurer = configureAuth(configurer, authMethod, authUsername, authPassword, authDomain, authHost, authMethods);
097            } else if (httpConfiguration != null) {
098                // or fallback to use component configuration
099                configurer = configureAuth(configurer, httpConfiguration.getAuthMethod(), httpConfiguration.getAuthUsername(),
100                        httpConfiguration.getAuthPassword(), httpConfiguration.getAuthDomain(), httpConfiguration.getAuthHost(), authMethods);
101            }
102    
103            // proxy authentication can be endpoint configured
104            String proxyAuthUsername = getAndRemoveParameter(parameters, "proxyAuthUsername", String.class);
105            AuthMethod proxyAuthMethod = getAndRemoveParameter(parameters, "proxyAuthMethod", AuthMethod.class);
106            // validate that if proxy auth username is given then the proxy auth method is also provided
107            if (proxyAuthUsername != null && proxyAuthMethod == null) {
108                throw new IllegalArgumentException("Option proxyAuthMethod must be provided to use proxy authentication");
109            }
110            if (proxyAuthMethod != null) {
111                String proxyAuthPassword = getAndRemoveParameter(parameters, "proxyAuthPassword", String.class);
112                String proxyAuthDomain = getAndRemoveParameter(parameters, "proxyAuthDomain", String.class);
113                String proxyAuthHost = getAndRemoveParameter(parameters, "proxyAuthHost", String.class);
114                configurer = configureProxyAuth(configurer, proxyAuthMethod, proxyAuthUsername, proxyAuthPassword, proxyAuthDomain, proxyAuthHost, authMethods);
115            } else if (httpConfiguration != null) {
116                // or fallback to use component configuration
117                configurer = configureProxyAuth(configurer, httpConfiguration.getProxyAuthMethod(), httpConfiguration.getProxyAuthUsername(),
118                        httpConfiguration.getProxyAuthPassword(), httpConfiguration.getProxyAuthDomain(), httpConfiguration.getProxyAuthHost(), authMethods);
119            }
120    
121            return configurer;
122        }
123    
124        /**
125         * Configures the authentication method to be used
126         *
127         * @return configurer to used
128         */
129        protected HttpClientConfigurer configureAuth(HttpClientConfigurer configurer, AuthMethod authMethod, String username,
130                                                     String password, String domain, String host, Set<AuthMethod> authMethods) {
131    
132            // no auth is in use
133            if (username == null && authMethod == null) {
134                return configurer;
135            }
136    
137            // validate mandatory options given
138            if (username != null && authMethod == null) {
139                throw new IllegalArgumentException("Option authMethod must be provided to use authentication");
140            }
141            ObjectHelper.notNull(authMethod, "authMethod");
142            ObjectHelper.notNull(username, "authUsername");
143            ObjectHelper.notNull(password, "authPassword");
144    
145            // add it as a auth method used
146            authMethods.add(authMethod);
147    
148            if (authMethod == AuthMethod.Basic || authMethod == AuthMethod.Digest) {
149                return CompositeHttpConfigurer.combineConfigurers(configurer,
150                        new BasicAuthenticationHttpClientConfigurer(false, username, password));
151            } else if (authMethod == AuthMethod.NTLM) {
152                // domain is mandatory for NTLM
153                ObjectHelper.notNull(domain, "authDomain");
154                return CompositeHttpConfigurer.combineConfigurers(configurer,
155                        new NTLMAuthenticationHttpClientConfigurer(false, username, password, domain, host));
156            }
157    
158            throw new IllegalArgumentException("Unknown authMethod " + authMethod);
159        }
160        
161        /**
162         * Configures the proxy authentication method to be used
163         *
164         * @return configurer to used
165         */
166        protected HttpClientConfigurer configureProxyAuth(HttpClientConfigurer configurer, AuthMethod authMethod, String username,
167                                                          String password, String domain, String host, Set<AuthMethod> authMethods) {
168            // no proxy auth is in use
169            if (username == null && authMethod == null) {
170                return configurer;
171            }
172    
173            // validate mandatory options given
174            if (username != null && authMethod == null) {
175                throw new IllegalArgumentException("Option proxyAuthMethod must be provided to use proxy authentication");
176            }
177            ObjectHelper.notNull(authMethod, "proxyAuthMethod");
178            ObjectHelper.notNull(username, "proxyAuthUsername");
179            ObjectHelper.notNull(password, "proxyAuthPassword");
180    
181            // add it as a auth method used
182            authMethods.add(authMethod);
183    
184            if (authMethod == AuthMethod.Basic || authMethod == AuthMethod.Digest) {
185                return CompositeHttpConfigurer.combineConfigurers(configurer,
186                        new BasicAuthenticationHttpClientConfigurer(true, username, password));
187            } else if (authMethod == AuthMethod.NTLM) {
188                // domain is mandatory for NTML
189                ObjectHelper.notNull(domain, "proxyAuthDomain");
190                return CompositeHttpConfigurer.combineConfigurers(configurer,
191                        new NTLMAuthenticationHttpClientConfigurer(true, username, password, domain, host));
192            }
193    
194            throw new IllegalArgumentException("Unknown proxyAuthMethod " + authMethod);
195        }
196    
197        @Override
198        protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
199            String addressUri = uri;
200            if (!uri.startsWith("http:") && !uri.startsWith("https:")) {
201                addressUri = remaining;
202            }
203            Map<String, Object> httpClientParameters = new HashMap<String, Object>(parameters);
204            // must extract well known parameters before we create the endpoint
205            HttpBinding binding = resolveAndRemoveReferenceParameter(parameters, "httpBindingRef", HttpBinding.class);
206            if (binding == null) {
207                // try without ref
208                binding = resolveAndRemoveReferenceParameter(parameters, "httpBinding", HttpBinding.class);
209            }
210            Boolean throwExceptionOnFailure = getAndRemoveParameter(parameters, "throwExceptionOnFailure", Boolean.class);
211            Boolean transferException = getAndRemoveParameter(parameters, "transferException", Boolean.class);
212            Boolean bridgeEndpoint = getAndRemoveParameter(parameters, "bridgeEndpoint", Boolean.class);
213            Boolean matchOnUriPrefix = getAndRemoveParameter(parameters, "matchOnUriPrefix", Boolean.class);
214            Boolean disableStreamCache = getAndRemoveParameter(parameters, "disableStreamCache", Boolean.class);
215            String proxyHost = getAndRemoveParameter(parameters, "proxyHost", String.class);
216            Integer proxyPort = getAndRemoveParameter(parameters, "proxyPort", Integer.class);
217            String authMethodPriority = getAndRemoveParameter(parameters, "authMethodPriority", String.class);
218            // http client can be configured from URI options
219            HttpClientParams clientParams = new HttpClientParams();
220            IntrospectionSupport.setProperties(clientParams, parameters, "httpClient.");
221            // validate that we could resolve all httpClient. parameters as this component is lenient
222            validateParameters(uri, parameters, "httpClient.");       
223            
224            // create the configurer to use for this endpoint (authMethods contains the used methods created by the configurer)
225            final Set<AuthMethod> authMethods = new LinkedHashSet<AuthMethod>();
226            HttpClientConfigurer configurer = createHttpClientConfigurer(parameters, authMethods);
227            URI endpointUri = URISupport.createRemainingURI(new URI(addressUri), CastUtils.cast(httpClientParameters));
228            // restructure uri to be based on the parameters left as we dont want to include the Camel internal options
229            URI httpUri = URISupport.createRemainingURI(new URI(addressUri), CastUtils.cast(parameters));
230            
231            // validate http uri that end-user did not duplicate the http part that can be a common error
232            String part = httpUri.getSchemeSpecificPart();
233            if (part != null) {
234                part = part.toLowerCase();
235                if (part.startsWith("//http//") || part.startsWith("//https//") || part.startsWith("//http://") || part.startsWith("//https://")) {
236                    throw new ResolveEndpointFailedException(uri,
237                            "The uri part is not configured correctly. You have duplicated the http(s) protocol.");
238                }
239            }
240            // need to keep the parameters of http client configure to avoid unwiser endpoint caching
241    
242            // create the endpoint
243            HttpEndpoint endpoint = new HttpEndpoint(endpointUri.toString(), this, httpUri, clientParams, httpConnectionManager, configurer);
244            setEndpointHeaderFilterStrategy(endpoint);
245    
246            // prefer to use endpoint configured over component configured
247            if (binding == null) {
248                // fallback to component configured
249                binding = getHttpBinding();
250            }
251            if (binding != null) {
252                endpoint.setBinding(binding);
253            }
254            // should we use an exception for failed error codes?
255            if (throwExceptionOnFailure != null) {
256                endpoint.setThrowExceptionOnFailure(throwExceptionOnFailure);
257            }
258            // should we transfer exception as serialized object
259            if (transferException != null) {
260                endpoint.setTransferException(transferException);
261            }
262            if (bridgeEndpoint != null) {
263                endpoint.setBridgeEndpoint(bridgeEndpoint);
264            }
265            if (matchOnUriPrefix != null) {
266                endpoint.setMatchOnUriPrefix(matchOnUriPrefix);
267            }
268            if (disableStreamCache != null) {
269                endpoint.setDisableStreamCache(disableStreamCache);
270            }
271            if (proxyHost != null) {
272                endpoint.setProxyHost(proxyHost);
273                endpoint.setProxyPort(proxyPort);
274            } else if (httpConfiguration != null) {
275                endpoint.setProxyHost(httpConfiguration.getProxyHost());
276                endpoint.setProxyPort(httpConfiguration.getProxyPort());
277            }
278            if (authMethodPriority != null) {
279                endpoint.setAuthMethodPriority(authMethodPriority);
280            } else if (httpConfiguration != null && httpConfiguration.getAuthMethodPriority() != null) {
281                endpoint.setAuthMethodPriority(httpConfiguration.getAuthMethodPriority());
282            } else {
283                // no explicit auth method priority configured, so use convention over configuration
284                // and set priority based on auth method
285                if (!authMethods.isEmpty()) {
286                    authMethodPriority = CollectionHelper.collectionAsCommaDelimitedString(authMethods);
287                    endpoint.setAuthMethodPriority(authMethodPriority);
288                }
289            }
290    
291            setProperties(endpoint, parameters);
292            return endpoint;
293        }
294        
295        @Override
296        protected boolean useIntrospectionOnEndpoint() {
297            return false;
298        }
299    
300        public HttpClientConfigurer getHttpClientConfigurer() {
301            return httpClientConfigurer;
302        }
303    
304        public void setHttpClientConfigurer(HttpClientConfigurer httpClientConfigurer) {
305            this.httpClientConfigurer = httpClientConfigurer;
306        }
307    
308        public HttpConnectionManager getHttpConnectionManager() {
309            return httpConnectionManager;
310        }
311    
312        public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) {
313            this.httpConnectionManager = httpConnectionManager;
314        }
315    
316        public HttpBinding getHttpBinding() {
317            return httpBinding;
318        }
319    
320        public void setHttpBinding(HttpBinding httpBinding) {
321            this.httpBinding = httpBinding;
322        }
323    
324        public HttpConfiguration getHttpConfiguration() {
325            return httpConfiguration;
326        }
327    
328        public void setHttpConfiguration(HttpConfiguration httpConfiguration) {
329            this.httpConfiguration = httpConfiguration;
330        }
331    
332    }