410175 - WebSocketSession#isSecure() doesn't return true for SSL session on the server side

+ Fixing server side support for Session.isSecure()
 + Standardizing the WSURI translation into new
   org.eclipse.jetty.websocket.api.util.WSURI class
 + Adding testcase for SSL on server side
This commit is contained in:
Joakim Erdfelt 2013-06-10 13:48:18 -07:00
parent 9abed8e85d
commit e61c161ebd
14 changed files with 487 additions and 389 deletions

View File

@ -42,6 +42,7 @@ public class UpgradeRequest
private String httpVersion; private String httpVersion;
private String method; private String method;
private String host; private String host;
private boolean secure = false;
protected UpgradeRequest() protected UpgradeRequest()
{ {
@ -225,6 +226,11 @@ public class UpgradeRequest
return test.equalsIgnoreCase(getOrigin()); return test.equalsIgnoreCase(getOrigin());
} }
public boolean isSecure()
{
return secure;
}
public void setCookies(List<HttpCookie> cookies) public void setCookies(List<HttpCookie> cookies)
{ {
this.cookies = cookies; this.cookies = cookies;
@ -261,6 +267,19 @@ public class UpgradeRequest
public void setRequestURI(URI uri) public void setRequestURI(URI uri)
{ {
this.requestURI = uri; this.requestURI = uri;
String scheme = uri.getScheme();
if ("ws".equalsIgnoreCase(scheme))
{
secure = false;
}
else if ("wss".equalsIgnoreCase(scheme))
{
secure = true;
}
else
{
throw new IllegalArgumentException("URI scheme must be 'ws' or 'wss'");
}
this.host = this.requestURI.getHost(); this.host = this.requestURI.getHost();
this.parameters.clear(); this.parameters.clear();
} }

View File

@ -0,0 +1,145 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.api.util;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Objects;
/**
* Utility methods for converting a {@link URI} between a HTTP(S) and WS(S) URI.
*/
public final class WSURI
{
/**
* Convert to HTTP <code>http</code> or <code>https</code> scheme URIs.
* <p>
* Converting <code>ws</code> and <code>wss</code> URIs to their HTTP equivalent
*
* @param inputUri
* the input URI
* @return the HTTP scheme URI for the input URI.
* @throws URISyntaxException
* if unable to convert the input URI
*/
public static URI toHttp(final URI inputUri) throws URISyntaxException
{
Objects.requireNonNull(inputUri,"Input URI must not be null");
String wsScheme = inputUri.getScheme();
String httpScheme = null;
if ("http".equalsIgnoreCase(wsScheme) || "https".equalsIgnoreCase(wsScheme))
{
// leave alone
httpScheme = wsScheme;
}
else if ("ws".equalsIgnoreCase(wsScheme))
{
// convert to http
httpScheme = "http";
}
else if ("wss".equalsIgnoreCase(wsScheme))
{
// convert to https
httpScheme = "https";
}
else
{
throw new URISyntaxException(inputUri.toString(),"Unrecognized WebSocket scheme");
}
return new URI(httpScheme,inputUri.getUserInfo(),inputUri.getHost(),inputUri.getPort(),inputUri.getPath(),inputUri.getQuery(),inputUri.getFragment());
}
/**
* Convert to WebSocket <code>ws</code> or <code>wss</code> scheme URIs
* <p>
* Converting <code>http</code> and <code>https</code> URIs to their WebSocket equivalent
*
* @param inputUrl
* the input URI
* @return the WebSocket scheme URI for the input URI.
* @throws URISyntaxException
* if unable to convert the input URI
*/
public static URI toWebsocket(CharSequence inputUrl) throws URISyntaxException
{
return toWebsocket(new URI(inputUrl.toString()));
}
/**
* Convert to WebSocket <code>ws</code> or <code>wss</code> scheme URIs
* <p>
* Converting <code>http</code> and <code>https</code> URIs to their WebSocket equivalent
*
* @param inputUrl
* the input URI
* @param query
* the optional query string
* @return the WebSocket scheme URI for the input URI.
* @throws URISyntaxException
* if unable to convert the input URI
*/
public static URI toWebsocket(CharSequence inputUrl, String query) throws URISyntaxException
{
if (query == null)
{
return toWebsocket(new URI(inputUrl.toString()));
}
return toWebsocket(new URI(inputUrl.toString() + '?' + query));
}
/**
* Convert to WebSocket <code>ws</code> or <code>wss</code> scheme URIs
*
* <p>
* Converting <code>http</code> and <code>https</code> URIs to their WebSocket equivalent
*
* @param inputUri
* the input URI
* @return the WebSocket scheme URI for the input URI.
* @throws URISyntaxException
* if unable to convert the input URI
*/
public static URI toWebsocket(final URI inputUri) throws URISyntaxException
{
Objects.requireNonNull(inputUri,"Input URI must not be null");
String httpScheme = inputUri.getScheme();
String wsScheme = null;
if ("ws".equalsIgnoreCase(httpScheme) || "wss".equalsIgnoreCase(httpScheme))
{
// keep as-is
wsScheme = httpScheme;
}
else if ("http".equalsIgnoreCase(httpScheme))
{
// convert to ws
wsScheme = "ws";
}
else if ("https".equalsIgnoreCase(httpScheme))
{
// convert to wss
wsScheme = "wss";
}
else
{
throw new URISyntaxException(inputUri.toString(),"Unrecognized HTTP scheme");
}
return new URI(wsScheme,inputUri.getUserInfo(),inputUri.getHost(),inputUri.getPort(),inputUri.getPath(),inputUri.getQuery(),inputUri.getFragment());
}
}

View File

@ -0,0 +1,88 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.api.util;
import static org.hamcrest.Matchers.*;
import java.net.URI;
import java.net.URISyntaxException;
import org.junit.Assert;
import org.junit.Test;
public class WSURITest
{
private void assertURI(URI actual, URI expected)
{
Assert.assertThat(actual.toASCIIString(),is(expected.toASCIIString()));
}
@Test
public void testHttpsToHttps() throws URISyntaxException
{
assertURI(WSURI.toHttp(URI.create("https://localhost/")),URI.create("https://localhost/"));
}
@Test
public void testHttpsToWss() throws URISyntaxException
{
assertURI(WSURI.toWebsocket(URI.create("https://localhost/")),URI.create("wss://localhost/"));
}
@Test
public void testHttpToHttp() throws URISyntaxException
{
assertURI(WSURI.toHttp(URI.create("http://localhost/")),URI.create("http://localhost/"));
}
@Test
public void testHttpToWs() throws URISyntaxException
{
assertURI(WSURI.toWebsocket(URI.create("http://localhost/")),URI.create("ws://localhost/"));
assertURI(WSURI.toWebsocket(URI.create("http://localhost:8080/deeper/")),URI.create("ws://localhost:8080/deeper/"));
assertURI(WSURI.toWebsocket("http://localhost/"),URI.create("ws://localhost/"));
assertURI(WSURI.toWebsocket("http://localhost/",null),URI.create("ws://localhost/"));
assertURI(WSURI.toWebsocket("http://localhost/","a=b"),URI.create("ws://localhost/?a=b"));
}
@Test
public void testWssToHttps() throws URISyntaxException
{
assertURI(WSURI.toHttp(URI.create("wss://localhost/")),URI.create("https://localhost/"));
}
@Test
public void testWssToWss() throws URISyntaxException
{
assertURI(WSURI.toWebsocket(URI.create("wss://localhost/")),URI.create("wss://localhost/"));
}
@Test
public void testWsToHttp() throws URISyntaxException
{
assertURI(WSURI.toHttp(URI.create("ws://localhost/")),URI.create("http://localhost/"));
}
@Test
public void testWsToWs() throws URISyntaxException
{
assertURI(WSURI.toWebsocket(URI.create("ws://localhost/")),URI.create("ws://localhost/"));
}
}

View File

@ -37,6 +37,11 @@
<artifactId>websocket-common</artifactId> <artifactId>websocket-common</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty.websocket</groupId> <groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-servlet</artifactId> <artifactId>websocket-servlet</artifactId>

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.server;
import java.net.HttpCookie; import java.net.HttpCookie;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.URISyntaxException;
import java.security.Principal; import java.security.Principal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -36,14 +37,15 @@ import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.util.QuoteUtil; import org.eclipse.jetty.websocket.api.util.QuoteUtil;
import org.eclipse.jetty.websocket.api.util.WSURI;
public class ServletWebSocketRequest extends UpgradeRequest public class ServletWebSocketRequest extends UpgradeRequest
{ {
private HttpServletRequest req; private HttpServletRequest req;
public ServletWebSocketRequest(HttpServletRequest request) public ServletWebSocketRequest(HttpServletRequest request) throws URISyntaxException
{ {
super(request.getRequestURI()); super(WSURI.toWebsocket(request.getRequestURL(),request.getQueryString()));
this.req = request; this.req = request;
// Copy Request Line Details // Copy Request Line Details

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.websocket.server; package org.eclipse.jetty.websocket.server;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -27,6 +28,7 @@ import java.util.Map;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -133,6 +135,8 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
@Override @Override
public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response) throws IOException public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response) throws IOException
{
try
{ {
ServletWebSocketRequest sockreq = new ServletWebSocketRequest(request); ServletWebSocketRequest sockreq = new ServletWebSocketRequest(request);
ServletWebSocketResponse sockresp = new ServletWebSocketResponse(response); ServletWebSocketResponse sockresp = new ServletWebSocketResponse(response);
@ -167,6 +171,11 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
EventDriver driver = eventDriverFactory.wrap(websocketPojo); EventDriver driver = eventDriverFactory.wrap(websocketPojo);
return upgrade(sockreq,sockresp,driver); return upgrade(sockreq,sockresp,driver);
} }
catch (URISyntaxException e)
{
throw new IOException("Unable to accept websocket due to mangled URI",e);
}
}
@Override @Override
public void cleanup() public void cleanup()

View File

@ -19,35 +19,95 @@
package org.eclipse.jetty.websocket.server; package org.eclipse.jetty.websocket.server;
import java.net.URI; import java.net.URI;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory;
public class SimpleServletServer public class SimpleServletServer
{ {
private static final Logger LOG = Log.getLogger(SimpleServletServer.class);
private Server server; private Server server;
private ServerConnector connector; private ServerConnector connector;
private URI serverUri; private URI serverUri;
private HttpServlet servlet; private HttpServlet servlet;
private boolean ssl = false;
private SslContextFactory sslContextFactory;
public SimpleServletServer(HttpServlet servlet) public SimpleServletServer(HttpServlet servlet)
{ {
this.servlet = servlet; this.servlet = servlet;
} }
public void enableSsl(boolean ssl)
{
this.ssl = ssl;
}
public URI getServerUri() public URI getServerUri()
{ {
return serverUri; return serverUri;
} }
public SslContextFactory getSslContextFactory()
{
return sslContextFactory;
}
public boolean isSslEnabled()
{
return ssl;
}
public void start() throws Exception public void start() throws Exception
{ {
// Configure Server // Configure Server
server = new Server(); server = new Server();
if (ssl)
{
// HTTP Configuration
HttpConfiguration http_config = new HttpConfiguration();
http_config.setSecureScheme("https");
http_config.setSecurePort(0);
http_config.setOutputBufferSize(32768);
http_config.setRequestHeaderSize(8192);
http_config.setResponseHeaderSize(8192);
http_config.setSendServerVersion(true);
http_config.setSendDateHeader(false);
sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath());
sslContextFactory.setKeyStorePassword("storepwd");
sslContextFactory.setKeyManagerPassword("keypwd");
sslContextFactory.setExcludeCipherSuites("SSL_RSA_WITH_DES_CBC_SHA","SSL_DHE_RSA_WITH_DES_CBC_SHA","SSL_DHE_DSS_WITH_DES_CBC_SHA",
"SSL_RSA_EXPORT_WITH_RC4_40_MD5","SSL_RSA_EXPORT_WITH_DES40_CBC_SHA","SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA");
// SSL HTTP Configuration
HttpConfiguration https_config = new HttpConfiguration(http_config);
https_config.addCustomizer(new SecureRequestCustomizer());
// SSL Connector
connector = new ServerConnector(server,new SslConnectionFactory(sslContextFactory,"http/1.1"),new HttpConnectionFactory(https_config));
connector.setPort(0);
}
else
{
// Basic HTTP connector
connector = new ServerConnector(server); connector = new ServerConnector(server);
connector.setPort(0);
}
server.addConnector(connector); server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler(); ServletContextHandler context = new ServletContextHandler();
@ -60,13 +120,20 @@ public class SimpleServletServer
// Start Server // Start Server
server.start(); server.start();
// Establish the Server URI
String host = connector.getHost(); String host = connector.getHost();
if (host == null) if (host == null)
{ {
host = "localhost"; host = "localhost";
} }
int port = connector.getLocalPort(); int port = connector.getLocalPort();
serverUri = new URI(String.format("ws://%s:%d/",host,port)); serverUri = new URI(String.format("%s://%s:%d/",ssl?"wss":"ws",host,port));
// Some debugging
if (LOG.isDebugEnabled())
{
LOG.debug(server.dump());
}
} }
public void stop() public void stop()

View File

@ -1,222 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.server;
import static org.junit.Assert.*;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.management.ManagementFactory;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
import org.eclipse.jetty.websocket.common.Generator;
import org.eclipse.jetty.websocket.common.Parser;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.server.examples.MyEchoSocket;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
public class WebSocketLoadRFC6455Test
{
private class WebSocketClient implements Runnable
{
private final Socket socket;
private final BufferedWriter output;
private final BufferedReader input;
private final int iterations;
private final CountDownLatch latch;
private/* final */EndPoint _endp;
private final Generator _generator;
private final Parser _parser;
private final IncomingFrames _handler = new IncomingFrames()
{
@Override
public void incomingError(WebSocketException e)
{
}
@Override
public void incomingFrame(Frame frame)
{
}
};
private volatile ByteBuffer _response;
public WebSocketClient(String host, int port, int readTimeout, CountDownLatch latch, int iterations) throws IOException
{
this.latch = latch;
socket = new Socket(host,port);
socket.setSoTimeout(readTimeout);
output = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"ISO-8859-1"));
input = new BufferedReader(new InputStreamReader(socket.getInputStream(),"ISO-8859-1"));
this.iterations = iterations;
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
// _endp=new SocketEndPoint(socket);
ByteBufferPool bufferPool = new MappedByteBufferPool();
_generator = new Generator(policy,bufferPool);
_parser = new Parser(policy,bufferPool);
}
public void close() throws IOException
{
socket.close();
}
private void open() throws IOException
{
output.write("GET /chat HTTP/1.1\r\n" + "Host: server.example.com\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: onConnect\r\n" + "Sec-WebSocket-Version: 7\r\n" + "\r\n");
output.flush();
String responseLine = input.readLine();
assertTrue(responseLine.startsWith("HTTP/1.1 101 Switching Protocols"));
// Read until we find an empty line, which signals the end of the http response
String line;
while ((line = input.readLine()) != null)
{
if (line.length() == 0)
{
break;
}
}
}
@Override
public void run()
{
try
{
String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
for (int i = 0; i < iterations; ++i)
{
WebSocketFrame txt = WebSocketFrame.text(message);
ByteBuffer buf = _generator.generate(txt);
// TODO: Send it
// TODO: Receive response
Assert.assertEquals(message,_response.toString());
latch.countDown();
}
}
catch (Throwable x)
{
throw new RuntimeException(x);
}
}
}
private static Server _server;
private static ServerConnector _connector;
@BeforeClass
public static void startServer() throws Exception
{
QueuedThreadPool threadPool = new QueuedThreadPool(200);
threadPool.setStopTimeout(1000);
_server = new Server(threadPool);
_server.manage(threadPool);
_connector = new ServerConnector(_server);
_server.addConnector(_connector);
WebSocketHandler wsHandler = new WebSocketHandler.Simple(MyEchoSocket.class);
wsHandler.setHandler(new DefaultHandler());
_server.setHandler(wsHandler);
_server.start();
}
@AfterClass
public static void stopServer() throws Exception
{
_server.stop();
_server.join();
}
@Test
@Ignore("Not yet converted to new jetty-9 structure")
public void testLoad() throws Exception
{
int count = 50;
int iterations = 100;
ExecutorService threadPool = Executors.newCachedThreadPool();
try
{
CountDownLatch latch = new CountDownLatch(count * iterations);
WebSocketClient[] clients = new WebSocketClient[count];
for (int i = 0; i < clients.length; ++i)
{
clients[i] = new WebSocketClient("localhost",_connector.getLocalPort(),1000,latch,iterations);
clients[i].open();
}
// long start = System.nanoTime();
for (WebSocketClient client : clients)
{
threadPool.execute(client);
}
int parallelism = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors();
long maxTimePerIteration = 5;
assertTrue(latch.await(iterations * ((count / parallelism) + 1) * maxTimePerIteration,TimeUnit.MILLISECONDS));
// long end = System.nanoTime();
// System.err.println("Elapsed: " + TimeUnit.NANOSECONDS.toMillis(end - start) + " ms");
for (WebSocketClient client : clients)
{
client.close();
}
}
finally
{
threadPool.shutdown();
assertTrue(threadPool.awaitTermination(2,TimeUnit.SECONDS));
}
}
}

View File

@ -18,155 +18,111 @@
package org.eclipse.jetty.websocket.server; package org.eclipse.jetty.websocket.server;
import java.util.Arrays; import static org.hamcrest.Matchers.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.toolchain.test.EventQueue;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketAdapter; import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.server.examples.MyEchoSocket; import org.eclipse.jetty.websocket.server.helper.CaptureSocket;
import org.junit.After; import org.eclipse.jetty.websocket.server.helper.SessionServlet;
import org.junit.AfterClass;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Ignore; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
public class WebSocketOverSSLTest public class WebSocketOverSSLTest
{ {
private Server _server; private static SimpleServletServer server;
private int _port;
private QueuedThreadPool _threadPool;
private Session _session; @BeforeClass
public static void startServer() throws Exception
@After
public void destroy() throws Exception
{ {
if (_session != null) server = new SimpleServletServer(new SessionServlet());
{ server.enableSsl(true);
_session.close(); server.start();
} }
// if (_wsFactory != null) @AfterClass
// _wsFactory.stop(); public static void stopServer()
if (_threadPool != null)
{ {
_threadPool.stop(); server.stop();
}
if (_server != null)
{
_server.stop();
_server.join();
}
}
private void startClient(final Object webSocket) throws Exception
{
Assert.assertTrue(_server.isStarted());
_threadPool = new QueuedThreadPool();
_threadPool.setName("wsc-" + _threadPool.getName());
_threadPool.start();
// _wsFactory = new WebSocketClientFactory(_threadPool, new ZeroMasker());
// SslContextFactory cf = _wsFactory.getSslContextFactory();
// cf.setKeyStorePath(MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath());
// cf.setKeyStorePassword("storepwd");
// cf.setKeyManagerPassword("keypwd");
// _wsFactory.start();
// WebSocketClient client = new WebSocketClient(_wsFactory);
// _connection = client.open(new URI("wss://localhost:" + _port), webSocket).get(5, TimeUnit.SECONDS);
}
private void startServer(final Object websocket) throws Exception
{
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath());
sslContextFactory.setKeyStorePassword("storepwd");
sslContextFactory.setKeyManagerPassword("keypwd");
_server = new Server();
ServerConnector connector = new ServerConnector(_server,sslContextFactory);
_server.addConnector(connector);
_server.setHandler(new WebSocketHandler.Simple(websocket.getClass()));
_server.start();
_port = connector.getLocalPort();
} }
/**
* Test the requirement of issuing socket and receiving echo response
*/
@Test @Test
@Ignore("SSL Not yet implemented") public void testEcho() throws Exception
public void testManyMessages() throws Exception
{ {
startServer(MyEchoSocket.class); Assert.assertThat("server scheme",server.getServerUri().getScheme(),is("wss"));
int count = 1000; WebSocketClient client = new WebSocketClient(server.getSslContextFactory());
final CountDownLatch clientLatch = new CountDownLatch(count); try
startClient(new WebSocketAdapter()
{ {
@Override client.start();
public void onWebSocketText(String message)
{
clientLatch.countDown();
}
});
char[] chars = new char[256]; CaptureSocket clientSocket = new CaptureSocket();
Arrays.fill(chars,'x'); Future<Session> fut = client.connect(clientSocket,server.getServerUri());
String message = new String(chars);
for (int i = 0; i < count; ++i) // wait for connect
Session session = fut.get(1,TimeUnit.SECONDS);
// Ask server socket
// Generate text frame
String msg = "this is an echo ... cho ... ho ... o";
session.getRemote().sendString(msg);
// Read frame (hopefully text frame)
clientSocket.messages.awaitEventCount(1,500,TimeUnit.MILLISECONDS);
EventQueue<String> captured = clientSocket.messages;
Assert.assertThat("Text Message",captured.poll(),is(msg));
// Shutdown the socket
clientSocket.close();
}
finally
{ {
_session.getRemote().sendStringByFuture(message); client.stop();
} }
Assert.assertTrue(clientLatch.await(20,TimeUnit.SECONDS));
// While messages may have all arrived, the SSL close alert
// may be in the way so give some time for it to be processed.
TimeUnit.SECONDS.sleep(1);
} }
/**
* Test that server session reports as secure
*/
@Test @Test
@Ignore("SSL Not yet implemented") public void testServerSessionIsSecure() throws Exception
public void testWebSocketOverSSL() throws Exception
{ {
final String message = "message"; Assert.assertThat("server scheme",server.getServerUri().getScheme(),is("wss"));
final CountDownLatch serverLatch = new CountDownLatch(1); WebSocketClient client = new WebSocketClient(server.getSslContextFactory());
startServer(new WebSocketAdapter() try
{ {
private Session session; client.start();
@Override CaptureSocket clientSocket = new CaptureSocket();
public void onWebSocketConnect(Session session) Future<Session> fut = client.connect(clientSocket,server.getServerUri());
{
this.session = session;
}
@Override // wait for connect
public void onWebSocketText(String message) Session session = fut.get(1,TimeUnit.SECONDS);
{
Assert.assertEquals(message,message);
session.getRemote().sendStringByFuture(message);
serverLatch.countDown();
}
});
final CountDownLatch clientLatch = new CountDownLatch(1);
startClient(new WebSocketAdapter()
{
@Override
public void onWebSocketText(String data)
{
Assert.assertEquals(message,data);
clientLatch.countDown();
}
});
_session.getRemote().sendStringByFuture(message);
Assert.assertTrue(serverLatch.await(5,TimeUnit.SECONDS)); // Ask server socket
Assert.assertTrue(clientLatch.await(5,TimeUnit.SECONDS));
// Generate text frame
session.getRemote().sendString("session.isSecure");
// Read frame (hopefully text frame)
clientSocket.messages.awaitEventCount(1,500,TimeUnit.MILLISECONDS);
EventQueue<String> captured = clientSocket.messages;
Assert.assertThat("Server.session.isSecure",captured.poll(),is("session.isSecure=true"));
// Shutdown the socket
clientSocket.close();
}
finally
{
client.stop();
}
} }
} }

View File

@ -163,7 +163,7 @@ public class WebSocketServletRFCTest
} }
/** /**
* Test the requirement of issuing * Test the requirement of issuing socket and receiving echo response
*/ */
@Test @Test
public void testEcho() throws Exception public void testEcho() throws Exception

View File

@ -55,6 +55,7 @@ import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames; import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames; import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
import org.eclipse.jetty.websocket.api.util.WSURI;
import org.eclipse.jetty.websocket.common.AcceptHash; import org.eclipse.jetty.websocket.common.AcceptHash;
import org.eclipse.jetty.websocket.common.CloseInfo; import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.ConnectionState; import org.eclipse.jetty.websocket.common.ConnectionState;
@ -128,9 +129,12 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti
if (destWebsocketURI.getScheme().equals("wss")) if (destWebsocketURI.getScheme().equals("wss"))
{ {
scheme = "https"; scheme = "https";
throw new RuntimeException("Sorry, BlockheadClient does not support SSL");
} }
this.destHttpURI = new URI(scheme,destWebsocketURI.getUserInfo(),destWebsocketURI.getHost(),destWebsocketURI.getPort(),destWebsocketURI.getPath(), this.destHttpURI = WSURI.toHttp(destWebsocketURI);
destWebsocketURI.getQuery(),destWebsocketURI.getFragment());
LOG.debug("WebSocket URI: {}",destWebsocketURI);
LOG.debug(" HTTP URI: {}",destHttpURI);
this.bufferPool = new MappedByteBufferPool(8192); this.bufferPool = new MappedByteBufferPool(8192);
this.generator = new Generator(policy,bufferPool); this.generator = new Generator(policy,bufferPool);

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.websocket.server.blockhead; package org.eclipse.jetty.websocket.server.blockhead;
import static org.hamcrest.Matchers.*;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.ArrayList; import java.util.ArrayList;
@ -30,8 +32,6 @@ import org.junit.runner.RunWith;
import org.junit.runners.Parameterized; import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters; import org.junit.runners.Parameterized.Parameters;
import static org.hamcrest.Matchers.is;
/** /**
* Gotta test some basic constructors of the BlockheadClient. * Gotta test some basic constructors of the BlockheadClient.
*/ */
@ -47,7 +47,6 @@ public class BlockheadClientConstructionTest
data.add(new Object[] { "ws://localhost:8080/", "http://localhost:8080/" }); data.add(new Object[] { "ws://localhost:8080/", "http://localhost:8080/" });
data.add(new Object[] { "ws://webtide.com/", "http://webtide.com/" }); data.add(new Object[] { "ws://webtide.com/", "http://webtide.com/" });
data.add(new Object[] { "ws://www.webtide.com/sockets/chat", "http://www.webtide.com/sockets/chat" }); data.add(new Object[] { "ws://www.webtide.com/sockets/chat", "http://www.webtide.com/sockets/chat" });
data.add(new Object[] { "wss://dummy.eclipse.org:5454/", "https://dummy.eclipse.org:5454/" });
// @formatter:on // @formatter:on
return data; return data;
} }

View File

@ -18,22 +18,22 @@
package org.eclipse.jetty.websocket.server.helper; package org.eclipse.jetty.websocket.server.helper;
import java.io.IOException;
import java.sql.Connection; import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.EventQueue;
import org.eclipse.jetty.websocket.api.WebSocketAdapter; import org.eclipse.jetty.websocket.api.WebSocketAdapter;
public class CaptureSocket extends WebSocketAdapter public class CaptureSocket extends WebSocketAdapter
{ {
private final CountDownLatch latch = new CountDownLatch(1); private final CountDownLatch latch = new CountDownLatch(1);
public List<String> messages; public EventQueue<String> messages;
public CaptureSocket() public CaptureSocket()
{ {
messages = new ArrayList<String>(); messages = new EventQueue<String>();
} }
public boolean awaitConnected(long timeout) throws InterruptedException public boolean awaitConnected(long timeout) throws InterruptedException
@ -41,6 +41,18 @@ public class CaptureSocket extends WebSocketAdapter
return latch.await(timeout,TimeUnit.MILLISECONDS); return latch.await(timeout,TimeUnit.MILLISECONDS);
} }
public void close()
{
try
{
getSession().close();
}
catch (IOException ignore)
{
/* ignore */
}
}
public void onClose(int closeCode, String message) public void onClose(int closeCode, String message)
{ {
} }

View File

@ -81,6 +81,20 @@ public class SessionSocket
return; return;
} }
if ("session.isSecure".equals(message))
{
String issecure = String.format("session.isSecure=%b",session.isSecure());
session.getRemote().sendStringByFuture(issecure);
return;
}
if ("getRequestURI".equals(message))
{
String requestURI = session.getUpgradeRequest().getRequestURI().toASCIIString();
session.getRemote().sendStringByFuture(requestURI);
return;
}
if ("harsh-disconnect".equals(message)) if ("harsh-disconnect".equals(message))
{ {
session.disconnect(); session.disconnect();