parent
a3e4a08903
commit
8a05c651c0
|
@ -0,0 +1,545 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 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.server.handler;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.NetworkConnector;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
public class IPAccessHandlerTest
|
||||
{
|
||||
private static Server _server;
|
||||
private static NetworkConnector _connector;
|
||||
private static IPAccessHandler _handler;
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp()
|
||||
throws Exception
|
||||
{
|
||||
_server = new Server();
|
||||
_connector = new ServerConnector(_server);
|
||||
_server.setConnectors(new Connector[] { _connector });
|
||||
|
||||
_handler = new IPAccessHandler();
|
||||
_handler.setHandler(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
response.setStatus(HttpStatus.OK_200);
|
||||
}
|
||||
});
|
||||
_server.setHandler(_handler);
|
||||
_server.start();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@AfterAll
|
||||
public static void tearDown()
|
||||
throws Exception
|
||||
{
|
||||
_server.stop();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@ParameterizedTest
|
||||
@MethodSource("data")
|
||||
public void testHandler(String white, String black, String host, String uri, String code, boolean byPath)
|
||||
throws Exception
|
||||
{
|
||||
_handler.setWhite(white.split(";",-1));
|
||||
_handler.setBlack(black.split(";",-1));
|
||||
_handler.setWhiteListByPath(byPath);
|
||||
|
||||
String request = "GET " + uri + " HTTP/1.1\n" + "Host: "+ host + "\n\n";
|
||||
Socket socket = new Socket("127.0.0.1", _connector.getLocalPort());
|
||||
socket.setSoTimeout(5000);
|
||||
try
|
||||
{
|
||||
OutputStream output = socket.getOutputStream();
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
||||
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
|
||||
Response response = readResponse(input);
|
||||
Object[] params = new Object[]{
|
||||
"Request WBHUC", white, black, host, uri, code,
|
||||
"Response", response.getCode()};
|
||||
assertEquals(code, response.getCode(), Arrays.deepToString(params));
|
||||
}
|
||||
finally
|
||||
{
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected Response readResponse(BufferedReader reader)
|
||||
throws IOException
|
||||
{
|
||||
// Simplified parser for HTTP responses
|
||||
String line = reader.readLine();
|
||||
if (line == null)
|
||||
throw new EOFException();
|
||||
Matcher responseLine = Pattern.compile("HTTP/1\\.1\\s+(\\d+)").matcher(line);
|
||||
assertTrue(responseLine.lookingAt());
|
||||
String code = responseLine.group(1);
|
||||
|
||||
Map<String, String> headers = new LinkedHashMap<>();
|
||||
while ((line = reader.readLine()) != null)
|
||||
{
|
||||
if (line.trim().length() == 0)
|
||||
break;
|
||||
|
||||
Matcher header = Pattern.compile("([^:]+):\\s*(.*)").matcher(line);
|
||||
assertTrue(header.lookingAt());
|
||||
String headerName = header.group(1);
|
||||
String headerValue = header.group(2);
|
||||
headers.put(headerName.toLowerCase(Locale.ENGLISH), headerValue.toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
StringBuilder body = new StringBuilder();
|
||||
if (headers.containsKey("content-length"))
|
||||
{
|
||||
int length = Integer.parseInt(headers.get("content-length"));
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
char c = (char)reader.read();
|
||||
body.append(c);
|
||||
}
|
||||
}
|
||||
else if ("chunked".equals(headers.get("transfer-encoding")))
|
||||
{
|
||||
while ((line = reader.readLine()) != null)
|
||||
{
|
||||
if ("0".equals(line))
|
||||
{
|
||||
line = reader.readLine();
|
||||
assertEquals("", line);
|
||||
break;
|
||||
}
|
||||
|
||||
int length = Integer.parseInt(line, 16);
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
char c = (char)reader.read();
|
||||
body.append(c);
|
||||
}
|
||||
line = reader.readLine();
|
||||
assertEquals("", line);
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(code, headers, body.toString().trim());
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected class Response
|
||||
{
|
||||
private final String code;
|
||||
private final Map<String, String> headers;
|
||||
private final String body;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
private Response(String code, Map<String, String> headers, String body)
|
||||
{
|
||||
this.code = code;
|
||||
this.headers = headers;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public String getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public Map<String, String> getHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public String getBody()
|
||||
{
|
||||
return body;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(code).append("\r\n");
|
||||
for (Map.Entry<String, String> entry : headers.entrySet())
|
||||
builder.append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
|
||||
builder.append("\r\n");
|
||||
builder.append(body);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public static Stream<Arguments> data() {
|
||||
Object[][] data = new Object[][] {
|
||||
// Empty lists
|
||||
{"", "", "127.0.0.1", "/", "200", false},
|
||||
{"", "", "127.0.0.1", "/dump/info", "200", false},
|
||||
|
||||
// White list
|
||||
{"127.0.0.1", "", "127.0.0.1", "/", "200", false},
|
||||
{"127.0.0.1", "", "127.0.0.1", "/dispatch", "200", false},
|
||||
{"127.0.0.1", "", "127.0.0.1", "/dump/info", "200", false},
|
||||
|
||||
{"127.0.0.1|/", "", "127.0.0.1", "/", "200", false},
|
||||
{"127.0.0.1|/", "", "127.0.0.1", "/dispatch", "403", false},
|
||||
{"127.0.0.1|/", "", "127.0.0.1", "/dump/info", "403", false},
|
||||
|
||||
{"127.0.0.1|/*", "", "127.0.0.1", "/", "200", false},
|
||||
{"127.0.0.1|/*", "", "127.0.0.1", "/dispatch", "200", false},
|
||||
{"127.0.0.1|/*", "", "127.0.0.1", "/dump/info", "200", false},
|
||||
|
||||
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/", "403", false},
|
||||
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/dispatch", "403", false},
|
||||
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/dump/info", "200", false},
|
||||
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/dump/test", "200", false},
|
||||
|
||||
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/", "403", false},
|
||||
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/dispatch", "403", false},
|
||||
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/dump/info", "200", false},
|
||||
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/dump/test", "403", false},
|
||||
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/", "403", false},
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dispatch", "403", false},
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/info", "200", false},
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/test", "200", false},
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/fail", "403", false},
|
||||
|
||||
{"127.0.0.0-2|", "", "127.0.0.1", "/", "200", false},
|
||||
{"127.0.0.0-2|", "", "127.0.0.1", "/dump/info", "403", false},
|
||||
|
||||
{"127.0.0.0-2|/", "", "127.0.0.1", "/", "200", false},
|
||||
{"127.0.0.0-2|/", "", "127.0.0.1", "/dispatch", "403", false},
|
||||
{"127.0.0.0-2|/", "", "127.0.0.1", "/dump/info", "403", false},
|
||||
|
||||
{"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/", "403", false},
|
||||
{"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/dispatch", "403", false},
|
||||
{"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/dump/info", "200", false},
|
||||
|
||||
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/", "403", false},
|
||||
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dispatch", "403", false},
|
||||
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dump/info", "200", false},
|
||||
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dump/test", "403", false},
|
||||
|
||||
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/", "403", false},
|
||||
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dispatch", "403", false},
|
||||
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/info", "200", false},
|
||||
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/test", "200", false},
|
||||
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/fail", "403", false},
|
||||
|
||||
// Black list
|
||||
{"", "127.0.0.1", "127.0.0.1", "/", "403", false},
|
||||
{"", "127.0.0.1", "127.0.0.1", "/dispatch", "403", false},
|
||||
{"", "127.0.0.1", "127.0.0.1", "/dump/info", "403", false},
|
||||
|
||||
{"", "127.0.0.1|/", "127.0.0.1", "/", "403", false},
|
||||
{"", "127.0.0.1|/", "127.0.0.1", "/dispatch", "200", false},
|
||||
{"", "127.0.0.1|/", "127.0.0.1", "/dump/info", "200", false},
|
||||
|
||||
{"", "127.0.0.1|/*", "127.0.0.1", "/", "403", false},
|
||||
{"", "127.0.0.1|/*", "127.0.0.1", "/dispatch", "403", false},
|
||||
{"", "127.0.0.1|/*", "127.0.0.1", "/dump/info", "403", false},
|
||||
|
||||
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/", "200", false},
|
||||
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/dispatch", "200", false},
|
||||
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/dump/info", "403", false},
|
||||
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/dump/test", "403", false},
|
||||
|
||||
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/", "200", false},
|
||||
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/dispatch", "200", false},
|
||||
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/dump/info", "403", false},
|
||||
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/dump/test", "200", false},
|
||||
|
||||
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/", "200", false},
|
||||
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dispatch", "200", false},
|
||||
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/info", "403", false},
|
||||
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/test", "403", false},
|
||||
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/fail", "200", false},
|
||||
|
||||
{"", "127.0.0.0-2|", "127.0.0.1", "/", "403", false},
|
||||
{"", "127.0.0.0-2|", "127.0.0.1", "/dump/info", "200", false},
|
||||
|
||||
{"", "127.0.0.0-2|/", "127.0.0.1", "/", "403", false},
|
||||
{"", "127.0.0.0-2|/", "127.0.0.1", "/dispatch", "200", false},
|
||||
{"", "127.0.0.0-2|/", "127.0.0.1", "/dump/info", "200", false},
|
||||
|
||||
{"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/", "200", false},
|
||||
{"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/dispatch", "200", false},
|
||||
{"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/dump/info", "403", false},
|
||||
|
||||
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/", "200", false},
|
||||
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dispatch", "200", false},
|
||||
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dump/info", "403", false},
|
||||
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dump/test", "200", false},
|
||||
|
||||
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/", "200", false},
|
||||
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dispatch", "200", false},
|
||||
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/info", "403", false},
|
||||
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/test", "403", false},
|
||||
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/fail", "200", false},
|
||||
|
||||
// Both lists
|
||||
{"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump", "200", false},
|
||||
{"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "403", false},
|
||||
{"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", false},
|
||||
|
||||
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump", "200", false},
|
||||
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200", false},
|
||||
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", false},
|
||||
|
||||
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump", "200", false},
|
||||
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200", false},
|
||||
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/test", "403", false},
|
||||
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", false},
|
||||
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump", "403", false},
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/info", "200", false},
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/test", "403", false},
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/fail", "403", false},
|
||||
|
||||
{"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/", "200", false},
|
||||
{"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/dump/info", "200", false},
|
||||
{"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/dump/fail", "403", false},
|
||||
|
||||
// Different address
|
||||
{"127.0.0.2", "", "127.0.0.1", "/", "403", false},
|
||||
{"127.0.0.2", "", "127.0.0.1", "/dump/info", "403", false},
|
||||
|
||||
{"127.0.0.2|/dump/*", "", "127.0.0.1", "/", "403", false},
|
||||
{"127.0.0.2|/dump/*", "", "127.0.0.1", "/dump/info", "403", false},
|
||||
|
||||
{"127.0.0.2|/dump/info", "", "127.0.0.1", "/", "403", false},
|
||||
{"127.0.0.2|/dump/info", "", "127.0.0.1", "/dump/info", "403", false},
|
||||
{"127.0.0.2|/dump/info", "", "127.0.0.1", "/dump/test", "403", false},
|
||||
|
||||
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/", "403", false},
|
||||
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dispatch", "403", false},
|
||||
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/info", "200", false},
|
||||
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/test", "403", false},
|
||||
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/fail", "403", false},
|
||||
|
||||
{"172.0.0.0-255", "", "127.0.0.1", "/", "403", false},
|
||||
{"172.0.0.0-255", "", "127.0.0.1", "/dump/info", "403", false},
|
||||
|
||||
{"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/", "403", false},
|
||||
{"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/dispatch", "403", false},
|
||||
{"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/dump/info", "200", false},
|
||||
|
||||
/*-----------------------------------------------------------------------------------------*/
|
||||
// Match by path starts with [117]
|
||||
// test cases affected by _whiteListByPath highlighted accordingly
|
||||
|
||||
{"", "", "127.0.0.1", "/", "200", true},
|
||||
{"", "", "127.0.0.1", "/dump/info", "200", true},
|
||||
|
||||
// White list
|
||||
{"127.0.0.1", "", "127.0.0.1", "/", "200", true},
|
||||
{"127.0.0.1", "", "127.0.0.1", "/dispatch", "200", true},
|
||||
{"127.0.0.1", "", "127.0.0.1", "/dump/info", "200", true},
|
||||
|
||||
{"127.0.0.1|/", "", "127.0.0.1", "/", "200", true},
|
||||
{"127.0.0.1|/", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.1|/", "", "127.0.0.1", "/dump/info", "200", true}, // _whiteListByPath
|
||||
|
||||
{"127.0.0.1|/*", "", "127.0.0.1", "/", "200", true},
|
||||
{"127.0.0.1|/*", "", "127.0.0.1", "/dispatch", "200", true},
|
||||
{"127.0.0.1|/*", "", "127.0.0.1", "/dump/info", "200", true},
|
||||
|
||||
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/dump/info", "200", true},
|
||||
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/dump/test", "200", true},
|
||||
|
||||
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/dump/info", "200", true},
|
||||
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/dump/test", "200", true}, // _whiteListByPath
|
||||
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/info", "200", true},
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/test", "200", true},
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/fail", "200", true}, // _whiteListByPath
|
||||
|
||||
{"127.0.0.0-2|", "", "127.0.0.1", "/", "200", true},
|
||||
{"127.0.0.0-2|", "", "127.0.0.1", "/dump/info", "200", true},
|
||||
|
||||
{"127.0.0.0-2|/", "", "127.0.0.1", "/", "200", true},
|
||||
{"127.0.0.0-2|/", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.0-2|/", "", "127.0.0.1", "/dump/info", "200", true}, // _whiteListByPath
|
||||
|
||||
{"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/dump/info", "200", true},
|
||||
|
||||
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dump/info", "200", true},
|
||||
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dump/test", "200", true}, // _whiteListByPath
|
||||
|
||||
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/info", "200", true},
|
||||
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/test", "200", true},
|
||||
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/fail", "200", true}, // _whiteListByPath
|
||||
|
||||
// Black list
|
||||
{"", "127.0.0.1", "127.0.0.1", "/", "403", true},
|
||||
{"", "127.0.0.1", "127.0.0.1", "/dispatch", "403", true},
|
||||
{"", "127.0.0.1", "127.0.0.1", "/dump/info", "403", true},
|
||||
|
||||
{"", "127.0.0.1|/", "127.0.0.1", "/", "403", true},
|
||||
{"", "127.0.0.1|/", "127.0.0.1", "/dispatch", "200", true},
|
||||
{"", "127.0.0.1|/", "127.0.0.1", "/dump/info", "200", true},
|
||||
|
||||
{"", "127.0.0.1|/*", "127.0.0.1", "/", "403", true},
|
||||
{"", "127.0.0.1|/*", "127.0.0.1", "/dispatch", "403", true},
|
||||
{"", "127.0.0.1|/*", "127.0.0.1", "/dump/info", "403", true},
|
||||
|
||||
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/", "200", true},
|
||||
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/dispatch", "200", true},
|
||||
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/dump/info", "403", true},
|
||||
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/dump/test", "403", true},
|
||||
|
||||
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/", "200", true},
|
||||
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/dispatch", "200", true},
|
||||
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/dump/info", "403", true},
|
||||
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/dump/test", "200", true},
|
||||
|
||||
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/", "200", true},
|
||||
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dispatch", "200", true},
|
||||
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/info", "403", true},
|
||||
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/test", "403", true},
|
||||
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/fail", "200", true},
|
||||
|
||||
{"", "127.0.0.0-2|", "127.0.0.1", "/", "403", true},
|
||||
{"", "127.0.0.0-2|", "127.0.0.1", "/dump/info", "200", true},
|
||||
|
||||
{"", "127.0.0.0-2|/", "127.0.0.1", "/", "403", true},
|
||||
{"", "127.0.0.0-2|/", "127.0.0.1", "/dispatch", "200", true},
|
||||
{"", "127.0.0.0-2|/", "127.0.0.1", "/dump/info", "200", true},
|
||||
|
||||
{"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/", "200", true},
|
||||
{"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/dispatch", "200", true},
|
||||
{"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/dump/info", "403", true},
|
||||
|
||||
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/", "200", true},
|
||||
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dispatch", "200", true},
|
||||
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dump/info", "403", true},
|
||||
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dump/test", "200", true},
|
||||
|
||||
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/", "200", true},
|
||||
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dispatch", "200", true},
|
||||
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/info", "403", true},
|
||||
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/test", "403", true},
|
||||
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/fail", "200", true},
|
||||
|
||||
// Both lists
|
||||
{"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump", "200", true},
|
||||
{"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", true},
|
||||
|
||||
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump", "200", true},
|
||||
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200", true},
|
||||
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", true},
|
||||
|
||||
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump", "200", true},
|
||||
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200", true},
|
||||
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/test", "403", true},
|
||||
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", true},
|
||||
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/info", "200", true},
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/test", "403", true},
|
||||
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/fail", "200", true}, // _whiteListByPath
|
||||
|
||||
{"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/", "200", true},
|
||||
{"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/dump/info", "200", true},
|
||||
{"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/dump/fail", "403", true},
|
||||
|
||||
// Different address
|
||||
{"127.0.0.2", "", "127.0.0.1", "/", "403", true},
|
||||
{"127.0.0.2", "", "127.0.0.1", "/dump/info", "403", true},
|
||||
|
||||
{"127.0.0.2|/dump/*", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.2|/dump/*", "", "127.0.0.1", "/dump/info", "403", true},
|
||||
|
||||
{"127.0.0.2|/dump/info", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.2|/dump/info", "", "127.0.0.1", "/dump/info", "403", true},
|
||||
{"127.0.0.2|/dump/info", "", "127.0.0.1", "/dump/test", "200", true}, // _whiteListByPath
|
||||
|
||||
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
|
||||
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/info", "200", true},
|
||||
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/test", "403", true},
|
||||
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/fail", "200", true}, // _whiteListByPath
|
||||
|
||||
{"172.0.0.0-255", "", "127.0.0.1", "/", "403", true},
|
||||
{"172.0.0.0-255", "", "127.0.0.1", "/dump/info", "403", true},
|
||||
|
||||
{"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
|
||||
{"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
|
||||
{"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/dump/info", "200", true},
|
||||
};
|
||||
return Arrays.asList(data).stream().map(Arguments::of);
|
||||
}
|
||||
}
|
|
@ -18,8 +18,14 @@
|
|||
|
||||
package org.eclipse.jetty.webapp;
|
||||
|
||||
<<<<<<< HEAD
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
=======
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -27,7 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
|||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
>>>>>>> a3f1592c50... Issue #2431 - Upgrade to Junit 5 (#2436)
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
@ -113,8 +119,7 @@ public class WebAppContextTest
|
|||
|
||||
//test if no classnames set, its the defaults
|
||||
WebAppContext wac = new WebAppContext();
|
||||
assertThat(wac.getWebAppConfigurations().stream().map(c->{return c.getClass().getName();}).collect(Collectors.toList()),
|
||||
containsInAnyOrder(known_and_enabled));
|
||||
assertThat(wac.getWebAppConfigurations().stream().map(c->{return c.getClass().getName();}).collect(Collectors.toList()),Matchers.containsInAnyOrder(known_and_enabled));
|
||||
String[] classNames = wac.getConfigurationClasses();
|
||||
assertNotNull(classNames);
|
||||
|
||||
|
@ -128,8 +133,8 @@ public class WebAppContextTest
|
|||
{
|
||||
WebAppContext wac = new WebAppContext();
|
||||
wac.setServer(new Server());
|
||||
assertThat(wac.getWebAppConfigurations().stream().map(c->c.getClass().getName()).collect(Collectors.toList()),
|
||||
contains(
|
||||
Assert.assertThat(wac.getWebAppConfigurations().stream().map(c->c.getClass().getName()).collect(Collectors.toList()),
|
||||
Matchers.contains(
|
||||
"org.eclipse.jetty.webapp.JmxConfiguration",
|
||||
"org.eclipse.jetty.webapp.WebInfConfiguration",
|
||||
"org.eclipse.jetty.webapp.WebXmlConfiguration",
|
||||
|
@ -145,14 +150,14 @@ public class WebAppContextTest
|
|||
Configuration[] configs = {new WebInfConfiguration()};
|
||||
WebAppContext wac = new WebAppContext();
|
||||
wac.setConfigurations(configs);
|
||||
assertThat(wac.getWebAppConfigurations(),contains(configs));
|
||||
Assert.assertThat(wac.getWebAppConfigurations(),Matchers.contains(configs));
|
||||
|
||||
//test that explicit config instances override any from server
|
||||
String[] classNames = {"x.y.z"};
|
||||
Server server = new Server();
|
||||
server.setAttribute(Configuration.ATTR, classNames);
|
||||
wac.setServer(server);
|
||||
assertThat(wac.getWebAppConfigurations(),contains(configs));
|
||||
Assert.assertThat(wac.getWebAppConfigurations(),Matchers.contains(configs));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -259,8 +264,11 @@ public class WebAppContextTest
|
|||
try
|
||||
{
|
||||
String response = connector.getResponse("GET http://localhost:8080 HTTP/1.1\r\nHost: localhost:8080\r\nConnection: close\r\n\r\n");
|
||||
<<<<<<< HEAD
|
||||
assertThat(response,containsString("200 OK"));
|
||||
|
||||
=======
|
||||
assertTrue(response.indexOf("200 OK")>=0);
|
||||
>>>>>>> a3f1592c50... Issue #2431 - Upgrade to Junit 5 (#2436)
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 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.webapp;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.anyOf;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.util.JavaVersion;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledOnJre;
|
||||
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
||||
import org.junit.jupiter.api.condition.EnabledOnJre;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
|
||||
/**
|
||||
* WebInfConfigurationTest
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class WebInfConfigurationTest
|
||||
{
|
||||
|
||||
/**
|
||||
* Assume target < jdk9. In this case, we should be able to extract
|
||||
* the urls from the application classloader, and we should not look
|
||||
* at the java.class.path property.
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
@EnabledOnJre(JRE.JAVA_8)
|
||||
public void testFindAndFilterContainerPaths()
|
||||
throws Exception
|
||||
{
|
||||
WebInfConfiguration config = new WebInfConfiguration();
|
||||
WebAppContext context = new WebAppContext();
|
||||
context.setAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN, ".*/jetty-util-[^/]*\\.jar$|.*/jetty-util/target/classes/");
|
||||
|
||||
WebAppClassLoader loader = new WebAppClassLoader(context);
|
||||
context.setClassLoader(loader);
|
||||
config.findAndFilterContainerPaths(context);
|
||||
List<Resource> containerResources = context.getMetaData().getContainerResources();
|
||||
assertEquals(1, containerResources.size());
|
||||
assertThat(containerResources.get(0).toString(), containsString("jetty-util"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assume target jdk9 or above. In this case we should extract what we need
|
||||
* from the java.class.path. We should also examine the module path.
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
@DisabledOnJre(JRE.JAVA_8)
|
||||
@EnabledIfSystemProperty(named="jdk.module.path", matches=".*")
|
||||
public void testFindAndFilterContainerPathsJDK9()
|
||||
throws Exception
|
||||
{
|
||||
WebInfConfiguration config = new WebInfConfiguration();
|
||||
WebAppContext context = new WebAppContext();
|
||||
context.setAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN, ".*/jetty-util-[^/]*\\.jar$|.*/jetty-util/target/classes/$|.*/foo-bar-janb.jar");
|
||||
WebAppClassLoader loader = new WebAppClassLoader(context);
|
||||
context.setClassLoader(loader);
|
||||
config.findAndFilterContainerPaths(context);
|
||||
List<Resource> containerResources = context.getMetaData().getContainerResources();
|
||||
assertEquals(2, containerResources.size());
|
||||
for (Resource r:containerResources)
|
||||
{
|
||||
String s = r.toString();
|
||||
assertThat(s, anyOf(endsWith("foo-bar-janb.jar"), containsString("jetty-util")));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Assume runtime is jdk9 or above. Target is jdk 8. In this
|
||||
* case we must extract from the java.class.path (because jdk 9
|
||||
* has no url based application classloader), but we should
|
||||
* ignore the module path.
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
@DisabledOnJre(JRE.JAVA_8)
|
||||
@EnabledIfSystemProperty(named="jdk.module.path", matches=".*")
|
||||
public void testFindAndFilterContainerPathsTarget8()
|
||||
throws Exception
|
||||
{
|
||||
WebInfConfiguration config = new WebInfConfiguration();
|
||||
WebAppContext context = new WebAppContext();
|
||||
context.setAttribute(JavaVersion.JAVA_TARGET_PLATFORM, "8");
|
||||
context.setAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN, ".*/jetty-util-[^/]*\\.jar$|.*/jetty-util/target/classes/$|.*/foo-bar-janb.jar");
|
||||
WebAppClassLoader loader = new WebAppClassLoader(context);
|
||||
context.setClassLoader(loader);
|
||||
config.findAndFilterContainerPaths(context);
|
||||
List<Resource> containerResources = context.getMetaData().getContainerResources();
|
||||
assertEquals(1, containerResources.size());
|
||||
assertThat(containerResources.get(0).toString(), containsString("jetty-util"));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,673 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 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.allOf;
|
||||
import static org.hamcrest.Matchers.anyOf;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
|
||||
import static java.time.Duration.ofSeconds;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
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.jupiter.api.Assertions.assertTimeoutPreemptively;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
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;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
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.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.ProtocolException;
|
||||
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.common.CloseInfo;
|
||||
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.frames.TextFrame;
|
||||
import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadConnection;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadServer;
|
||||
import org.eclipse.jetty.websocket.common.test.RawFrameBuilder;
|
||||
import org.eclipse.jetty.websocket.common.test.Timeouts;
|
||||
import org.hamcrest.Matcher;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
||||
public class ClientCloseTest
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ClientCloseTest.class);
|
||||
|
||||
private static class CloseTrackingSocket extends WebSocketAdapter
|
||||
{
|
||||
private static final Logger LOG = ClientCloseTest.LOG.getLogger("CloseTrackingSocket");
|
||||
|
||||
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 LinkedBlockingQueue<String> messageQueue = new LinkedBlockingQueue<>();
|
||||
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 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.debug("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();
|
||||
}
|
||||
}
|
||||
|
||||
private static BlockheadServer server;
|
||||
private WebSocketClient client;
|
||||
|
||||
private void confirmConnection(CloseTrackingSocket clientSocket, Future<Session> clientFuture, BlockheadConnection serverConns) throws Exception
|
||||
{
|
||||
// Wait for client connect on via future
|
||||
clientFuture.get(30,TimeUnit.SECONDS);
|
||||
|
||||
// Wait for client connect via client websocket
|
||||
assertThat("Client WebSocket is Open",clientSocket.openLatch.await(30,TimeUnit.SECONDS),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(Timeouts.SEND, Timeouts.SEND_UNIT);
|
||||
|
||||
// Read Frame on server side
|
||||
LinkedBlockingQueue<WebSocketFrame> serverCapture = serverConns.getFrameQueue();
|
||||
WebSocketFrame frame = serverCapture.poll(Timeouts.POLL_EVENT, Timeouts.POLL_EVENT_UNIT);
|
||||
assertThat("Server received frame",frame.getOpCode(),is(OpCode.TEXT));
|
||||
assertThat("Server received frame payload",frame.getPayloadAsUTF8(),is(echoMsg));
|
||||
|
||||
// Server send echo reply
|
||||
serverConns.write(new TextFrame().setPayload(echoMsg));
|
||||
|
||||
// Verify received message
|
||||
String recvMsg = clientSocket.messageQueue.poll(Timeouts.POLL_EVENT, Timeouts.POLL_EVENT_UNIT);
|
||||
assertThat("Received message",recvMsg,is(echoMsg));
|
||||
|
||||
// Verify that there are no errors
|
||||
assertThat("Error events",clientSocket.error.get(),nullValue());
|
||||
}
|
||||
finally
|
||||
{
|
||||
clientSocket.clearQueues();
|
||||
}
|
||||
}
|
||||
|
||||
private void confirmServerReceivedCloseFrame(BlockheadConnection serverConn, int expectedCloseCode, Matcher<String> closeReasonMatcher) throws InterruptedException
|
||||
{
|
||||
LinkedBlockingQueue<WebSocketFrame> serverCapture = serverConn.getFrameQueue();
|
||||
WebSocketFrame frame = serverCapture.poll(Timeouts.POLL_EVENT, Timeouts.POLL_EVENT_UNIT);
|
||||
assertThat("Server close frame", frame, is(notNullValue()));
|
||||
assertThat("Server received close frame",frame.getOpCode(),is(OpCode.CLOSE));
|
||||
CloseInfo closeInfo = new CloseInfo(frame);
|
||||
assertThat("Server received close code",closeInfo.getStatusCode(),is(expectedCloseCode));
|
||||
if (closeReasonMatcher == null)
|
||||
{
|
||||
assertThat("Server received close reason",closeInfo.getReason(),nullValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
assertThat("Server received close reason",closeInfo.getReason(),closeReasonMatcher);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestClientTransportOverHTTP extends HttpClientTransportOverHTTP
|
||||
{
|
||||
@Override
|
||||
protected SelectorManager newSelectorManager(HttpClient client)
|
||||
{
|
||||
return new ClientSelectorManager(client, 1){
|
||||
@Override
|
||||
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
|
||||
{
|
||||
TestEndPoint endPoint = new TestEndPoint(channel,selector,key,getScheduler());
|
||||
endPoint.setIdleTimeout(client.getIdleTimeout());
|
||||
return endPoint;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestEndPoint extends SocketChannelEndPoint
|
||||
{
|
||||
public AtomicBoolean congestedFlush = new AtomicBoolean(false);
|
||||
|
||||
public TestEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler)
|
||||
{
|
||||
super((SocketChannel)channel,selector,key,scheduler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean flush(ByteBuffer... buffers) throws IOException
|
||||
{
|
||||
boolean flushed = super.flush(buffers);
|
||||
congestedFlush.set(!flushed);
|
||||
return flushed;
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void startClient() throws Exception
|
||||
{
|
||||
HttpClient httpClient = new HttpClient(new TestClientTransportOverHTTP(), null);
|
||||
client = new WebSocketClient(httpClient);
|
||||
client.addBean(httpClient);
|
||||
client.start();
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
public static void startServer() throws Exception
|
||||
{
|
||||
server = new BlockheadServer();
|
||||
server.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void stopClient() throws Exception
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void stopServer() throws Exception
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHalfClose() throws Exception
|
||||
{
|
||||
// Set client timeout
|
||||
final int timeout = 5000;
|
||||
client.setMaxIdleTimeout(timeout);
|
||||
|
||||
// Hook into server connection creation
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
server.addConnectFuture(serverConnFut);
|
||||
|
||||
// Client connects
|
||||
CloseTrackingSocket clientSocket = new CloseTrackingSocket();
|
||||
Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri());
|
||||
|
||||
try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
|
||||
{
|
||||
// client confirms connection via echo
|
||||
confirmConnection(clientSocket, clientConnectFuture, serverConn);
|
||||
|
||||
// client sends close frame (code 1000, normal)
|
||||
final String origCloseReason = "Normal Close";
|
||||
clientSocket.getSession().close(StatusCode.NORMAL, origCloseReason);
|
||||
|
||||
// server receives close frame
|
||||
confirmServerReceivedCloseFrame(serverConn, StatusCode.NORMAL, is(origCloseReason));
|
||||
|
||||
// server sends 2 messages
|
||||
serverConn.write(new TextFrame().setPayload("Hello"));
|
||||
serverConn.write(new TextFrame().setPayload("World"));
|
||||
|
||||
// server sends close frame (code 1000, no reason)
|
||||
CloseInfo sclose = new CloseInfo(StatusCode.NORMAL, "From Server");
|
||||
serverConn.write(sclose.asFrame());
|
||||
|
||||
// Verify received messages
|
||||
String recvMsg = clientSocket.messageQueue.poll(Timeouts.POLL_EVENT, Timeouts.POLL_EVENT_UNIT);
|
||||
assertThat("Received message 1", recvMsg, is("Hello"));
|
||||
recvMsg = clientSocket.messageQueue.poll(Timeouts.POLL_EVENT, Timeouts.POLL_EVENT_UNIT);
|
||||
assertThat("Received message 2", recvMsg, is("World"));
|
||||
|
||||
// Verify that there are no errors
|
||||
assertThat("Error events", clientSocket.error.get(), nullValue());
|
||||
|
||||
// client close event on ws-endpoint
|
||||
clientSocket.assertReceivedCloseEvent(timeout, is(StatusCode.NORMAL), containsString("From Server"));
|
||||
}
|
||||
|
||||
assertThat("Client Open Sessions", client.getOpenSessions(), empty());
|
||||
}
|
||||
|
||||
@Disabled("Need sbordet's help here")
|
||||
@Test
|
||||
public void testNetworkCongestion() throws Exception
|
||||
{
|
||||
// Set client timeout
|
||||
final int timeout = 1000;
|
||||
client.setMaxIdleTimeout(timeout);
|
||||
|
||||
// Hook into server connection creation
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
server.addConnectFuture(serverConnFut);
|
||||
|
||||
// Client connects
|
||||
CloseTrackingSocket clientSocket = new CloseTrackingSocket();
|
||||
Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri());
|
||||
|
||||
try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
|
||||
{
|
||||
// client confirms connection via echo
|
||||
confirmConnection(clientSocket, clientConnectFuture, serverConn);
|
||||
|
||||
// client sends BIG frames (until it cannot write anymore)
|
||||
// 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();
|
||||
assertThat("EndPoint is testable", endp, instanceOf(TestEndPoint.class));
|
||||
TestEndPoint testendp = (TestEndPoint) endp;
|
||||
|
||||
char msg[] = new char[10240];
|
||||
int writeCount = 0;
|
||||
long writeSize = 0;
|
||||
int i = 0;
|
||||
while (!testendp.congestedFlush.get())
|
||||
{
|
||||
int z = i - ((i / 26) * 26);
|
||||
char c = (char) ('a' + z);
|
||||
Arrays.fill(msg, c);
|
||||
clientSocket.getRemote().sendStringByFuture(String.valueOf(msg));
|
||||
writeCount++;
|
||||
writeSize += msg.length;
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProtocolException() throws Exception
|
||||
{
|
||||
// Set client timeout
|
||||
final int timeout = 1000;
|
||||
client.setMaxIdleTimeout(timeout);
|
||||
|
||||
// Hook into server connection creation
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
server.addConnectFuture(serverConnFut);
|
||||
|
||||
// Client connects
|
||||
CloseTrackingSocket clientSocket = new CloseTrackingSocket();
|
||||
Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri());
|
||||
|
||||
try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
|
||||
{
|
||||
// client confirms connection via echo
|
||||
confirmConnection(clientSocket, clientConnectFuture, serverConn);
|
||||
|
||||
// client should not have received close message (yet)
|
||||
clientSocket.assertNoCloseEvent();
|
||||
|
||||
// server sends bad close frame (too big of a reason message)
|
||||
byte msg[] = new byte[400];
|
||||
Arrays.fill(msg, (byte) 'x');
|
||||
ByteBuffer bad = ByteBuffer.allocate(500);
|
||||
RawFrameBuilder.putOpFin(bad, OpCode.CLOSE, true);
|
||||
RawFrameBuilder.putLength(bad, msg.length + 2, false);
|
||||
bad.putShort((short) StatusCode.NORMAL);
|
||||
bad.put(msg);
|
||||
BufferUtil.flipToFlush(bad, 0);
|
||||
|
||||
try (StacklessLogging ignore = new StacklessLogging(Parser.class))
|
||||
{
|
||||
serverConn.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"));
|
||||
|
||||
// client parse invalid frame, notifies server of close (protocol error)
|
||||
confirmServerReceivedCloseFrame(serverConn, StatusCode.PROTOCOL, allOf(containsString("Invalid control frame"), containsString("length")));
|
||||
}
|
||||
}
|
||||
|
||||
// client triggers close event on client ws-endpoint
|
||||
clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.PROTOCOL),allOf(containsString("Invalid control frame"),containsString("length")));
|
||||
assertThat("Client Open Sessions", client.getOpenSessions(), empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadEOF() throws Exception
|
||||
{
|
||||
// Set client timeout
|
||||
final int timeout = 1000;
|
||||
client.setMaxIdleTimeout(timeout);
|
||||
|
||||
// Hook into server connection creation
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
server.addConnectFuture(serverConnFut);
|
||||
|
||||
// Client connects
|
||||
CloseTrackingSocket clientSocket = new CloseTrackingSocket();
|
||||
Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri());
|
||||
|
||||
try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
|
||||
{
|
||||
// client confirms connection via echo
|
||||
confirmConnection(clientSocket, clientConnectFuture, serverConn);
|
||||
|
||||
// client sends close frame
|
||||
final String origCloseReason = "Normal Close";
|
||||
clientSocket.getSession().close(StatusCode.NORMAL, origCloseReason);
|
||||
|
||||
// server receives close frame
|
||||
confirmServerReceivedCloseFrame(serverConn, StatusCode.NORMAL, is(origCloseReason));
|
||||
|
||||
// client should not have received close message (yet)
|
||||
clientSocket.assertNoCloseEvent();
|
||||
|
||||
// server shuts down connection (no frame reply)
|
||||
serverConn.abort();
|
||||
|
||||
// client reads -1 (EOF)
|
||||
// client triggers close event on client ws-endpoint
|
||||
clientSocket.assertReceivedCloseEvent(timeout, is(StatusCode.ABNORMAL),
|
||||
anyOf(
|
||||
containsString("EOF"),
|
||||
containsString("Disconnected")
|
||||
));
|
||||
}
|
||||
assertThat("Client Open Sessions", client.getOpenSessions(), empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerNoCloseHandshake() throws Exception
|
||||
{
|
||||
// Set client timeout
|
||||
final int timeout = 1000;
|
||||
client.setMaxIdleTimeout(timeout);
|
||||
|
||||
// Hook into server connection creation
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
server.addConnectFuture(serverConnFut);
|
||||
|
||||
// Client connects
|
||||
CloseTrackingSocket clientSocket = new CloseTrackingSocket();
|
||||
Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri());
|
||||
|
||||
try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
|
||||
{
|
||||
// client confirms connection via echo
|
||||
confirmConnection(clientSocket, clientConnectFuture, serverConn);
|
||||
|
||||
// client sends close frame
|
||||
final String origCloseReason = "Normal Close";
|
||||
clientSocket.getSession().close(StatusCode.NORMAL, origCloseReason);
|
||||
|
||||
// server receives close frame
|
||||
confirmServerReceivedCloseFrame(serverConn, StatusCode.NORMAL, is(origCloseReason));
|
||||
|
||||
// client should not have received close message (yet)
|
||||
clientSocket.assertNoCloseEvent();
|
||||
|
||||
// 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(TimeoutException.class));
|
||||
|
||||
// client close should occur
|
||||
clientSocket.assertReceivedCloseEvent(timeout, is(StatusCode.ABNORMAL),
|
||||
anyOf(
|
||||
containsString("Timeout"),
|
||||
containsString("Disconnected")
|
||||
));
|
||||
}
|
||||
assertThat("Client Open Sessions", client.getOpenSessions(), empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStopLifecycle() throws Exception
|
||||
{
|
||||
// Set client timeout
|
||||
final int timeout = 1000;
|
||||
client.setMaxIdleTimeout(timeout);
|
||||
|
||||
int clientCount = 3;
|
||||
List<CloseTrackingSocket> clientSockets = new ArrayList<>();
|
||||
List<CompletableFuture<BlockheadConnection>> serverConnFuts = new ArrayList<>();
|
||||
List<BlockheadConnection> serverConns = new ArrayList<>();
|
||||
|
||||
try
|
||||
{
|
||||
assertTimeoutPreemptively(ofSeconds(5), ()-> {
|
||||
// Open Multiple Clients
|
||||
for (int i = 0; i < clientCount; i++)
|
||||
{
|
||||
// Client Request Upgrade
|
||||
CloseTrackingSocket clientSocket = new CloseTrackingSocket();
|
||||
clientSockets.add(clientSocket);
|
||||
Future<Session> clientConnectFuture = client.connect(clientSocket, server.getWsUri());
|
||||
|
||||
// Server accepts connection
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
serverConnFuts.add(serverConnFut);
|
||||
server.addConnectFuture(serverConnFut);
|
||||
BlockheadConnection serverConn = serverConnFut.get();
|
||||
serverConns.add(serverConn);
|
||||
|
||||
// client confirms connection via echo
|
||||
confirmConnection(clientSocket, clientConnectFuture, serverConn);
|
||||
}
|
||||
|
||||
// client lifecycle stop (the meat of this test)
|
||||
client.stop();
|
||||
|
||||
// clients send close frames (code 1001, shutdown)
|
||||
for (int i = 0; i < clientCount; i++)
|
||||
{
|
||||
// server receives close frame
|
||||
confirmServerReceivedCloseFrame(serverConns.get(i), StatusCode.SHUTDOWN, containsString("Shutdown"));
|
||||
}
|
||||
|
||||
// clients disconnect
|
||||
for (int i = 0; i < clientCount; i++)
|
||||
{
|
||||
clientSockets.get(i).assertReceivedCloseEvent(timeout, is(StatusCode.SHUTDOWN), containsString("Shutdown"));
|
||||
}
|
||||
assertThat("Client Open Sessions", client.getOpenSessions(), empty());
|
||||
|
||||
// clients disconnect
|
||||
for (int i = 0; i < clientCount; i++)
|
||||
{
|
||||
clientSockets.get(i).assertReceivedCloseEvent(timeout, is(StatusCode.SHUTDOWN), containsString("Shutdown"));
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
for(BlockheadConnection serverConn: serverConns)
|
||||
{
|
||||
try
|
||||
{
|
||||
serverConn.close();
|
||||
}
|
||||
catch (Exception ignore)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteException() throws Exception
|
||||
{
|
||||
// Set client timeout
|
||||
final int timeout = 1000;
|
||||
client.setMaxIdleTimeout(timeout);
|
||||
|
||||
// Hook into server connection creation
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
server.addConnectFuture(serverConnFut);
|
||||
|
||||
// Client connects
|
||||
CloseTrackingSocket clientSocket = new CloseTrackingSocket();
|
||||
Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri());
|
||||
|
||||
try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
|
||||
{
|
||||
// client confirms connection via echo
|
||||
confirmConnection(clientSocket, clientConnectFuture, serverConn);
|
||||
|
||||
// setup client endpoint for write failure (test only)
|
||||
EndPoint endp = clientSocket.getEndPoint();
|
||||
endp.shutdownOutput();
|
||||
|
||||
// client enqueue close frame
|
||||
// client write failure
|
||||
final String origCloseReason = "Normal Close";
|
||||
clientSocket.getSession().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"));
|
||||
}
|
||||
assertThat("Client Open Sessions", client.getOpenSessions(), empty());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,463 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 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.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.anyOf;
|
||||
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.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
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 javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||
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.BlockheadConnection;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadServer;
|
||||
import org.eclipse.jetty.websocket.common.test.Timeouts;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Various connect condition testing
|
||||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
public class ClientConnectTest
|
||||
{
|
||||
public ByteBufferPool bufferPool = new MappedByteBufferPool();
|
||||
|
||||
private static BlockheadServer server;
|
||||
private WebSocketClient client;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <E extends Throwable> E assertExpectedError(ExecutionException e, JettyTrackingSocket wsocket, Matcher<Throwable> errorMatcher)
|
||||
{
|
||||
// Validate thrown cause
|
||||
Throwable cause = e.getCause();
|
||||
|
||||
assertThat("ExecutionException.cause",cause,errorMatcher);
|
||||
|
||||
// Validate websocket captured cause
|
||||
assertThat("Error Queue Length",wsocket.errorQueue.size(),greaterThanOrEqualTo(1));
|
||||
Throwable capcause = wsocket.errorQueue.poll();
|
||||
assertThat("Error Queue[0]",capcause,notNullValue());
|
||||
assertThat("Error Queue[0]",capcause,errorMatcher);
|
||||
|
||||
// Validate that websocket didn't see an open event
|
||||
wsocket.assertNotOpened();
|
||||
|
||||
// Return the captured cause
|
||||
return (E)capcause;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void startClient() throws Exception
|
||||
{
|
||||
client = new WebSocketClient();
|
||||
client.setBufferPool(bufferPool);
|
||||
client.setConnectTimeout(Timeouts.CONNECT_UNIT.toMillis(Timeouts.CONNECT));
|
||||
client.start();
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
public static void startServer() throws Exception
|
||||
{
|
||||
server = new BlockheadServer();
|
||||
server.start();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void resetServerHandler()
|
||||
{
|
||||
// for each test, reset the server request handling to default
|
||||
server.resetRequestHandling();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void stopClient() throws Exception
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static 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);
|
||||
|
||||
Session sess = future.get(30,TimeUnit.SECONDS);
|
||||
|
||||
wsocket.waitForConnected();
|
||||
|
||||
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();
|
||||
try
|
||||
{
|
||||
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());
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
httpClient.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpgradeWithAuthorizationHeader() throws Exception
|
||||
{
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
|
||||
// Hook into server connection creation
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
server.addConnectFuture(serverConnFut);
|
||||
|
||||
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", "Basic YWxhZGRpbjpvcGVuc2VzYW1l");
|
||||
Future<Session> future = client.connect(wsocket,wsUri,upgradeRequest);
|
||||
|
||||
try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
|
||||
{
|
||||
HttpFields upgradeRequestHeaders = serverConn.getUpgradeRequestHeaders();
|
||||
|
||||
Session sess = future.get(30, TimeUnit.SECONDS);
|
||||
|
||||
HttpField authHeader = upgradeRequestHeaders.getField(HttpHeader.AUTHORIZATION);
|
||||
assertThat("Server Request Authorization Header", authHeader, is(notNullValue()));
|
||||
assertThat("Server Request Authorization Value", authHeader.getValue(), is("Basic YWxhZGRpbjpvcGVuc2VzYW1l"));
|
||||
assertThat("Connect.UpgradeRequest", wsocket.connectUpgradeRequest, notNullValue());
|
||||
assertThat("Connect.UpgradeResponse", wsocket.connectUpgradeResponse, notNullValue());
|
||||
|
||||
sess.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadHandshake() throws Exception
|
||||
{
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
|
||||
// Force 404 response, no upgrade for this test
|
||||
server.setRequestHandling((req, resp) -> {
|
||||
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
return true;
|
||||
});
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
Future<Session> future = client.connect(wsocket,wsUri);
|
||||
|
||||
// The attempt to get upgrade response future should throw error
|
||||
ExecutionException e = assertThrows(ExecutionException.class,
|
||||
()-> future.get(30,TimeUnit.SECONDS));
|
||||
|
||||
UpgradeException ue = assertExpectedError(e,wsocket,instanceOf(UpgradeException.class));
|
||||
assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
|
||||
assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
|
||||
assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(404));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadHandshake_GetOK() throws Exception
|
||||
{
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
|
||||
// Force 200 response, no response body content, no upgrade for this test
|
||||
server.setRequestHandling((req, resp) -> {
|
||||
resp.setStatus(HttpServletResponse.SC_OK);
|
||||
return true;
|
||||
});
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
Future<Session> future = client.connect(wsocket,wsUri);
|
||||
|
||||
// The attempt to get upgrade response future should throw error
|
||||
ExecutionException e = assertThrows(ExecutionException.class,
|
||||
()-> future.get(30,TimeUnit.SECONDS));
|
||||
|
||||
UpgradeException ue = assertExpectedError(e,wsocket,instanceOf(UpgradeException.class));
|
||||
assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
|
||||
assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
|
||||
assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(200));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadHandshake_GetOK_WithSecWebSocketAccept() throws Exception
|
||||
{
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
|
||||
// Force 200 response, no response body content, incomplete websocket response headers, no actual upgrade for this test
|
||||
server.setRequestHandling((req, resp) -> {
|
||||
String key = req.getHeader(HttpHeader.SEC_WEBSOCKET_KEY.toString());
|
||||
resp.setStatus(HttpServletResponse.SC_OK);
|
||||
resp.setHeader(HttpHeader.SEC_WEBSOCKET_ACCEPT.toString(), AcceptHash.hashKey(key));
|
||||
return true;
|
||||
});
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
Future<Session> future = client.connect(wsocket,wsUri);
|
||||
|
||||
// The attempt to get upgrade response future should throw error
|
||||
ExecutionException e = assertThrows(ExecutionException.class,
|
||||
()-> future.get(30,TimeUnit.SECONDS));
|
||||
|
||||
UpgradeException ue = assertExpectedError(e,wsocket,instanceOf(UpgradeException.class));
|
||||
assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
|
||||
assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
|
||||
assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(200));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadHandshake_SwitchingProtocols_InvalidConnectionHeader() throws Exception
|
||||
{
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
|
||||
// Force 101 response, with invalid Connection header, invalid handshake
|
||||
server.setRequestHandling((req, resp) -> {
|
||||
String key = req.getHeader(HttpHeader.SEC_WEBSOCKET_KEY.toString());
|
||||
resp.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
|
||||
resp.setHeader(HttpHeader.CONNECTION.toString(), "close");
|
||||
resp.setHeader(HttpHeader.SEC_WEBSOCKET_ACCEPT.toString(), AcceptHash.hashKey(key));
|
||||
return true;
|
||||
});
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
Future<Session> future = client.connect(wsocket,wsUri);
|
||||
|
||||
// The attempt to get upgrade response future should throw error
|
||||
ExecutionException e = assertThrows(ExecutionException.class,
|
||||
()-> future.get(30,TimeUnit.SECONDS));
|
||||
|
||||
UpgradeException ue = assertExpectedError(e,wsocket,instanceOf(UpgradeException.class));
|
||||
assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
|
||||
assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
|
||||
assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(101));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadHandshake_SwitchingProtocols_NoConnectionHeader() throws Exception
|
||||
{
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
|
||||
// Force 101 response, with no Connection header, invalid handshake
|
||||
server.setRequestHandling((req, resp) -> {
|
||||
String key = req.getHeader(HttpHeader.SEC_WEBSOCKET_KEY.toString());
|
||||
resp.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
|
||||
// Intentionally leave out Connection header
|
||||
resp.setHeader(HttpHeader.SEC_WEBSOCKET_ACCEPT.toString(), AcceptHash.hashKey(key));
|
||||
return true;
|
||||
});
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
Future<Session> future = client.connect(wsocket,wsUri);
|
||||
|
||||
// The attempt to get upgrade response future should throw error
|
||||
ExecutionException e = assertThrows(ExecutionException.class,
|
||||
()-> future.get(30,TimeUnit.SECONDS));
|
||||
|
||||
UpgradeException ue = assertExpectedError(e,wsocket,instanceOf(UpgradeException.class));
|
||||
assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
|
||||
assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
|
||||
assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(101));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadUpgrade() throws Exception
|
||||
{
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
|
||||
// Force 101 response, with invalid response accept header
|
||||
server.setRequestHandling((req, resp) -> {
|
||||
resp.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
|
||||
resp.setHeader(HttpHeader.SEC_WEBSOCKET_ACCEPT.toString(), "rubbish");
|
||||
return true;
|
||||
});
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
Future<Session> future = client.connect(wsocket,wsUri);
|
||||
|
||||
// The attempt to get upgrade response future should throw error
|
||||
ExecutionException e = assertThrows(ExecutionException.class,
|
||||
()-> future.get(30,TimeUnit.SECONDS));
|
||||
|
||||
UpgradeException ue = assertExpectedError(e,wsocket,instanceOf(UpgradeException.class));
|
||||
assertThat("UpgradeException.requestURI",ue.getRequestURI(),notNullValue());
|
||||
assertThat("UpgradeException.requestURI",ue.getRequestURI().toASCIIString(),is(wsUri.toASCIIString()));
|
||||
assertThat("UpgradeException.responseStatusCode",ue.getResponseStatusCode(),is(101));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectionNotAccepted() throws Exception
|
||||
{
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
|
||||
try(ServerSocket serverSocket = new ServerSocket())
|
||||
{
|
||||
InetAddress addr = InetAddress.getByName("localhost");
|
||||
InetSocketAddress endpoint = new InetSocketAddress(addr, 0);
|
||||
serverSocket.bind(endpoint, 1);
|
||||
int port = serverSocket.getLocalPort();
|
||||
URI wsUri = URI.create(String.format("ws://%s:%d/", addr.getHostAddress(), port));
|
||||
Future<Session> future = client.connect(wsocket, wsUri);
|
||||
|
||||
// Intentionally not accept incoming socket.
|
||||
// serverSocket.accept();
|
||||
|
||||
try
|
||||
{
|
||||
future.get(3, TimeUnit.SECONDS);
|
||||
fail("Should have Timed Out");
|
||||
}
|
||||
catch (ExecutionException e)
|
||||
{
|
||||
assertExpectedError(e, wsocket, instanceOf(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);
|
||||
fail("Expected ExecutionException -> ConnectException");
|
||||
}
|
||||
catch (ConnectException e)
|
||||
{
|
||||
Throwable t = wsocket.errorQueue.remove();
|
||||
assertThat("Error Queue[0]",t,instanceOf(ConnectException.class));
|
||||
wsocket.assertNotOpened();
|
||||
}
|
||||
catch (ExecutionException e)
|
||||
{
|
||||
assertExpectedError(e, wsocket,
|
||||
anyOf(
|
||||
instanceOf(UpgradeException.class),
|
||||
instanceOf(SocketTimeoutException.class),
|
||||
instanceOf(ConnectException.class)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectionTimeout_Concurrent() throws Exception
|
||||
{
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
|
||||
try(ServerSocket serverSocket = new ServerSocket())
|
||||
{
|
||||
InetAddress addr = InetAddress.getByName("localhost");
|
||||
InetSocketAddress endpoint = new InetSocketAddress(addr, 0);
|
||||
serverSocket.bind(endpoint, 1);
|
||||
int port = serverSocket.getLocalPort();
|
||||
URI wsUri = URI.create(String.format("ws://%s:%d/", addr.getHostAddress(), port));
|
||||
Future<Session> future = client.connect(wsocket, wsUri);
|
||||
|
||||
// Accept the connection, but do nothing on it (no response, no upgrade, etc)
|
||||
serverSocket.accept();
|
||||
|
||||
// The attempt to get upgrade response future should throw error
|
||||
Exception e = assertThrows(Exception.class,
|
||||
()-> future.get(3, TimeUnit.SECONDS));
|
||||
|
||||
if (e instanceof ExecutionException)
|
||||
{
|
||||
assertExpectedError((ExecutionException) e, wsocket, anyOf(
|
||||
instanceOf(ConnectException.class),
|
||||
instanceOf(UpgradeException.class)
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
assertThat("Should have been a TimeoutException", e, instanceOf(TimeoutException.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 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.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.eclipse.jetty.websocket.client.io.ConnectionManager;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ConnectionManagerTest
|
||||
{
|
||||
private void assertToSocketAddress(String uriStr, String expectedHost, int expectedPort) throws URISyntaxException
|
||||
{
|
||||
URI uri = new URI(uriStr);
|
||||
|
||||
InetSocketAddress addr = ConnectionManager.toSocketAddress(uri);
|
||||
assertThat("URI (" + uri + ").host",addr.getHostName(),is(expectedHost));
|
||||
assertThat("URI (" + uri + ").port",addr.getPort(),is(expectedPort));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToSocketAddress_AltWsPort() throws Exception
|
||||
{
|
||||
assertToSocketAddress("ws://localhost:8099","localhost",8099);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToSocketAddress_AltWssPort() throws Exception
|
||||
{
|
||||
assertToSocketAddress("wss://localhost","localhost",443);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToSocketAddress_DefaultWsPort() throws Exception
|
||||
{
|
||||
assertToSocketAddress("ws://localhost","localhost",80);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToSocketAddress_DefaultWsPort_Path() throws Exception
|
||||
{
|
||||
assertToSocketAddress("ws://localhost/sockets/chat","localhost",80);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToSocketAddress_DefaultWssPort() throws Exception
|
||||
{
|
||||
assertToSocketAddress("wss://localhost:9443","localhost",9443);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToSocketAddress_DefaultWssPort_Path() throws Exception
|
||||
{
|
||||
assertToSocketAddress("wss://localhost/sockets/chat","localhost",443);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 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.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.net.CookieManager;
|
||||
import java.net.HttpCookie;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
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.common.CloseInfo;
|
||||
import org.eclipse.jetty.websocket.common.frames.TextFrame;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadConnection;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadServer;
|
||||
import org.eclipse.jetty.websocket.common.test.Timeouts;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class CookieTest
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(CookieTest.class);
|
||||
|
||||
public static class CookieTrackingSocket extends WebSocketAdapter
|
||||
{
|
||||
public LinkedBlockingQueue<String> messageQueue = new LinkedBlockingQueue<>();
|
||||
public LinkedBlockingQueue<Throwable> errorQueue = new LinkedBlockingQueue<>();
|
||||
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(openLatch.await(duration,unit), "Open Latch");
|
||||
}
|
||||
}
|
||||
|
||||
private static BlockheadServer server;
|
||||
private WebSocketClient client;
|
||||
|
||||
@BeforeEach
|
||||
public void startClient() throws Exception
|
||||
{
|
||||
client = new WebSocketClient();
|
||||
client.start();
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
public static void startServer() throws Exception
|
||||
{
|
||||
server = new BlockheadServer();
|
||||
server.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void stopClient() throws Exception
|
||||
{
|
||||
if (client.isRunning())
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static 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);
|
||||
|
||||
// Hook into server connection creation
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
server.addConnectFuture(serverConnFut);
|
||||
|
||||
// Client connects
|
||||
CookieTrackingSocket clientSocket = new CookieTrackingSocket();
|
||||
Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri());
|
||||
|
||||
try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
|
||||
{
|
||||
// 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));
|
||||
|
||||
// Hook into server connection creation
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
server.addConnectFuture(serverConnFut);
|
||||
|
||||
// Client connects
|
||||
CookieTrackingSocket clientSocket = new CookieTrackingSocket();
|
||||
Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri(),request);
|
||||
|
||||
try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
|
||||
{
|
||||
// client confirms upgrade and receipt of frame
|
||||
String serverCookies = confirmClientUpgradeAndCookies(clientSocket, clientConnectFuture, serverConn);
|
||||
|
||||
assertThat("Cookies seen at server side", serverCookies, containsString("hello=world"));
|
||||
}
|
||||
}
|
||||
|
||||
private String confirmClientUpgradeAndCookies(CookieTrackingSocket clientSocket, Future<Session> clientConnectFuture, BlockheadConnection serverConn)
|
||||
throws Exception
|
||||
{
|
||||
// Server side upgrade information
|
||||
HttpFields upgradeRequestHeaders = serverConn.getUpgradeRequestHeaders();
|
||||
HttpField cookieField = upgradeRequestHeaders.getField(HttpHeader.COOKIE);
|
||||
|
||||
// Server responds with cookies it knows about
|
||||
TextFrame serverCookieFrame = new TextFrame();
|
||||
serverCookieFrame.setFin(true);
|
||||
serverCookieFrame.setPayload(cookieField.getValue());
|
||||
serverConn.write(serverCookieFrame);
|
||||
|
||||
// Confirm client connect on future
|
||||
clientConnectFuture.get(10,TimeUnit.SECONDS);
|
||||
clientSocket.awaitOpen(2,TimeUnit.SECONDS);
|
||||
|
||||
// Wait for client receipt of cookie frame via client websocket
|
||||
String cookies = clientSocket.messageQueue.poll(Timeouts.POLL_EVENT, Timeouts.POLL_EVENT_UNIT);
|
||||
LOG.debug("Cookies seen at server: {}",cookies);
|
||||
|
||||
// Server closes connection
|
||||
serverConn.write(new CloseInfo(StatusCode.NORMAL).asFrame());
|
||||
|
||||
return cookies;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 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.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Exchanger;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
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.api.UpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
||||
import org.eclipse.jetty.websocket.common.test.Timeouts;
|
||||
|
||||
|
||||
/**
|
||||
* Testing Socket used on client side WebSocket testing.
|
||||
*/
|
||||
public class JettyTrackingSocket extends WebSocketAdapter
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(JettyTrackingSocket.class);
|
||||
|
||||
public int closeCode = -1;
|
||||
public Exchanger<String> messageExchanger;
|
||||
public UpgradeRequest connectUpgradeRequest;
|
||||
public UpgradeResponse connectUpgradeResponse;
|
||||
public StringBuilder closeMessage = new StringBuilder();
|
||||
public CountDownLatch openLatch = new CountDownLatch(1);
|
||||
public CountDownLatch closeLatch = new CountDownLatch(1);
|
||||
public CountDownLatch dataLatch = new CountDownLatch(1);
|
||||
public LinkedBlockingQueue<String> messageQueue = new LinkedBlockingQueue<>();
|
||||
public LinkedBlockingQueue<Throwable> errorQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
public void assertClose(int expectedStatusCode, String expectedReason) throws InterruptedException
|
||||
{
|
||||
assertCloseCode(expectedStatusCode);
|
||||
assertCloseReason(expectedReason);
|
||||
}
|
||||
|
||||
public void assertCloseCode(int expectedCode) throws InterruptedException
|
||||
{
|
||||
assertThat("Was Closed",closeLatch.await(50,TimeUnit.MILLISECONDS),is(true));
|
||||
assertThat("Close Code / Received [" + closeMessage + "]",closeCode,is(expectedCode));
|
||||
}
|
||||
|
||||
private void assertCloseReason(String expectedReason)
|
||||
{
|
||||
assertThat("Close Reason",closeMessage.toString(),is(expectedReason));
|
||||
}
|
||||
|
||||
public void assertIsOpen() throws InterruptedException
|
||||
{
|
||||
assertWasOpened();
|
||||
assertNotClosed();
|
||||
}
|
||||
|
||||
public void assertNotClosed()
|
||||
{
|
||||
LOG.debug("assertNotClosed() - {}", closeLatch.getCount());
|
||||
assertThat("Closed Latch",closeLatch.getCount(),greaterThanOrEqualTo(1L));
|
||||
}
|
||||
|
||||
public void assertNotOpened()
|
||||
{
|
||||
LOG.debug("assertNotOpened() - {}", openLatch.getCount());
|
||||
assertThat("Open Latch",openLatch.getCount(),greaterThanOrEqualTo(1L));
|
||||
}
|
||||
|
||||
public void assertWasOpened() throws InterruptedException
|
||||
{
|
||||
LOG.debug("assertWasOpened() - {}", openLatch.getCount());
|
||||
assertThat("Was Opened",openLatch.await(30,TimeUnit.SECONDS),is(true));
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
messageQueue.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketBinary(byte[] payload, int offset, int len)
|
||||
{
|
||||
LOG.debug("onWebSocketBinary()");
|
||||
dataLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketClose(int statusCode, String reason)
|
||||
{
|
||||
LOG.debug("onWebSocketClose({},{})",statusCode,reason);
|
||||
super.onWebSocketClose(statusCode,reason);
|
||||
closeCode = statusCode;
|
||||
closeMessage.append(reason);
|
||||
closeLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketConnect(Session session)
|
||||
{
|
||||
super.onWebSocketConnect(session);
|
||||
assertThat("Session", session, notNullValue());
|
||||
connectUpgradeRequest = session.getUpgradeRequest();
|
||||
connectUpgradeResponse = session.getUpgradeResponse();
|
||||
openLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketError(Throwable cause)
|
||||
{
|
||||
LOG.debug("onWebSocketError",cause);
|
||||
assertThat("Error capture",errorQueue.offer(cause),is(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketText(String message)
|
||||
{
|
||||
LOG.debug("onWebSocketText({})",message);
|
||||
messageQueue.offer(message);
|
||||
dataLatch.countDown();
|
||||
|
||||
if (messageExchanger != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
messageExchanger.exchange(message);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
LOG.debug(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForClose(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
|
||||
{
|
||||
assertThat("Client Socket Closed",closeLatch.await(timeoutDuration,timeoutUnit),is(true));
|
||||
}
|
||||
|
||||
public void waitForConnected() throws InterruptedException
|
||||
{
|
||||
assertThat("Client Socket Connected",openLatch.await(Timeouts.CONNECT,Timeouts.CONNECT_UNIT),is(true));
|
||||
}
|
||||
|
||||
public void waitForMessage(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
|
||||
{
|
||||
LOG.debug("Waiting for message");
|
||||
assertThat("Message Received",dataLatch.await(timeoutDuration,timeoutUnit),is(true));
|
||||
}
|
||||
|
||||
public void close()
|
||||
{
|
||||
getSession().close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 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.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
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.WebSocketFrame;
|
||||
import org.eclipse.jetty.websocket.common.WebSocketSession;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadConnection;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadServer;
|
||||
import org.eclipse.jetty.websocket.common.test.Timeouts;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class SessionTest
|
||||
{
|
||||
private static BlockheadServer server;
|
||||
|
||||
@BeforeAll
|
||||
public static void startServer() throws Exception
|
||||
{
|
||||
server = new BlockheadServer();
|
||||
server.start();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void stopServer() throws Exception
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // TODO fix frequent failure
|
||||
public void testBasicEcho_FromClient() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.start();
|
||||
try
|
||||
{
|
||||
JettyTrackingSocket cliSock = new JettyTrackingSocket();
|
||||
|
||||
// Hook into server connection creation
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
server.addConnectFuture(serverConnFut);
|
||||
|
||||
client.getPolicy().setIdleTimeout(10000);
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
request.setSubProtocols("echo");
|
||||
Future<Session> future = client.connect(cliSock,wsUri,request);
|
||||
|
||||
try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
|
||||
{
|
||||
// Setup echo of frames on server side
|
||||
serverConn.setIncomingFrameConsumer((frame)->{
|
||||
WebSocketFrame copy = WebSocketFrame.copy(frame);
|
||||
serverConn.write(copy);
|
||||
});
|
||||
|
||||
Session sess = future.get(30000, TimeUnit.MILLISECONDS);
|
||||
assertThat("Session", sess, notNullValue());
|
||||
assertThat("Session.open", sess.isOpen(), is(true));
|
||||
assertThat("Session.upgradeRequest", sess.getUpgradeRequest(), notNullValue());
|
||||
assertThat("Session.upgradeResponse", sess.getUpgradeResponse(), notNullValue());
|
||||
|
||||
cliSock.assertWasOpened();
|
||||
cliSock.assertNotClosed();
|
||||
|
||||
Collection<WebSocketSession> sessions = client.getBeans(WebSocketSession.class);
|
||||
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();
|
||||
}
|
||||
|
||||
// wait for response from server
|
||||
cliSock.waitForMessage(30000, TimeUnit.MILLISECONDS);
|
||||
|
||||
Set<WebSocketSession> open = client.getOpenSessions();
|
||||
assertThat("(Before Close) Open Sessions.size", open.size(), is(1));
|
||||
|
||||
String received = cliSock.messageQueue.poll(Timeouts.POLL_EVENT, Timeouts.POLL_EVENT_UNIT);
|
||||
assertThat("Message", received, containsString("Hello World!"));
|
||||
|
||||
cliSock.close();
|
||||
}
|
||||
|
||||
cliSock.waitForClose(30000, TimeUnit.MILLISECONDS);
|
||||
Set<WebSocketSession> open = client.getOpenSessions();
|
||||
|
||||
// TODO this sometimes fails!
|
||||
assertThat("(After Close) Open Sessions.size", open.size(), is(0));
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 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.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.StatusCode;
|
||||
import org.eclipse.jetty.websocket.common.CloseInfo;
|
||||
import org.eclipse.jetty.websocket.common.OpCode;
|
||||
import org.eclipse.jetty.websocket.common.WebSocketFrame;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadConnection;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadServer;
|
||||
import org.eclipse.jetty.websocket.common.test.Timeouts;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class SlowClientTest
|
||||
{
|
||||
private static BlockheadServer server;
|
||||
private WebSocketClient client;
|
||||
|
||||
@BeforeEach
|
||||
public void startClient() throws Exception
|
||||
{
|
||||
client = new WebSocketClient();
|
||||
client.getPolicy().setIdleTimeout(60000);
|
||||
client.start();
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
public static void startServer() throws Exception
|
||||
{
|
||||
server = new BlockheadServer();
|
||||
server.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void stopClient() throws Exception
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void stopServer() throws Exception
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientSlowToSend() throws Exception
|
||||
{
|
||||
JettyTrackingSocket tsocket = new JettyTrackingSocket();
|
||||
client.getPolicy().setIdleTimeout(60000);
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
Future<Session> future = client.connect(tsocket, wsUri);
|
||||
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
server.addConnectFuture(serverConnFut);
|
||||
|
||||
// Confirm connected
|
||||
future.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT);
|
||||
tsocket.waitForConnected();
|
||||
|
||||
int messageCount = 10;
|
||||
|
||||
try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
|
||||
{
|
||||
// Have client write slowly.
|
||||
ClientWriteThread writer = new ClientWriteThread(tsocket.getSession());
|
||||
writer.setMessageCount(messageCount);
|
||||
writer.setMessage("Hello");
|
||||
writer.setSlowness(10);
|
||||
writer.start();
|
||||
writer.join();
|
||||
|
||||
LinkedBlockingQueue<WebSocketFrame> serverFrames = serverConn.getFrameQueue();
|
||||
|
||||
for (int i = 0; i < messageCount; i++)
|
||||
{
|
||||
WebSocketFrame serverFrame = serverFrames.poll(Timeouts.POLL_EVENT, Timeouts.POLL_EVENT_UNIT);
|
||||
String prefix = "Server frame[" + i + "]";
|
||||
assertThat(prefix + ".opcode", serverFrame.getOpCode(), is(OpCode.TEXT));
|
||||
assertThat(prefix + ".payload", serverFrame.getPayloadAsUTF8(), is("Hello/" + i + "/"));
|
||||
}
|
||||
|
||||
// Close
|
||||
tsocket.getSession().close(StatusCode.NORMAL, "Done");
|
||||
|
||||
// confirm close received on server
|
||||
WebSocketFrame serverFrame = serverFrames.poll(Timeouts.POLL_EVENT, Timeouts.POLL_EVENT_UNIT);
|
||||
assertThat("close frame", serverFrame.getOpCode(), is(OpCode.CLOSE));
|
||||
CloseInfo closeInfo = new CloseInfo(serverFrame);
|
||||
assertThat("close info", closeInfo.getStatusCode(), is(StatusCode.NORMAL));
|
||||
WebSocketFrame respClose = WebSocketFrame.copy(serverFrame);
|
||||
respClose.setMask(null); // remove client mask (if present)
|
||||
serverConn.write(respClose);
|
||||
|
||||
// Verify server response
|
||||
assertTrue(tsocket.closeLatch.await(3, TimeUnit.MINUTES), "Client Socket Closed");
|
||||
tsocket.assertCloseCode(StatusCode.NORMAL);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 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.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.client.masks.ZeroMasker;
|
||||
import org.eclipse.jetty.websocket.common.OpCode;
|
||||
import org.eclipse.jetty.websocket.common.WebSocketFrame;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadConnection;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadServer;
|
||||
import org.eclipse.jetty.websocket.common.test.Timeouts;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class SlowServerTest
|
||||
{
|
||||
private BlockheadServer server;
|
||||
private WebSocketClient client;
|
||||
|
||||
@BeforeEach
|
||||
public void startClient() throws Exception
|
||||
{
|
||||
client = new WebSocketClient();
|
||||
client.setMaxIdleTimeout(60000);
|
||||
client.start();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void startServer() throws Exception
|
||||
{
|
||||
server = new BlockheadServer();
|
||||
server.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void stopClient() throws Exception
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void stopServer() throws Exception
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerSlowToRead() throws Exception
|
||||
{
|
||||
JettyTrackingSocket tsocket = new JettyTrackingSocket();
|
||||
client.setMasker(new ZeroMasker());
|
||||
client.setMaxIdleTimeout(60000);
|
||||
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
server.addConnectFuture(serverConnFut);
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
Future<Session> future = client.connect(tsocket,wsUri);
|
||||
|
||||
try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
|
||||
{
|
||||
// slow down reads
|
||||
serverConn.setIncomingFrameConsumer((frame)-> {
|
||||
try
|
||||
{
|
||||
TimeUnit.MILLISECONDS.sleep(100);
|
||||
}
|
||||
catch (InterruptedException ignore)
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
// Confirm connected
|
||||
future.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT);
|
||||
tsocket.waitForConnected();
|
||||
|
||||
int messageCount = 10;
|
||||
|
||||
// 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
|
||||
LinkedBlockingQueue<WebSocketFrame> serverFrames = serverConn.getFrameQueue();
|
||||
for(int i=0; i< messageCount; i++)
|
||||
{
|
||||
WebSocketFrame serverFrame = serverFrames.poll(Timeouts.POLL_EVENT, Timeouts.POLL_EVENT_UNIT);
|
||||
String prefix = "Server Frame[" + i + "]";
|
||||
assertThat(prefix, serverFrame, is(notNullValue()));
|
||||
assertThat(prefix + ".opCode", serverFrame.getOpCode(), is(OpCode.TEXT));
|
||||
assertThat(prefix + ".payload", serverFrame.getPayloadAsUTF8(), is("Hello/" + i + "/"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerSlowToSend() throws Exception
|
||||
{
|
||||
JettyTrackingSocket clientSocket = new JettyTrackingSocket();
|
||||
client.setMaxIdleTimeout(60000);
|
||||
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
server.addConnectFuture(serverConnFut);
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
Future<Session> clientConnectFuture = client.connect(clientSocket,wsUri);
|
||||
|
||||
try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
|
||||
{
|
||||
// Confirm connected
|
||||
clientConnectFuture.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT);
|
||||
clientSocket.waitForConnected();
|
||||
|
||||
// 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
|
||||
assertThat("Message Receive Count", clientSocket.messageQueue.size(), is(messageCount));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,328 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 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.greaterThan;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.net.InetAddress;
|
||||
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.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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.WebSocketFrame;
|
||||
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.BlockheadConnection;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadServer;
|
||||
import org.eclipse.jetty.websocket.common.test.Timeouts;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class WebSocketClientTest
|
||||
{
|
||||
private static BlockheadServer server;
|
||||
private WebSocketClient client;
|
||||
|
||||
@BeforeEach
|
||||
public void startClient() throws Exception
|
||||
{
|
||||
client = new WebSocketClient();
|
||||
client.start();
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
public static void startServer() throws Exception
|
||||
{
|
||||
server = new BlockheadServer();
|
||||
server.getPolicy().setMaxTextMessageSize(200 * 1024);
|
||||
server.getPolicy().setMaxBinaryMessageSize(200 * 1024);
|
||||
server.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void stopClient() throws Exception
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void stopServer() throws Exception
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
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");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, ()-> {
|
||||
// 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);
|
||||
|
||||
// Hook into server connection creation
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
server.addConnectFuture(serverConnFut);
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
request.setSubProtocols("echo");
|
||||
Future<Session> future = client.connect(cliSock,wsUri,request);
|
||||
|
||||
try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
|
||||
{
|
||||
// Setup echo of frames on server side
|
||||
serverConn.setIncomingFrameConsumer((frame)->{
|
||||
WebSocketFrame copy = WebSocketFrame.copy(frame);
|
||||
copy.setMask(null); // strip client mask (if present)
|
||||
serverConn.write(copy);
|
||||
});
|
||||
|
||||
Session sess = future.get(30,TimeUnit.SECONDS);
|
||||
assertThat("Session",sess,notNullValue());
|
||||
assertThat("Session.open",sess.isOpen(),is(true));
|
||||
assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
|
||||
assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
|
||||
|
||||
cliSock.assertWasOpened();
|
||||
cliSock.assertNotClosed();
|
||||
|
||||
Collection<WebSocketSession> sessions = client.getOpenSessions();
|
||||
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();
|
||||
|
||||
// wait for response from server
|
||||
String received = cliSock.messageQueue.poll(Timeouts.POLL_EVENT, Timeouts.POLL_EVENT_UNIT);
|
||||
assertThat("Message", received, containsString("Hello World"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicEcho_UsingCallback() throws Exception
|
||||
{
|
||||
client.setMaxIdleTimeout(160000);
|
||||
JettyTrackingSocket cliSock = new JettyTrackingSocket();
|
||||
|
||||
// Hook into server connection creation
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
server.addConnectFuture(serverConnFut);
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
request.setSubProtocols("echo");
|
||||
Future<Session> future = client.connect(cliSock,wsUri,request);
|
||||
|
||||
try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
|
||||
{
|
||||
Session sess = future.get(30, TimeUnit.SECONDS);
|
||||
assertThat("Session", sess, notNullValue());
|
||||
assertThat("Session.open", sess.isOpen(), is(true));
|
||||
assertThat("Session.upgradeRequest", sess.getUpgradeRequest(), notNullValue());
|
||||
assertThat("Session.upgradeResponse", sess.getUpgradeResponse(), notNullValue());
|
||||
|
||||
cliSock.assertWasOpened();
|
||||
cliSock.assertNotClosed();
|
||||
|
||||
Collection<WebSocketSession> sessions = client.getBeans(WebSocketSession.class);
|
||||
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
|
||||
{
|
||||
// Hook into server connection creation
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
server.addConnectFuture(serverConnFut);
|
||||
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
Future<Session> future = client.connect(wsocket,server.getWsUri());
|
||||
|
||||
try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
|
||||
{
|
||||
// Validate connect
|
||||
Session sess = future.get(30, TimeUnit.SECONDS);
|
||||
assertThat("Session", sess, notNullValue());
|
||||
assertThat("Session.open", sess.isOpen(), is(true));
|
||||
assertThat("Session.upgradeRequest", sess.getUpgradeRequest(), notNullValue());
|
||||
assertThat("Session.upgradeResponse", sess.getUpgradeResponse(), notNullValue());
|
||||
|
||||
// Have server send initial message
|
||||
serverConn.write(new TextFrame().setPayload("Hello World"));
|
||||
|
||||
// Verify connect
|
||||
future.get(30, TimeUnit.SECONDS);
|
||||
wsocket.assertWasOpened();
|
||||
|
||||
String received = wsocket.messageQueue.poll(Timeouts.POLL_EVENT, Timeouts.POLL_EVENT_UNIT);
|
||||
assertThat("Message", received, containsString("Hello World"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalRemoteAddress() throws Exception
|
||||
{
|
||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
Future<Session> future = client.connect(wsocket,wsUri);
|
||||
|
||||
future.get(30,TimeUnit.SECONDS);
|
||||
|
||||
assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
|
||||
|
||||
InetSocketAddress local = wsocket.getSession().getLocalAddress();
|
||||
InetSocketAddress remote = wsocket.getSession().getRemoteAddress();
|
||||
|
||||
assertThat("Local Socket Address",local,notNullValue());
|
||||
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
|
||||
assertThat("Local Socket Address / Host",local.getAddress().getHostAddress(),notNullValue());
|
||||
assertThat("Local Socket Address / Port",local.getPort(),greaterThan(0));
|
||||
|
||||
String uriHostAddress = InetAddress.getByName(wsUri.getHost()).getHostAddress();
|
||||
assertThat("Remote Socket Address / Host",remote.getAddress().getHostAddress(),is(uriHostAddress));
|
||||
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
|
||||
{
|
||||
MaxMessageSocket wsocket = new MaxMessageSocket();
|
||||
|
||||
// Hook into server connection creation
|
||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
||||
server.addConnectFuture(serverConnFut);
|
||||
|
||||
URI wsUri = server.getWsUri();
|
||||
Future<Session> future = client.connect(wsocket,wsUri);
|
||||
|
||||
try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
|
||||
{
|
||||
// Setup echo of frames on server side
|
||||
serverConn.setIncomingFrameConsumer((frame)->{
|
||||
WebSocketFrame copy = WebSocketFrame.copy(frame);
|
||||
copy.setMask(null); // strip client mask (if present)
|
||||
serverConn.write(copy);
|
||||
});
|
||||
|
||||
wsocket.awaitConnect(1,TimeUnit.SECONDS);
|
||||
|
||||
Session sess = future.get(30,TimeUnit.SECONDS);
|
||||
assertThat("Session",sess,notNullValue());
|
||||
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);
|
||||
|
||||
// wait for response from server
|
||||
wsocket.waitForMessage(1, TimeUnit.SECONDS);
|
||||
|
||||
wsocket.assertMessage(msg);
|
||||
|
||||
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);
|
||||
|
||||
future.get(30,TimeUnit.SECONDS);
|
||||
|
||||
assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
|
||||
|
||||
Session session = wsocket.getSession();
|
||||
UpgradeRequest req = session.getUpgradeRequest();
|
||||
assertThat("Upgrade Request",req,notNullValue());
|
||||
|
||||
Map<String, List<String>> parameterMap = req.getParameterMap();
|
||||
assertThat("Parameter Map",parameterMap,notNullValue());
|
||||
|
||||
assertThat("Parameter[snack]",parameterMap.get("snack"),is(Arrays.asList(new String[] { "cashews" })));
|
||||
assertThat("Parameter[amount]",parameterMap.get("amount"),is(Arrays.asList(new String[] { "handful" })));
|
||||
assertThat("Parameter[brand]",parameterMap.get("brand"),is(Arrays.asList(new String[] { "off" })));
|
||||
|
||||
assertThat("Parameter[cost]",parameterMap.get("cost"),nullValue());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue