Issue #207 - moved client tests to websocket-tests

This commit is contained in:
Joakim Erdfelt 2017-04-26 16:01:07 -07:00
parent ee6d495af4
commit e276705948
21 changed files with 1446 additions and 1919 deletions

View File

@ -1,491 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.client;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.UpgradeException;
import org.eclipse.jetty.websocket.common.AcceptHash;
import org.eclipse.jetty.websocket.common.test.IBlockheadServerConnection;
import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
import org.eclipse.jetty.websocket.common.test.XBlockheadServer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
/**
* Various connect condition testing
*/
@SuppressWarnings("Duplicates")
public class ClientConnectTest
{
@Rule
public TestTracker tt = new TestTracker();
@Rule
public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
private final int timeout = 500;
private XBlockheadServer server;
private WebSocketClient client;
@SuppressWarnings("unchecked")
private <E extends Throwable> E assertExpectedError(ExecutionException e, JettyTrackingSocket wsocket, Class<E> errorClass) throws IOException
{
// Validate thrown cause
Throwable cause = e.getCause();
if (!errorClass.isInstance(cause))
{
cause.printStackTrace(System.err);
Assert.assertThat("ExecutionException.cause", cause, instanceOf(errorClass));
}
if (wsocket.getSession() != null)
{
// Validate websocket captured cause
Assert.assertThat("Error Queue Length", wsocket.errorQueue.size(), greaterThanOrEqualTo(1));
Throwable capcause = wsocket.errorQueue.poll();
Assert.assertThat("Error Queue[0]", capcause, notNullValue());
Assert.assertThat("Error Queue[0]", capcause, instanceOf(errorClass));
// Validate that websocket didn't see an open event
wsocket.assertNotOpened();
// Return the captured cause
return (E) capcause;
}
else
{
return (E) cause;
}
}
@Before
public void startClient() throws Exception
{
client = new WebSocketClient();
client.setBufferPool(bufferPool);
client.setConnectTimeout(timeout);
client.start();
}
@Before
public void startServer() throws Exception
{
server = new XBlockheadServer();
server.start();
}
@After
public void stopClient() throws Exception
{
client.stop();
}
@After
public void stopServer() throws Exception
{
server.stop();
}
@Test
public void testUpgradeRequest() throws Exception
{
JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
IBlockheadServerConnection connection = server.accept();
connection.upgrade();
Session sess = future.get(30,TimeUnit.SECONDS);
wsocket.waitForConnected(1, TimeUnit.SECONDS);
assertThat("Connect.UpgradeRequest", wsocket.connectUpgradeRequest, notNullValue());
assertThat("Connect.UpgradeResponse", wsocket.connectUpgradeResponse, notNullValue());
sess.close();
}
@Test
public void testAltConnect() throws Exception
{
JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
HttpClient httpClient = new HttpClient();
httpClient.start();
WebSocketUpgradeRequest req = new WebSocketUpgradeRequest(new WebSocketClient(), httpClient, wsUri, wsocket);
req.header("X-Foo","Req");
CompletableFuture<Session> sess = req.sendAsync();
sess.thenAccept((s) -> {
System.out.printf("Session: %s%n",s);
s.close();
assertThat("Connect.UpgradeRequest",wsocket.connectUpgradeRequest,notNullValue());
assertThat("Connect.UpgradeResponse",wsocket.connectUpgradeResponse,notNullValue());
});
IBlockheadServerConnection connection = server.accept();
connection.upgrade();
}
@Test
public void testUpgradeWithAuthorizationHeader() throws Exception
{
JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest();
// actual value for this test is irrelevant, its important that this
// header actually be sent with a value (the value specified)
upgradeRequest.setHeader("Authorization", "Bogus SHA1");
Future<Session> future = client.connect(wsocket,wsUri,upgradeRequest);
IBlockheadServerConnection connection = server.accept();
List<String> requestLines = connection.upgrade();
Session sess = future.get(30,TimeUnit.SECONDS);
sess.close();
String authLine = requestLines.stream()
.filter((line) -> line.startsWith("Authorization:"))
.findFirst().get();
assertThat("Request Container Authorization", authLine, is("Authorization: Bogus SHA1"));
assertThat("Connect.UpgradeRequest", wsocket.connectUpgradeRequest, notNullValue());
assertThat("Connect.UpgradeResponse", wsocket.connectUpgradeResponse, notNullValue());
}
@Test
public void testBadHandshake() throws Exception
{
JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
IBlockheadServerConnection connection = server.accept();
connection.readRequest();
// no upgrade, just fail with a 404 error
connection.respond("HTTP/1.1 404 NOT FOUND\r\n" +
"Content-Length: 0\r\n" +
"\r\n");
// The attempt to get upgrade response future should throw error
try
{
future.get(30,TimeUnit.SECONDS);
Assert.fail("Expected ExecutionException -> UpgradeException");
}
catch (ExecutionException e)
{
// Expected Path
UpgradeException ue = assertExpectedError(e,wsocket,UpgradeException.class);
Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
Assert.assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(404));
}
}
@Test
public void testBadHandshake_GetOK() throws Exception
{
JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
IBlockheadServerConnection connection = server.accept();
connection.readRequest();
// Send OK to GET but not upgrade
connection.respond("HTTP/1.1 200 OK\r\n" +
"Content-Length: 0\r\n" +
"\r\n");
// The attempt to get upgrade response future should throw error
try
{
future.get(30,TimeUnit.SECONDS);
Assert.fail("Expected ExecutionException -> UpgradeException");
}
catch (ExecutionException e)
{
// Expected Path
UpgradeException ue = assertExpectedError(e,wsocket,UpgradeException.class);
Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
Assert.assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(200));
}
}
@Test
public void testBadHandshake_GetOK_WithSecWebSocketAccept() throws Exception
{
JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
IBlockheadServerConnection connection = server.accept();
List<String> requestLines = connection.readRequestLines();
String key = connection.parseWebSocketKey(requestLines);
// Send OK to GET but not upgrade
StringBuilder resp = new StringBuilder();
resp.append("HTTP/1.1 200 OK\r\n"); // intentionally 200 (not 101)
// Include a value accept key
resp.append("Sec-WebSocket-Accept: ").append(AcceptHash.hashKey(key)).append("\r\n");
resp.append("Content-Length: 0\r\n");
resp.append("\r\n");
connection.respond(resp.toString());
// The attempt to get upgrade response future should throw error
try
{
future.get(30,TimeUnit.SECONDS);
Assert.fail("Expected ExecutionException -> UpgradeException");
}
catch (ExecutionException e)
{
// Expected Path
UpgradeException ue = assertExpectedError(e,wsocket,UpgradeException.class);
Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
Assert.assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(200));
}
}
@Test
public void testBadHandshake_SwitchingProtocols_InvalidConnectionHeader() throws Exception
{
JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
IBlockheadServerConnection connection = server.accept();
List<String> requestLines = connection.readRequestLines();
String key = connection.parseWebSocketKey(requestLines);
// Send Switching Protocols 101, but invalid 'Connection' header
StringBuilder resp = new StringBuilder();
resp.append("HTTP/1.1 101 Switching Protocols\r\n");
resp.append("Sec-WebSocket-Accept: ").append(AcceptHash.hashKey(key)).append("\r\n");
resp.append("Connection: close\r\n");
resp.append("\r\n");
connection.respond(resp.toString());
// The attempt to get upgrade response future should throw error
try
{
future.get(30,TimeUnit.SECONDS);
Assert.fail("Expected ExecutionException -> UpgradeException");
}
catch (ExecutionException e)
{
// Expected Path
UpgradeException ue = assertExpectedError(e,wsocket,UpgradeException.class);
Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
Assert.assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(101));
}
}
@Test
public void testBadHandshake_SwitchingProtocols_NoConnectionHeader() throws Exception
{
JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
IBlockheadServerConnection connection = server.accept();
List<String> requestLines = connection.readRequestLines();
String key = connection.parseWebSocketKey(requestLines);
// Send Switching Protocols 101, but no 'Connection' header
StringBuilder resp = new StringBuilder();
resp.append("HTTP/1.1 101 Switching Protocols\r\n");
resp.append("Sec-WebSocket-Accept: ").append(AcceptHash.hashKey(key)).append("\r\n");
// Intentionally leave out Connection header
resp.append("\r\n");
connection.respond(resp.toString());
// The attempt to get upgrade response future should throw error
try
{
future.get(30,TimeUnit.SECONDS);
Assert.fail("Expected ExecutionException -> UpgradeException");
}
catch (ExecutionException e)
{
// Expected Path
UpgradeException ue = assertExpectedError(e,wsocket,UpgradeException.class);
Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
Assert.assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(101));
}
}
@Test
public void testBadUpgrade() throws Exception
{
JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
IBlockheadServerConnection connection = server.accept();
connection.readRequest();
// Upgrade badly
connection.respond("HTTP/1.1 101 Upgrade\r\n" + "Sec-WebSocket-Accept: rubbish\r\n" + "\r\n");
// The attempt to get upgrade response future should throw error
try
{
future.get(30,TimeUnit.SECONDS);
Assert.fail("Expected ExecutionException -> UpgradeException");
}
catch (ExecutionException e)
{
// Expected Path
UpgradeException ue = assertExpectedError(e,wsocket,UpgradeException.class);
Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
Assert.assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
Assert.assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(101));
}
}
@Test
public void testConnectionNotAccepted() throws Exception
{
JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
// Intentionally not accept incoming socket.
// server.accept();
try
{
future.get(3,TimeUnit.SECONDS);
Assert.fail("Should have Timed Out");
}
catch (ExecutionException e)
{
assertExpectedError(e,wsocket,UpgradeException.class);
// Possible Passing Path (active session wait timeout)
wsocket.assertNotOpened();
}
catch (TimeoutException e)
{
// Possible Passing Path (concurrency timeout)
wsocket.assertNotOpened();
}
}
@Test
public void testConnectionRefused() throws Exception
{
JettyTrackingSocket wsocket = new JettyTrackingSocket();
// Intentionally bad port with nothing listening on it
URI wsUri = new URI("ws://127.0.0.1:1");
try
{
Future<Session> future = client.connect(wsocket,wsUri);
// The attempt to get upgrade response future should throw error
future.get(3,TimeUnit.SECONDS);
Assert.fail("Expected ExecutionException -> ConnectException");
}
catch (ConnectException e)
{
Throwable t = wsocket.errorQueue.remove();
Assert.assertThat("Error Queue[0]",t,instanceOf(ConnectException.class));
wsocket.assertNotOpened();
}
catch (ExecutionException e)
{
if (OS.IS_WINDOWS)
{
// On windows, this is a SocketTimeoutException
assertExpectedError(e, wsocket, SocketTimeoutException.class);
}
else
{
// Expected path - java.net.ConnectException
assertExpectedError(e, wsocket, ConnectException.class);
}
}
}
@Test(expected = TimeoutException.class)
public void testConnectionTimeout_Concurrent() throws Exception
{
JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
IBlockheadServerConnection ssocket = server.accept();
Assert.assertNotNull(ssocket);
// Intentionally don't upgrade
// ssocket.upgrade();
// The attempt to get upgrade response future should throw error
try
{
future.get(3,TimeUnit.SECONDS);
Assert.fail("Expected ExecutionException -> TimeoutException");
}
catch (ExecutionException e)
{
// Expected path - java.net.ConnectException ?
assertExpectedError(e,wsocket,ConnectException.class);
}
}
}

View File

@ -1,211 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.client;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.net.CookieManager;
import java.net.HttpCookie;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.toolchain.test.EventQueue;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import org.eclipse.jetty.websocket.api.util.QuoteUtil;
import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.common.test.IBlockheadServerConnection;
import org.eclipse.jetty.websocket.common.test.XBlockheadServer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class CookieTest
{
private static final Logger LOG = Log.getLogger(CookieTest.class);
public static class CookieTrackingSocket extends WebSocketAdapter
{
public EventQueue<String> messageQueue = new EventQueue<>();
public EventQueue<Throwable> errorQueue = new EventQueue<>();
private CountDownLatch openLatch = new CountDownLatch(1);
@Override
public void onWebSocketConnect(Session sess)
{
openLatch.countDown();
super.onWebSocketConnect(sess);
}
@Override
public void onWebSocketText(String message)
{
System.err.printf("onTEXT - %s%n",message);
messageQueue.add(message);
}
@Override
public void onWebSocketError(Throwable cause)
{
System.err.printf("onERROR - %s%n",cause);
errorQueue.add(cause);
}
public void awaitOpen(int duration, TimeUnit unit) throws InterruptedException
{
assertTrue("Open Latch", openLatch.await(duration,unit));
}
}
private WebSocketClient client;
private XBlockheadServer server;
@Before
public void startClient() throws Exception
{
client = new WebSocketClient();
client.start();
}
@Before
public void startServer() throws Exception
{
server = new XBlockheadServer();
server.start();
}
@After
public void stopClient() throws Exception
{
if (client.isRunning())
{
client.stop();
}
}
@After
public void stopServer() throws Exception
{
server.stop();
}
@Test
public void testViaCookieManager() throws Exception
{
// Setup client
CookieManager cookieMgr = new CookieManager();
client.setCookieStore(cookieMgr.getCookieStore());
HttpCookie cookie = new HttpCookie("hello","world");
cookie.setPath("/");
cookie.setVersion(0);
cookie.setMaxAge(100000);
cookieMgr.getCookieStore().add(server.getWsUri(),cookie);
cookie = new HttpCookie("foo","bar is the word");
cookie.setPath("/");
cookie.setMaxAge(100000);
cookieMgr.getCookieStore().add(server.getWsUri(),cookie);
// Client connects
CookieTrackingSocket clientSocket = new CookieTrackingSocket();
Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri());
// Server accepts connect
IBlockheadServerConnection serverConn = server.accept();
// client confirms upgrade and receipt of frame
String serverCookies = confirmClientUpgradeAndCookies(clientSocket,clientConnectFuture,serverConn);
assertThat("Cookies seen at server side",serverCookies,containsString("hello=world"));
assertThat("Cookies seen at server side",serverCookies,containsString("foo=bar is the word"));
}
@Test
public void testViaServletUpgradeRequest() throws Exception
{
// Setup client
HttpCookie cookie = new HttpCookie("hello","world");
cookie.setPath("/");
cookie.setMaxAge(100000);
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setCookies(Collections.singletonList(cookie));
// Client connects
CookieTrackingSocket clientSocket = new CookieTrackingSocket();
Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri(),request);
// Server accepts connect
IBlockheadServerConnection serverConn = server.accept();
// client confirms upgrade and receipt of frame
String serverCookies = confirmClientUpgradeAndCookies(clientSocket,clientConnectFuture,serverConn);
Assert.assertThat("Cookies seen at server side",serverCookies,containsString("hello=world"));
}
private String confirmClientUpgradeAndCookies(CookieTrackingSocket clientSocket, Future<Session> clientConnectFuture, IBlockheadServerConnection serverConn)
throws Exception
{
// Server upgrades
List<String> upgradeRequestLines = serverConn.upgrade();
List<String> upgradeRequestCookies = serverConn.regexFind(upgradeRequestLines,"^Cookie: (.*)$");
// Server responds with cookies it knows about
TextFrame serverCookieFrame = new TextFrame();
serverCookieFrame.setFin(true);
serverCookieFrame.setPayload(QuoteUtil.join(upgradeRequestCookies,","));
serverConn.write(serverCookieFrame);
serverConn.flush();
// Confirm client connect on future
clientConnectFuture.get(10,TimeUnit.SECONDS);
clientSocket.awaitOpen(2,TimeUnit.SECONDS);
try
{
// Wait for client receipt of cookie frame via client websocket
clientSocket.messageQueue.awaitEventCount(1, 3, TimeUnit.SECONDS);
}
catch (TimeoutException e)
{
e.printStackTrace(System.err);
assertThat("Message Count", clientSocket.messageQueue.size(), is(1));
}
String cookies = clientSocket.messageQueue.poll();
LOG.debug("Cookies seen at server: {}",cookies);
// Server closes connection
serverConn.close(StatusCode.NORMAL);
return cookies;
}
}

View File

@ -1,117 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.client;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import java.net.URI;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.test.IBlockheadServerConnection;
import org.eclipse.jetty.websocket.common.test.XBlockheadServer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class SessionTest
{
private XBlockheadServer server;
@Before
public void startServer() throws Exception
{
server = new XBlockheadServer();
server.start();
}
@After
public void stopServer() throws Exception
{
server.stop();
}
@Test
public void testBasicEcho_FromClient() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.start();
try
{
JettyTrackingSocket cliSock = new JettyTrackingSocket();
client.getPolicy().setIdleTimeout(10000);
URI wsUri = server.getWsUri();
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setSubProtocols("echo");
Future<Session> future = client.connect(cliSock,wsUri,request);
final IBlockheadServerConnection srvSock = server.accept();
srvSock.upgrade();
Session sess = future.get(30000,TimeUnit.MILLISECONDS);
Assert.assertThat("Session",sess,notNullValue());
Assert.assertThat("Session.open",sess.isOpen(),is(true));
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
cliSock.assertWasOpened();
cliSock.assertNotClosed();
Collection<WebSocketSession> sessions = client.getBeans(WebSocketSession.class);
Assert.assertThat("client.connectionManager.sessions.size",sessions.size(),is(1));
RemoteEndpoint remote = cliSock.getSession().getRemote();
remote.sendStringByFuture("Hello World!");
if (remote.getBatchMode() == BatchMode.ON)
{
remote.flush();
}
srvSock.echoMessage(1,30000,TimeUnit.MILLISECONDS);
// wait for response from server
cliSock.waitForMessage(30000,TimeUnit.MILLISECONDS);
Set<WebSocketSession> open = client.getOpenSessions();
Assert.assertThat("(Before Close) Open Sessions.size", open.size(), is(1));
cliSock.assertMessage("Hello World!");
cliSock.close();
srvSock.close();
cliSock.waitForClose(30000,TimeUnit.MILLISECONDS);
open = client.getOpenSessions();
// TODO this sometimes fails!
// Assert.assertThat("(After Close) Open Sessions.size", open.size(), is(0));
}
finally
{
client.stop();
}
}
}

View File

@ -1,160 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.client;
import static org.hamcrest.Matchers.is;
import java.net.URI;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.client.masks.ZeroMasker;
import org.eclipse.jetty.websocket.common.test.IBlockheadServerConnection;
import org.eclipse.jetty.websocket.common.test.XBlockheadServer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
public class SlowServerTest
{
@Rule
public TestTracker tt = new TestTracker();
private XBlockheadServer server;
private WebSocketClient client;
@Before
public void startClient() throws Exception
{
client = new WebSocketClient();
client.setMaxIdleTimeout(60000);
client.start();
}
@Before
public void startServer() throws Exception
{
server = new XBlockheadServer();
server.start();
}
@After
public void stopClient() throws Exception
{
client.stop();
}
@After
public void stopServer() throws Exception
{
server.stop();
}
@Test
@Slow
public void testServerSlowToRead() throws Exception
{
JettyTrackingSocket tsocket = new JettyTrackingSocket();
client.setMasker(new ZeroMasker());
client.setMaxIdleTimeout(60000);
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(tsocket,wsUri);
IBlockheadServerConnection sconnection = server.accept();
sconnection.setSoTimeout(60000);
sconnection.upgrade();
// Confirm connected
future.get(30,TimeUnit.SECONDS);
tsocket.waitForConnected(30,TimeUnit.SECONDS);
int messageCount = 10;
// Setup slow server read thread
ServerReadThread reader = new ServerReadThread(sconnection, messageCount);
reader.setSlowness(100); // slow it down
reader.start();
// Have client write as quickly as it can.
ClientWriteThread writer = new ClientWriteThread(tsocket.getSession());
writer.setMessageCount(messageCount);
writer.setMessage("Hello");
writer.setSlowness(-1); // disable slowness
writer.start();
writer.join();
// Verify receive
reader.waitForExpectedMessageCount(10,TimeUnit.SECONDS);
Assert.assertThat("Frame Receive Count",reader.getFrameCount(),is(messageCount));
// Close
tsocket.getSession().close(StatusCode.NORMAL,"Done");
Assert.assertTrue("Client Socket Closed",tsocket.closeLatch.await(10,TimeUnit.SECONDS));
tsocket.assertCloseCode(StatusCode.NORMAL);
reader.cancel(); // stop reading
}
@Test
@Slow
public void testServerSlowToSend() throws Exception
{
JettyTrackingSocket clientSocket = new JettyTrackingSocket();
client.setMasker(new ZeroMasker());
client.setMaxIdleTimeout(60000);
URI wsUri = server.getWsUri();
Future<Session> clientConnectFuture = client.connect(clientSocket,wsUri);
IBlockheadServerConnection serverConn = server.accept();
serverConn.setSoTimeout(60000);
serverConn.upgrade();
// Confirm connected
clientConnectFuture.get(30,TimeUnit.SECONDS);
clientSocket.waitForConnected(30,TimeUnit.SECONDS);
// Have server write slowly.
int messageCount = 1000;
ServerWriteThread writer = new ServerWriteThread(serverConn);
writer.setMessageCount(messageCount);
writer.setMessage("Hello");
writer.setSlowness(10);
writer.start();
writer.join();
// Verify receive
Assert.assertThat("Message Receive Count",clientSocket.messageQueue.size(),is(messageCount));
// Close
serverConn.close(StatusCode.NORMAL);
Assert.assertTrue("Client Socket Closed",clientSocket.closeLatch.await(10,TimeUnit.SECONDS));
clientSocket.assertCloseCode(StatusCode.NORMAL);
}
}

View File

@ -1,127 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.client;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import org.eclipse.jetty.websocket.common.test.IBlockheadServerConnection;
import org.eclipse.jetty.websocket.common.test.XBlockheadServer;
import org.junit.Assert;
import org.junit.Test;
public class TomcatServerQuirksTest
{
public static class LatchedSocket extends WebSocketAdapter
{
final CountDownLatch openLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
final CountDownLatch closeLatch = new CountDownLatch(1);
@Override
public void onWebSocketClose(int statusCode, String reason)
{
closeLatch.countDown();
}
@Override
public void onWebSocketConnect(Session session)
{
openLatch.countDown();
}
@Override
public void onWebSocketText(String message)
{
dataLatch.countDown();
}
}
/**
* Test for when encountering a "Transfer-Encoding: chunked" on a Upgrade Response header.
* <ul>
* <li><a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=393075">Eclipse Jetty Bug #393075</a></li>
* <li><a href="https://issues.apache.org/bugzilla/show_bug.cgi?id=54067">Apache Tomcat Bug #54067</a></li>
* </ul>
*
* @throws Exception on test failure
*/
@Test
public void testTomcat7_0_32_WithTransferEncoding() throws Exception
{
XBlockheadServer server = new XBlockheadServer();
WebSocketClient client = new WebSocketClient();
try
{
final int bufferSize = 512;
server.start();
// Setup Client Factory
client.start();
// Create End User WebSocket Class
LatchedSocket websocket = new LatchedSocket();
// Open connection
URI wsURI = server.getWsUri();
client.connect(websocket,wsURI);
// Accept incoming connection
IBlockheadServerConnection socket = server.accept();
socket.setSoTimeout(2000); // timeout
// Issue upgrade
// Add the extra problematic header that triggers bug found in jetty-io
socket.addResponseHeader("Transfer-Encoding","chunked");
socket.upgrade();
// Wait for proper upgrade
Assert.assertTrue("Timed out waiting for Client side WebSocket open event",websocket.openLatch.await(1,TimeUnit.SECONDS));
// Have server write frame.
byte payload[] = new byte[bufferSize / 2];
Arrays.fill(payload,(byte)'x');
ByteBuffer serverFrame = BufferUtil.allocate(bufferSize);
BufferUtil.flipToFill(serverFrame);
serverFrame.put((byte)(0x80 | 0x01)); // FIN + TEXT
serverFrame.put((byte)0x7E); // No MASK and 2 bytes length
serverFrame.put((byte)(payload.length >> 8)); // first length byte
serverFrame.put((byte)(payload.length & 0xFF)); // second length byte
serverFrame.put(payload);
BufferUtil.flipToFlush(serverFrame,0);
socket.write(serverFrame);
socket.flush();
Assert.assertTrue(websocket.dataLatch.await(1000,TimeUnit.SECONDS));
}
finally
{
client.stop();
server.stop();
}
}
}

View File

@ -1,315 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.client;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
import org.eclipse.jetty.websocket.common.test.IBlockheadServerConnection;
import org.eclipse.jetty.websocket.common.test.XBlockheadServer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AdvancedRunner.class)
public class WebSocketClientTest
{
private XBlockheadServer server;
private WebSocketClient client;
@Before
public void startClientServer() throws Exception
{
client = new WebSocketClient();
client.start();
server = new XBlockheadServer();
server.start();
}
@After
public void stopClientServer() throws Exception
{
client.stop();
server.stop();
}
@Test(expected = IllegalArgumentException.class)
public void testAddExtension_NotInstalled() throws Exception
{
JettyTrackingSocket cliSock = new JettyTrackingSocket();
client.getPolicy().setIdleTimeout(10000);
URI wsUri = server.getWsUri();
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setSubProtocols("echo");
request.addExtensions("x-bad");
// Should trigger failure on bad extension
client.connect(cliSock,wsUri,request);
}
@Test
public void testBasicEcho_FromClient() throws Exception
{
JettyTrackingSocket cliSock = new JettyTrackingSocket();
client.getPolicy().setIdleTimeout(10000);
URI wsUri = server.getWsUri();
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setSubProtocols("echo");
Future<Session> future = client.connect(cliSock,wsUri,request);
final IBlockheadServerConnection srvSock = server.accept();
srvSock.upgrade();
Session sess = future.get(30,TimeUnit.SECONDS);
Assert.assertThat("Session",sess,notNullValue());
Assert.assertThat("Session.open",sess.isOpen(),is(true));
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
cliSock.assertWasOpened();
cliSock.assertNotClosed();
Collection<WebSocketSession> sessions = client.getOpenSessions();
Assert.assertThat("client.connectionManager.sessions.size",sessions.size(),is(1));
RemoteEndpoint remote = cliSock.getSession().getRemote();
remote.sendStringByFuture("Hello World!");
if (remote.getBatchMode() == BatchMode.ON)
remote.flush();
srvSock.echoMessage(1,30,TimeUnit.SECONDS);
// wait for response from server
cliSock.waitForMessage(30,TimeUnit.SECONDS);
cliSock.assertMessage("Hello World!");
}
@Test
public void testBasicEcho_UsingCallback() throws Exception
{
client.setMaxIdleTimeout(160000);
JettyTrackingSocket cliSock = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setSubProtocols("echo");
Future<Session> future = client.connect(cliSock,wsUri,request);
final IBlockheadServerConnection srvSock = server.accept();
srvSock.upgrade();
Session sess = future.get(30,TimeUnit.SECONDS);
Assert.assertThat("Session",sess,notNullValue());
Assert.assertThat("Session.open",sess.isOpen(),is(true));
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
cliSock.assertWasOpened();
cliSock.assertNotClosed();
Collection<WebSocketSession> sessions = client.getBeans(WebSocketSession.class);
Assert.assertThat("client.connectionManager.sessions.size",sessions.size(),is(1));
FutureWriteCallback callback = new FutureWriteCallback();
cliSock.getSession().getRemote().sendString("Hello World!",callback);
callback.get(1,TimeUnit.SECONDS);
}
@Test
public void testBasicEcho_FromServer() throws Exception
{
JettyTrackingSocket wsocket = new JettyTrackingSocket();
Future<Session> future = client.connect(wsocket,server.getWsUri());
// Server
final IBlockheadServerConnection srvSock = server.accept();
srvSock.upgrade();
// Validate connect
Session sess = future.get(30,TimeUnit.SECONDS);
Assert.assertThat("Session",sess,notNullValue());
Assert.assertThat("Session.open",sess.isOpen(),is(true));
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
// Have server send initial message
srvSock.write(new TextFrame().setPayload("Hello World"));
// Verify connect
future.get(30,TimeUnit.SECONDS);
wsocket.assertWasOpened();
wsocket.awaitMessage(1,TimeUnit.SECONDS,2);
wsocket.assertMessage("Hello World");
}
@Test
public void testLocalRemoteAddress() throws Exception
{
JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
IBlockheadServerConnection ssocket = server.accept();
ssocket.upgrade();
future.get(30,TimeUnit.SECONDS);
Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
InetSocketAddress local = wsocket.getSession().getLocalAddress();
InetSocketAddress remote = wsocket.getSession().getRemoteAddress();
Assert.assertThat("Local Socket Address",local,notNullValue());
Assert.assertThat("Remote Socket Address",remote,notNullValue());
// Hard to validate (in a portable unit test) the local address that was used/bound in the low level Jetty Endpoint
Assert.assertThat("Local Socket Address / Host",local.getAddress().getHostAddress(),notNullValue());
Assert.assertThat("Local Socket Address / Port",local.getPort(),greaterThan(0));
Assert.assertThat("Remote Socket Address / Host",remote.getAddress().getHostAddress(),is(wsUri.getHost()));
Assert.assertThat("Remote Socket Address / Port",remote.getPort(),greaterThan(0));
}
@Test
public void testMessageBiggerThanBufferSize() throws Exception
{
int bufferSize = 512;
JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
IBlockheadServerConnection ssocket = server.accept();
ssocket.upgrade();
future.get(30,TimeUnit.SECONDS);
Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
int length = bufferSize + (bufferSize / 2); // 1.5 times buffer size
ssocket.write(0x80 | 0x01); // FIN + TEXT
ssocket.write(0x7E); // No MASK and 2 bytes length
ssocket.write(length >> 8); // first length byte
ssocket.write(length & 0xFF); // second length byte
for (int i = 0; i < length; ++i)
{
ssocket.write('x');
}
ssocket.flush();
Assert.assertTrue(wsocket.dataLatch.await(1000,TimeUnit.SECONDS));
}
/**
* Ensure that <code>@WebSocket(maxTextMessageSize = 100*1024)</code> behaves as expected.
*
* @throws Exception
* on test failure
*/
@Test
public void testMaxMessageSize() throws Exception
{
MaxMessageSocket wsocket = new MaxMessageSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
IBlockheadServerConnection ssocket = server.accept();
ssocket.upgrade();
wsocket.awaitConnect(1,TimeUnit.SECONDS);
Session sess = future.get(30,TimeUnit.SECONDS);
Assert.assertThat("Session",sess,notNullValue());
Assert.assertThat("Session.open",sess.isOpen(),is(true));
// Create string that is larger than default size of 64k
// but smaller than maxMessageSize of 100k
byte buf[] = new byte[80 * 1024];
Arrays.fill(buf,(byte)'x');
String msg = StringUtil.toUTF8String(buf,0,buf.length);
wsocket.getSession().getRemote().sendStringByFuture(msg);
ssocket.echoMessage(1,2,TimeUnit.SECONDS);
// wait for response from server
wsocket.waitForMessage(1,TimeUnit.SECONDS);
wsocket.assertMessage(msg);
Assert.assertTrue(wsocket.dataLatch.await(2,TimeUnit.SECONDS));
}
@Test
public void testParameterMap() throws Exception
{
JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri().resolve("/test?snack=cashews&amount=handful&brand=off");
Future<Session> future = client.connect(wsocket,wsUri);
IBlockheadServerConnection ssocket = server.accept();
ssocket.upgrade();
future.get(30,TimeUnit.SECONDS);
Assert.assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
Session session = wsocket.getSession();
UpgradeRequest req = session.getUpgradeRequest();
Assert.assertThat("Upgrade Request",req,notNullValue());
Map<String, List<String>> parameterMap = req.getParameterMap();
Assert.assertThat("Parameter Map",parameterMap,notNullValue());
Assert.assertThat("Parameter[snack]",parameterMap.get("snack"),is(Arrays.asList(new String[] { "cashews" })));
Assert.assertThat("Parameter[amount]",parameterMap.get("amount"),is(Arrays.asList(new String[] { "handful" })));
Assert.assertThat("Parameter[brand]",parameterMap.get("brand"),is(Arrays.asList(new String[] { "off" })));
Assert.assertThat("Parameter[cost]",parameterMap.get("cost"),nullValue());
}
}

View File

@ -16,40 +16,13 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.tests.client;
package org.eclipse.jetty.websocket.tests;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.hamcrest.Matcher;
@WebSocket
public class TrackingSocket
public final class Defaults
{
private Session session;
public void assertClose(int expectedStatusCode, Matcher<String> reasonMatcher)
{
}
public Session getSession()
{
return session;
}
@OnWebSocketConnect
public void onOpen(Session session)
{
this.session = session;
}
public void waitForClose(int timeout, TimeUnit unit)
{
}
public void waitForConnected(int timeout, TimeUnit unit)
{
}
public static final long CONNECT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
public static final long OPEN_EVENT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
public static final long CLOSE_EVENT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
}

View File

@ -22,33 +22,44 @@ import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.nio.ByteBuffer;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.api.WebSocketFrameListener;
import org.eclipse.jetty.websocket.api.WebSocketListener;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.LogicalConnection;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
public class TrackingEndpoint implements WebSocketListener, WebSocketFrameListener
{
private final Logger LOG;
public final Logger LOG;
public CountDownLatch openLatch = new CountDownLatch(1);
public UpgradeRequest openUpgradeRequest;
public UpgradeResponse openUpgradeResponse;
public CountDownLatch closeLatch = new CountDownLatch(1);
public AtomicReference<CloseInfo> closeInfo = new AtomicReference<>();
public AtomicReference<Throwable> error = new AtomicReference<>();
@ -57,7 +68,7 @@ public class TrackingEndpoint implements WebSocketListener, WebSocketFrameListen
public BlockingQueue<ByteBuffer> bufferQueue = new LinkedBlockingDeque<>();
public BlockingQueue<WebSocketFrame> framesQueue = new LinkedBlockingDeque<>();
private WebSocketSession session;
public WebSocketSession session;
public TrackingEndpoint(String id)
{
@ -72,6 +83,37 @@ public class TrackingEndpoint implements WebSocketListener, WebSocketFrameListen
assertThat(prefix + " received close reason", close.getReason(), reasonMatcher);
}
public void assertNotOpened(String prefix)
{
assertTrue(prefix + " open event should not have occurred", openLatch.getCount() > 0);
}
public void assertNotClosed(String prefix)
{
assertTrue(prefix + " close event should not have occurred", closeLatch.getCount() > 0);
}
public void assertNoErrorEvents(String prefix)
{
assertTrue(prefix + " error event should not have occurred", error.get() == null);
}
public void assertErrorEvent(String prefix, Matcher<Throwable> throwableMatcher, Matcher<? super String> messageMatcher)
{
assertThat(prefix + " error event type", error.get(), throwableMatcher);
assertThat(prefix + " error event message", error.get().getMessage(), messageMatcher);
}
public void awaitOpenEvent(String prefix) throws InterruptedException
{
assertTrue(prefix + " onOpen event", openLatch.await(Defaults.OPEN_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
public void awaitCloseEvent(String prefix) throws InterruptedException
{
assertTrue(prefix + " onClose event", closeLatch.await(Defaults.CLOSE_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
public void close(int statusCode, String reason)
{
this.session.close(statusCode, reason);
@ -106,6 +148,8 @@ public class TrackingEndpoint implements WebSocketListener, WebSocketFrameListen
{
assertThat("Session type", session, instanceOf(WebSocketSession.class));
this.session = (WebSocketSession) session;
this.openUpgradeRequest = session.getUpgradeRequest();
this.openUpgradeResponse = session.getUpgradeResponse();
if (LOG.isDebugEnabled())
{
LOG.debug("onWebSocketConnect()");
@ -143,7 +187,27 @@ public class TrackingEndpoint implements WebSocketListener, WebSocketFrameListen
{
LOG.debug("onWSText(\"{}\")", text);
}
messageQueue.offer(text);
}
public AbstractWebSocketConnection getConnection()
{
LogicalConnection connection = this.session.getConnection();
if (connection instanceof AbstractWebSocketConnection)
{
return (AbstractWebSocketConnection) connection;
}
return null;
}
public EndPoint getJettyEndPoint()
{
AbstractWebSocketConnection connection = getConnection();
if (connection != null)
{
return connection.getEndPoint();
}
return null;
}
}

View File

@ -36,14 +36,14 @@ public class UntrustedWSEndpoint extends TrackingEndpoint implements WebSocketLi
private static final Logger LOG = Log.getLogger(UntrustedWSEndpoint.class);
private UntrustedWSSession untrustedSession;
private CompletableFuture<UntrustedWSSession> connectFuture;
private CompletableFuture<UntrustedWSSession> onOpenFuture;
private BiFunction<UntrustedWSSession, String, String> onTextFunction;
private BiFunction<UntrustedWSSession, ByteBuffer, ByteBuffer> onBinaryFunction;
public CompletableFuture<UntrustedWSSession> getConnectFuture()
public CompletableFuture<UntrustedWSSession> getOnOpenFuture()
{
return connectFuture;
return onOpenFuture;
}
public UntrustedWSEndpoint(String id)
@ -56,9 +56,9 @@ public class UntrustedWSEndpoint extends TrackingEndpoint implements WebSocketLi
{
assertThat("Session type", session, instanceOf(UntrustedWSSession.class));
this.untrustedSession = (UntrustedWSSession) session;
if (this.connectFuture != null)
if (this.onOpenFuture != null)
{
this.connectFuture.complete(this.untrustedSession);
this.onOpenFuture.complete(this.untrustedSession);
}
super.onWebSocketConnect(session);
@ -67,10 +67,10 @@ public class UntrustedWSEndpoint extends TrackingEndpoint implements WebSocketLi
@Override
public void onWebSocketError(Throwable cause)
{
if (this.connectFuture != null)
if (this.onOpenFuture != null)
{
// Always trip this, doesn't matter if if completed normally first.
this.connectFuture.completeExceptionally(cause);
this.onOpenFuture.completeExceptionally(cause);
}
super.onWebSocketError(cause);
@ -121,9 +121,9 @@ public class UntrustedWSEndpoint extends TrackingEndpoint implements WebSocketLi
}
}
public void setConnectFuture(CompletableFuture<UntrustedWSSession> future)
public void setOnOpenFuture(CompletableFuture<UntrustedWSSession> future)
{
this.connectFuture = future;
this.onOpenFuture = future;
}
public void setOnBinaryFunction(BiFunction<UntrustedWSSession, ByteBuffer, ByteBuffer> onBinaryFunction)

View File

@ -22,6 +22,10 @@ import java.net.URI;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.HttpConfiguration;
@ -31,12 +35,16 @@ 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.component.ContainerLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.websocket.api.util.WSURI;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.eclipse.jetty.websocket.tests.servlets.BiConsumerServiceServlet;
import org.junit.rules.TestName;
public class UntrustedWSServer extends ContainerLifeCycle implements UntrustedWSSessionFactory.Listener
{
@ -47,7 +55,8 @@ public class UntrustedWSServer extends ContainerLifeCycle implements UntrustedWS
private boolean ssl = false;
private SslContextFactory sslContextFactory;
private Map<URI, CompletableFuture<UntrustedWSSession>> connectionFutures = new ConcurrentHashMap<>();
private Map<URI, CompletableFuture<UntrustedWSSession>> onOpenFutures = new ConcurrentHashMap<>();
private final ServletContextHandler context = new ServletContextHandler();
@Override
protected void doStart() throws Exception
@ -65,21 +74,21 @@ public class UntrustedWSServer extends ContainerLifeCycle implements UntrustedWS
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",
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, HttpVersion.HTTP_1_1.asString()),new HttpConnectionFactory(https_config));
connector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), new HttpConnectionFactory(https_config));
connector.setPort(0);
}
else
@ -89,27 +98,26 @@ public class UntrustedWSServer extends ContainerLifeCycle implements UntrustedWS
connector.setPort(0);
}
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
server.setHandler(context);
// Serve untrusted endpoint
context.addServlet(UntrustedWSServlet.class, "/untrusted/*").setInitOrder(1);
// Start Server
addBean(server);
super.doStart();
// Wireup Context related things
UntrustedWSSessionFactory sessionFactory = (UntrustedWSSessionFactory) context.getServletContext().getAttribute(UntrustedWSSessionFactory.class.getName());
sessionFactory.addListener(this);
// Establish the Server URI
URI serverUri = server.getURI();
wsUri = WSURI.toWebsocket(serverUri).resolve("/");
// Some debugging
if (LOG.isDebugEnabled())
{
@ -125,21 +133,37 @@ public class UntrustedWSServer extends ContainerLifeCycle implements UntrustedWS
return wsUri;
}
public URI getUntrustedWsUri(Class<?> clazz, TestName testname)
{
return wsUri.resolve("/untrusted/" + clazz.getSimpleName() + "/" + testname.getMethodName());
}
public void registerHttpService(String urlPattern, BiConsumer<HttpServletRequest, HttpServletResponse> serviceConsumer)
{
ServletHolder holder = new ServletHolder(new BiConsumerServiceServlet(serviceConsumer));
context.addServlet(holder, urlPattern);
}
public void registerWebSocket(String urlPattern, WebSocketCreator creator)
{
ServletHolder holder = new ServletHolder(new UntrustedWSServlet(creator));
context.addServlet(holder, urlPattern);
}
@Override
public void onSessionCreate(UntrustedWSSession session, URI requestURI)
{
// A new session was created (but not connected, yet)
CompletableFuture<UntrustedWSSession> sessionFuture = this.connectionFutures.get(requestURI);
if(sessionFuture != null)
CompletableFuture<UntrustedWSSession> sessionFuture = this.onOpenFutures.get(requestURI);
if (sessionFuture != null)
{
session.getUntrustedEndpoint().setConnectFuture(sessionFuture);
session.getUntrustedEndpoint().setOnOpenFuture(sessionFuture);
this.onOpenFutures.put(requestURI, sessionFuture);
}
this.connectionFutures.put(requestURI, session.getUntrustedEndpoint().getConnectFuture());
}
public void registerConnectFuture(URI uri, CompletableFuture<UntrustedWSSession> sessionFuture)
public void registerOnOpenFuture(URI uri, CompletableFuture<UntrustedWSSession> sessionFuture)
{
this.connectionFutures.put(uri, sessionFuture);
this.onOpenFutures.put(uri, sessionFuture);
}
}

View File

@ -20,27 +20,42 @@ package org.eclipse.jetty.websocket.tests;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
public class UntrustedWSServlet extends WebSocketServlet implements WebSocketCreator
public class UntrustedWSServlet extends WebSocketServlet
{
private final WebSocketCreator creator;
@SuppressWarnings("unused")
public UntrustedWSServlet()
{
this((req, resp) ->
{
UntrustedWSEndpoint endpoint = new UntrustedWSEndpoint(WebSocketBehavior.SERVER.name());
if (req.hasSubProtocol("echo"))
{
endpoint.setOnTextFunction((session, payload) -> payload);
endpoint.setOnBinaryFunction((session, payload) -> payload);
resp.setAcceptedSubProtocol("echo");
}
return endpoint;
});
}
public UntrustedWSServlet(WebSocketCreator creator)
{
this.creator = creator;
}
@Override
public void configure(WebSocketServletFactory factory)
{
WebSocketServerFactory serverFactory = (WebSocketServerFactory) factory;
serverFactory.setCreator(this);
serverFactory.setCreator(this.creator);
UntrustedWSSessionFactory sessionFactory = new UntrustedWSSessionFactory(serverFactory);
this.getServletContext().setAttribute(UntrustedWSSessionFactory.class.getName(), sessionFactory);
serverFactory.setSessionFactories(sessionFactory);
}
@Override
public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
return new UntrustedWSEndpoint(WebSocketBehavior.SERVER.name());
}
}

View File

@ -0,0 +1,46 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.tests.servlets;
import java.io.IOException;
import java.util.function.BiConsumer;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Utility Servlet to make easier testcases
*/
public class BiConsumerServiceServlet extends HttpServlet
{
private final BiConsumer<HttpServletRequest, HttpServletResponse> consumer;
public BiConsumerServiceServlet(BiConsumer<HttpServletRequest, HttpServletResponse> consumer)
{
this.consumer = consumer;
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
this.consumer.accept(req,resp);
}
}

View File

@ -19,6 +19,8 @@
package org.eclipse.jetty.websocket.tests.client;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.net.URI;
import java.util.concurrent.CompletableFuture;
@ -28,7 +30,9 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.tests.Defaults;
import org.eclipse.jetty.websocket.tests.LeakTrackingBufferPoolRule;
import org.eclipse.jetty.websocket.tests.TrackingEndpoint;
import org.eclipse.jetty.websocket.tests.UntrustedWSServer;
import org.eclipse.jetty.websocket.tests.UntrustedWSSession;
import org.junit.After;
@ -81,35 +85,34 @@ public class BadNetworkTest
@Test
public void testAbruptClientClose() throws Exception
{
TrackingSocket wsocket = new TrackingSocket();
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket, wsUri);
Future<Session> clientConnectFuture = client.connect(clientSocket, wsUri);
// Validate that we are connected
future.get(30, TimeUnit.SECONDS);
wsocket.waitForConnected(30, TimeUnit.SECONDS);
assertThat("Client Open Event Received", clientSocket.openLatch.await(30, TimeUnit.SECONDS), is(true));
// Have client disconnect abruptly
Session session = wsocket.getSession();
session.disconnect();
Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
clientSession.disconnect();
// Client Socket should see close
wsocket.waitForClose(10, TimeUnit.SECONDS);
clientSocket.awaitCloseEvent("Client");
// Client Socket should see a close event, with status NO_CLOSE
// This event is automatically supplied by the underlying WebSocketClientConnection
// in the situation of a bad network connection.
wsocket.assertClose(StatusCode.NO_CLOSE, containsString("disconnect"));
clientSocket.assertCloseInfo("Client", StatusCode.NO_CLOSE, containsString("disconnect"));
}
@Test
public void testAbruptServerClose() throws Exception
{
TrackingSocket wsocket = new TrackingSocket();
URI wsURI = server.getWsUri().resolve("/untrusted/" + testname.getMethodName());
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
CompletableFuture<UntrustedWSSession> sessionFuture = new CompletableFuture<UntrustedWSSession>()
{
@ -121,19 +124,19 @@ public class BadNetworkTest
return super.complete(session);
}
};
server.registerConnectFuture(wsURI, sessionFuture);
Future<Session> future = client.connect(wsocket, wsURI);
server.registerOnOpenFuture(wsUri, sessionFuture);
Future<Session> clientConnectFuture = client.connect(clientSocket, wsUri);
Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
// Validate that we are connected
future.get(30, TimeUnit.SECONDS);
wsocket.waitForConnected(30, TimeUnit.SECONDS);
clientSocket.awaitOpenEvent("Client");
// Wait for close (as response to idle timeout)
wsocket.waitForClose(10, TimeUnit.SECONDS);
clientSocket.awaitCloseEvent("Client");
// Client Socket should see a close event, with status NO_CLOSE
// This event is automatically supplied by the underlying WebSocketClientConnection
// in the situation of a bad network connection.
wsocket.assertClose(StatusCode.PROTOCOL, containsString("EOF"));
clientSocket.assertCloseInfo("Client", StatusCode.PROTOCOL, containsString("EOF"));
}
}

View File

@ -19,15 +19,15 @@
package org.eclipse.jetty.websocket.tests.client;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anything;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.nio.ByteBuffer;
@ -36,12 +36,9 @@ import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
@ -50,30 +47,28 @@ import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.SelectorManager;
import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.toolchain.test.EventQueue;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.ProtocolException;
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.Parser;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection;
import org.eclipse.jetty.websocket.tests.Defaults;
import org.eclipse.jetty.websocket.tests.RawFrameBuilder;
import org.eclipse.jetty.websocket.tests.TrackingEndpoint;
import org.eclipse.jetty.websocket.tests.UntrustedWSConnection;
import org.eclipse.jetty.websocket.tests.UntrustedWSEndpoint;
import org.eclipse.jetty.websocket.tests.UntrustedWSServer;
import org.eclipse.jetty.websocket.tests.UntrustedWSSession;
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@ -84,110 +79,6 @@ public class ClientCloseTest
{
private static final Logger LOG = Log.getLogger(ClientCloseTest.class);
private static class CloseTrackingSocket extends WebSocketAdapter
{
private static final Logger LOG = Log.getLogger(CloseTrackingSocket.class);
public int closeCode = -1;
public String closeReason = null;
public CountDownLatch closeLatch = new CountDownLatch(1);
public AtomicInteger closeCount = new AtomicInteger(0);
public CountDownLatch openLatch = new CountDownLatch(1);
public CountDownLatch errorLatch = new CountDownLatch(1);
public EventQueue<String> messageQueue = new EventQueue<>();
public AtomicReference<Throwable> error = new AtomicReference<>();
public void assertNoCloseEvent()
{
assertThat("Client Close Event", closeLatch.getCount(), is(1L));
assertThat("Client Close Event Status Code ", closeCode, is(-1));
}
public void assertReceivedCloseEvent(int clientTimeoutMs, Matcher<Integer> statusCodeMatcher, Matcher<String> reasonMatcher)
throws InterruptedException
{
long maxTimeout = clientTimeoutMs * 4;
assertThat("Client Close Event Occurred", closeLatch.await(maxTimeout, TimeUnit.MILLISECONDS), is(true));
assertThat("Client Close Event Count", closeCount.get(), is(1));
assertThat("Client Close Event Status Code", closeCode, statusCodeMatcher);
if (reasonMatcher == null)
{
assertThat("Client Close Event Reason", closeReason, nullValue());
}
else
{
assertThat("Client Close Event Reason", closeReason, reasonMatcher);
}
}
public void assertReceivedErrorEvent(int clientTimeoutMs, Class<? extends Throwable> expectedCause, Matcher<String> messageMatcher) throws InterruptedException
{
long maxTimeout = clientTimeoutMs * 4;
assertThat("Client Error Event Occurred", errorLatch.await(maxTimeout, TimeUnit.MILLISECONDS), is(true));
assertThat("Client Error Type", error.get(), instanceOf(expectedCause));
assertThat("Client Error Message", error.get().getMessage(), messageMatcher);
}
public void clearQueues()
{
messageQueue.clear();
}
@Override
public void onWebSocketClose(int statusCode, String reason)
{
LOG.debug("onWebSocketClose({},{})", statusCode, reason);
super.onWebSocketClose(statusCode, reason);
closeCount.incrementAndGet();
closeCode = statusCode;
closeReason = reason;
closeLatch.countDown();
}
@Override
public void onWebSocketConnect(Session session)
{
LOG.debug("onWebSocketConnect({})", session);
super.onWebSocketConnect(session);
openLatch.countDown();
}
@Override
public void onWebSocketError(Throwable cause)
{
LOG.warn("onWebSocketError", cause);
assertThat("Unique Error Event", error.compareAndSet(null, cause), is(true));
errorLatch.countDown();
}
@Override
public void onWebSocketText(String message)
{
LOG.debug("onWebSocketText({})", message);
messageQueue.offer(message);
}
public EndPoint getEndPoint() throws Exception
{
Session session = getSession();
assertThat("Session type", session, instanceOf(WebSocketSession.class));
WebSocketSession wssession = (WebSocketSession) session;
Field fld = wssession.getClass().getDeclaredField("connection");
fld.setAccessible(true);
assertThat("Field: connection", fld, notNullValue());
Object val = fld.get(wssession);
assertThat("Connection type", val, instanceOf(AbstractWebSocketConnection.class));
@SuppressWarnings("resource")
AbstractWebSocketConnection wsconn = (AbstractWebSocketConnection) val;
return wsconn.getEndPoint();
}
}
@Rule
public TestName testname = new TestName();
@ -197,48 +88,40 @@ public class ClientCloseTest
private UntrustedWSServer server;
private WebSocketClient client;
private void confirmConnection(CloseTrackingSocket clientSocket, Future<Session> clientFuture, UntrustedWSSession serverSession) throws Exception
private void confirmConnection(TrackingEndpoint clientSocket, Future<Session> clientFuture, UntrustedWSSession serverSession) throws Exception
{
// Wait for client connect on via future
clientFuture.get(30, TimeUnit.SECONDS);
Session clientSession = clientFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
clientSession.getRemote().setBatchMode(BatchMode.OFF);
// Wait for client connect via client websocket
assertThat("Client WebSocket is Open", clientSocket.openLatch.await(30, TimeUnit.SECONDS), is(true));
UntrustedWSEndpoint serverEndpoint = serverSession.getUntrustedEndpoint();
// Future<List<WebSocketFrame>> futFrames = serverEndpoint.expectedFrames(1);
assertThat("Client WebSocket is Open", clientSocket.openLatch.await(Defaults.OPEN_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS), is(true));
try
{
// Send message from client to server
final String echoMsg = "echo-test";
Future<Void> testFut = clientSocket.getRemote().sendStringByFuture(echoMsg);
// Wait for send future
testFut.get(30, TimeUnit.SECONDS);
// Read Frame on server side
WebSocketFrame frame = serverEndpoint.framesQueue.poll(10, TimeUnit.SECONDS);
assertThat("Server received frame", frame.getOpCode(), is(OpCode.TEXT));
assertThat("Server received frame payload", frame.getPayloadAsUTF8(), is(echoMsg));
// Server send echo reply
serverEndpoint.getRemote().sendString(echoMsg);
// Wait for received echo
clientSocket.messageQueue.awaitEventCount(1, 1, TimeUnit.SECONDS);
// Verify received message
String recvMsg = clientSocket.messageQueue.poll();
assertThat("Received message", recvMsg, is(echoMsg));
// Verify that there are no errors
assertThat("Error events", clientSocket.error.get(), nullValue());
}
finally
{
clientSocket.clearQueues();
}
UntrustedWSEndpoint serverEndpoint = serverSession.getUntrustedEndpoint();
// Send message from client to server
final String echoMsg = "echo-test";
Future<Void> testFut = clientSocket.getRemote().sendStringByFuture(echoMsg);
// Wait for send future
testFut.get(30, TimeUnit.SECONDS);
// Read Frame on server side
WebSocketFrame frame = serverEndpoint.framesQueue.poll(10, TimeUnit.SECONDS);
assertThat("Server received frame", frame.getOpCode(), is(OpCode.TEXT));
assertThat("Server received frame payload", frame.getPayloadAsUTF8(), is(echoMsg));
// Server send echo reply
serverEndpoint.getRemote().sendString(echoMsg);
// Wait for received echo
String incomingMessage = clientSocket.messageQueue.poll(1, TimeUnit.SECONDS);
// Verify received message
assertThat("Received message", incomingMessage, is(echoMsg));
// Verify that there are no errors
assertThat("Error events", clientSocket.error.get(), nullValue());
}
public static class TestClientTransportOverHTTP extends HttpClientTransportOverHTTP
@ -312,13 +195,13 @@ public class ClientCloseTest
final int timeout = 1000;
client.setMaxIdleTimeout(timeout);
URI wsURI = server.getWsUri().resolve("/untrusted/" + testname.getMethodName());
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
CompletableFuture<UntrustedWSSession> serverSessionFut = new CompletableFuture<>();
server.registerConnectFuture(wsURI, serverSessionFut);
server.registerOnOpenFuture(wsUri, serverSessionFut);
// Client connects
CloseTrackingSocket clientSocket = new CloseTrackingSocket();
Future<Session> clientConnectFuture = client.connect(clientSocket, wsURI);
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
Future<Session> clientConnectFuture = client.connect(clientSocket, wsUri);
// Server accepts connect
UntrustedWSSession serverSession = serverSessionFut.get(10, TimeUnit.SECONDS);
@ -328,11 +211,11 @@ public class ClientCloseTest
// client sends close frame (code 1000, normal)
final String origCloseReason = "Normal Close";
clientSocket.getSession().close(StatusCode.NORMAL, origCloseReason);
clientSocket.close(StatusCode.NORMAL, origCloseReason);
// server receives close frame
serverSession.getUntrustedEndpoint().assertCloseInfo("Server", StatusCode.NORMAL, is(origCloseReason));
// server sends 2 messages
RemoteEndpoint remote = serverSession.getRemote();
remote.sendString("Hello");
@ -342,19 +225,18 @@ public class ClientCloseTest
serverSession.close(StatusCode.NORMAL, "From Server");
// client receives 2 messages
clientSocket.messageQueue.awaitEventCount(2, 1, TimeUnit.SECONDS);
// Verify received messages
String recvMsg = clientSocket.messageQueue.poll();
assertThat("Received message 1", recvMsg, is("Hello"));
recvMsg = clientSocket.messageQueue.poll();
assertThat("Received message 2", recvMsg, is("World"));
String incomingMessage;
incomingMessage = clientSocket.messageQueue.poll(1, TimeUnit.SECONDS);
assertThat("Received message 1", incomingMessage, is("Hello"));
incomingMessage = clientSocket.messageQueue.poll(1, TimeUnit.SECONDS);
assertThat("Received message 1", incomingMessage, is("World"));
// Verify that there are no errors
assertThat("Error events", clientSocket.error.get(), nullValue());
clientSocket.assertNoErrorEvents("Client");
// client close event on ws-endpoint
clientSocket.assertReceivedCloseEvent(timeout, is(StatusCode.NORMAL), containsString("From Server"));
assertTrue("Client close event", clientSocket.closeLatch.await(Defaults.CLOSE_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
clientSocket.assertCloseInfo("Client", StatusCode.NORMAL, containsString("From Server"));
}
@Test
@ -364,13 +246,13 @@ public class ClientCloseTest
final int timeout = 1000;
client.setMaxIdleTimeout(timeout);
URI wsURI = server.getWsUri().resolve("/untrusted/" + testname.getMethodName());
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
CompletableFuture<UntrustedWSSession> serverSessionFut = new CompletableFuture<>();
server.registerConnectFuture(wsURI, serverSessionFut);
server.registerOnOpenFuture(wsUri, serverSessionFut);
// Client connects
CloseTrackingSocket clientSocket = new CloseTrackingSocket();
Future<Session> clientConnectFuture = client.connect(clientSocket, wsURI);
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
Future<Session> clientConnectFuture = client.connect(clientSocket, wsUri);
// Server accepts connect
UntrustedWSSession serverSession = serverSessionFut.get(10, TimeUnit.SECONDS);
@ -382,7 +264,7 @@ public class ClientCloseTest
// server must not read (for test purpose, in order to congest connection)
// when write is congested, client enqueue close frame
// client initiate write, but write never completes
EndPoint endp = clientSocket.getEndPoint();
EndPoint endp = clientSocket.getJettyEndPoint();
assertThat("EndPoint is testable", endp, instanceOf(TestEndPoint.class));
TestEndPoint testendp = (TestEndPoint) endp;
@ -402,8 +284,7 @@ public class ClientCloseTest
LOG.info("Wrote {} frames totalling {} bytes of payload before congestion kicked in", writeCount, writeSize);
// Verify timeout error
assertThat("OnError Latch", clientSocket.errorLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat("OnError", clientSocket.error.get(), instanceOf(SocketTimeoutException.class));
clientSocket.assertErrorEvent("Client", instanceOf(SocketTimeoutException.class), anything());
}
@Test
@ -413,13 +294,13 @@ public class ClientCloseTest
final int timeout = 1000;
client.setMaxIdleTimeout(timeout);
URI wsURI = server.getWsUri().resolve("/untrusted/" + testname.getMethodName());
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
CompletableFuture<UntrustedWSSession> serverSessionFut = new CompletableFuture<>();
server.registerConnectFuture(wsURI, serverSessionFut);
server.registerOnOpenFuture(wsUri, serverSessionFut);
// Client connects
CloseTrackingSocket clientSocket = new CloseTrackingSocket();
Future<Session> clientConnectFuture = client.connect(clientSocket, wsURI);
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
Future<Session> clientConnectFuture = client.connect(clientSocket, wsUri);
// Server accepts connect
UntrustedWSSession serverSession = serverSessionFut.get(10, TimeUnit.SECONDS);
@ -428,7 +309,7 @@ public class ClientCloseTest
confirmConnection(clientSocket, clientConnectFuture, serverSession);
// client should not have received close message (yet)
clientSocket.assertNoCloseEvent();
clientSocket.assertNotClosed("Client");
// server sends bad close frame (too big of a reason message)
byte msg[] = new byte[400];
@ -444,9 +325,7 @@ public class ClientCloseTest
serverSession.getUntrustedConnection().writeRaw(bad);
// client should have noticed the error
assertThat("OnError Latch", clientSocket.errorLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat("OnError", clientSocket.error.get(), instanceOf(ProtocolException.class));
assertThat("OnError", clientSocket.error.get().getMessage(), containsString("Invalid control frame"));
clientSocket.assertErrorEvent("Client", instanceOf(ProtocolException.class), containsString("Invalid control frame"));
// client parse invalid frame, notifies server of close (protocol error)
serverSession.getUntrustedEndpoint().assertCloseInfo("Server", StatusCode.PROTOCOL, allOf(containsString("Invalid control frame"), containsString("length")));
@ -455,8 +334,9 @@ public class ClientCloseTest
// server disconnects
serverSession.disconnect();
// client triggers close event on client ws-endpoint
clientSocket.assertReceivedCloseEvent(timeout, is(StatusCode.PROTOCOL), allOf(containsString("Invalid control frame"), containsString("length")));
// client close event on ws-endpoint
assertTrue("Client close event", clientSocket.closeLatch.await(Defaults.CLOSE_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
clientSocket.assertCloseInfo("Client", StatusCode.PROTOCOL, allOf(containsString("Invalid control frame"), containsString("length")));
}
@Test
@ -466,13 +346,13 @@ public class ClientCloseTest
final int timeout = 1000;
client.setMaxIdleTimeout(timeout);
URI wsURI = server.getWsUri().resolve("/untrusted/" + testname.getMethodName());
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
CompletableFuture<UntrustedWSSession> serverSessionFut = new CompletableFuture<>();
server.registerConnectFuture(wsURI, serverSessionFut);
server.registerOnOpenFuture(wsUri, serverSessionFut);
// Client connects
CloseTrackingSocket clientSocket = new CloseTrackingSocket();
Future<Session> clientConnectFuture = client.connect(clientSocket, wsURI);
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
Future<Session> clientConnectFuture = client.connect(clientSocket, wsUri);
// Server accepts connect
UntrustedWSSession serverSession = serverSessionFut.get(10, TimeUnit.SECONDS);
@ -480,25 +360,26 @@ public class ClientCloseTest
// client confirms connection via echo
confirmConnection(clientSocket, clientConnectFuture, serverSession);
try (StacklessLogging ignored = new StacklessLogging(CloseTrackingSocket.class))
try (StacklessLogging ignored = new StacklessLogging(clientSocket.LOG))
{
// client sends close frame
final String origCloseReason = "Normal Close";
clientSocket.getSession().close(StatusCode.NORMAL, origCloseReason);
clientSocket.close(StatusCode.NORMAL, origCloseReason);
// server receives close frame
serverSession.getUntrustedEndpoint().assertCloseInfo("Server", StatusCode.NORMAL, is(origCloseReason));
// client should not have received close message (yet)
clientSocket.assertNoCloseEvent();
clientSocket.assertNotClosed("Client");
// server shuts down connection (no frame reply)
serverSession.disconnect();
// client reads -1 (EOF)
clientSocket.assertReceivedErrorEvent(timeout, IOException.class, containsString("EOF"));
clientSocket.assertErrorEvent("Client", instanceOf(IOException.class), containsString("EOF"));
// client triggers close event on client ws-endpoint
clientSocket.assertReceivedCloseEvent(timeout, is(StatusCode.ABNORMAL), containsString("Disconnected"));
assertTrue("Client close event", clientSocket.closeLatch.await(Defaults.CLOSE_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
clientSocket.assertCloseInfo("Client", StatusCode.ABNORMAL, containsString("Disconnected"));
}
}
@ -509,13 +390,13 @@ public class ClientCloseTest
final int timeout = 1000;
client.setMaxIdleTimeout(timeout);
URI wsURI = server.getWsUri().resolve("/untrusted/" + testname.getMethodName());
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
CompletableFuture<UntrustedWSSession> serverSessionFut = new CompletableFuture<>();
server.registerConnectFuture(wsURI, serverSessionFut);
server.registerOnOpenFuture(wsUri, serverSessionFut);
// Client connects
CloseTrackingSocket clientSocket = new CloseTrackingSocket();
Future<Session> clientConnectFuture = client.connect(clientSocket, wsURI);
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
Future<Session> clientConnectFuture = client.connect(clientSocket, wsUri);
// Server accepts connect
UntrustedWSSession serverSession = serverSessionFut.get(10, TimeUnit.SECONDS);
@ -526,21 +407,19 @@ public class ClientCloseTest
// client sends close frame
final String origCloseReason = "Normal Close";
clientSocket.getSession().close(StatusCode.NORMAL, origCloseReason);
clientSocket.close(StatusCode.NORMAL, origCloseReason);
// server receives close frame
serverSession.getUntrustedEndpoint().assertCloseInfo("Server", StatusCode.NORMAL, is(origCloseReason));
// client should not have received close message (yet)
clientSocket.assertNoCloseEvent();
clientSocket.assertNotClosed("Client");
// server never sends close frame handshake
// server sits idle
// client idle timeout triggers close event on client ws-endpoint
assertThat("OnError Latch", clientSocket.errorLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat("OnError", clientSocket.error.get(), instanceOf(SocketTimeoutException.class));
assertThat("OnError", clientSocket.error.get().getMessage(), containsString("Timeout on Read"));
clientSocket.assertErrorEvent("Client", instanceOf(SocketTimeoutException.class), containsString("Timeout on Read"));
}
@Test(timeout = 5000L)
@ -551,19 +430,20 @@ public class ClientCloseTest
client.setMaxIdleTimeout(timeout);
int clientCount = 3;
CloseTrackingSocket clientSockets[] = new CloseTrackingSocket[clientCount];
TrackingEndpoint clientSockets[] = new TrackingEndpoint[clientCount];
UntrustedWSSession serverSessions[] = new UntrustedWSSession[clientCount];
// Connect Multiple Clients
for (int i = 0; i < clientCount; i++)
{
URI wsURI = server.getWsUri().resolve("/untrusted/" + testname.getMethodName() + "/" + i);
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname).resolve(Integer.toString(i));
CompletableFuture<UntrustedWSSession> serverSessionFut = new CompletableFuture<>();
server.registerConnectFuture(wsURI, serverSessionFut);
server.registerOnOpenFuture(wsUri, serverSessionFut);
// Client Request Upgrade
clientSockets[i] = new CloseTrackingSocket();
Future<Session> clientConnectFuture = client.connect(clientSockets[i], wsURI);
clientSockets[i] = new TrackingEndpoint(testname.getMethodName() + "[" + i + "]");
Future<Session> clientConnectFuture = client.connect(clientSockets[i], wsUri);
// Server accepts connection
serverSessions[i] = serverSessionFut.get(10, TimeUnit.SECONDS);
@ -573,19 +453,23 @@ public class ClientCloseTest
}
// client lifecycle stop
// every open client should send a close frame
client.stop();
// clients send close frames (code 1001, shutdown)
for (int i = 0; i < clientCount; i++)
{
// server receives close frame
serverSessions[i].getUntrustedEndpoint().assertCloseInfo("Server", StatusCode.SHUTDOWN, containsString("Shutdown"));
UntrustedWSEndpoint serverEndpoint = serverSessions[i].getUntrustedEndpoint();
assertTrue("Close of server session[" + i + "]", serverEndpoint.closeLatch.await(Defaults.CLOSE_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
serverEndpoint.assertCloseInfo("Server", StatusCode.SHUTDOWN, containsString("Shutdown"));
}
// clients disconnect
for (int i = 0; i < clientCount; i++)
{
clientSockets[i].assertReceivedCloseEvent(timeout, is(StatusCode.SHUTDOWN), containsString("Shutdown"));
assertTrue("Close of client endpoint[" + i + "]", clientSockets[i].closeLatch.await(1, TimeUnit.SECONDS));
clientSockets[i].assertCloseInfo("Client", StatusCode.SHUTDOWN, containsString("Shutdown"));
}
}
@ -596,22 +480,13 @@ public class ClientCloseTest
final int timeout = 1000;
client.setMaxIdleTimeout(timeout);
URI wsURI = server.getWsUri().resolve("/untrusted/" + testname.getMethodName());
CompletableFuture<UntrustedWSSession> serverSessionFut = new CompletableFuture<UntrustedWSSession>()
{
@Override
public boolean complete(UntrustedWSSession session)
{
// echo back text as-well
session.getUntrustedEndpoint().setOnTextFunction((serverSession, text) -> text);
return super.complete(session);
}
};
server.registerConnectFuture(wsURI, serverSessionFut);
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
CompletableFuture<UntrustedWSSession> serverSessionFut = new CompletableFuture<>();
server.registerOnOpenFuture(wsUri, serverSessionFut);
// Client connects
CloseTrackingSocket clientSocket = new CloseTrackingSocket();
Future<Session> clientConnectFuture = client.connect(clientSocket, wsURI);
Future<Session> clientConnectFuture = client.connect(clientSocket, wsUri);
// Server accepts connect
UntrustedWSSession serverSession = serverSessionFut.get(10, TimeUnit.SECONDS);
@ -620,20 +495,19 @@ public class ClientCloseTest
confirmConnection(clientSocket, clientConnectFuture, serverSession);
// setup client endpoint for write failure (test only)
EndPoint endp = clientSocket.getEndPoint();
EndPoint endp = clientSocket.getJettyEndPoint();
endp.shutdownOutput();
// client enqueue close frame
// client write failure
final String origCloseReason = "Normal Close";
clientSocket.getSession().close(StatusCode.NORMAL, origCloseReason);
clientSocket.close(StatusCode.NORMAL, origCloseReason);
assertThat("OnError Latch", clientSocket.errorLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat("OnError", clientSocket.error.get(), instanceOf(EofException.class));
// client triggers close event on client ws-endpoint
// assert - close code==1006 (abnormal)
// assert - close reason message contains (write failure)
clientSocket.assertReceivedCloseEvent(timeout, is(StatusCode.ABNORMAL), containsString("EOF"));
assertTrue("Client onClose not called", clientSocket.closeLatch.getCount() > 0);
}
}

View File

@ -0,0 +1,492 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.tests.client;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.UpgradeException;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.client.WebSocketUpgradeRequest;
import org.eclipse.jetty.websocket.common.AcceptHash;
import org.eclipse.jetty.websocket.tests.Defaults;
import org.eclipse.jetty.websocket.tests.LeakTrackingBufferPoolRule;
import org.eclipse.jetty.websocket.tests.TrackingEndpoint;
import org.eclipse.jetty.websocket.tests.UntrustedWSEndpoint;
import org.eclipse.jetty.websocket.tests.UntrustedWSServer;
import org.eclipse.jetty.websocket.tests.UntrustedWSSession;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
/**
* Various connect condition testing
*/
@SuppressWarnings("Duplicates")
public class ClientConnectTest
{
@Rule
public TestName testname = new TestName();
@Rule
public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
private final int timeout = 500;
private UntrustedWSServer server;
private WebSocketClient client;
@SuppressWarnings("unchecked")
private <E extends Throwable> E assertExpectedError(Throwable t, TrackingEndpoint clientSocket, Class<E> errorClass) throws IOException
{
// Validate thrown cause
Throwable cause = t;
while (cause instanceof ExecutionException)
{
cause = cause.getCause();
}
Assert.assertThat("Cause", cause, instanceOf(errorClass));
if (clientSocket.session != null)
{
// Validate websocket captured cause
Throwable clientCause = clientSocket.error.get();
Assert.assertThat("Client Error", clientCause, notNullValue());
Assert.assertThat("Client Error", clientCause, instanceOf(errorClass));
// Validate that websocket didn't see an open event
assertThat("Client socket isOpen", clientSocket.session.isOpen(), is(false));
// Return the captured cause
return (E) clientCause;
}
else
{
return (E) cause;
}
}
@Before
public void startClient() throws Exception
{
client = new WebSocketClient();
client.setBufferPool(bufferPool);
client.setConnectTimeout(timeout);
client.start();
}
@Before
public void startServer() throws Exception
{
server = new UntrustedWSServer();
server.start();
}
@After
public void stopClient() throws Exception
{
client.stop();
}
@After
public void stopServer() throws Exception
{
server.stop();
}
@Test
public void testUpgradeRequest() throws Exception
{
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
Future<Session> clientConnectFuture = client.connect(clientSocket, wsUri);
Session sess = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
clientSocket.openLatch.await(Defaults.OPEN_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
assertThat("OnOpen.UpgradeRequest", clientSocket.openUpgradeRequest, notNullValue());
assertThat("OnOpen.UpgradeResponse", clientSocket.openUpgradeResponse, notNullValue());
sess.close();
}
@Test
public void testAltConnect() throws Exception
{
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
HttpClient httpClient = new HttpClient();
httpClient.start();
WebSocketUpgradeRequest req = new WebSocketUpgradeRequest(new WebSocketClient(), httpClient, wsUri, clientSocket);
req.header("X-Foo", "Req");
CompletableFuture<Session> sess = req.sendAsync();
sess.thenAccept((s) ->
{
System.out.printf("Session: %s%n", s);
s.close();
assertThat("OnOpen.UpgradeRequest", clientSocket.openUpgradeRequest, notNullValue());
assertThat("OnOpen.UpgradeResponse", clientSocket.openUpgradeResponse, notNullValue());
});
}
@Test
public void testUpgradeWithAuthorizationHeader() throws Exception
{
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
CompletableFuture<UntrustedWSSession> serverSessionFut = new CompletableFuture<UntrustedWSSession>()
{
@Override
public boolean complete(UntrustedWSSession session)
{
// echo back text as-well
session.getUntrustedEndpoint().setOnTextFunction((serverSession, text) -> text);
return super.complete(session);
}
};
server.registerOnOpenFuture(wsUri, serverSessionFut);
ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest();
// actual value for this test is irrelevant, its important that this
// header actually be sent with a value (the value specified)
upgradeRequest.setHeader("Authorization", "Bogus SHA1");
Future<Session> future = client.connect(clientSocket, wsUri, upgradeRequest);
Session clientSession = future.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
clientSession.close();
UntrustedWSSession serverSession = serverSessionFut.get(Defaults.OPEN_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
String authLine = serverSession.getUntrustedEndpoint().openUpgradeRequest.getHeader("Authorization");
assertThat("Request Container Authorization", authLine, is("Authorization: Bogus SHA1"));
assertThat("OnOpen.UpgradeRequest", clientSocket.openUpgradeRequest, notNullValue());
assertThat("OnOpen.UpgradeResponse", clientSocket.openUpgradeResponse, notNullValue());
}
@Test
public void testBadHandshake() throws Exception
{
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
server.registerHttpService("/empty-404", (req, resp) ->
{
resp.setStatus(404);
resp.setHeader("Connection", "close");
});
URI wsUri = server.getWsUri().resolve("/empty-404");
Future<Session> future = client.connect(clientSocket, wsUri);
// The attempt to get upgrade response future should throw error
try
{
future.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.fail("Expected ExecutionException -> UpgradeException");
}
catch (ExecutionException e)
{
// Expected Path
UpgradeException ue = assertExpectedError(e, clientSocket, UpgradeException.class);
Assert.assertThat("UpgradeException.requestURI", ue.getRequestURI(), notNullValue());
Assert.assertThat("UpgradeException.requestURI", ue.getRequestURI().toASCIIString(), is(wsUri.toASCIIString()));
Assert.assertThat("UpgradeException.responseStatusCode", ue.getResponseStatusCode(), is(404));
}
}
@Test
public void testBadHandshake_GetOK() throws Exception
{
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
server.registerHttpService("/empty-200", (req, resp) ->
{
resp.setStatus(200);
resp.setHeader("Connection", "close");
});
URI wsUri = server.getWsUri().resolve("/empty-200");
Future<Session> future = client.connect(clientSocket, wsUri);
// The attempt to get upgrade response future should throw error
try
{
future.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.fail("Expected ExecutionException -> UpgradeException");
}
catch (ExecutionException e)
{
// Expected Path
UpgradeException ue = assertExpectedError(e, clientSocket, UpgradeException.class);
Assert.assertThat("UpgradeException.requestURI", ue.getRequestURI(), notNullValue());
Assert.assertThat("UpgradeException.requestURI", ue.getRequestURI().toASCIIString(), is(wsUri.toASCIIString()));
Assert.assertThat("UpgradeException.responseStatusCode", ue.getResponseStatusCode(), is(200));
}
}
@Test
public void testBadHandshake_GetOK_WithSecWebSocketAccept() throws Exception
{
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
server.registerHttpService("/bad-accept-200", (req, resp) ->
{
// Simulate a bad server that doesn't follow RFC6455 completely.
// A status 200 (not upgrade), but with some RFC6455 headers.
resp.setStatus(200);
String key = req.getHeader("Sec-WebSocket-Key");
resp.setHeader("Sec-WebSocket-Accept", AcceptHash.hashKey(key));
});
URI wsUri = server.getWsUri().resolve("/bad-accept-200");
Future<Session> future = client.connect(clientSocket, wsUri);
// The attempt to get upgrade response future should throw error
try
{
future.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.fail("Expected ExecutionException -> UpgradeException");
}
catch (ExecutionException e)
{
// Expected Path
UpgradeException ue = assertExpectedError(e, clientSocket, UpgradeException.class);
Assert.assertThat("UpgradeException.requestURI", ue.getRequestURI(), notNullValue());
Assert.assertThat("UpgradeException.requestURI", ue.getRequestURI().toASCIIString(), is(wsUri.toASCIIString()));
Assert.assertThat("UpgradeException.responseStatusCode", ue.getResponseStatusCode(), is(200));
}
}
@Test
public void testBadHandshake_SwitchingProtocols_InvalidConnectionHeader() throws Exception
{
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
server.registerHttpService("/bad-connection-header", (req, resp) ->
{
// Simulate a bad server that doesn't follow RFC6455 completely.
// A status 101 (switching protocol), but with "Connection: close"
resp.setStatus(101);
String key = req.getHeader("Sec-WebSocket-Key");
resp.setHeader("Sec-WebSocket-Accept", AcceptHash.hashKey(key));
resp.setHeader("Connection", "close");
});
URI wsUri = server.getWsUri().resolve("/bad-connection-header");
Future<Session> future = client.connect(clientSocket, wsUri);
// The attempt to get upgrade response future should throw error
try
{
future.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.fail("Expected ExecutionException -> UpgradeException");
}
catch (ExecutionException e)
{
// Expected Path
UpgradeException ue = assertExpectedError(e, clientSocket, UpgradeException.class);
Assert.assertThat("UpgradeException.requestURI", ue.getRequestURI(), notNullValue());
Assert.assertThat("UpgradeException.requestURI", ue.getRequestURI().toASCIIString(), is(wsUri.toASCIIString()));
Assert.assertThat("UpgradeException.responseStatusCode", ue.getResponseStatusCode(), is(101));
}
}
@Test
public void testBadHandshake_SwitchingProtocols_NoConnectionHeader() throws Exception
{
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
server.registerHttpService("/bad-switching-protocols-no-connection-header", (req, resp) ->
{
// Simulate a bad server that doesn't follow RFC6455 completely.
// Send Switching Protocols 101, but no 'Connection' header
resp.setStatus(101);
String key = req.getHeader("Sec-WebSocket-Key");
resp.setHeader("Sec-WebSocket-Accept", AcceptHash.hashKey(key));
// Intentionally leave out Connection header
});
URI wsUri = server.getWsUri().resolve("/bad-switching-protocols-no-connection-header");
Future<Session> future = client.connect(clientSocket, wsUri);
// The attempt to get upgrade response future should throw error
try
{
future.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.fail("Expected ExecutionException -> UpgradeException");
}
catch (ExecutionException e)
{
// Expected Path
UpgradeException ue = assertExpectedError(e, clientSocket, UpgradeException.class);
Assert.assertThat("UpgradeException.requestURI", ue.getRequestURI(), notNullValue());
Assert.assertThat("UpgradeException.requestURI", ue.getRequestURI().toASCIIString(), is(wsUri.toASCIIString()));
Assert.assertThat("UpgradeException.responseStatusCode", ue.getResponseStatusCode(), is(101));
}
}
@Test
public void testBadHandshake_InvalidWsAccept() throws Exception
{
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
server.registerHttpService("/bad-switching-protocols-invalid-ws-accept", (req, resp) ->
{
// Simulate a bad server that doesn't follow RFC6455 completely.
// Send Switching Protocols 101, with bad Sec-WebSocket-Accept header
resp.setStatus(101);
resp.setHeader("Sec-WebSocket-Accept", "rubbish");
});
URI wsUri = server.getWsUri().resolve("/bad-switching-protocols-invalid-ws-accept");
Future<Session> future = client.connect(clientSocket, wsUri);
// The attempt to get upgrade response future should throw error
try
{
future.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.fail("Expected ExecutionException -> UpgradeException");
}
catch (ExecutionException e)
{
// Expected Path
UpgradeException ue = assertExpectedError(e, clientSocket, UpgradeException.class);
Assert.assertThat("UpgradeException.requestURI", ue.getRequestURI(), notNullValue());
Assert.assertThat("UpgradeException.requestURI", ue.getRequestURI().toASCIIString(), is(wsUri.toASCIIString()));
Assert.assertThat("UpgradeException.responseStatusCode", ue.getResponseStatusCode(), is(101));
}
}
/**
* Test for when encountering a "Transfer-Encoding: chunked" on a Upgrade Response header.
* <ul>
* <li><a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=393075">Eclipse Jetty Bug #393075</a></li>
* <li><a href="https://issues.apache.org/bugzilla/show_bug.cgi?id=54067">Apache Tomcat Bug #54067</a></li>
* </ul>
*
* @throws Exception on test failure
*/
@Test
public void testHandshakeQuirk_TransferEncoding() throws Exception
{
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
server.registerWebSocket("/quirk/tomcat", (upgradeRequest, upgradeResponse) ->
{
// Extra header that Tomcat 7.x returns
upgradeResponse.addHeader("Transfer-Encoding", "chunked");
return new UntrustedWSEndpoint("tomcat-quirk");
});
URI wsUri = server.getWsUri().resolve("/quirk/tomcat");
Future<Session> clientConnectFuture = client.connect(clientSocket, wsUri);
Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
assertThat("Client saw Transfer-Encoding header",
clientSession.getUpgradeResponse().getHeader("Transfer-Encoding"),
is("chunked"));
assertThat("Client open event occurred",
clientSocket.openLatch.await(Defaults.OPEN_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS),
is(true));
clientSession.close();
}
@Test
public void testConnection_Refused() throws Exception
{
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
// This should be a ws:// uri to a machine that exists, but to a port
// that isn't listening.
// Intentionally bad port with nothing listening on it
URI wsUri = new URI("ws://127.0.0.1:1");
try
{
Future<Session> future = client.connect(clientSocket, wsUri);
// The attempt to get upgrade response future should throw error
future.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.fail("Expected ExecutionException -> ConnectException");
}
catch (ConnectException e)
{
assertExpectedError(e, clientSocket, ConnectException.class);
}
catch (ExecutionException e)
{
if (OS.IS_WINDOWS)
{
// On windows, this is a SocketTimeoutException
assertExpectedError(e, clientSocket, SocketTimeoutException.class);
}
else
{
// Expected path - java.net.ConnectException
assertExpectedError(e, clientSocket, ConnectException.class);
}
}
}
@Test(expected = TimeoutException.class)
public void testConnectionTimeout_AcceptNoUpgradeResponse() throws Exception
{
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
server.registerHttpService("/accept-no-upgrade-timeout", (req, resp) ->
{
// Intentionally take a long time here
// This simulates a server that accepts the request, but doesn't send
// any response (either at all, or in a timely manner)
try
{
TimeUnit.MICROSECONDS.sleep(5);
}
catch (InterruptedException ignore)
{
}
});
URI wsUri = server.getWsUri().resolve("/accept-no-upgrade-timeout");
Future<Session> future = client.connect(clientSocket, wsUri);
// The attempt to get upgrade response future should throw error
try
{
future.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.fail("Expected ExecutionException -> TimeoutException");
}
catch (ExecutionException e)
{
// Expected path - java.net.ConnectException
assertExpectedError(e, clientSocket, ConnectException.class);
}
}
}

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.client;
package org.eclipse.jetty.websocket.tests.client;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

View File

@ -0,0 +1,149 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.tests.client;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat;
import java.net.CookieManager;
import java.net.HttpCookie;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.tests.Defaults;
import org.eclipse.jetty.websocket.tests.TrackingEndpoint;
import org.eclipse.jetty.websocket.tests.UntrustedWSServer;
import org.eclipse.jetty.websocket.tests.UntrustedWSSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
public class CookieTest
{
private static final Logger LOG = Log.getLogger(CookieTest.class);
@Rule
public TestName testname = new TestName();
private UntrustedWSServer server;
private WebSocketClient client;
@Before
public void startClient() throws Exception
{
client = new WebSocketClient();
client.start();
}
@Before
public void startServer() throws Exception
{
server = new UntrustedWSServer();
server.start();
}
@After
public void stopClient() throws Exception
{
client.stop();
}
@After
public void stopServer() throws Exception
{
server.stop();
}
@Test
public void testViaCookieManager() throws Exception
{
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
CompletableFuture<UntrustedWSSession> serverSessionFut = new CompletableFuture<>();
server.registerOnOpenFuture(wsUri, serverSessionFut);
// Setup client
CookieManager cookieMgr = new CookieManager();
client.setCookieStore(cookieMgr.getCookieStore());
HttpCookie cookie = new HttpCookie("hello", "world");
cookie.setPath("/");
cookie.setVersion(0);
cookie.setMaxAge(100000);
cookieMgr.getCookieStore().add(server.getWsUri(), cookie);
cookie = new HttpCookie("foo", "bar is the word");
cookie.setPath("/");
cookie.setMaxAge(100000);
cookieMgr.getCookieStore().add(server.getWsUri(), cookie);
// Client connects
Future<Session> clientConnectFuture = client.connect(clientSocket, wsUri);
Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
// Server accepts connect
UntrustedWSSession serverSession = serverSessionFut.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
// client confirms upgrade and receipt of frame
List<String> serverCookies = serverSession.getUntrustedEndpoint().openUpgradeRequest.getHeaders("Cookie");
assertThat("Cookies seen at server side", serverCookies, hasItem(containsString("hello=world")));
assertThat("Cookies seen at server side", serverCookies, hasItem(containsString("foo=bar is the word")));
}
@Test
public void testViaServletUpgradeRequest() throws Exception
{
TrackingEndpoint clientSocket = new TrackingEndpoint(testname.getMethodName());
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
CompletableFuture<UntrustedWSSession> serverSessionFut = new CompletableFuture<>();
server.registerOnOpenFuture(wsUri, serverSessionFut);
// Setup client
HttpCookie cookie = new HttpCookie("hello", "world");
cookie.setPath("/");
cookie.setMaxAge(100000);
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setCookies(Collections.singletonList(cookie));
// Client connects
Future<Session> clientConnectFuture = client.connect(clientSocket, wsUri, request);
Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
// Server accepts connect
UntrustedWSSession serverSession = serverSessionFut.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
// client confirms upgrade and receipt of frame
List<String> serverCookies = serverSession.getUntrustedEndpoint().openUpgradeRequest.getHeaders("Cookie");
assertThat("Cookies seen at server side", serverCookies, hasItem(containsString("hello=world")));
}
}

View File

@ -1,131 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.tests.client;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.tests.TrackingEndpoint;
import org.eclipse.jetty.websocket.tests.UntrustedWSEndpoint;
import org.eclipse.jetty.websocket.tests.UntrustedWSServer;
import org.eclipse.jetty.websocket.tests.UntrustedWSSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
public class EchoTest
{
@Rule
public TestName testname = new TestName();
private UntrustedWSServer server;
private WebSocketClient client;
@Before
public void startClient() throws Exception
{
client = new WebSocketClient();
client.start();
}
@Before
public void startServer() throws Exception
{
server = new UntrustedWSServer();
server.start();
}
@After
public void stopClient() throws Exception
{
client.stop();
}
@After
public void stopServer() throws Exception
{
server.stop();
}
@Test
public void testBasicEcho() throws IOException, InterruptedException, ExecutionException, TimeoutException
{
// Set client timeout
final int timeout = 1000;
client.setMaxIdleTimeout(timeout);
URI wsURI = server.getWsUri().resolve("/untrusted/" + testname.getMethodName());
CompletableFuture<UntrustedWSSession> serverSessionFut = new CompletableFuture<UntrustedWSSession>()
{
@Override
public boolean complete(UntrustedWSSession session)
{
// echo back text as-well
session.getUntrustedEndpoint().setOnTextFunction((serverSession, text) -> text);
return super.complete(session);
}
};
server.registerConnectFuture(wsURI, serverSessionFut);
// Client connects
TrackingEndpoint clientEndpoint = new TrackingEndpoint(WebSocketBehavior.CLIENT.name());
Future<Session> clientConnectFuture = client.connect(clientEndpoint, wsURI);
// Server accepts connect
UntrustedWSSession serverSession = serverSessionFut.get(10, TimeUnit.SECONDS);
UntrustedWSEndpoint serverEndpoint = serverSession.getUntrustedEndpoint();
// client confirms connection via echo
assertThat("Client onOpen event", clientEndpoint.openLatch.await(5, TimeUnit.SECONDS), is(true));
// client sends message
clientEndpoint.getRemote().sendString("Hello Echo");
// Wait for response to echo
String message = clientEndpoint.messageQueue.poll(5, TimeUnit.SECONDS);
assertThat("message", message, is("Hello Echo"));
// client closes
clientEndpoint.close(StatusCode.NORMAL, "Normal Close");
// Server close event
assertThat("Server onClose event", serverSession.getUntrustedEndpoint().closeLatch.await(5, TimeUnit.SECONDS), is(true));
serverEndpoint.assertCloseInfo("Server", StatusCode.NORMAL, containsString("Normal Close"));
// client triggers close event on client ws-endpoint
assertThat("Client onClose event", clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS), is(true));
clientEndpoint.assertCloseInfo("Client", StatusCode.NORMAL, containsString("Normal Close"));
}
}

View File

@ -16,34 +16,38 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.client;
package org.eclipse.jetty.websocket.tests.client;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.net.URI;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.test.IBlockheadServerConnection;
import org.eclipse.jetty.websocket.common.test.XBlockheadServer;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.tests.Defaults;
import org.eclipse.jetty.websocket.tests.TrackingEndpoint;
import org.eclipse.jetty.websocket.tests.UntrustedWSServer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
public class SlowClientTest
{
@Rule
public TestTracker tt = new TestTracker();
private XBlockheadServer server;
public TestName testname = new TestName();
private UntrustedWSServer server;
private WebSocketClient client;
@Before
public void startClient() throws Exception
{
@ -51,69 +55,65 @@ public class SlowClientTest
client.getPolicy().setIdleTimeout(60000);
client.start();
}
@Before
public void startServer() throws Exception
{
server = new XBlockheadServer();
server = new UntrustedWSServer();
server.start();
}
@After
public void stopClient() throws Exception
{
client.stop();
}
@After
public void stopServer() throws Exception
{
server.stop();
}
@Test
@Slow
public void testClientSlowToSend() throws Exception
{
JettyTrackingSocket tsocket = new JettyTrackingSocket();
TrackingEndpoint clientEndpoint = new TrackingEndpoint(testname.getMethodName());
client.getPolicy().setIdleTimeout(60000);
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(tsocket, wsUri);
IBlockheadServerConnection sconnection = server.accept();
sconnection.setSoTimeout(60000);
sconnection.upgrade();
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest();
upgradeRequest.setSubProtocols("echo");
Future<Session> clientConnectFuture = client.connect(clientEndpoint, wsUri);
// Confirm connected
future.get(30,TimeUnit.SECONDS);
tsocket.waitForConnected(30,TimeUnit.SECONDS);
Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
assertThat("Client open event", clientEndpoint.openLatch.await(Defaults.OPEN_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS), is(true));
int messageCount = 10;
// Setup server read thread
ServerReadThread reader = new ServerReadThread(sconnection, messageCount);
reader.start();
// Have client write slowly.
ClientWriteThread writer = new ClientWriteThread(tsocket.getSession());
ClientWriteThread writer = new ClientWriteThread(clientSession);
writer.setMessageCount(messageCount);
writer.setMessage("Hello");
writer.setSlowness(10);
writer.start();
writer.join();
reader.waitForExpectedMessageCount(1, TimeUnit.MINUTES);
// Verify receive
Assert.assertThat("Frame Receive Count", reader.getFrameCount(), is(messageCount));
for (int i = 0; i < messageCount; i++)
{
String expectedMsg = "Hello";
String incomingMessage = clientEndpoint.messageQueue.poll(1, TimeUnit.SECONDS);
assertThat("Received Message[" + (i + 1) + "/" + messageCount + "]", incomingMessage, is(expectedMsg));
}
// Wait for completion
writer.join();
// Close
tsocket.getSession().close(StatusCode.NORMAL, "Done");
Assert.assertTrue("Client Socket Closed", tsocket.closeLatch.await(3, TimeUnit.MINUTES));
tsocket.assertCloseCode(StatusCode.NORMAL);
reader.cancel(); // stop reading
clientSession.close();
assertTrue("Client close event", clientEndpoint.closeLatch.await(Defaults.CLOSE_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
clientEndpoint.assertCloseInfo("Client", StatusCode.NORMAL, is("Done"));
}
}

View File

@ -0,0 +1,173 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.tests.client;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.net.URI;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.tests.Defaults;
import org.eclipse.jetty.websocket.tests.TrackingEndpoint;
import org.eclipse.jetty.websocket.tests.UntrustedWSServer;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
public class SlowServerTest
{
@Rule
public TestName testname = new TestName();
private UntrustedWSServer server;
private WebSocketClient client;
@Before
public void startClient() throws Exception
{
client = new WebSocketClient();
client.setMaxIdleTimeout(60000);
client.start();
}
@Before
public void startServer() throws Exception
{
server = new UntrustedWSServer();
server.start();
}
@After
public void stopClient() throws Exception
{
client.stop();
}
@After
public void stopServer() throws Exception
{
server.stop();
}
@Test
@Slow
public void testServerSlowToRead() throws Exception
{
TrackingEndpoint clientEndpoint = new TrackingEndpoint(testname.getMethodName());
// client.setMasker(new ZeroMasker());
client.setMaxIdleTimeout(60000);
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest();
upgradeRequest.setSubProtocols("echo");
Future<Session> clientConnectFuture = client.connect(clientEndpoint, wsUri);
// Confirm connected
Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
assertThat("Client open event", clientEndpoint.openLatch.await(Defaults.OPEN_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS), is(true));
int messageCount = 10;
// Setup slow server read
// TODO: setup a slow EndPoint READ on the server
// Have client write as quickly as it can.
ClientWriteThread writer = new ClientWriteThread(clientSession);
writer.setMessageCount(messageCount);
writer.setMessage("Hello");
writer.setSlowness(-1); // disable slowness
writer.start();
writer.join();
// Verify receive
for (int i = 0; i < messageCount; i++)
{
String expectedMsg = "Hello";
String incomingMessage = clientEndpoint.messageQueue.poll(1, TimeUnit.SECONDS);
assertThat("Received Message[" + (i + 1) + "/" + messageCount + "]", incomingMessage, is(expectedMsg));
}
// Wait for completion
writer.join();
// Close
clientSession.close();
assertTrue("Client close event", clientEndpoint.closeLatch.await(Defaults.CLOSE_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
clientEndpoint.assertCloseInfo("Client", StatusCode.NORMAL, is("Done"));
}
@Test
@Slow
public void testServerSlowToSend() throws Exception
{
TrackingEndpoint clientEndpoint = new TrackingEndpoint(testname.getMethodName());
// client.setMasker(new ZeroMasker());
client.setMaxIdleTimeout(60000);
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest();
upgradeRequest.setSubProtocols("echo");
Future<Session> clientConnectFuture = client.connect(clientEndpoint, wsUri);
// Confirm connected
Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
assertThat("Client open event", clientEndpoint.openLatch.await(Defaults.OPEN_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS), is(true));
int messageCount = 1000;
// Setup slow server read
// TODO: setup a slow EndPoint READ on the server
// Have server write slowly.
// ServerWriteThread writer = new ServerWriteThread(serverConn);
// writer.setMessageCount(messageCount);
// writer.setMessage("Hello");
// writer.setSlowness(10);
// writer.start();
// writer.join();
// Verify receive
for (int i = 0; i < messageCount; i++)
{
String expectedMsg = "Hello";
String incomingMessage = clientEndpoint.messageQueue.poll(1, TimeUnit.SECONDS);
assertThat("Received Message[" + (i + 1) + "/" + messageCount + "]", incomingMessage, is(expectedMsg));
}
// Wait for completion
// Close
clientSession.close();
assertTrue("Client close event", clientEndpoint.closeLatch.await(Defaults.CLOSE_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
clientEndpoint.assertCloseInfo("Client", StatusCode.NORMAL, is("Done"));
}
}

View File

@ -0,0 +1,266 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.tests.client;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
import org.eclipse.jetty.websocket.tests.Defaults;
import org.eclipse.jetty.websocket.tests.TrackingEndpoint;
import org.eclipse.jetty.websocket.tests.UntrustedWSEndpoint;
import org.eclipse.jetty.websocket.tests.UntrustedWSServer;
import org.eclipse.jetty.websocket.tests.UntrustedWSSession;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TestName;
public class WebSocketClientTest
{
@Rule
public TestName testname = new TestName();
@Rule
public ExpectedException expectedException = ExpectedException.none();
private UntrustedWSServer server;
private WebSocketClient client;
@Before
public void startClient() throws Exception
{
client = new WebSocketClient();
client.start();
}
@Before
public void startServer() throws Exception
{
server = new UntrustedWSServer();
server.start();
}
@After
public void stopClient() throws Exception
{
client.stop();
}
@After
public void stopServer() throws Exception
{
server.stop();
}
@Test
public void testAddExtension_NotInstalled() throws Exception
{
TrackingEndpoint clientEndpoint = new TrackingEndpoint(testname.getMethodName());
client.getPolicy().setIdleTimeout(10000);
URI wsUri = server.getWsUri();
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setSubProtocols("echo");
request.addExtensions("x-bad"); // extension that doesn't exist
// Should trigger failure on bad extension
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(containsString("x-bad"));
client.connect(clientEndpoint, wsUri, request);
}
@Test
public void testBasicEcho() throws IOException, InterruptedException, ExecutionException, TimeoutException
{
// Set client timeout
final int timeout = 1000;
client.setMaxIdleTimeout(timeout);
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
CompletableFuture<UntrustedWSSession> serverSessionFut = new CompletableFuture<>();
server.registerOnOpenFuture(wsUri, serverSessionFut);
// Client connects
TrackingEndpoint clientEndpoint = new TrackingEndpoint(testname.getMethodName());
ClientUpgradeRequest clientUpgradeRequest = new ClientUpgradeRequest();
clientUpgradeRequest.setSubProtocols("echo");
Future<Session> clientConnectFuture = client.connect(clientEndpoint, wsUri, clientUpgradeRequest);
// Verify Client Session
Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
assertThat("Client Session", clientSession, notNullValue());
assertThat("Client Session.open", clientSession.isOpen(), is(true));
assertThat("Client Session.upgradeRequest", clientSession.getUpgradeRequest(), notNullValue());
assertThat("Client Session.upgradeRequest", clientSession.getUpgradeResponse(), notNullValue());
// Verify Client Session Tracking
Collection<WebSocketSession> sessions = client.getBeans(WebSocketSession.class);
Assert.assertThat("client.beans[session].size", sessions.size(), is(1));
// Server accepts connect
UntrustedWSSession serverSession = serverSessionFut.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
UntrustedWSEndpoint serverEndpoint = serverSession.getUntrustedEndpoint();
// client confirms connection via echo
clientEndpoint.awaitOpenEvent("Client");
// client sends message
clientEndpoint.getRemote().sendString("Hello Echo");
// Wait for response to echo
String message = clientEndpoint.messageQueue.poll(5, TimeUnit.SECONDS);
assertThat("message", message, is("Hello Echo"));
// client closes
clientEndpoint.close(StatusCode.NORMAL, "Normal Close");
// Server close event
serverSession.getUntrustedEndpoint().awaitCloseEvent("Server");
serverEndpoint.assertCloseInfo("Server", StatusCode.NORMAL, containsString("Normal Close"));
// client triggers close event on client ws-endpoint
clientEndpoint.awaitCloseEvent("Client");
clientEndpoint.assertCloseInfo("Client", StatusCode.NORMAL, containsString("Normal Close"));
// Verify Client Session Tracking
sessions = client.getBeans(WebSocketSession.class);
// TODO: Assert.assertThat("client.beans[session].size", sessions.size(), is(0));
}
@Test
public void testBasicEcho_UsingCallback() throws Exception
{
client.setMaxIdleTimeout(160000);
TrackingEndpoint clientEndpoint = new TrackingEndpoint(testname.getMethodName());
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setSubProtocols("echo");
Future<Session> clientConnectFuture = client.connect(clientEndpoint, wsUri, request);
Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
assertThat("Client session", clientSession, notNullValue());
FutureWriteCallback callback = new FutureWriteCallback();
clientEndpoint.session.getRemote().sendString("Hello World!", callback);
callback.get(5, TimeUnit.SECONDS);
}
@Test
public void testLocalRemoteAddress() throws Exception
{
TrackingEndpoint clientEndpoint = new TrackingEndpoint(testname.getMethodName());
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
Future<Session> clientConnectFuture = client.connect(clientEndpoint, wsUri);
Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
InetSocketAddress local = clientSession.getLocalAddress();
InetSocketAddress remote = clientSession.getRemoteAddress();
Assert.assertThat("Local Socket Address", local, notNullValue());
Assert.assertThat("Remote Socket Address", remote, notNullValue());
// Hard to validate (in a portable unit test) the local address that was used/bound in the low level Jetty Endpoint
Assert.assertThat("Local Socket Address / Host", local.getAddress().getHostAddress(), notNullValue());
Assert.assertThat("Local Socket Address / Port", local.getPort(), greaterThan(0));
Assert.assertThat("Remote Socket Address / Host", remote.getAddress().getHostAddress(), is(wsUri.getHost()));
Assert.assertThat("Remote Socket Address / Port", remote.getPort(), greaterThan(0));
}
/**
* Ensure that <code>@WebSocket(maxTextMessageSize = 100*1024)</code> behaves as expected.
*
* @throws Exception on test failure
*/
@Test
public void testMaxMessageSize() throws Exception
{
TrackingEndpoint clientEndpoint = new TrackingEndpoint(testname.getMethodName());
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname);
ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest();
upgradeRequest.setSubProtocols("echo");
Future<Session> clientConnectFuture = client.connect(clientEndpoint, wsUri, upgradeRequest);
Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
// Create string that is larger than default size of 64k
// but smaller than maxMessageSize of 100k
byte buf[] = new byte[80 * 1024];
Arrays.fill(buf, (byte) 'x');
String outgoingMessage = StringUtil.toUTF8String(buf, 0, buf.length);
clientSession.getRemote().sendStringByFuture(outgoingMessage);
String incomingMessage = clientEndpoint.messageQueue.poll(5, TimeUnit.SECONDS);
assertThat("Message received", incomingMessage, is(outgoingMessage));
clientSession.close();
}
@Test
public void testParameterMap() throws Exception
{
TrackingEndpoint clientEndpoint = new TrackingEndpoint(testname.getMethodName());
URI wsUri = server.getUntrustedWsUri(this.getClass(), testname).resolve("?snack=cashews&amount=handful&brand=off");
assertThat("wsUri has query", wsUri.getQuery(), notNullValue());
Future<Session> clientConnectFuture = client.connect(clientEndpoint, wsUri);
Session clientSession = clientConnectFuture.get(Defaults.CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
UpgradeRequest req = clientSession.getUpgradeRequest();
Assert.assertThat("Upgrade Request", req, notNullValue());
Map<String, List<String>> parameterMap = req.getParameterMap();
Assert.assertThat("Parameter Map", parameterMap, notNullValue());
Assert.assertThat("Parameter[snack]", parameterMap.get("snack"), is(Arrays.asList(new String[]{"cashews"})));
Assert.assertThat("Parameter[amount]", parameterMap.get("amount"), is(Arrays.asList(new String[]{"handful"})));
Assert.assertThat("Parameter[brand]", parameterMap.get("brand"), is(Arrays.asList(new String[]{"off"})));
Assert.assertThat("Parameter[cost]", parameterMap.get("cost"), nullValue());
}
}