Author: markt
Date: Tue Mar 26 21:17:18 2013
New Revision: 1461317
URL: http://svn.apache.org/r1461317
Log:
First cut of an SSL client implementation for WebSocket and a simple test case
to demonstrate that it works.
Modified:
tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapper.java
tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperNonSecure.java
tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java
tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
tomcat/trunk/test/org/apache/tomcat/util/net/TesterSupport.java
tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java
Modified: tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapper.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapper.java?rev=1461317&r1=1461316&r2=1461317&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapper.java
(original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapper.java Tue
Mar 26 21:17:18 2013
@@ -21,6 +21,8 @@ import java.nio.channels.CompletionHandl
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+import javax.net.ssl.SSLException;
+
/**
* This is a wrapper for a {@link java.nio.channels.AsynchronousSocketChannel}
* that limits the methods available thereby simplifying the process of
@@ -40,4 +42,6 @@ public interface AsyncChannelWrapper {
CompletionHandler<Long,? super A> handler);
void close();
+
+ Future<Void> handshake() throws SSLException;
}
Modified:
tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperNonSecure.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperNonSecure.java?rev=1461317&r1=1461316&r2=1461317&view=diff
==============================================================================
---
tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperNonSecure.java
(original)
+++
tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperNonSecure.java
Tue Mar 26 21:17:18 2013
@@ -20,8 +20,10 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* Generally, just passes calls straight to the wrapped
@@ -30,6 +32,8 @@ import java.util.concurrent.TimeUnit;
*/
public class AsyncChannelWrapperNonSecure implements AsyncChannelWrapper {
+ private static final Future<Void> NOOP_FUTURE = new NoOpFuture();
+
private final AsynchronousSocketChannel socketChannel;
public AsyncChannelWrapperNonSecure(
@@ -69,4 +73,40 @@ public class AsyncChannelWrapperNonSecur
// Ignore
}
}
+
+ @Override
+ public Future<Void> handshake() {
+ return NOOP_FUTURE;
+ }
+
+
+ private static final class NoOpFuture implements Future<Void> {
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return false;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public boolean isDone() {
+ return true;
+ }
+
+ @Override
+ public Void get() throws InterruptedException, ExecutionException {
+ return null;
+ }
+
+ @Override
+ public Void get(long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException,
+ TimeoutException {
+ return null;
+ }
+ }
}
Modified:
tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java?rev=1461317&r1=1461316&r2=1461317&view=diff
==============================================================================
---
tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java
(original)
+++
tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java
Tue Mar 26 21:17:18 2013
@@ -16,53 +16,505 @@
*/
package org.apache.tomcat.websocket;
+import java.io.EOFException;
+import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+import javax.net.ssl.SSLException;
/**
- * Wraps the {@link AsynchronousSocketChannel} with SSL/TLS.
+ * Wraps the {@link AsynchronousSocketChannel} with SSL/TLS. This needs a lot
+ * more testing before it can be considered robust.
*/
public class AsyncChannelWrapperSecure implements AsyncChannelWrapper {
+ private static final ByteBuffer DUMMY = ByteBuffer.allocate(8192);
private final AsynchronousSocketChannel socketChannel;
+ private final SSLEngine sslEngine;
+ private final ByteBuffer socketReadBuffer;
+ private final ByteBuffer socketWriteBuffer;
+ // One thread for read, one for write
+ private final ExecutorService executor = Executors.newFixedThreadPool(2);
+ private AtomicBoolean writing = new AtomicBoolean(false);
+ private AtomicBoolean reading = new AtomicBoolean(false);
- public AsyncChannelWrapperSecure(AsynchronousSocketChannel socketChannel) {
+ public AsyncChannelWrapperSecure(AsynchronousSocketChannel socketChannel,
+ SSLEngine sslEngine) {
this.socketChannel = socketChannel;
+ this.sslEngine = sslEngine;
+
+ int socketBufferSize = sslEngine.getSession().getPacketBufferSize();
+ socketReadBuffer = ByteBuffer.allocateDirect(socketBufferSize);
+ socketWriteBuffer = ByteBuffer.allocateDirect(socketBufferSize);
}
@Override
public Future<Integer> read(ByteBuffer dst) {
- // TODO
- throw new UnsupportedOperationException();
+ WrapperFuture<Integer,Void> future = new WrapperFuture<>();
+
+ if (!reading.compareAndSet(false, true)) {
+ // TODO
+ throw new IllegalStateException();
+ }
+
+ ReadTask readTask = new ReadTask(dst, future);
+
+ executor.execute(readTask);
+
+ return future;
}
@Override
public <A> void read(ByteBuffer dst, A attachment,
CompletionHandler<Integer,? super A> handler) {
- // TODO
- throw new UnsupportedOperationException();
+
+ WrapperFuture<Integer,? super A> future =
+ new WrapperFuture<>(handler, attachment);
+
+ if (!reading.compareAndSet(false, true)) {
+ // TODO
+ throw new IllegalStateException();
+ }
+
+ ReadTask readTask = new ReadTask(dst, future);
+
+ executor.execute(readTask);
}
@Override
public Future<Integer> write(ByteBuffer src) {
- // TODO
- throw new UnsupportedOperationException();
+
+ WrapperFuture<Long,Void> inner = new WrapperFuture<>();
+
+ if (!writing.compareAndSet(false, true)) {
+ // TODO
+ throw new IllegalStateException();
+ }
+
+ WriteTask writeTask =
+ new WriteTask(new ByteBuffer[] {src}, 0, 1, inner);
+
+ executor.execute(writeTask);
+
+ Future<Integer> future = new LongToIntegerFuture(inner);
+ return future;
}
@Override
public <A> void write(ByteBuffer[] srcs, int offset, int length,
long timeout, TimeUnit unit, A attachment,
CompletionHandler<Long,? super A> handler) {
- // TODO
- throw new UnsupportedOperationException();
+
+ WrapperFuture<Long,? super A> future =
+ new WrapperFuture<>(handler, attachment);
+
+ if (!writing.compareAndSet(false, true)) {
+ // TODO
+ throw new IllegalStateException();
+ }
+
+ WriteTask writeTask = new WriteTask(srcs, offset, length, future);
+
+ executor.execute(writeTask);
}
@Override
public void close() {
- // TODO
- throw new UnsupportedOperationException();
+ try {
+ socketChannel.close();
+ } catch (IOException e) {
+ // TODO
+ }
+ }
+
+ @Override
+ public Future<Void> handshake() throws SSLException {
+
+ WrapperFuture<Void,Void> wFuture = new WrapperFuture<>();
+
+ Thread t = new WebSocketSslHandshakeThread(wFuture);
+ t.start();
+
+ return wFuture;
+ }
+
+
+ private class WriteTask implements Runnable {
+
+ private final ByteBuffer[] srcs;
+ private final int offset;
+ private final int length;
+ private final WrapperFuture<Long,?> future;
+
+ public WriteTask(ByteBuffer[] srcs, int offset, int length,
+ WrapperFuture<Long,?> future) {
+ this.srcs = srcs;
+ this.future = future;
+ this.offset = offset;
+ this.length = length;
+ }
+
+ @Override
+ public void run() {
+ long written = 0;
+
+ try {
+ for (int i = offset; i < offset + length; i++) {
+ ByteBuffer src = srcs[i];
+ while (src.hasRemaining()) {
+ socketWriteBuffer.clear();
+
+ // Encrypt the data
+ SSLEngineResult r = sslEngine.wrap(src,
socketWriteBuffer);
+ written += r.bytesConsumed();
+ Status s = r.getStatus();
+
+ if (s == Status.OK || s == Status.BUFFER_OVERFLOW) {
+ // Need to write out the bytes and may need to
read from
+ // the source again to empty it
+ } else {
+ // Status.BUFFER_UNDERFLOW - only happens on unwrap
+ // Status.CLOSED - unexpected
+ // TODO
+ throw new IllegalStateException();
+ }
+
+ // Check for tasks
+ if (r.getHandshakeStatus() ==
HandshakeStatus.NEED_TASK) {
+ Runnable runnable = sslEngine.getDelegatedTask();
+ while (runnable != null) {
+ runnable.run();
+ runnable = sslEngine.getDelegatedTask();
+ }
+ }
+
+ socketWriteBuffer.flip();
+
+ // Do the write
+ Future<Integer> f =
socketChannel.write(socketWriteBuffer);
+ Integer socketWrite = f.get();
+ if (socketWrite.intValue() != r.bytesProduced()) {
+ throw new IOException();
+ }
+ }
+ }
+
+
+ if (writing.compareAndSet(true, false)) {
+ future.complete(Long.valueOf(written));
+ } else {
+ // TODO
+ future.fail(new IllegalStateException());
+ }
+ } catch (Exception e) {
+ future.fail(e);
+ }
+ }
+ }
+
+
+ private class ReadTask implements Runnable {
+
+ private final ByteBuffer dest;
+ private final WrapperFuture<Integer,?> future;
+
+ public ReadTask(ByteBuffer dest, WrapperFuture<Integer,?> future) {
+ this.dest = dest;
+ this.future = future;
+ }
+
+ @Override
+ public void run() {
+ int read = 0;
+
+ boolean forceRead = false;
+
+ try {
+ while (read == 0) {
+ socketReadBuffer.compact();
+
+ if (forceRead) {
+ Future<Integer> f =
+ socketChannel.read(socketReadBuffer);
+ Integer socketRead = f.get();
+ if (socketRead.intValue() == -1) {
+ // TODO
+ throw new EOFException();
+ }
+ }
+
+ socketReadBuffer.flip();
+
+ if (socketReadBuffer.hasRemaining()) {
+ // Decrypt the data in the buffer
+ SSLEngineResult r =
+ sslEngine.unwrap(socketReadBuffer, dest);
+ read += r.bytesProduced();
+ Status s = r.getStatus();
+
+ if (s == Status.OK || s == Status.BUFFER_OVERFLOW) {
+ // Bytes available for reading and there may be
+ // sufficientNeed data in the socketReadBuffer to
+ // support further reads without reading from the
+ // socket
+ } else if (s == Status.BUFFER_UNDERFLOW) {
+ // There is partial data in the socketReadBuffer
+ if (read == 0) {
+ // Need more data before the partial data can
be
+ // processed and some output generated
+ forceRead = true;
+ }
+ // else return the data we have and deal with the
+ // partial data on the next read
+ } else {
+ // Status.CLOSED - unexpected
+ // TODO
+ throw new IllegalStateException();
+ }
+
+ // Check for tasks
+ if (r.getHandshakeStatus() ==
HandshakeStatus.NEED_TASK) {
+ Runnable runnable = sslEngine.getDelegatedTask();
+ while (runnable != null) {
+ runnable.run();
+ runnable = sslEngine.getDelegatedTask();
+ }
+ }
+ } else {
+ forceRead = true;
+ }
+ }
+
+
+ if (reading.compareAndSet(true, false)) {
+ future.complete(Integer.valueOf(read));
+ } else {
+ // TODO
+ future.fail(new IllegalStateException());
+ }
+ } catch (Exception e) {
+ future.fail(e);
+ }
+ }
+ }
+
+
+ private class WebSocketSslHandshakeThread extends Thread {
+
+ private final WrapperFuture<Void,Void> hFuture;
+
+ public WebSocketSslHandshakeThread(WrapperFuture<Void,Void> hFuture) {
+ this.hFuture = hFuture;
+ }
+
+ @Override
+ public void run() {
+ try {
+ sslEngine.beginHandshake();
+ // So the first compact does the right thing
+ socketReadBuffer.position(socketReadBuffer.limit());
+
+ HandshakeStatus handshakeStatus =
+ sslEngine.getHandshakeStatus();
+ boolean handshaking = true;
+
+ while(handshaking) {
+ switch (handshakeStatus) {
+ case NEED_WRAP: {
+ socketWriteBuffer.clear();
+ SSLEngineResult r =
+ sslEngine.wrap(DUMMY, socketWriteBuffer);
+ handshakeStatus = checkResult(r, true);
+ socketWriteBuffer.flip();
+ Future<Integer> fWrite =
+ socketChannel.write(socketWriteBuffer);
+ fWrite.get();
+ break;
+ }
+ case NEED_UNWRAP: {
+ socketReadBuffer.compact();
+ if (socketReadBuffer.position() == 0) {
+ Future<Integer> fRead =
+ socketChannel.read(socketReadBuffer);
+ fRead.get();
+ }
+ socketReadBuffer.flip();
+ SSLEngineResult r =
+ sslEngine.unwrap(socketReadBuffer, DUMMY);
+ handshakeStatus = checkResult(r, false);
+ break;
+ }
+ case NEED_TASK: {
+ Runnable r = null;
+ while ((r = sslEngine.getDelegatedTask()) != null)
{
+ r.run();
+ }
+ handshakeStatus = sslEngine.getHandshakeStatus();
+ break;
+ }
+ case FINISHED: {
+ handshaking = false;
+ break;
+ }
+ default: {
+ throw new SSLException("TODO");
+ }
+ }
+ }
+ } catch (SSLException | InterruptedException |
+ ExecutionException e) {
+ hFuture.fail(e);
+ }
+
+ hFuture.complete(null);
+ }
+
+ private HandshakeStatus checkResult(SSLEngineResult result,
+ boolean wrap) throws SSLException {
+
+ if (result.getStatus() != Status.OK) {
+ throw new SSLException("TODO");
+ }
+ if (wrap && result.bytesConsumed() != 0) {
+ throw new SSLException("TODO");
+ }
+ if (!wrap && result.bytesProduced() != 0) {
+ throw new SSLException("TODO");
+ }
+ return result.getHandshakeStatus();
+ }
+ }
+
+
+ private static class WrapperFuture<T,A> implements Future<T> {
+
+ private final CompletionHandler<T,A> handler;
+ private final A attachment;
+
+ private volatile T result = null;
+ private volatile Throwable throwable = null;
+ private CountDownLatch completionLatch = new CountDownLatch(1);
+
+ public WrapperFuture() {
+ this(null, null);
+ }
+
+ public WrapperFuture(CompletionHandler<T,A> handler, A attachment) {
+ this.handler = handler;
+ this.attachment = attachment;
+ }
+
+ public void complete(T result) {
+ this.result = result;
+ completionLatch.countDown();
+ if (handler != null) {
+ handler.completed(result, attachment);
+ }
+ }
+
+ public void fail(Throwable t) {
+ throwable = t;
+ completionLatch.countDown();
+ if (handler != null) {
+ handler.failed(throwable, attachment);
+ }
+ }
+
+ @Override
+ public final boolean cancel(boolean mayInterruptIfRunning) {
+ // Could support cancellation by closing the connection
+ return false;
+ }
+
+ @Override
+ public final boolean isCancelled() {
+ // Could support cancellation by closing the connection
+ return false;
+ }
+
+ @Override
+ public final boolean isDone() {
+ return completionLatch.getCount() > 0;
+ }
+
+ @Override
+ public T get() throws InterruptedException, ExecutionException {
+ completionLatch.await();
+ if (throwable != null) {
+ throw new ExecutionException(throwable);
+ }
+ return result;
+ }
+
+ @Override
+ public T get(long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException,
+ TimeoutException {
+ completionLatch.await(timeout, unit);
+ if (throwable != null) {
+ throw new ExecutionException(throwable);
+ }
+ return result;
+ }
+ }
+
+ private static final class LongToIntegerFuture implements Future<Integer> {
+
+ private final Future<Long> wrapped;
+
+ public LongToIntegerFuture(Future<Long> wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return wrapped.cancel(mayInterruptIfRunning);
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return wrapped.isCancelled();
+ }
+
+ @Override
+ public boolean isDone() {
+ return wrapped.isDone();
+ }
+
+ @Override
+ public Integer get() throws InterruptedException, ExecutionException {
+ Long result = wrapped.get();
+ if (result.longValue() > Integer.MAX_VALUE) {
+ // TODO
+ throw new ExecutionException("Result too big", null);
+ }
+ return new Integer(result.intValue());
+ }
+
+ @Override
+ public Integer get(long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException,
+ TimeoutException {
+ Long result = wrapped.get(timeout, unit);
+ if (result.longValue() > Integer.MAX_VALUE) {
+ // TODO
+ throw new ExecutionException("Result too big", null);
+ }
+ return new Integer(result.intValue());
+ }
}
}
Modified:
tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java?rev=1461317&r1=1461316&r2=1461317&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
(original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java Tue
Mar 26 21:17:18 2013
@@ -16,13 +16,17 @@
*/
package org.apache.tomcat.websocket;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.charset.Charset;
+import java.security.KeyStore;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -38,6 +42,10 @@ import java.util.concurrent.ConcurrentHa
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
import javax.websocket.ClientEndpoint;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.ClientEndpointConfig.Builder;
@@ -57,6 +65,19 @@ import org.apache.tomcat.websocket.pojo.
public class WsWebSocketContainer
implements WebSocketContainer, BackgroundProcess {
+ /**
+ * Property name to set to configure the value that is passed to
+ * {@link SSLEngine#setEnabledProtocols(String[])}. The value should be a
+ * comma separated string.
+ */
+ public static final String SSL_PROTOCOLS_PROPERTY =
+ "org.apache.tomcat.websocket.SSL_PROTOCOLS";
+ public static final String SSL_TRUSTSTORE_PROPERTY =
+ "org.apache.tomcat.websocket.SSL_TRUSTSTORE";
+ public static final String SSL_TRUSTSTORE_PWD_PROPERTY =
+ "org.apache.tomcat.websocket.SSL_TRUSTSTORE_PWD";
+ public static final String SSL_TRUSTSTORE_PWD_DEFAULT = "changeit";
+
private static final StringManager sm =
StringManager.getManager(Constants.PACKAGE_NAME);
private static final Random random = new Random();
@@ -202,6 +223,9 @@ public class WsWebSocketContainer
sm.getString("wsWebSocketContainer.invalidScheme"));
}
} else {
+ if ("wss".equalsIgnoreCase(scheme)) {
+ secure = true;
+ }
sa = new InetSocketAddress(host, port);
}
@@ -216,16 +240,21 @@ public class WsWebSocketContainer
AsyncChannelWrapper channel;
if (secure) {
- channel = new AsyncChannelWrapperSecure(socketChannel);
+ SSLEngine sslEngine = createSSLEngine(
+ clientEndpointConfiguration.getUserProperties());
+ channel = new AsyncChannelWrapperSecure(socketChannel, sslEngine);
} else {
channel = new AsyncChannelWrapperNonSecure(socketChannel);
}
-
ByteBuffer response;
String subProtocol;
try {
fConnect.get();
+
+ Future<Void> fHandshake = channel.handshake();
+ fHandshake.get();
+
int toWrite = request.limit();
Future<Integer> fWrite = channel.write(request);
@@ -256,7 +285,7 @@ public class WsWebSocketContainer
throw new DeploymentException(
sm.getString("Sec-WebSocket-Protocol"));
}
- } catch (ExecutionException | InterruptedException e) {
+ } catch (ExecutionException | InterruptedException | SSLException e) {
throw new DeploymentException(
sm.getString("wsWebSocketContainer.httpRequestFailed"), e);
}
@@ -524,6 +553,55 @@ public class WsWebSocketContainer
}
+ private SSLEngine createSSLEngine(Map<String,Object> userProperties)
+ throws DeploymentException {
+
+ try {
+ // Create the SSL Context
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+
+ // Trust store
+ String sslTrustStoreValue =
+ (String) userProperties.get(SSL_TRUSTSTORE_PROPERTY);
+ if (sslTrustStoreValue != null) {
+ String sslTrustStorePwdValue = (String) userProperties.get(
+ SSL_TRUSTSTORE_PWD_PROPERTY);
+ if (sslTrustStorePwdValue == null) {
+ sslTrustStorePwdValue = SSL_TRUSTSTORE_PWD_DEFAULT;
+ }
+
+ File keyStoreFile = new File(sslTrustStoreValue);
+ KeyStore ks = KeyStore.getInstance("JKS");
+ try (InputStream is = new FileInputStream(keyStoreFile)) {
+ ks.load(is, sslTrustStorePwdValue.toCharArray());
+ }
+
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(ks);
+
+ sslContext.init(null, tmf.getTrustManagers(), null);
+ } else {
+ sslContext.init(null, null, null);
+ }
+
+ SSLEngine engine = sslContext.createSSLEngine();
+
+ String sslProtocolsValue =
+ (String) userProperties.get(SSL_PROTOCOLS_PROPERTY);
+ if (sslProtocolsValue != null) {
+ engine.setEnabledProtocols(sslProtocolsValue.split(","));
+ }
+
+ engine.setUseClientMode(true);
+
+ return engine;
+ } catch (Exception e) {
+ throw new DeploymentException("TODO", e);
+ }
+ }
+
+
@Override
public long getDefaultMaxSessionIdleTimeout() {
return defaultMaxSessionIdleTimeout;
Modified: tomcat/trunk/test/org/apache/tomcat/util/net/TesterSupport.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/tomcat/util/net/TesterSupport.java?rev=1461317&r1=1461316&r2=1461317&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/tomcat/util/net/TesterSupport.java (original)
+++ tomcat/trunk/test/org/apache/tomcat/util/net/TesterSupport.java Tue Mar 26
21:17:18 2013
@@ -77,7 +77,7 @@ public final class TesterSupport {
RFC_5746_SUPPORTED = result;
}
- protected static void initSsl(Tomcat tomcat) {
+ public static void initSsl(Tomcat tomcat) {
initSsl(tomcat, "localhost.jks", null, null);
}
Modified:
tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java?rev=1461317&r1=1461316&r2=1461317&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java
(original)
+++ tomcat/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java
Tue Mar 26 21:17:18 2013
@@ -46,6 +46,7 @@ import org.apache.catalina.Context;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.TomcatBaseTest;
import org.apache.coyote.http11.Http11Protocol;
+import org.apache.tomcat.util.net.TesterSupport;
import org.apache.tomcat.websocket.TesterSingleMessageClient.BasicBinary;
import org.apache.tomcat.websocket.TesterSingleMessageClient.BasicHandler;
import org.apache.tomcat.websocket.TesterSingleMessageClient.BasicText;
@@ -710,4 +711,43 @@ public class TestWsWebSocketContainer ex
}
}
+
+ @Test
+ public void testConnectToServerEndpointSSL() throws Exception {
+
+ Tomcat tomcat = getTomcatInstance();
+ // Must have a real docBase - just use temp
+ Context ctx =
+ tomcat.addContext("", System.getProperty("java.io.tmpdir"));
+ ctx.addApplicationListener(TesterEchoServer.Config.class.getName());
+
+ TesterSupport.initSsl(tomcat);
+
+ tomcat.start();
+
+ WebSocketContainer wsContainer =
+ ContainerProvider.getWebSocketContainer();
+ ClientEndpointConfig clientEndpointConfig =
+ ClientEndpointConfig.Builder.create().build();
+ clientEndpointConfig.getUserProperties().put(
+ WsWebSocketContainer.SSL_TRUSTSTORE_PROPERTY,
+ "test/org/apache/tomcat/util/net/ca.jks");
+ Session wsSession = wsContainer.connectToServer(
+ TesterProgrammaticEndpoint.class,
+ clientEndpointConfig,
+ new URI("wss://localhost:" + getPort() +
+ TesterEchoServer.Config.PATH_ASYNC));
+ CountDownLatch latch = new CountDownLatch(1);
+ BasicText handler = new BasicText(latch);
+ wsSession.addMessageHandler(handler);
+ wsSession.getBasicRemote().sendText(MESSAGE_STRING_1);
+
+ boolean latchResult = handler.getLatch().await(10, TimeUnit.SECONDS);
+
+ Assert.assertTrue(latchResult);
+
+ List<String> messages = handler.getMessages();
+ Assert.assertEquals(1, messages.size());
+ Assert.assertEquals(MESSAGE_STRING_1, messages.get(0));
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]