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:
parent
9abed8e85d
commit
e61c161ebd
|
@ -42,6 +42,7 @@ public class UpgradeRequest
|
|||
private String httpVersion;
|
||||
private String method;
|
||||
private String host;
|
||||
private boolean secure = false;
|
||||
|
||||
protected UpgradeRequest()
|
||||
{
|
||||
|
@ -225,6 +226,11 @@ public class UpgradeRequest
|
|||
return test.equalsIgnoreCase(getOrigin());
|
||||
}
|
||||
|
||||
public boolean isSecure()
|
||||
{
|
||||
return secure;
|
||||
}
|
||||
|
||||
public void setCookies(List<HttpCookie> cookies)
|
||||
{
|
||||
this.cookies = cookies;
|
||||
|
@ -261,6 +267,19 @@ public class UpgradeRequest
|
|||
public void setRequestURI(URI 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.parameters.clear();
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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/"));
|
||||
}
|
||||
|
||||
}
|
|
@ -37,6 +37,11 @@
|
|||
<artifactId>websocket-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-servlet</artifactId>
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.server;
|
|||
|
||||
import java.net.HttpCookie;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
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.extensions.ExtensionConfig;
|
||||
import org.eclipse.jetty.websocket.api.util.QuoteUtil;
|
||||
import org.eclipse.jetty.websocket.api.util.WSURI;
|
||||
|
||||
public class ServletWebSocketRequest extends UpgradeRequest
|
||||
{
|
||||
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;
|
||||
|
||||
// Copy Request Line Details
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package org.eclipse.jetty.websocket.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -27,6 +28,7 @@ import java.util.Map;
|
|||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
|
@ -134,38 +136,45 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
|
|||
@Override
|
||||
public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
ServletWebSocketRequest sockreq = new ServletWebSocketRequest(request);
|
||||
ServletWebSocketResponse sockresp = new ServletWebSocketResponse(response);
|
||||
|
||||
WebSocketCreator creator = getCreator();
|
||||
|
||||
UpgradeContext context = getActiveUpgradeContext();
|
||||
if (context == null)
|
||||
try
|
||||
{
|
||||
context = new UpgradeContext();
|
||||
setActiveUpgradeContext(context);
|
||||
ServletWebSocketRequest sockreq = new ServletWebSocketRequest(request);
|
||||
ServletWebSocketResponse sockresp = new ServletWebSocketResponse(response);
|
||||
|
||||
WebSocketCreator creator = getCreator();
|
||||
|
||||
UpgradeContext context = getActiveUpgradeContext();
|
||||
if (context == null)
|
||||
{
|
||||
context = new UpgradeContext();
|
||||
setActiveUpgradeContext(context);
|
||||
}
|
||||
context.setRequest(sockreq);
|
||||
context.setResponse(sockresp);
|
||||
|
||||
Object websocketPojo = creator.createWebSocket(sockreq,sockresp);
|
||||
|
||||
// Handle response forbidden (and similar paths)
|
||||
if (sockresp.isCommitted())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (websocketPojo == null)
|
||||
{
|
||||
// no creation, sorry
|
||||
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send the upgrade
|
||||
EventDriver driver = eventDriverFactory.wrap(websocketPojo);
|
||||
return upgrade(sockreq,sockresp,driver);
|
||||
}
|
||||
context.setRequest(sockreq);
|
||||
context.setResponse(sockresp);
|
||||
|
||||
Object websocketPojo = creator.createWebSocket(sockreq,sockresp);
|
||||
|
||||
// Handle response forbidden (and similar paths)
|
||||
if (sockresp.isCommitted())
|
||||
catch (URISyntaxException e)
|
||||
{
|
||||
return false;
|
||||
throw new IOException("Unable to accept websocket due to mangled URI",e);
|
||||
}
|
||||
|
||||
if (websocketPojo == null)
|
||||
{
|
||||
// no creation, sorry
|
||||
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send the upgrade
|
||||
EventDriver driver = eventDriverFactory.wrap(websocketPojo);
|
||||
return upgrade(sockreq,sockresp,driver);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -307,7 +316,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
|
|||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
*
|
||||
* @see org.eclipse.jetty.websocket.server.WebSocketServletFactory#register(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
|
@ -348,7 +357,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
|
|||
* <p>
|
||||
* This method will not normally return, but will instead throw a UpgradeConnectionException, to exit HTTP handling and initiate WebSocket handling of the
|
||||
* connection.
|
||||
*
|
||||
*
|
||||
* @param request
|
||||
* The request to upgrade
|
||||
* @param response
|
||||
|
|
|
@ -19,35 +19,95 @@
|
|||
package org.eclipse.jetty.websocket.server;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
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.ServerConnector;
|
||||
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
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
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(SimpleServletServer.class);
|
||||
private Server server;
|
||||
private ServerConnector connector;
|
||||
private URI serverUri;
|
||||
private HttpServlet servlet;
|
||||
private boolean ssl = false;
|
||||
private SslContextFactory sslContextFactory;
|
||||
|
||||
public SimpleServletServer(HttpServlet servlet)
|
||||
{
|
||||
this.servlet = servlet;
|
||||
}
|
||||
|
||||
public void enableSsl(boolean ssl)
|
||||
{
|
||||
this.ssl = ssl;
|
||||
}
|
||||
|
||||
public URI getServerUri()
|
||||
{
|
||||
return serverUri;
|
||||
}
|
||||
|
||||
public SslContextFactory getSslContextFactory()
|
||||
{
|
||||
return sslContextFactory;
|
||||
}
|
||||
|
||||
public boolean isSslEnabled()
|
||||
{
|
||||
return ssl;
|
||||
}
|
||||
|
||||
public void start() throws Exception
|
||||
{
|
||||
// Configure Server
|
||||
server = new Server();
|
||||
connector = new ServerConnector(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.setPort(0);
|
||||
}
|
||||
server.addConnector(connector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler();
|
||||
|
@ -60,13 +120,20 @@ public class SimpleServletServer
|
|||
// Start Server
|
||||
server.start();
|
||||
|
||||
// Establish the Server URI
|
||||
String host = connector.getHost();
|
||||
if (host == null)
|
||||
{
|
||||
host = "localhost";
|
||||
}
|
||||
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()
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,155 +18,111 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.server;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.server.Server;
|
||||
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.toolchain.test.EventQueue;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
||||
import org.eclipse.jetty.websocket.server.examples.MyEchoSocket;
|
||||
import org.junit.After;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.eclipse.jetty.websocket.server.helper.CaptureSocket;
|
||||
import org.eclipse.jetty.websocket.server.helper.SessionServlet;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
public class WebSocketOverSSLTest
|
||||
{
|
||||
private Server _server;
|
||||
private int _port;
|
||||
private QueuedThreadPool _threadPool;
|
||||
private static SimpleServletServer server;
|
||||
|
||||
private Session _session;
|
||||
|
||||
@After
|
||||
public void destroy() throws Exception
|
||||
@BeforeClass
|
||||
public static void startServer() throws Exception
|
||||
{
|
||||
if (_session != null)
|
||||
{
|
||||
_session.close();
|
||||
}
|
||||
|
||||
// if (_wsFactory != null)
|
||||
// _wsFactory.stop();
|
||||
|
||||
if (_threadPool != null)
|
||||
{
|
||||
_threadPool.stop();
|
||||
}
|
||||
|
||||
if (_server != null)
|
||||
{
|
||||
_server.stop();
|
||||
_server.join();
|
||||
}
|
||||
server = new SimpleServletServer(new SessionServlet());
|
||||
server.enableSsl(true);
|
||||
server.start();
|
||||
}
|
||||
|
||||
private void startClient(final Object webSocket) throws Exception
|
||||
@AfterClass
|
||||
public static void stopServer()
|
||||
{
|
||||
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();
|
||||
server.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the requirement of issuing socket and receiving echo response
|
||||
*/
|
||||
@Test
|
||||
@Ignore("SSL Not yet implemented")
|
||||
public void testManyMessages() throws Exception
|
||||
public void testEcho() throws Exception
|
||||
{
|
||||
startServer(MyEchoSocket.class);
|
||||
int count = 1000;
|
||||
final CountDownLatch clientLatch = new CountDownLatch(count);
|
||||
startClient(new WebSocketAdapter()
|
||||
Assert.assertThat("server scheme",server.getServerUri().getScheme(),is("wss"));
|
||||
WebSocketClient client = new WebSocketClient(server.getSslContextFactory());
|
||||
try
|
||||
{
|
||||
@Override
|
||||
public void onWebSocketText(String message)
|
||||
{
|
||||
clientLatch.countDown();
|
||||
}
|
||||
});
|
||||
client.start();
|
||||
|
||||
char[] chars = new char[256];
|
||||
Arrays.fill(chars,'x');
|
||||
String message = new String(chars);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
_session.getRemote().sendStringByFuture(message);
|
||||
CaptureSocket clientSocket = new CaptureSocket();
|
||||
Future<Session> fut = client.connect(clientSocket,server.getServerUri());
|
||||
|
||||
// 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
|
||||
{
|
||||
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
|
||||
@Ignore("SSL Not yet implemented")
|
||||
public void testWebSocketOverSSL() throws Exception
|
||||
public void testServerSessionIsSecure() throws Exception
|
||||
{
|
||||
final String message = "message";
|
||||
final CountDownLatch serverLatch = new CountDownLatch(1);
|
||||
startServer(new WebSocketAdapter()
|
||||
Assert.assertThat("server scheme",server.getServerUri().getScheme(),is("wss"));
|
||||
WebSocketClient client = new WebSocketClient(server.getSslContextFactory());
|
||||
try
|
||||
{
|
||||
private Session session;
|
||||
client.start();
|
||||
|
||||
@Override
|
||||
public void onWebSocketConnect(Session session)
|
||||
{
|
||||
this.session = session;
|
||||
}
|
||||
CaptureSocket clientSocket = new CaptureSocket();
|
||||
Future<Session> fut = client.connect(clientSocket,server.getServerUri());
|
||||
|
||||
@Override
|
||||
public void onWebSocketText(String message)
|
||||
{
|
||||
Assert.assertEquals(message,message);
|
||||
session.getRemote().sendStringByFuture(message);
|
||||
serverLatch.countDown();
|
||||
}
|
||||
});
|
||||
final CountDownLatch clientLatch = new CountDownLatch(1);
|
||||
startClient(new WebSocketAdapter()
|
||||
// wait for connect
|
||||
Session session = fut.get(1,TimeUnit.SECONDS);
|
||||
|
||||
// Ask server socket
|
||||
|
||||
// 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
|
||||
{
|
||||
@Override
|
||||
public void onWebSocketText(String data)
|
||||
{
|
||||
Assert.assertEquals(message,data);
|
||||
clientLatch.countDown();
|
||||
}
|
||||
});
|
||||
_session.getRemote().sendStringByFuture(message);
|
||||
|
||||
Assert.assertTrue(serverLatch.await(5,TimeUnit.SECONDS));
|
||||
Assert.assertTrue(clientLatch.await(5,TimeUnit.SECONDS));
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -163,7 +163,7 @@ public class WebSocketServletRFCTest
|
|||
}
|
||||
|
||||
/**
|
||||
* Test the requirement of issuing
|
||||
* Test the requirement of issuing socket and receiving echo response
|
||||
*/
|
||||
@Test
|
||||
public void testEcho() throws Exception
|
||||
|
|
|
@ -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.IncomingFrames;
|
||||
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.CloseInfo;
|
||||
import org.eclipse.jetty.websocket.common.ConnectionState;
|
||||
|
@ -128,9 +129,12 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti
|
|||
if (destWebsocketURI.getScheme().equals("wss"))
|
||||
{
|
||||
scheme = "https";
|
||||
throw new RuntimeException("Sorry, BlockheadClient does not support SSL");
|
||||
}
|
||||
this.destHttpURI = new URI(scheme,destWebsocketURI.getUserInfo(),destWebsocketURI.getHost(),destWebsocketURI.getPort(),destWebsocketURI.getPath(),
|
||||
destWebsocketURI.getQuery(),destWebsocketURI.getFragment());
|
||||
this.destHttpURI = WSURI.toHttp(destWebsocketURI);
|
||||
|
||||
LOG.debug("WebSocket URI: {}",destWebsocketURI);
|
||||
LOG.debug(" HTTP URI: {}",destHttpURI);
|
||||
|
||||
this.bufferPool = new MappedByteBufferPool(8192);
|
||||
this.generator = new Generator(policy,bufferPool);
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.server.blockhead;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -30,8 +32,6 @@ import org.junit.runner.RunWith;
|
|||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
/**
|
||||
* 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://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[] { "wss://dummy.eclipse.org:5454/", "https://dummy.eclipse.org:5454/" });
|
||||
// @formatter:on
|
||||
return data;
|
||||
}
|
||||
|
|
|
@ -18,22 +18,22 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.server.helper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.toolchain.test.EventQueue;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
||||
|
||||
public class CaptureSocket extends WebSocketAdapter
|
||||
{
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
public List<String> messages;
|
||||
public EventQueue<String> messages;
|
||||
|
||||
public CaptureSocket()
|
||||
{
|
||||
messages = new ArrayList<String>();
|
||||
messages = new EventQueue<String>();
|
||||
}
|
||||
|
||||
public boolean awaitConnected(long timeout) throws InterruptedException
|
||||
|
@ -41,6 +41,18 @@ public class CaptureSocket extends WebSocketAdapter
|
|||
return latch.await(timeout,TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void close()
|
||||
{
|
||||
try
|
||||
{
|
||||
getSession().close();
|
||||
}
|
||||
catch (IOException ignore)
|
||||
{
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -81,6 +81,20 @@ public class SessionSocket
|
|||
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))
|
||||
{
|
||||
session.disconnect();
|
||||
|
|
Loading…
Reference in New Issue