Issue #2767 - WebSocket Policy on JSR356 ClientContainer not represented correctly

+ Added Client unit tests for large messages
+ Re-enabled Server unit tests for large messages
+ Added more Server unit tests for large messages
+ In case of JSR356 Server with policy (and Behavior of SERVER)
  is controlling javax.websocker.server.ServerContainer
  and a user chooses to use that ServerContainer to
  connect to a remote websocket endpoint (using ServerContainer
  as a client), then the policy is delegated down to the
  Client Container with a different behavior (only).

Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
This commit is contained in:
Joakim Erdfelt 2018-08-02 11:38:09 -05:00
parent 8eb11a56c8
commit 9a4b780781
18 changed files with 1053 additions and 90 deletions

View File

@ -406,7 +406,7 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont
@Override @Override
public WebSocketPolicy getPolicy() public WebSocketPolicy getPolicy()
{ {
return scopeDelegate.getPolicy(); return client.getPolicy();
} }
@Override @Override
@ -482,10 +482,11 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont
@Override @Override
public void setDefaultMaxBinaryMessageBufferSize(int max) public void setDefaultMaxBinaryMessageBufferSize(int max)
{ {
// overall message limit (used in non-streaming)
client.getPolicy().setMaxBinaryMessageSize(max);
// incoming streaming buffer size // incoming streaming buffer size
client.setMaxBinaryMessageBufferSize(max); client.setMaxBinaryMessageBufferSize(max);
// bump overall message limit (used in non-streaming)
client.getPolicy().setMaxBinaryMessageSize(max);
} }
@Override @Override
@ -497,9 +498,10 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont
@Override @Override
public void setDefaultMaxTextMessageBufferSize(int max) public void setDefaultMaxTextMessageBufferSize(int max)
{ {
// overall message limit (used in non-streaming)
client.getPolicy().setMaxTextMessageSize(max);
// incoming streaming buffer size // incoming streaming buffer size
client.setMaxTextMessageBufferSize(max); client.setMaxTextMessageBufferSize(max);
// bump overall message limit (used in non-streaming)
client.getPolicy().setMaxTextMessageSize(max);
} }
} }

View File

@ -328,7 +328,11 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess
@Override @Override
public void setMaxBinaryMessageBufferSize(int length) public void setMaxBinaryMessageBufferSize(int length)
{ {
// incoming streaming buffer size
getPolicy().setMaxBinaryMessageBufferSize(length); getPolicy().setMaxBinaryMessageBufferSize(length);
// bump overall message limit (used in non-streaming)
getPolicy().setMaxBinaryMessageSize(length);
} }
@Override @Override
@ -341,7 +345,11 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess
@Override @Override
public void setMaxTextMessageBufferSize(int length) public void setMaxTextMessageBufferSize(int length)
{ {
// incoming streaming buffer size
getPolicy().setMaxTextMessageBufferSize(length); getPolicy().setMaxTextMessageBufferSize(length);
// bump overall message limit (used in non-streaming)
getPolicy().setMaxTextMessageSize(length);
} }
public void setPathParameters(Map<String, String> pathParams) public void setPathParameters(Map<String, String> pathParams)

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.BatchMode; import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.RemoteEndpoint; import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketAdapter; import org.eclipse.jetty.websocket.api.WebSocketAdapter;
/** /**
@ -78,4 +79,14 @@ public class JettyEchoSocket extends WebSocketAdapter
throw new RuntimeIOException(x); throw new RuntimeIOException(x);
} }
} }
@Override
public void onWebSocketClose(int statusCode, String reason)
{
super.onWebSocketClose(statusCode, reason);
if (statusCode != StatusCode.NORMAL)
{
LOG.warn("Closed {} {}", statusCode, reason);
}
}
} }

View File

@ -0,0 +1,105 @@
//
// ========================================================================
// 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.jsr356;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import java.net.URI;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import javax.websocket.ContainerProvider;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.util.WSURI;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class LargeMessageTest
{
private static final int LARGER_THAN_DEFAULT_SIZE;
private Server server;
static
{
WebSocketPolicy defaultPolicy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
LARGER_THAN_DEFAULT_SIZE = defaultPolicy.getMaxTextMessageSize() * 3;
}
@Before
public void startServer() throws Exception
{
server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.addConnector(connector);
// This handler is expected to handle echoing of 2MB messages (max)
EchoHandler echoHandler = new EchoHandler();
ContextHandler context = new ContextHandler();
context.setContextPath("/");
context.setHandler(echoHandler);
server.setHandler(context);
server.start();
}
@After
public void stopServer() throws Exception
{
server.stop();
}
@SuppressWarnings("Duplicates")
@Test
public void testLargeEcho_AsEndpointInstance() throws Exception
{
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
server.addBean(container); // allow to shutdown with server
container.setDefaultMaxTextMessageBufferSize(LARGER_THAN_DEFAULT_SIZE);
EndpointEchoClient echoer = new EndpointEchoClient();
Assert.assertThat(echoer,instanceOf(javax.websocket.Endpoint.class));
URI wsUri = WSURI.toWebsocket(server.getURI()).resolve("/");
// Issue connect using instance of class that extends Endpoint
Session session = container.connectToServer(echoer,wsUri);
byte buf[] = new byte[LARGER_THAN_DEFAULT_SIZE];
Arrays.fill(buf, (byte)'x');
String message = new String(buf, UTF_8);
session.getBasicRemote().sendText(message);
String echoed = echoer.textCapture.messages.poll(1, TimeUnit.SECONDS);
assertThat("Echoed", echoed, is(message));
}
}

View File

@ -20,76 +20,79 @@ package org.eclipse.jetty.websocket.jsr356.server;
import java.net.URI; import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.common.test.Timeouts; import org.eclipse.jetty.websocket.common.test.Timeouts;
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.LargeEchoConfiguredSocket; import org.eclipse.jetty.websocket.jsr356.server.samples.echo.LargeEchoAnnotatedSocket;
import org.junit.AfterClass;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Ignore; import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
/** /**
* Test Echo of Large messages, targeting the {@link javax.websocket.Session#setMaxTextMessageBufferSize(int)} functionality * Test Echo of Large messages, targeting the {@code @OnMessage(maxMessage=###)} functionality
*/ */
@Ignore
public class LargeAnnotatedTest public class LargeAnnotatedTest
{ {
@Rule private static WSServer server;
public TestingDir testdir = new TestingDir();
public ByteBufferPool bufferPool = new MappedByteBufferPool(); @BeforeClass
public static void startServer() throws Exception
{
Path testDir = MavenTestingUtils.getTargetTestingPath(LargeOnOpenSessionConfiguredTest.class.getSimpleName());
server = new WSServer(testDir,"app");
server.createWebInf();
server.copyEndpoint(LargeEchoAnnotatedSocket.class);
server.start();
}
@AfterClass
public static void stopServer()
{
server.stop();
}
@SuppressWarnings("Duplicates")
@Test @Test
public void testEcho() throws Exception public void testEcho() throws Exception
{ {
WSServer wsb = new WSServer(testdir,"app"); URI uri = server.getServerBaseURI();
wsb.createWebInf();
wsb.copyEndpoint(LargeEchoConfiguredSocket.class);
WebAppContext webapp = server.createWebAppContext();
server.deployWebapp(webapp);
// wsb.dump();
WebSocketClient client = new WebSocketClient();
try try
{ {
wsb.start(); client.getPolicy().setMaxTextMessageSize(128*1024);
URI uri = wsb.getServerBaseURI(); client.start();
JettyEchoSocket clientEcho = new JettyEchoSocket();
Future<Session> foo = client.connect(clientEcho,uri.resolve("echo/large"));
WebAppContext webapp = wsb.createWebAppContext(); // wait for connect
wsb.deployWebapp(webapp); foo.get(1,TimeUnit.SECONDS);
// wsb.dump(); // The message size should be bigger than default, but smaller than the limit that LargeEchoSocket specifies
byte txt[] = new byte[100 * 1024];
WebSocketClient client = new WebSocketClient(bufferPool); Arrays.fill(txt,(byte)'o');
try String msg = new String(txt,StandardCharsets.UTF_8);
{ clientEcho.sendMessage(msg);
client.getPolicy().setMaxTextMessageSize(128*1024); LinkedBlockingQueue<String> msgs = clientEcho.incomingMessages;
client.start(); Assert.assertEquals("Expected message",msg,msgs.poll(Timeouts.POLL_EVENT, Timeouts.POLL_EVENT_UNIT));
JettyEchoSocket clientEcho = new JettyEchoSocket();
Future<Session> foo = client.connect(clientEcho,uri.resolve("echo/large"));
// wait for connect
foo.get(1,TimeUnit.SECONDS);
// The message size should be bigger than default, but smaller than the limit that LargeEchoSocket specifies
byte txt[] = new byte[100 * 1024];
Arrays.fill(txt,(byte)'o');
String msg = new String(txt,StandardCharsets.UTF_8);
clientEcho.sendMessage(msg);
LinkedBlockingQueue<String> msgs = clientEcho.incomingMessages;
Assert.assertEquals("Expected message",msg,msgs.poll(Timeouts.POLL_EVENT, Timeouts.POLL_EVENT_UNIT));
}
finally
{
client.stop();
}
} }
finally finally
{ {
wsb.stop(); client.stop();
} }
} }
} }

View File

@ -0,0 +1,39 @@
//
// ========================================================================
// 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.jsr356.server;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.eclipse.jetty.websocket.jsr356.JettyClientContainerProvider;
public class LargeClientContainerInitAsServerListener implements ServletContextListener
{
@Override
public void contextInitialized(ServletContextEvent sce)
{
JettyClientContainerProvider.useServerContainer(true);
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
/* ignore */
}
}

View File

@ -0,0 +1,136 @@
//
// ========================================================================
// 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.jsr356.server;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.websocket.ClientEndpoint;
import javax.websocket.ContainerProvider;
import javax.websocket.DeploymentException;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
public class LargeClientContainerServlet extends HttpServlet
{
private static final Logger LOG = Log.getLogger(LargeClientContainerServlet.class);
private static final int LARGER_THAN_DEFAULT_SIZE;
private WebSocketContainer clientContainer;
static
{
WebSocketPolicy defaultPolicy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
LARGER_THAN_DEFAULT_SIZE = defaultPolicy.getMaxTextMessageSize() * 3;
}
private synchronized WebSocketContainer getClientContainer()
{
if(clientContainer == null)
{
clientContainer = ContainerProvider.getWebSocketContainer();
clientContainer.setDefaultMaxTextMessageBufferSize(LARGER_THAN_DEFAULT_SIZE);
}
return clientContainer;
}
@SuppressWarnings("Duplicates")
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
{
int size = LARGER_THAN_DEFAULT_SIZE;
String sizeParam = req.getParameter("size");
if(StringUtil.isNotBlank(sizeParam))
{
size = Integer.parseInt(sizeParam);
}
byte buf[] = new byte[size];
Arrays.fill(buf, (byte) 'x');
String destUrl = req.getParameter("destUrl");
if(StringUtil.isBlank(destUrl))
{
resp.sendError(HttpServletResponse.SC_EXPECTATION_FAILED, "Missing destUrl");
return;
}
WebSocketContainer client = getClientContainer();
URI wsUri = URI.create(destUrl);
try
{
Session session = client.connectToServer(EchoClientSocket.class, wsUri);
EchoClientSocket clientSocket = (EchoClientSocket) session.getUserProperties().get("endpoint");
String message = new String(buf, UTF_8);
session.getBasicRemote().sendText(message);
String echoed = clientSocket.messages.poll(1, TimeUnit.SECONDS);
assertThat("Echoed", echoed, is(message));
resp.setStatus(HttpServletResponse.SC_OK);
resp.getWriter().println("Success");
}
catch (DeploymentException e)
{
LOG.warn("Unable to deploy client socket", e);
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Deployment error");
}
catch (InterruptedException e)
{
LOG.warn("Unable to find echoed message", e);
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Echoed message missing?");
}
}
@ClientEndpoint
public static class EchoClientSocket
{
public BlockingArrayQueue<String> messages = new BlockingArrayQueue<>();
@OnOpen
public void onOpen(Session session)
{
session.getUserProperties().put("endpoint", this);
}
@OnMessage
public void onMessage(String msg)
{
messages.offer(msg);
}
}
}

View File

@ -0,0 +1,165 @@
//
// ========================================================================
// 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.jsr356.server;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.LargeEchoAnnotatedSocket;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
/**
* Test send of large messages from within a Server using a ClientContainer
*/
@RunWith(Parameterized.class)
public class LargeNestedClientContainerTest
{
public static abstract class WSServerConfig
{
private final String description;
public WSServerConfig(String description)
{
this.description = description;
}
public abstract void configure(WSServer server) throws Exception;
@Override
public String toString()
{
return this.description;
}
}
@Parameterized.Parameters(name = "[{index}] {0}")
public static List<WSServerConfig[]> usecases()
{
List<WSServerConfig[]> scenarios = new ArrayList<>();
scenarios.add(new WSServerConfig[]{
new WSServerConfig("Servlet using ContainerProvider.getWebSocketContainer() (default)")
{
@Override
public void configure(WSServer server) throws Exception
{
server.copyWebInf("large-client-container-servlet-web.xml");
server.copyClass(LargeClientContainerServlet.class);
server.copyEndpoint(LargeEchoAnnotatedSocket.class);
}
}
});
scenarios.add(new WSServerConfig[]{
new WSServerConfig("Servlet using ContainerProvider.getWebSocketContainer() (init / server-container)")
{
@Override
public void configure(WSServer server) throws Exception
{
server.copyWebInf("large-client-container-servlet-init-use-server-web.xml");
server.copyClass(LargeClientContainerInitAsServerListener.class);
server.copyClass(LargeClientContainerServlet.class);
server.copyEndpoint(LargeEchoAnnotatedSocket.class);
}
}
});
scenarios.add(new WSServerConfig[]{
new WSServerConfig("Servlet using ServerContainer as ClientContainer")
{
@Override
public void configure(WSServer server) throws Exception
{
server.copyWebInf("large-client-container-servlet-web.xml");
server.copyClass(LargeServerContainerAsClientContainerServlet.class);
server.copyEndpoint(LargeEchoAnnotatedSocket.class);
}
}
});
return scenarios;
}
private static final AtomicInteger appDirIdx = new AtomicInteger(0);
@Parameterized.Parameter
public WSServerConfig serverConfig;
@Test
public void testLargeEcho() throws Exception
{
Path testDir = MavenTestingUtils.getTargetTestingPath(LargeNestedClientContainerTest.class.getSimpleName() + "-" + appDirIdx.getAndIncrement());
WSServer server = new WSServer(testDir, "app");
server.createWebInf();
serverConfig.configure(server);
try
{
server.start();
WebAppContext webapp = server.createWebAppContext();
server.deployWebapp(webapp);
// server.dump();
HttpClient client = new HttpClient();
try
{
client.start();
URI destUri = server.getServerBaseURI().resolve("/app/echo/large");
String destUrl = URLEncoder.encode(destUri.toASCIIString(), "utf-8");
WebSocketPolicy defaultPolicy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
int defaultTextSize = defaultPolicy.getMaxTextMessageSize();
URI uri = server.getServer().getURI().resolve("/app/echo/servlet?size=" + (defaultTextSize * 2) + "&destUrl=" + destUrl);
ContentResponse response = client.GET(uri);
assertThat("Response.status", response.getStatus(), is(200));
}
finally
{
client.stop();
}
}
finally
{
server.stop();
}
}
}

View File

@ -0,0 +1,99 @@
//
// ========================================================================
// 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.jsr356.server;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.common.test.Timeouts;
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.LargeEchoConfiguredSocket;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* Test Echo of Large messages, targeting the use of {@link javax.websocket.Session#setMaxTextMessageBufferSize(int)}
* from within the {@code @OnOpen} method call functionality.
*/
public class LargeOnOpenSessionConfiguredTest
{
private WSServer server;
@Before
public void startServer() throws Exception
{
Path testDir = MavenTestingUtils.getTargetTestingPath(LargeOnOpenSessionConfiguredTest.class.getSimpleName());
server = new WSServer(testDir,"app");
server.createWebInf();
server.copyEndpoint(LargeEchoConfiguredSocket.class);
server.start();
}
@After
public void stopServer()
{
server.stop();
}
@SuppressWarnings("Duplicates")
@Test
public void testEcho() throws Exception
{
URI uri = server.getServerBaseURI();
WebAppContext webapp = server.createWebAppContext();
server.deployWebapp(webapp);
// wsb.dump();
WebSocketClient client = new WebSocketClient();
try
{
client.getPolicy().setMaxTextMessageSize(128 * 1024);
client.start();
JettyEchoSocket clientEcho = new JettyEchoSocket();
Future<Session> foo = client.connect(clientEcho,uri.resolve("echo/large"));
// wait for connect
foo.get(1,TimeUnit.SECONDS);
// The message size should be bigger than default, but smaller than the limit that LargeEchoSocket specifies
byte txt[] = new byte[100 * 1024];
Arrays.fill(txt,(byte)'o');
String msg = new String(txt,StandardCharsets.UTF_8);
clientEcho.sendMessage(msg);
LinkedBlockingQueue<String> msgs = clientEcho.incomingMessages;
Assert.assertEquals("Expected message",msg,msgs.poll(Timeouts.POLL_EVENT, Timeouts.POLL_EVENT_UNIT));
}
finally
{
client.stop();
}
}
}

View File

@ -0,0 +1,134 @@
//
// ========================================================================
// 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.jsr356.server;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.websocket.ClientEndpoint;
import javax.websocket.DeploymentException;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import javax.websocket.server.ServerContainer;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
public class LargeServerContainerAsClientContainerServlet extends HttpServlet
{
private static final Logger LOG = Log.getLogger(LargeServerContainerAsClientContainerServlet.class);
private static final int LARGER_THAN_DEFAULT_SIZE;
private WebSocketContainer clientContainer;
static
{
WebSocketPolicy defaultPolicy = new WebSocketPolicy(WebSocketBehavior.CLIENT);
LARGER_THAN_DEFAULT_SIZE = defaultPolicy.getMaxTextMessageSize() * 3;
}
@Override
public void init() throws ServletException
{
super.init();
ServerContainer serverContainer = (ServerContainer) getServletContext().getAttribute(ServerContainer.class.getName());
clientContainer = serverContainer;
}
@SuppressWarnings("Duplicates")
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
{
int size = LARGER_THAN_DEFAULT_SIZE;
String sizeParam = req.getParameter("size");
if(StringUtil.isNotBlank(sizeParam))
{
size = Integer.parseInt(sizeParam);
}
byte buf[] = new byte[size];
Arrays.fill(buf, (byte) 'x');
String destUrl = req.getParameter("destUrl");
if(StringUtil.isBlank(destUrl))
{
resp.sendError(HttpServletResponse.SC_EXPECTATION_FAILED, "Missing destUrl");
return;
}
URI wsUri = URI.create(destUrl);
try
{
Session session = clientContainer.connectToServer(EchoClientSocket.class, wsUri);
EchoClientSocket clientSocket = (EchoClientSocket) session.getUserProperties().get("endpoint");
String message = new String(buf, UTF_8);
session.getBasicRemote().sendText(message);
String echoed = clientSocket.messages.poll(1, TimeUnit.SECONDS);
assertThat("Echoed", echoed, is(message));
resp.setStatus(HttpServletResponse.SC_OK);
resp.getWriter().println("Success");
}
catch (DeploymentException e)
{
LOG.warn("Unable to deploy client socket", e);
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Deployment error");
}
catch (InterruptedException e)
{
LOG.warn("Unable to find echoed message", e);
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Echoed message missing?");
}
}
@ClientEndpoint
public static class EchoClientSocket
{
public BlockingArrayQueue<String> messages = new BlockingArrayQueue<>();
@OnOpen
public void onOpen(Session session)
{
session.getUserProperties().put("endpoint", this);
}
@OnMessage
public void onMessage(String msg)
{
messages.offer(msg);
}
}
}

View File

@ -22,9 +22,9 @@ import static org.hamcrest.Matchers.notNullValue;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import java.nio.file.Path;
import org.eclipse.jetty.annotations.AnnotationConfiguration; import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.plus.webapp.EnvConfiguration; import org.eclipse.jetty.plus.webapp.EnvConfiguration;
@ -36,11 +36,10 @@ import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.IO; import org.eclipse.jetty.toolchain.test.IO;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.toolchain.test.TestingDir; import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.webapp.Configuration; import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.FragmentConfiguration; import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.MetaInfConfiguration; import org.eclipse.jetty.webapp.MetaInfConfiguration;
@ -57,22 +56,27 @@ import org.junit.Assert;
public class WSServer public class WSServer
{ {
private static final Logger LOG = Log.getLogger(WSServer.class); private static final Logger LOG = Log.getLogger(WSServer.class);
private final File contextDir; private final Path contextDir;
private final String contextPath; private final String contextPath;
private Server server; private Server server;
private URI serverUri; private URI serverUri;
private ContextHandlerCollection contexts; private ContextHandlerCollection contexts;
private File webinf; private Path webinf;
private File classesDir; private Path classesDir;
public WSServer(TestingDir testdir, String contextName) public WSServer(TestingDir testdir, String contextName)
{ {
this(testdir.getPath().toFile(),contextName); this(testdir.getPath(),contextName);
} }
public WSServer(File testdir, String contextName) public WSServer(File testdir, String contextName)
{ {
this.contextDir = new File(testdir,contextName); this(testdir.toPath(), contextName);
}
public WSServer(Path testdir, String contextName)
{
this.contextDir = testdir.resolve(contextName);
this.contextPath = "/" + contextName; this.contextPath = "/" + contextName;
FS.ensureEmpty(contextDir); FS.ensureEmpty(contextDir);
} }
@ -83,10 +87,10 @@ public class WSServer
String endpointPath = clazz.getName().replace('.','/') + ".class"; String endpointPath = clazz.getName().replace('.','/') + ".class";
URL classUrl = cl.getResource(endpointPath); URL classUrl = cl.getResource(endpointPath);
Assert.assertThat("Class URL for: " + clazz,classUrl,notNullValue()); Assert.assertThat("Class URL for: " + clazz,classUrl,notNullValue());
File destFile = new File(classesDir,OS.separators(endpointPath)); Path destFile = classesDir.resolve(endpointPath);
FS.ensureDirExists(destFile.getParentFile()); FS.ensureDirExists(destFile.getParent());
File srcFile = new File(classUrl.toURI()); Path srcFile = new File(classUrl.toURI()).toPath();
IO.copy(srcFile,destFile); IO.copy(srcFile.toFile(),destFile.toFile());
} }
public void copyEndpoint(Class<?> endpointClass) throws Exception public void copyEndpoint(Class<?> endpointClass) throws Exception
@ -96,23 +100,22 @@ public class WSServer
public void copyWebInf(String testResourceName) throws IOException public void copyWebInf(String testResourceName) throws IOException
{ {
webinf = new File(contextDir,"WEB-INF"); webinf = contextDir.resolve("WEB-INF");
FS.ensureDirExists(webinf); FS.ensureDirExists(webinf);
classesDir = new File(webinf,"classes"); classesDir = webinf.resolve("classes");
FS.ensureDirExists(classesDir); FS.ensureDirExists(classesDir);
File webxml = new File(webinf,"web.xml"); Path webxml = webinf.resolve("web.xml");
File testWebXml = MavenTestingUtils.getTestResourceFile(testResourceName); Path testWebXml = MavenTestingUtils.getTestResourcePath(testResourceName);
IO.copy(testWebXml,webxml); IO.copy(testWebXml.toFile(),webxml.toFile());
} }
public WebAppContext createWebAppContext() throws MalformedURLException, IOException public WebAppContext createWebAppContext()
{ {
WebAppContext context = new WebAppContext(); WebAppContext context = new WebAppContext();
context.setContextPath(this.contextPath); context.setContextPath(this.contextPath);
context.setBaseResource(Resource.newResource(this.contextDir)); context.setBaseResource(new PathResource(this.contextDir));
context.setAttribute("org.eclipse.jetty.websocket.jsr356",Boolean.TRUE); context.setAttribute("org.eclipse.jetty.websocket.jsr356",Boolean.TRUE);
// @formatter:off
context.setConfigurations(new Configuration[] { context.setConfigurations(new Configuration[] {
new AnnotationConfiguration(), new AnnotationConfiguration(),
new WebXmlConfiguration(), new WebXmlConfiguration(),
@ -121,7 +124,6 @@ public class WSServer
new MetaInfConfiguration(), new MetaInfConfiguration(),
new FragmentConfiguration(), new FragmentConfiguration(),
new EnvConfiguration()}); new EnvConfiguration()});
// @formatter:on
return context; return context;
} }
@ -159,7 +161,7 @@ public class WSServer
public File getWebAppDir() public File getWebAppDir()
{ {
return this.contextDir; return this.contextDir.toFile();
} }
public void start() throws Exception public void start() throws Exception

View File

@ -0,0 +1,46 @@
//
// ========================================================================
// 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.jsr356.server.samples.echo;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
/**
* Annotated echo socket
*/
@ServerEndpoint(value = "/echo/large")
public class LargeEchoAnnotatedSocket
{
private Session session;
@OnOpen
public void open(Session session)
{
this.session = session;
}
@OnMessage(maxMessageSize = 128 * 1024)
public void echo(String msg)
{
// reply with echo
session.getAsyncRemote().sendText(msg);
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
metadata-complete="false"
version="3.0">
<listener>
<listener-class>org.eclipse.jetty.websocket.jsr356.server.LargeClientContainerInitAsServerListener</listener-class>
</listener>
<servlet>
<servlet-name>large-client</servlet-name>
<servlet-class>org.eclipse.jetty.websocket.jsr356.server.LargeClientContainerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>large-client</servlet-name>
<url-pattern>/echo/servlet</url-pattern>
</servlet-mapping>
</web-app>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
metadata-complete="false"
version="3.0">
<servlet>
<servlet-name>large-client</servlet-name>
<servlet-class>org.eclipse.jetty.websocket.jsr356.server.LargeClientContainerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>large-client</servlet-name>
<url-pattern>/echo/servlet</url-pattern>
</servlet-mapping>
</web-app>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
metadata-complete="false"
version="3.0">
<servlet>
<servlet-name>large-client</servlet-name>
<servlet-class>org.eclipse.jetty.websocket.jsr356.server.LargeServerContainerAsClientContainerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>large-client</servlet-name>
<url-pattern>/echo/servlet</url-pattern>
</servlet-mapping>
</web-app>

View File

@ -142,22 +142,42 @@ public class WebSocketPolicy
} }
} }
/**
* Make a copy of the policy, with current values.
* @return the cloned copy of the policy.
*/
public WebSocketPolicy clonePolicy() public WebSocketPolicy clonePolicy()
{ {
return clonePolicy(this.behavior); WebSocketPolicy clone = new WebSocketPolicy(this.behavior);
clone.idleTimeout = this.getIdleTimeout();
clone.maxTextMessageSize = this.getMaxTextMessageSize();
clone.maxTextMessageBufferSize = this.getMaxTextMessageBufferSize();
clone.maxBinaryMessageSize = this.getMaxBinaryMessageSize();
clone.maxBinaryMessageBufferSize = this.getMaxBinaryMessageBufferSize();
clone.inputBufferSize = this.getInputBufferSize() ;
clone.asyncWriteTimeout = this.getAsyncWriteTimeout();
return clone;
} }
/**
* Make a copy of the policy, with current values, but a different behavior.
*
* @param behavior the behavior to copy/clone
* @return the cloned policy with a new behavior.
* @deprecated use {@link #delegateAs(WebSocketBehavior)} instead
*/
@Deprecated
public WebSocketPolicy clonePolicy(WebSocketBehavior behavior) public WebSocketPolicy clonePolicy(WebSocketBehavior behavior)
{ {
WebSocketPolicy clone = new WebSocketPolicy(behavior); return delegateAs(behavior);
clone.idleTimeout = this.idleTimeout; }
clone.maxTextMessageSize = this.maxTextMessageSize;
clone.maxTextMessageBufferSize = this.maxTextMessageBufferSize; public WebSocketPolicy delegateAs(WebSocketBehavior behavior)
clone.maxBinaryMessageSize = this.maxBinaryMessageSize; {
clone.maxBinaryMessageBufferSize = this.maxBinaryMessageBufferSize; if(behavior == this.behavior)
clone.inputBufferSize = this.inputBufferSize; return this;
clone.asyncWriteTimeout = this.asyncWriteTimeout;
return clone; return new WebSocketPolicy.Delegated(this, behavior);
} }
/** /**
@ -369,16 +389,152 @@ public class WebSocketPolicy
public String toString() public String toString()
{ {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("WebSocketPolicy@").append(Integer.toHexString(hashCode())); builder.append(this.getClass().getSimpleName());
builder.append("[behavior=").append(behavior); builder.append("@").append(Integer.toHexString(hashCode()));
builder.append(",maxTextMessageSize=").append(maxTextMessageSize); builder.append("[behavior=").append(getBehavior());
builder.append(",maxTextMessageBufferSize=").append(maxTextMessageBufferSize); builder.append(",maxTextMessageSize=").append(getMaxTextMessageSize());
builder.append(",maxBinaryMessageSize=").append(maxBinaryMessageSize); builder.append(",maxTextMessageBufferSize=").append(getMaxTextMessageBufferSize());
builder.append(",maxBinaryMessageBufferSize=").append(maxBinaryMessageBufferSize); builder.append(",maxBinaryMessageSize=").append(getMaxBinaryMessageSize());
builder.append(",asyncWriteTimeout=").append(asyncWriteTimeout); builder.append(",maxBinaryMessageBufferSize=").append(getMaxTextMessageBufferSize());
builder.append(",idleTimeout=").append(idleTimeout); builder.append(",asyncWriteTimeout=").append(getAsyncWriteTimeout());
builder.append(",inputBufferSize=").append(inputBufferSize); builder.append(",idleTimeout=").append(getIdleTimeout());
builder.append(",inputBufferSize=").append(getInputBufferSize());
builder.append("]"); builder.append("]");
return builder.toString(); return builder.toString();
} }
/**
* Allows Behavior to be changed, but the settings to delegated.
* <p>
* This rears its ugly head when a JSR356 Server Container is used as a
* JSR356 Client Container.
* The JSR356 Server Container is Behavior SERVER, but its container
* level Policy is shared with the JSR356 Client Container as well.
* This allows a delegate to the policy with a different behavior.
* </p>
*/
private class Delegated extends WebSocketPolicy
{
private final WebSocketPolicy delegated;
public Delegated(WebSocketPolicy policy, WebSocketBehavior behavior)
{
super(behavior);
this.delegated = policy;
}
@Override
public void assertValidBinaryMessageSize(int requestedSize)
{
delegated.assertValidBinaryMessageSize(requestedSize);
}
@Override
public void assertValidTextMessageSize(int requestedSize)
{
delegated.assertValidTextMessageSize(requestedSize);
}
@Override
public WebSocketPolicy clonePolicy()
{
return delegated.clonePolicy();
}
@Override
public WebSocketPolicy clonePolicy(WebSocketBehavior behavior)
{
return delegated.clonePolicy(behavior);
}
@Override
public WebSocketPolicy delegateAs(WebSocketBehavior behavior)
{
return delegated.delegateAs(behavior);
}
@Override
public long getAsyncWriteTimeout()
{
return delegated.getAsyncWriteTimeout();
}
@Override
public long getIdleTimeout()
{
return delegated.getIdleTimeout();
}
@Override
public int getInputBufferSize()
{
return delegated.getInputBufferSize();
}
@Override
public int getMaxBinaryMessageBufferSize()
{
return delegated.getMaxBinaryMessageBufferSize();
}
@Override
public int getMaxBinaryMessageSize()
{
return delegated.getMaxBinaryMessageSize();
}
@Override
public int getMaxTextMessageBufferSize()
{
return delegated.getMaxTextMessageBufferSize();
}
@Override
public int getMaxTextMessageSize()
{
return delegated.getMaxTextMessageSize();
}
@Override
public void setAsyncWriteTimeout(long ms)
{
delegated.setAsyncWriteTimeout(ms);
}
@Override
public void setIdleTimeout(long ms)
{
delegated.setIdleTimeout(ms);
}
@Override
public void setInputBufferSize(int size)
{
delegated.setInputBufferSize(size);
}
@Override
public void setMaxBinaryMessageBufferSize(int size)
{
delegated.setMaxBinaryMessageBufferSize(size);
}
@Override
public void setMaxBinaryMessageSize(int size)
{
delegated.setMaxBinaryMessageSize(size);
}
@Override
public void setMaxTextMessageBufferSize(int size)
{
delegated.setMaxTextMessageBufferSize(size);
}
@Override
public void setMaxTextMessageSize(int size)
{
delegated.setMaxTextMessageSize(size);
}
}
} }

View File

@ -269,7 +269,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
} }
// Ensure we get a Client version of the policy. // Ensure we get a Client version of the policy.
this.policy = scope.getPolicy().clonePolicy(WebSocketBehavior.CLIENT); this.policy = scope.getPolicy().delegateAs(WebSocketBehavior.CLIENT);
// Support Late Binding of Object Factory (for CDI) // Support Late Binding of Object Factory (for CDI)
this.objectFactorySupplier = () -> scope.getObjectFactory(); this.objectFactorySupplier = () -> scope.getObjectFactory();
this.extensionRegistry = new WebSocketExtensionFactory(this); this.extensionRegistry = new WebSocketExtensionFactory(this);

View File

@ -72,7 +72,6 @@ public class Parser
private ByteBuffer payload; private ByteBuffer payload;
private int payloadLength; private int payloadLength;
private PayloadProcessor maskProcessor = new DeMaskProcessor(); private PayloadProcessor maskProcessor = new DeMaskProcessor();
// private PayloadProcessor strictnessProcessor;
/** /**
* Is there an extension using RSV flag? * Is there an extension using RSV flag?