Issue #207 - moved client tests to websocket-tests
This commit is contained in:
parent
ee6d495af4
commit
e276705948
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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")));
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue