blob: 39c4f7bb2991914ed915f77111144f642dc70cb1 [file] [log] [blame]
//
// ========================================================================
// 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.*;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
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.BlockheadServer;
import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
/**
* Various connect condition testing
*/
public class ClientConnectTest
{
@Rule
public TestTracker tt = new TestTracker();
@Rule
public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
private final int timeout = 500;
private BlockheadServer 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));
}
// 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;
}
@Before
public void startClient() throws Exception
{
client = new WebSocketClient(bufferPool);
client.setConnectTimeout(timeout);
client.start();
}
@Before
public void startServer() throws Exception
{
server = new BlockheadServer();
server.start();
}
@After
public void stopClient() throws Exception
{
client.stop();
}
@After
public void stopServer() throws Exception
{
server.stop();
}
@Test
public void testBadHandshake() throws Exception
{
JettyTrackingSocket wsocket = new JettyTrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
ServerConnection connection = server.accept();
connection.readRequest();
// no upgrade, just fail with a 404 error
connection.respond("HTTP/1.1 404 NOT FOUND\r\n\r\n");
// The attempt to get upgrade response future should throw error
try
{
future.get(500,TimeUnit.MILLISECONDS);
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);
ServerConnection connection = server.accept();
connection.readRequest();
// Send OK to GET but not upgrade
connection.respond("HTTP/1.1 200 OK\r\n\r\n");
// The attempt to get upgrade response future should throw error
try
{
future.get(500,TimeUnit.MILLISECONDS);
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);
ServerConnection 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("\r\n");
connection.respond(resp.toString());
// The attempt to get upgrade response future should throw error
try
{
future.get(500,TimeUnit.MILLISECONDS);
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);
ServerConnection 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(500,TimeUnit.MILLISECONDS);
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);
ServerConnection 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(500,TimeUnit.MILLISECONDS);
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);
ServerConnection 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(500,TimeUnit.MILLISECONDS);
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(500,TimeUnit.MILLISECONDS);
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(1000,TimeUnit.MILLISECONDS);
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);
ServerConnection 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(500,TimeUnit.MILLISECONDS);
Assert.fail("Expected ExecutionException -> TimeoutException");
}
catch (ExecutionException e)
{
// Expected path - java.net.ConnectException ?
assertExpectedError(e,wsocket,ConnectException.class);
}
}
}