/**
* 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.ahc;
import java.net.URI;
import java.util.Map;
import javax.net.ssl.SSLContext;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.JdkSslContext;
import org.apache.camel.AsyncEndpoint;
import org.apache.camel.Consumer;
import org.apache.camel.Processor;
import org.apache.camel.Producer;
import org.apache.camel.http.common.cookie.CookieHandler;
import org.apache.camel.impl.DefaultEndpoint;
import org.apache.camel.spi.HeaderFilterStrategy;
import org.apache.camel.spi.HeaderFilterStrategyAware;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.UriEndpoint;
import org.apache.camel.spi.UriParam;
import org.apache.camel.spi.UriPath;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.jsse.SSLContextParameters;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.AsyncHttpClientConfig;
import org.asynchttpclient.DefaultAsyncHttpClient;
import org.asynchttpclient.DefaultAsyncHttpClientConfig;
/**
* To call external HTTP services using <a href="http://github.com/sonatype/async-http-client">Async Http Client</a>.
*/
@UriEndpoint(firstVersion = "2.8.0", scheme = "ahc", title = "AHC", syntax = "ahc:httpUri", producerOnly = true, label = "http", lenientProperties = true)
public class AhcEndpoint extends DefaultEndpoint implements AsyncEndpoint, HeaderFilterStrategyAware {
private AsyncHttpClient client;
@UriPath @Metadata(required = "true")
private URI httpUri;
@UriParam
private boolean bridgeEndpoint;
@UriParam(defaultValue = "true")
private boolean throwExceptionOnFailure = true;
@UriParam
private boolean transferException;
@UriParam(defaultValue = "" + 4 * 1024)
private int bufferSize = 4 * 1024;
@UriParam
private HeaderFilterStrategy headerFilterStrategy = new HttpHeaderFilterStrategy();
@UriParam(label = "advanced")
private AhcBinding binding;
@UriParam(label = "security")
private SSLContextParameters sslContextParameters;
@UriParam(label = "advanced")
private AsyncHttpClientConfig clientConfig;
@UriParam(label = "advanced", prefix = "clientConfig.", multiValue = true)
private Map<String, Object> clientConfigOptions;
@UriParam(label = "advanced,security", prefix = "clientConfig.realm.", multiValue = true)
private Map<String, Object> clientConfigRealmOptions;
@UriParam(label = "producer", defaultValue = "false")
private boolean connectionClose;
@UriParam(label = "producer")
private CookieHandler cookieHandler;
public AhcEndpoint(String endpointUri, AhcComponent component, URI httpUri) {
super(endpointUri, component);
this.httpUri = httpUri;
}
@Override
public AhcComponent getComponent() {
return (AhcComponent) super.getComponent();
}
@Override
public Producer createProducer() throws Exception {
ObjectHelper.notNull(client, "AsyncHttpClient", this);
ObjectHelper.notNull(httpUri, "HttpUri", this);
ObjectHelper.notNull(binding, "AhcBinding", this);
return new AhcProducer(this);
}
@Override
public Consumer createConsumer(Processor processor) throws Exception {
throw new UnsupportedOperationException("This component does not support consuming from this endpoint");
}
@Override
public boolean isLenientProperties() {
// true to allow dynamic URI options to be configured and passed to external system for eg. the HttpProducer
return true;
}
@Override
public boolean isSingleton() {
return true;
}
public AsyncHttpClient getClient() {
return client;
}
/**
* To use a custom {@link AsyncHttpClient}
*/
public void setClient(AsyncHttpClient client) {
this.client = client;
}
public AsyncHttpClientConfig getClientConfig() {
return clientConfig;
}
/**
* To configure the AsyncHttpClient to use a custom com.ning.http.client.AsyncHttpClientConfig instance.
*/
public void setClientConfig(AsyncHttpClientConfig clientConfig) {
this.clientConfig = clientConfig;
}
public URI getHttpUri() {
return httpUri;
}
/**
* The URI to use such as http://hostname:port/path
*/
public void setHttpUri(URI httpUri) {
this.httpUri = httpUri;
}
public AhcBinding getBinding() {
return binding;
}
/**
* To use a custom {@link AhcBinding} which allows to control how to bind between AHC and Camel.
*/
public void setBinding(AhcBinding binding) {
this.binding = binding;
}
public HeaderFilterStrategy getHeaderFilterStrategy() {
return headerFilterStrategy;
}
/**
* To use a custom HeaderFilterStrategy to filter header to and from Camel message.
*/
public void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy) {
this.headerFilterStrategy = headerFilterStrategy;
}
public boolean isBridgeEndpoint() {
return bridgeEndpoint;
}
/**
* If the option is true, then the Exchange.HTTP_URI header is ignored, and use the endpoint's URI for request.
* You may also set the throwExceptionOnFailure to be false to let the AhcProducer send all the fault response back.
*/
public void setBridgeEndpoint(boolean bridgeEndpoint) {
this.bridgeEndpoint = bridgeEndpoint;
}
public boolean isThrowExceptionOnFailure() {
return throwExceptionOnFailure;
}
/**
* Option to disable throwing the AhcOperationFailedException in case of failed responses from the remote server.
* This allows you to get all responses regardless of the HTTP status code.
*/
public void setThrowExceptionOnFailure(boolean throwExceptionOnFailure) {
this.throwExceptionOnFailure = throwExceptionOnFailure;
}
public boolean isTransferException() {
return transferException;
}
/**
* If enabled and an Exchange failed processing on the consumer side, and if the caused Exception was send back serialized
* in the response as a application/x-java-serialized-object content type (for example using Jetty or Servlet Camel components).
* On the producer side the exception will be deserialized and thrown as is, instead of the AhcOperationFailedException.
* The caused exception is required to be serialized.
* <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.
*/
public void setTransferException(boolean transferException) {
this.transferException = transferException;
}
public SSLContextParameters getSslContextParameters() {
return sslContextParameters;
}
/**
* Reference to a org.apache.camel.util.jsse.SSLContextParameters in the Registry.
* This reference overrides any configured SSLContextParameters at the component level.
* See Using the JSSE Configuration Utility.
* Note that configuring this option will override any SSL/TLS configuration options provided through the clientConfig option at the endpoint or component level.
*/
public void setSslContextParameters(SSLContextParameters sslContextParameters) {
this.sslContextParameters = sslContextParameters;
}
public int getBufferSize() {
return bufferSize;
}
/**
* The initial in-memory buffer size used when transferring data between Camel and AHC Client.
*/
public void setBufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
public Map<String, Object> getClientConfigOptions() {
return clientConfigOptions;
}
/**
* To configure the AsyncHttpClientConfig using the key/values from the Map.
*/
public void setClientConfigOptions(Map<String, Object> clientConfigOptions) {
this.clientConfigOptions = clientConfigOptions;
}
public Map<String, Object> getClientConfigRealmOptions() {
return clientConfigRealmOptions;
}
/**
* To configure the AsyncHttpClientConfig Realm using the key/values from the Map.
*/
public void setClientConfigRealmOptions(Map<String, Object> clientConfigRealmOptions) {
this.clientConfigRealmOptions = clientConfigRealmOptions;
}
public boolean isConnectionClose() {
return connectionClose;
}
/**
* Define if the Connection Close header has to be added to HTTP Request. This parameter is false by default
*/
public void setConnectionClose(boolean connectionClose) {
this.connectionClose = connectionClose;
}
public CookieHandler getCookieHandler() {
return cookieHandler;
}
/**
* Configure a cookie handler to maintain a HTTP session
*/
public void setCookieHandler(CookieHandler cookieHandler) {
this.cookieHandler = cookieHandler;
}
@Override
protected void doStart() throws Exception {
super.doStart();
if (client == null) {
AsyncHttpClientConfig config = null;
if (clientConfig != null) {
DefaultAsyncHttpClientConfig.Builder builder = AhcComponent.cloneConfig(clientConfig);
if (sslContextParameters != null) {
SSLContext sslContext = sslContextParameters.createSSLContext(getCamelContext());
JdkSslContext ssl = new JdkSslContext(sslContext, true, ClientAuth.REQUIRE);
builder.setSslContext(ssl);
}
config = builder.build();
} else {
if (sslContextParameters != null) {
DefaultAsyncHttpClientConfig.Builder builder = new DefaultAsyncHttpClientConfig.Builder();
SSLContext sslContext = sslContextParameters.createSSLContext(getCamelContext());
JdkSslContext ssl = new JdkSslContext(sslContext, true, ClientAuth.REQUIRE);
builder.setSslContext(ssl);
config = builder.build();
}
}
client = createClient(config);
}
}
protected AsyncHttpClient createClient(AsyncHttpClientConfig config) {
if (config == null) {
return new DefaultAsyncHttpClient();
} else {
return new DefaultAsyncHttpClient(config);
}
}
@Override
protected void doStop() throws Exception {
super.doStop();
// ensure client is closed when stopping
if (client != null && !client.isClosed()) {
client.close();
}
client = null;
}
}