
org.apache.flume.api.NettyAvroRpcClient Maven / Gradle / Ivy
Show all versions of flume-ng-sdk Show documentation
/*
* 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.flume.api;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.apache.avro.ipc.CallFuture;
import org.apache.avro.ipc.NettyTransceiver;
import org.apache.avro.ipc.Transceiver;
import org.apache.avro.ipc.specific.SpecificRequestor;
import org.apache.commons.lang.StringUtils;
import org.apache.flume.Event;
import org.apache.flume.EventDeliveryException;
import org.apache.flume.FlumeException;
import org.apache.flume.source.avro.AvroFlumeEvent;
import org.apache.flume.source.avro.AvroSourceProtocol;
import org.apache.flume.source.avro.Status;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.socket.SocketChannel;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.handler.codec.compression.ZlibDecoder;
import org.jboss.netty.handler.codec.compression.ZlibEncoder;
import org.jboss.netty.handler.ssl.SslHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Avro/Netty implementation of {@link RpcClient}.
* The connections are intended to be opened before clients are given access so
* that the object cannot ever be in an inconsistent when exposed to users.
*/
public class NettyAvroRpcClient extends AbstractRpcClient
implements RpcClient {
private ExecutorService callTimeoutPool;
private final ReentrantLock stateLock = new ReentrantLock();
/**
* Guarded by {@code stateLock}
*/
private ConnState connState;
private InetSocketAddress address;
private boolean enableSsl;
private boolean trustAllCerts;
private String truststore;
private String truststorePassword;
private String truststoreType;
private final List excludeProtocols = new LinkedList();
private Transceiver transceiver;
private AvroSourceProtocol.Callback avroClient;
private static final Logger logger = LoggerFactory
.getLogger(NettyAvroRpcClient.class);
private boolean enableDeflateCompression;
private int compressionLevel;
private int maxIoWorkers;
/**
* This constructor is intended to be called from {@link RpcClientFactory}.
* A call to this constructor should be followed by call to configure().
*/
protected NettyAvroRpcClient(){
}
/**
* This method should only be invoked by the build function
* @throws FlumeException
*/
private void connect() throws FlumeException {
connect(connectTimeout, TimeUnit.MILLISECONDS);
}
/**
* Internal only, for now
* @param timeout
* @param tu
* @throws FlumeException
*/
private void connect(long timeout, TimeUnit tu) throws FlumeException {
callTimeoutPool = Executors.newCachedThreadPool(
new TransceiverThreadFactory("Flume Avro RPC Client Call Invoker"));
NioClientSocketChannelFactory socketChannelFactory = null;
try {
ExecutorService bossExecutor =
Executors.newCachedThreadPool(new TransceiverThreadFactory(
"Avro " + NettyTransceiver.class.getSimpleName() + " Boss"));
ExecutorService workerExecutor =
Executors.newCachedThreadPool(new TransceiverThreadFactory(
"Avro " + NettyTransceiver.class.getSimpleName() + " I/O Worker"));
if (enableDeflateCompression || enableSsl) {
if (maxIoWorkers >= 1) {
socketChannelFactory = new SSLCompressionChannelFactory(
bossExecutor, workerExecutor,
enableDeflateCompression, enableSsl, trustAllCerts,
compressionLevel, truststore, truststorePassword, truststoreType,
excludeProtocols, maxIoWorkers);
} else {
socketChannelFactory = new SSLCompressionChannelFactory(
bossExecutor, workerExecutor,
enableDeflateCompression, enableSsl, trustAllCerts,
compressionLevel, truststore, truststorePassword, truststoreType,
excludeProtocols);
}
} else {
if (maxIoWorkers >= 1) {
socketChannelFactory = new NioClientSocketChannelFactory(
bossExecutor, workerExecutor, maxIoWorkers);
} else {
socketChannelFactory = new NioClientSocketChannelFactory(
bossExecutor, workerExecutor);
}
}
transceiver = new NettyTransceiver(this.address,
socketChannelFactory,
tu.toMillis(timeout));
avroClient =
SpecificRequestor.getClient(AvroSourceProtocol.Callback.class,
transceiver);
} catch (Throwable t) {
if (callTimeoutPool != null) {
callTimeoutPool.shutdownNow();
}
if (socketChannelFactory != null) {
socketChannelFactory.releaseExternalResources();
}
if (t instanceof IOException) {
throw new FlumeException(this + ": RPC connection error", t);
} else if (t instanceof FlumeException) {
throw (FlumeException) t;
} else if (t instanceof Error) {
throw (Error) t;
} else {
throw new FlumeException(this + ": Unexpected exception", t);
}
}
setState(ConnState.READY);
}
@Override
public void close() throws FlumeException {
if (callTimeoutPool != null) {
callTimeoutPool.shutdown();
try {
if (!callTimeoutPool.awaitTermination(requestTimeout,
TimeUnit.MILLISECONDS)) {
callTimeoutPool.shutdownNow();
if (!callTimeoutPool.awaitTermination(requestTimeout,
TimeUnit.MILLISECONDS)) {
logger.warn(this + ": Unable to cleanly shut down call timeout " +
"pool");
}
}
} catch (InterruptedException ex) {
logger.warn(this + ": Interrupted during close", ex);
// re-cancel if current thread also interrupted
callTimeoutPool.shutdownNow();
// preserve interrupt status
Thread.currentThread().interrupt();
}
callTimeoutPool = null;
}
try {
transceiver.close();
} catch (IOException ex) {
throw new FlumeException(this + ": Error closing transceiver.", ex);
} finally {
setState(ConnState.DEAD);
}
}
@Override
public String toString() {
return "NettyAvroRpcClient { host: " + address.getHostName() + ", port: " +
address.getPort() + " }";
}
@Override
public void append(Event event) throws EventDeliveryException {
try {
append(event, requestTimeout, TimeUnit.MILLISECONDS);
} catch (Throwable t) {
// we mark as no longer active without trying to clean up resources
// client is required to call close() to clean up resources
setState(ConnState.DEAD);
if (t instanceof Error) {
throw (Error) t;
}
if (t instanceof TimeoutException) {
throw new EventDeliveryException(this + ": Failed to send event. " +
"RPC request timed out after " + requestTimeout + "ms", t);
}
throw new EventDeliveryException(this + ": Failed to send event", t);
}
}
private void append(Event event, long timeout, TimeUnit tu)
throws EventDeliveryException {
assertReady();
final CallFuture callFuture = new CallFuture();
final AvroFlumeEvent avroEvent = new AvroFlumeEvent();
avroEvent.setBody(ByteBuffer.wrap(event.getBody()));
avroEvent.setHeaders(toCharSeqMap(event.getHeaders()));
Future handshake;
try {
// due to AVRO-1122, avroClient.append() may block
handshake = callTimeoutPool.submit(new Callable() {
@Override
public Void call() throws Exception {
avroClient.append(avroEvent, callFuture);
return null;
}
});
} catch (RejectedExecutionException ex) {
throw new EventDeliveryException(this + ": Executor error", ex);
}
try {
handshake.get(connectTimeout, TimeUnit.MILLISECONDS);
} catch (TimeoutException ex) {
throw new EventDeliveryException(this + ": Handshake timed out after " +
connectTimeout + " ms", ex);
} catch (InterruptedException ex) {
throw new EventDeliveryException(this + ": Interrupted in handshake", ex);
} catch (ExecutionException ex) {
throw new EventDeliveryException(this + ": RPC request exception", ex);
} catch (CancellationException ex) {
throw new EventDeliveryException(this + ": RPC request cancelled", ex);
} finally {
if (!handshake.isDone()) {
handshake.cancel(true);
}
}
waitForStatusOK(callFuture, timeout, tu);
}
@Override
public void appendBatch(List events) throws EventDeliveryException {
try {
appendBatch(events, requestTimeout, TimeUnit.MILLISECONDS);
} catch (Throwable t) {
// we mark as no longer active without trying to clean up resources
// client is required to call close() to clean up resources
setState(ConnState.DEAD);
if (t instanceof Error) {
throw (Error) t;
}
if (t instanceof TimeoutException) {
throw new EventDeliveryException(this + ": Failed to send event. " +
"RPC request timed out after " + requestTimeout + " ms", t);
}
throw new EventDeliveryException(this + ": Failed to send batch", t);
}
}
private void appendBatch(List events, long timeout, TimeUnit tu)
throws EventDeliveryException {
assertReady();
Iterator iter = events.iterator();
final List avroEvents = new LinkedList();
// send multiple batches... bail if there is a problem at any time
while (iter.hasNext()) {
avroEvents.clear();
for (int i = 0; i < batchSize && iter.hasNext(); i++) {
Event event = iter.next();
AvroFlumeEvent avroEvent = new AvroFlumeEvent();
avroEvent.setBody(ByteBuffer.wrap(event.getBody()));
avroEvent.setHeaders(toCharSeqMap(event.getHeaders()));
avroEvents.add(avroEvent);
}
final CallFuture callFuture = new CallFuture();
Future handshake;
try {
// due to AVRO-1122, avroClient.appendBatch() may block
handshake = callTimeoutPool.submit(new Callable() {
@Override
public Void call() throws Exception {
avroClient.appendBatch(avroEvents, callFuture);
return null;
}
});
} catch (RejectedExecutionException ex) {
throw new EventDeliveryException(this + ": Executor error", ex);
}
try {
handshake.get(connectTimeout, TimeUnit.MILLISECONDS);
} catch (TimeoutException ex) {
throw new EventDeliveryException(this + ": Handshake timed out after " +
connectTimeout + "ms", ex);
} catch (InterruptedException ex) {
throw new EventDeliveryException(this + ": Interrupted in handshake",
ex);
} catch (ExecutionException ex) {
throw new EventDeliveryException(this + ": RPC request exception", ex);
} catch (CancellationException ex) {
throw new EventDeliveryException(this + ": RPC request cancelled", ex);
} finally {
if (!handshake.isDone()) {
handshake.cancel(true);
}
}
waitForStatusOK(callFuture, timeout, tu);
}
}
/**
* Helper method that waits for a Status future to come back and validates
* that it returns Status == OK.
* @param callFuture Future to wait on
* @param timeout Time to wait before failing
* @param tu Time Unit of {@code timeout}
* @throws EventDeliveryException If there is a timeout or if Status != OK
*/
private void waitForStatusOK(CallFuture callFuture,
long timeout, TimeUnit tu) throws EventDeliveryException {
try {
Status status = callFuture.get(timeout, tu);
if (status != Status.OK) {
throw new EventDeliveryException(this + ": Avro RPC call returned " +
"Status: " + status);
}
} catch (CancellationException ex) {
throw new EventDeliveryException(this + ": RPC future was cancelled", ex);
} catch (ExecutionException ex) {
throw new EventDeliveryException(this + ": Exception thrown from " +
"remote handler", ex);
} catch (TimeoutException ex) {
throw new EventDeliveryException(this + ": RPC request timed out", ex);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new EventDeliveryException(this + ": RPC request interrupted", ex);
}
}
/**
* This method should always be used to change {@code connState} so we ensure
* that invalid state transitions do not occur and that the {@code isIdle}
* {@link Condition} variable gets signaled reliably.
* Throws {@code IllegalStateException} when called to transition from CLOSED
* to another state.
* @param newState
*/
private void setState(ConnState newState) {
stateLock.lock();
try {
if (connState == ConnState.DEAD && connState != newState) {
throw new IllegalStateException("Cannot transition from CLOSED state.");
}
connState = newState;
} finally {
stateLock.unlock();
}
}
/**
* If the connection state != READY, throws {@link EventDeliveryException}.
*/
private void assertReady() throws EventDeliveryException {
stateLock.lock();
try {
ConnState curState = connState;
if (curState != ConnState.READY) {
throw new EventDeliveryException("RPC failed, client in an invalid " +
"state: " + curState);
}
} finally {
stateLock.unlock();
}
}
/**
* Helper function to convert a map of String to a map of CharSequence.
*/
private static Map toCharSeqMap(
Map stringMap) {
Map charSeqMap =
new HashMap();
for (Map.Entry entry : stringMap.entrySet()) {
charSeqMap.put(entry.getKey(), entry.getValue());
}
return charSeqMap;
}
@Override
public boolean isActive() {
stateLock.lock();
try {
return (connState == ConnState.READY);
} finally {
stateLock.unlock();
}
}
private static enum ConnState {
INIT, READY, DEAD
}
/**
*
* Configure the actual client using the properties.
* properties should have at least 2 params:
*
hosts = alias_for_host
* alias_for_host = hostname:port.
* Only the first host is added, rest are discarded.
* Optionally it can also have a
* batch-size = batchSize
* @param properties The properties to instantiate the client with.
* @return
*/
@Override
public synchronized void configure(Properties properties)
throws FlumeException {
stateLock.lock();
try{
if(connState == ConnState.READY || connState == ConnState.DEAD){
throw new FlumeException("This client was already configured, " +
"cannot reconfigure.");
}
} finally {
stateLock.unlock();
}
// batch size
String strBatchSize = properties.getProperty(
RpcClientConfigurationConstants.CONFIG_BATCH_SIZE);
logger.debug("Batch size string = " + strBatchSize);
batchSize = RpcClientConfigurationConstants.DEFAULT_BATCH_SIZE;
if (strBatchSize != null && !strBatchSize.isEmpty()) {
try {
int parsedBatch = Integer.parseInt(strBatchSize);
if (parsedBatch < 1) {
logger.warn("Invalid value for batchSize: {}; Using default value.", parsedBatch);
} else {
batchSize = parsedBatch;
}
} catch (NumberFormatException e) {
logger.warn("Batchsize is not valid for RpcClient: " + strBatchSize +
". Default value assigned.", e);
}
}
// host and port
String hostNames = properties.getProperty(
RpcClientConfigurationConstants.CONFIG_HOSTS);
String[] hosts = null;
if (hostNames != null && !hostNames.isEmpty()) {
hosts = hostNames.split("\\s+");
} else {
throw new FlumeException("Hosts list is invalid: " + hostNames);
}
if (hosts.length > 1) {
logger.warn("More than one hosts are specified for the default client. "
+ "Only the first host will be used and others ignored. Specified: "
+ hostNames + "; to be used: " + hosts[0]);
}
String host = properties.getProperty(
RpcClientConfigurationConstants.CONFIG_HOSTS_PREFIX+hosts[0]);
if (host == null || host.isEmpty()) {
throw new FlumeException("Host not found: " + hosts[0]);
}
String[] hostAndPort = host.split(":");
if (hostAndPort.length != 2){
throw new FlumeException("Invalid hostname: " + hosts[0]);
}
Integer port = null;
try {
port = Integer.parseInt(hostAndPort[1]);
} catch (NumberFormatException e) {
throw new FlumeException("Invalid Port: " + hostAndPort[1], e);
}
this.address = new InetSocketAddress(hostAndPort[0], port);
// connect timeout
connectTimeout =
RpcClientConfigurationConstants.DEFAULT_CONNECT_TIMEOUT_MILLIS;
String strConnTimeout = properties.getProperty(
RpcClientConfigurationConstants.CONFIG_CONNECT_TIMEOUT);
if (strConnTimeout != null && strConnTimeout.trim().length() > 0) {
try {
connectTimeout = Long.parseLong(strConnTimeout);
if (connectTimeout < 1000) {
logger.warn("Connection timeout specified less than 1s. " +
"Using default value instead.");
connectTimeout =
RpcClientConfigurationConstants.DEFAULT_CONNECT_TIMEOUT_MILLIS;
}
} catch (NumberFormatException ex) {
logger.error("Invalid connect timeout specified: " + strConnTimeout);
}
}
// request timeout
requestTimeout =
RpcClientConfigurationConstants.DEFAULT_REQUEST_TIMEOUT_MILLIS;
String strReqTimeout = properties.getProperty(
RpcClientConfigurationConstants.CONFIG_REQUEST_TIMEOUT);
if (strReqTimeout != null && strReqTimeout.trim().length() > 0) {
try {
requestTimeout = Long.parseLong(strReqTimeout);
if (requestTimeout < 1000) {
logger.warn("Request timeout specified less than 1s. " +
"Using default value instead.");
requestTimeout =
RpcClientConfigurationConstants.DEFAULT_REQUEST_TIMEOUT_MILLIS;
}
} catch (NumberFormatException ex) {
logger.error("Invalid request timeout specified: " + strReqTimeout);
}
}
String enableCompressionStr = properties.getProperty(RpcClientConfigurationConstants.CONFIG_COMPRESSION_TYPE);
if (enableCompressionStr != null && enableCompressionStr.equalsIgnoreCase("deflate")) {
this.enableDeflateCompression = true;
String compressionLvlStr = properties.getProperty(RpcClientConfigurationConstants.CONFIG_COMPRESSION_LEVEL);
compressionLevel = RpcClientConfigurationConstants.DEFAULT_COMPRESSION_LEVEL;
if (compressionLvlStr != null) {
try {
compressionLevel = Integer.parseInt(compressionLvlStr);
} catch (NumberFormatException ex) {
logger.error("Invalid compression level: " + compressionLvlStr);
}
}
}
enableSsl = Boolean.parseBoolean(properties.getProperty(
RpcClientConfigurationConstants.CONFIG_SSL));
trustAllCerts = Boolean.parseBoolean(properties.getProperty(
RpcClientConfigurationConstants.CONFIG_TRUST_ALL_CERTS));
truststore = properties.getProperty(
RpcClientConfigurationConstants.CONFIG_TRUSTSTORE);
truststorePassword = properties.getProperty(
RpcClientConfigurationConstants.CONFIG_TRUSTSTORE_PASSWORD);
truststoreType = properties.getProperty(
RpcClientConfigurationConstants.CONFIG_TRUSTSTORE_TYPE, "JKS");
String excludeProtocolsStr = properties.getProperty(
RpcClientConfigurationConstants.CONFIG_EXCLUDE_PROTOCOLS);
if (excludeProtocolsStr == null) {
excludeProtocols.add("SSLv3");
} else {
excludeProtocols.addAll(Arrays.asList(excludeProtocolsStr.split(" ")));
if (!excludeProtocols.contains("SSLv3")) {
excludeProtocols.add("SSLv3");
}
}
String maxIoWorkersStr = properties.getProperty(
RpcClientConfigurationConstants.MAX_IO_WORKERS);
if (!StringUtils.isEmpty(maxIoWorkersStr)) {
try {
maxIoWorkers = Integer.parseInt(maxIoWorkersStr);
} catch (NumberFormatException ex) {
logger.warn ("Invalid maxIOWorkers:" + maxIoWorkersStr + " Using " +
"default maxIOWorkers.");
maxIoWorkers = -1;
}
}
if (maxIoWorkers < 1) {
logger.warn("Using default maxIOWorkers");
maxIoWorkers = -1;
}
this.connect();
}
/**
* A thread factor implementation modeled after the implementation of
* NettyTransceiver.NettyTransceiverThreadFactory class which is
* a private static class. The only difference between that and this
* implementation is that this implementation marks all the threads daemon
* which allows the termination of the VM when the non-daemon threads
* are done.
*/
private static class TransceiverThreadFactory implements ThreadFactory {
private final AtomicInteger threadId = new AtomicInteger(0);
private final String prefix;
/**
* Creates a TransceiverThreadFactory that creates threads with the
* specified name.
* @param prefix the name prefix to use for all threads created by this
* ThreadFactory. A unique ID will be appended to this prefix to form the
* final thread name.
*/
public TransceiverThreadFactory(String prefix) {
this.prefix = prefix;
}
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName(prefix + " " + threadId.incrementAndGet());
return thread;
}
}
/**
* Factory of SSL-enabled client channels
* Copied from Avro's org.apache.avro.ipc.TestNettyServerWithSSL test
*/
private static class SSLCompressionChannelFactory extends NioClientSocketChannelFactory {
private final boolean enableCompression;
private final int compressionLevel;
private final boolean enableSsl;
private final boolean trustAllCerts;
private final String truststore;
private final String truststorePassword;
private final String truststoreType;
private final List excludeProtocols;
public SSLCompressionChannelFactory(Executor bossExecutor, Executor workerExecutor,
boolean enableCompression, boolean enableSsl, boolean trustAllCerts,
int compressionLevel, String truststore, String truststorePassword,
String truststoreType, List excludeProtocols) {
super(bossExecutor, workerExecutor);
this.enableCompression = enableCompression;
this.enableSsl = enableSsl;
this.compressionLevel = compressionLevel;
this.trustAllCerts = trustAllCerts;
this.truststore = truststore;
this.truststorePassword = truststorePassword;
this.truststoreType = truststoreType;
this.excludeProtocols = excludeProtocols;
}
public SSLCompressionChannelFactory(Executor bossExecutor, Executor workerExecutor,
boolean enableCompression, boolean enableSsl, boolean trustAllCerts,
int compressionLevel, String truststore, String truststorePassword,
String truststoreType, List excludeProtocols, int maxIOWorkers) {
super(bossExecutor, workerExecutor, maxIOWorkers);
this.enableCompression = enableCompression;
this.enableSsl = enableSsl;
this.compressionLevel = compressionLevel;
this.trustAllCerts = trustAllCerts;
this.truststore = truststore;
this.truststorePassword = truststorePassword;
this.truststoreType = truststoreType;
this.excludeProtocols = excludeProtocols;
}
@Override
public SocketChannel newChannel(ChannelPipeline pipeline) {
TrustManager[] managers;
try {
if (enableCompression) {
ZlibEncoder encoder = new ZlibEncoder(compressionLevel);
pipeline.addFirst("deflater", encoder);
pipeline.addFirst("inflater", new ZlibDecoder());
}
if (enableSsl) {
if (trustAllCerts) {
logger.warn("No truststore configured, setting TrustManager to accept"
+ " all server certificates");
managers = new TrustManager[] { new PermissiveTrustManager() };
} else {
KeyStore keystore = null;
if (truststore != null) {
if (truststorePassword == null) {
throw new NullPointerException("truststore password is null");
}
InputStream truststoreStream = new FileInputStream(truststore);
keystore = KeyStore.getInstance(truststoreType);
keystore.load(truststoreStream, truststorePassword.toCharArray());
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
// null keystore is OK, with SunX509 it defaults to system CA Certs
// see http://docs.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#X509TrustManager
tmf.init(keystore);
managers = tmf.getTrustManagers();
}
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, managers, null);
SSLEngine sslEngine = sslContext.createSSLEngine();
sslEngine.setUseClientMode(true);
List enabledProtocols = new ArrayList();
for (String protocol : sslEngine.getEnabledProtocols()) {
if (!excludeProtocols.contains(protocol)) {
enabledProtocols.add(protocol);
}
}
sslEngine.setEnabledProtocols(enabledProtocols.toArray(new String[0]));
logger.info("SSLEngine protocols enabled: " +
Arrays.asList(sslEngine.getEnabledProtocols()));
// addFirst() will make SSL handling the first stage of decoding
// and the last stage of encoding this must be added after
// adding compression handling above
pipeline.addFirst("ssl", new SslHandler(sslEngine));
}
return super.newChannel(pipeline);
} catch (Exception ex) {
logger.error("Cannot create SSL channel", ex);
throw new RuntimeException("Cannot create SSL channel", ex);
}
}
}
/**
* Permissive trust manager accepting any certificate
*/
private static class PermissiveTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] certs, String s) {
// nothing
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String s) {
// nothing
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
}