/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.camel.component.http; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import org.apache.camel.CamelContext; import org.apache.camel.ComponentVerifier; import org.apache.camel.Endpoint; import org.apache.camel.Producer; import org.apache.camel.ResolveEndpointFailedException; import org.apache.camel.SSLContextParametersAware; import org.apache.camel.VerifiableComponent; import org.apache.camel.http.common.HttpBinding; import org.apache.camel.http.common.HttpCommonComponent; import org.apache.camel.http.common.HttpConfiguration; import org.apache.camel.http.common.HttpRestHeaderFilterStrategy; import org.apache.camel.http.common.UrlRewrite; import org.apache.camel.spi.HeaderFilterStrategy; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.RestProducerFactory; import org.apache.camel.util.FileUtil; import org.apache.camel.util.IntrospectionSupport; import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.ServiceHelper; import org.apache.camel.util.URISupport; import org.apache.camel.util.UnsafeUriCharactersEncoder; import org.apache.commons.httpclient.HttpConnectionManager; import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; import org.apache.commons.httpclient.params.HttpClientParams; import org.apache.commons.httpclient.params.HttpConnectionManagerParams; /** * The <a href="http://camel.apache.org/http.html">HTTP Component</a> * */ @Metadata(label = "verifiers", enums = "parameters,connectivity") public class HttpComponent extends HttpCommonComponent implements RestProducerFactory, VerifiableComponent, SSLContextParametersAware { @Metadata(label = "advanced") protected HttpClientConfigurer httpClientConfigurer; @Metadata(label = "advanced") protected HttpConnectionManager httpConnectionManager; @Metadata(label = "security", defaultValue = "false") private boolean useGlobalSslContextParameters; public HttpComponent() { super(HttpEndpoint.class); } public HttpComponent(Class<? extends HttpEndpoint> endpointClass) { super(endpointClass); } /** * Creates the HttpClientConfigurer based on the given parameters * * @param parameters the map of parameters * @return the configurer */ protected HttpClientConfigurer createHttpClientConfigurer(Map<String, Object> parameters, Set<AuthMethod> authMethods) { // prefer to use endpoint configured over component configured HttpClientConfigurer configurer = resolveAndRemoveReferenceParameter(parameters, "httpClientConfigurer", HttpClientConfigurer.class); if (configurer == null) { // fallback to component configured configurer = getHttpClientConfigurer(); } // authentication can be endpoint configured String authUsername = getParameter(parameters, "authUsername", String.class); String authMethod = getParameter(parameters, "authMethod", String.class); // validate that if auth username is given then the auth method is also provided if (authUsername != null && authMethod == null) { throw new IllegalArgumentException("Option authMethod must be provided to use authentication"); } if (authMethod != null) { String authPassword = getParameter(parameters, "authPassword", String.class); String authDomain = getParameter(parameters, "authDomain", String.class); String authHost = getParameter(parameters, "authHost", String.class); configurer = configureAuth(configurer, authMethod, authUsername, authPassword, authDomain, authHost, authMethods); } else if (httpConfiguration != null) { // or fallback to use component configuration configurer = configureAuth(configurer, httpConfiguration.getAuthMethod(), httpConfiguration.getAuthUsername(), httpConfiguration.getAuthPassword(), httpConfiguration.getAuthDomain(), httpConfiguration.getAuthHost(), authMethods); } // proxy authentication can be endpoint configured String proxyAuthUsername = getParameter(parameters, "proxyAuthUsername", String.class); String proxyAuthMethod = getParameter(parameters, "proxyAuthMethod", String.class); // validate that if proxy auth username is given then the proxy auth method is also provided if (proxyAuthUsername != null && proxyAuthMethod == null) { throw new IllegalArgumentException("Option proxyAuthMethod must be provided to use proxy authentication"); } if (proxyAuthMethod != null) { String proxyAuthPassword = getParameter(parameters, "proxyAuthPassword", String.class); String proxyAuthDomain = getParameter(parameters, "proxyAuthDomain", String.class); String proxyAuthHost = getParameter(parameters, "proxyAuthHost", String.class); configurer = configureProxyAuth(configurer, proxyAuthMethod, proxyAuthUsername, proxyAuthPassword, proxyAuthDomain, proxyAuthHost, authMethods); } else if (httpConfiguration != null) { // or fallback to use component configuration configurer = configureProxyAuth(configurer, httpConfiguration.getProxyAuthMethod(), httpConfiguration.getProxyAuthUsername(), httpConfiguration.getProxyAuthPassword(), httpConfiguration.getProxyAuthDomain(), httpConfiguration.getProxyAuthHost(), authMethods); } return configurer; } /** * Configures the authentication method to be used * * @return configurer to used */ protected HttpClientConfigurer configureAuth(HttpClientConfigurer configurer, String authMethod, String username, String password, String domain, String host, Set<AuthMethod> authMethods) { // no auth is in use if (username == null && authMethod == null) { return configurer; } // validate mandatory options given if (username != null && authMethod == null) { throw new IllegalArgumentException("Option authMethod must be provided to use authentication"); } ObjectHelper.notNull(authMethod, "authMethod"); ObjectHelper.notNull(username, "authUsername"); ObjectHelper.notNull(password, "authPassword"); AuthMethod auth = getCamelContext().getTypeConverter().convertTo(AuthMethod.class, authMethod); // add it as a auth method used authMethods.add(auth); if (auth == AuthMethod.Basic || auth == AuthMethod.Digest) { return CompositeHttpConfigurer.combineConfigurers(configurer, new BasicAuthenticationHttpClientConfigurer(false, username, password)); } else if (auth == AuthMethod.NTLM) { // domain is mandatory for NTLM ObjectHelper.notNull(domain, "authDomain"); return CompositeHttpConfigurer.combineConfigurers(configurer, new NTLMAuthenticationHttpClientConfigurer(false, username, password, domain, host)); } throw new IllegalArgumentException("Unknown authMethod " + authMethod); } /** * Configures the proxy authentication method to be used * * @return configurer to used */ protected HttpClientConfigurer configureProxyAuth(HttpClientConfigurer configurer, String authMethod, String username, String password, String domain, String host, Set<AuthMethod> authMethods) { // no proxy auth is in use if (username == null && authMethod == null) { return configurer; } // validate mandatory options given if (username != null && authMethod == null) { throw new IllegalArgumentException("Option proxyAuthMethod must be provided to use proxy authentication"); } ObjectHelper.notNull(authMethod, "proxyAuthMethod"); ObjectHelper.notNull(username, "proxyAuthUsername"); ObjectHelper.notNull(password, "proxyAuthPassword"); AuthMethod auth = getCamelContext().getTypeConverter().convertTo(AuthMethod.class, authMethod); // add it as a auth method used authMethods.add(auth); if (auth == AuthMethod.Basic || auth == AuthMethod.Digest) { return CompositeHttpConfigurer.combineConfigurers(configurer, new BasicAuthenticationHttpClientConfigurer(true, username, password)); } else if (auth == AuthMethod.NTLM) { // domain is mandatory for NTML ObjectHelper.notNull(domain, "proxyAuthDomain"); return CompositeHttpConfigurer.combineConfigurers(configurer, new NTLMAuthenticationHttpClientConfigurer(true, username, password, domain, host)); } throw new IllegalArgumentException("Unknown proxyAuthMethod " + authMethod); } @Override protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { String addressUri = "http://" + remaining; if (uri.startsWith("https:")) { addressUri = "https://" + remaining; } Map<String, Object> httpClientParameters = new HashMap<String, Object>(parameters); // must extract well known parameters before we create the endpoint HttpBinding binding = resolveAndRemoveReferenceParameter(parameters, "httpBinding", HttpBinding.class); HeaderFilterStrategy headerFilterStrategy = resolveAndRemoveReferenceParameter(parameters, "headerFilterStrategy", HeaderFilterStrategy.class); UrlRewrite urlRewrite = resolveAndRemoveReferenceParameter(parameters, "urlRewrite", UrlRewrite.class); // http client can be configured from URI options HttpClientParams clientParams = new HttpClientParams(); Map<String, Object> httpClientOptions = IntrospectionSupport.extractProperties(parameters, "httpClient."); IntrospectionSupport.setProperties(clientParams, httpClientOptions); // validate that we could resolve all httpClient. parameters as this component is lenient validateParameters(uri, httpClientOptions, null); // http client can be configured from URI options HttpConnectionManagerParams connectionManagerParams = new HttpConnectionManagerParams(); // setup the httpConnectionManagerParams Map<String, Object> httpConnectionManagerOptions = IntrospectionSupport.extractProperties(parameters, "httpConnectionManager."); IntrospectionSupport.setProperties(connectionManagerParams, httpConnectionManagerOptions); // validate that we could resolve all httpConnectionManager. parameters as this component is lenient validateParameters(uri, httpConnectionManagerOptions, null); // make sure the component httpConnectionManager is take effect HttpConnectionManager thisHttpConnectionManager = httpConnectionManager; if (thisHttpConnectionManager == null) { // only set the params on the new created http connection manager thisHttpConnectionManager = new MultiThreadedHttpConnectionManager(); thisHttpConnectionManager.setParams(connectionManagerParams); } // create the configurer to use for this endpoint (authMethods contains the used methods created by the configurer) final Set<AuthMethod> authMethods = new LinkedHashSet<AuthMethod>(); HttpClientConfigurer configurer = createHttpClientConfigurer(parameters, authMethods); addressUri = UnsafeUriCharactersEncoder.encodeHttpURI(addressUri); URI endpointUri = URISupport.createRemainingURI(new URI(addressUri), httpClientParameters); // create the endpoint and connectionManagerParams already be set HttpEndpoint endpoint = createHttpEndpoint(endpointUri.toString(), this, clientParams, thisHttpConnectionManager, configurer); // configure the endpoint with the common configuration from the component if (getHttpConfiguration() != null) { Map<String, Object> properties = new HashMap<>(); IntrospectionSupport.getProperties(getHttpConfiguration(), properties, null); setProperties(endpoint, properties); } if (headerFilterStrategy != null) { endpoint.setHeaderFilterStrategy(headerFilterStrategy); } else { setEndpointHeaderFilterStrategy(endpoint); } if (urlRewrite != null) { // let CamelContext deal with the lifecycle of the url rewrite // this ensures its being shutdown when Camel shutdown etc. getCamelContext().addService(urlRewrite); endpoint.setUrlRewrite(urlRewrite); } // prefer to use endpoint configured over component configured if (binding == null) { // fallback to component configured binding = getHttpBinding(); } if (binding != null) { endpoint.setBinding(binding); } setProperties(endpoint, parameters); // restructure uri to be based on the parameters left as we dont want to include the Camel internal options URI httpUri = URISupport.createRemainingURI(new URI(addressUri), parameters); // validate http uri that end-user did not duplicate the http part that can be a common error String part = httpUri.getSchemeSpecificPart(); if (part != null) { part = part.toLowerCase(); if (part.startsWith("//http//") || part.startsWith("//https//") || part.startsWith("//http://") || part.startsWith("//https://")) { throw new ResolveEndpointFailedException(uri, "The uri part is not configured correctly. You have duplicated the http(s) protocol."); } } endpoint.setHttpUri(httpUri); endpoint.setHttpClientOptions(httpClientOptions); return endpoint; } protected HttpEndpoint createHttpEndpoint(String uri, HttpComponent component, HttpClientParams clientParams, HttpConnectionManager connectionManager, HttpClientConfigurer configurer) throws URISyntaxException { return new HttpEndpoint(uri, component, clientParams, connectionManager, configurer); } @Override public Producer createProducer(CamelContext camelContext, String host, String verb, String basePath, String uriTemplate, String queryParameters, String consumes, String produces, Map<String, Object> parameters) throws Exception { // avoid leading slash basePath = FileUtil.stripLeadingSeparator(basePath); uriTemplate = FileUtil.stripLeadingSeparator(uriTemplate); // get the endpoint String url = host; if (!ObjectHelper.isEmpty(basePath)) { url += "/" + basePath; } if (!ObjectHelper.isEmpty(uriTemplate)) { url += "/" + uriTemplate; } HttpEndpoint endpoint = camelContext.getEndpoint(url, HttpEndpoint.class); if (parameters != null && !parameters.isEmpty()) { setProperties(camelContext, endpoint, parameters); } String path = uriTemplate != null ? uriTemplate : basePath; endpoint.setHeaderFilterStrategy(new HttpRestHeaderFilterStrategy(path, queryParameters)); // the endpoint must be started before creating the producer ServiceHelper.startService(endpoint); return endpoint.createProducer(); } public HttpClientConfigurer getHttpClientConfigurer() { return httpClientConfigurer; } /** * To use the custom HttpClientConfigurer to perform configuration of the HttpClient that will be used. */ public void setHttpClientConfigurer(HttpClientConfigurer httpClientConfigurer) { this.httpClientConfigurer = httpClientConfigurer; } public HttpConnectionManager getHttpConnectionManager() { return httpConnectionManager; } /** * To use a custom HttpConnectionManager to manage connections */ public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) { this.httpConnectionManager = httpConnectionManager; } /** * To use a custom HttpBinding to control the mapping between Camel message and HttpClient. */ @Override public void setHttpBinding(HttpBinding httpBinding) { // need to override and call super for component docs super.setHttpBinding(httpBinding); } /** * To use the shared HttpConfiguration as base configuration. */ @Override public void setHttpConfiguration(HttpConfiguration httpConfiguration) { // need to override and call super for component docs super.setHttpConfiguration(httpConfiguration); } /** * Whether to allow java serialization when a request uses context-type=application/x-java-serialized-object * <p/> * This is by default turned off. If you enable this then be aware that Java will deserialize the incoming * data from the request to Java and that can be a potential security risk. */ @Override public void setAllowJavaSerializedObject(boolean allowJavaSerializedObject) { // need to override and call super for component docs super.setAllowJavaSerializedObject(allowJavaSerializedObject); } @Override public boolean isUseGlobalSslContextParameters() { return this.useGlobalSslContextParameters; } /** * Enable usage of global SSL context parameters. */ @Override public void setUseGlobalSslContextParameters(boolean useGlobalSslContextParameters) { this.useGlobalSslContextParameters = useGlobalSslContextParameters; } /** * TODO: document */ public ComponentVerifier getVerifier() { return new HttpComponentVerifier(this); } }