Merge pull request #2769 from eclipse/jetty-9.4.x-issue-2767-websocket-client-default-policy

Issue #2767 - WebSocket Policy on JSR356 ClientContainer not represented correctly
This commit is contained in:
Simone Bordet 2018-08-08 13:02:09 +02:00 committed by GitHub
commit ee68e7fdec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1053 additions and 90 deletions

View File

@ -406,7 +406,7 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont
@Override
public WebSocketPolicy getPolicy()
{
return scopeDelegate.getPolicy();
return client.getPolicy();
}
@Override
@ -482,10 +482,11 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont
@Override
public void setDefaultMaxBinaryMessageBufferSize(int max)
{
// overall message limit (used in non-streaming)
client.getPolicy().setMaxBinaryMessageSize(max);
// incoming streaming buffer size
client.setMaxBinaryMessageBufferSize(max);
// bump overall message limit (used in non-streaming)
client.getPolicy().setMaxBinaryMessageSize(max);
}
@Override
@ -497,9 +498,10 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont
@Override
public void setDefaultMaxTextMessageBufferSize(int max)
{
// overall message limit (used in non-streaming)
client.getPolicy().setMaxTextMessageSize(max);
// incoming streaming buffer size
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
public void setMaxBinaryMessageBufferSize(int length)
{
// incoming streaming buffer size
getPolicy().setMaxBinaryMessageBufferSize(length);
// bump overall message limit (used in non-streaming)
getPolicy().setMaxBinaryMessageSize(length);
}
@Override
@ -341,7 +345,11 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess
@Override
public void setMaxTextMessageBufferSize(int length)
{
// incoming streaming buffer size
getPolicy().setMaxTextMessageBufferSize(length);
// bump overall message limit (used in non-streaming)
getPolicy().setMaxTextMessageSize(length);
}
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.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
/**
@ -78,4 +79,14 @@ public class JettyEchoSocket extends WebSocketAdapter
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.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.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.toolchain.test.TestingDir;
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.eclipse.jetty.websocket.jsr356.server.samples.echo.LargeEchoAnnotatedSocket;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.BeforeClass;
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
{
@Rule
public TestingDir testdir = new TestingDir();
private static WSServer server;
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
public void testEcho() throws Exception
{
WSServer wsb = new WSServer(testdir,"app");
wsb.createWebInf();
wsb.copyEndpoint(LargeEchoConfiguredSocket.class);
URI uri = server.getServerBaseURI();
WebAppContext webapp = server.createWebAppContext();
server.deployWebapp(webapp);
// wsb.dump();
WebSocketClient client = new WebSocketClient();
try
{
wsb.start();
URI uri = wsb.getServerBaseURI();
client.getPolicy().setMaxTextMessageSize(128*1024);
client.start();
JettyEchoSocket clientEcho = new JettyEchoSocket();
Future<Session> foo = client.connect(clientEcho,uri.resolve("echo/large"));
WebAppContext webapp = wsb.createWebAppContext();
wsb.deployWebapp(webapp);
// wsb.dump();
WebSocketClient client = new WebSocketClient(bufferPool);
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();
}
// 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
{
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.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
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.IO;
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.util.log.Log;
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.FragmentConfiguration;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
@ -57,22 +56,27 @@ import org.junit.Assert;
public class WSServer
{
private static final Logger LOG = Log.getLogger(WSServer.class);
private final File contextDir;
private final Path contextDir;
private final String contextPath;
private Server server;
private URI serverUri;
private ContextHandlerCollection contexts;
private File webinf;
private File classesDir;
private Path webinf;
private Path classesDir;
public WSServer(TestingDir testdir, String contextName)
{
this(testdir.getPath().toFile(),contextName);
this(testdir.getPath(),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;
FS.ensureEmpty(contextDir);
}
@ -83,10 +87,10 @@ public class WSServer
String endpointPath = clazz.getName().replace('.','/') + ".class";
URL classUrl = cl.getResource(endpointPath);
Assert.assertThat("Class URL for: " + clazz,classUrl,notNullValue());
File destFile = new File(classesDir,OS.separators(endpointPath));
FS.ensureDirExists(destFile.getParentFile());
File srcFile = new File(classUrl.toURI());
IO.copy(srcFile,destFile);
Path destFile = classesDir.resolve(endpointPath);
FS.ensureDirExists(destFile.getParent());
Path srcFile = new File(classUrl.toURI()).toPath();
IO.copy(srcFile.toFile(),destFile.toFile());
}
public void copyEndpoint(Class<?> endpointClass) throws Exception
@ -96,23 +100,22 @@ public class WSServer
public void copyWebInf(String testResourceName) throws IOException
{
webinf = new File(contextDir,"WEB-INF");
webinf = contextDir.resolve("WEB-INF");
FS.ensureDirExists(webinf);
classesDir = new File(webinf,"classes");
classesDir = webinf.resolve("classes");
FS.ensureDirExists(classesDir);
File webxml = new File(webinf,"web.xml");
File testWebXml = MavenTestingUtils.getTestResourceFile(testResourceName);
IO.copy(testWebXml,webxml);
Path webxml = webinf.resolve("web.xml");
Path testWebXml = MavenTestingUtils.getTestResourcePath(testResourceName);
IO.copy(testWebXml.toFile(),webxml.toFile());
}
public WebAppContext createWebAppContext() throws MalformedURLException, IOException
public WebAppContext createWebAppContext()
{
WebAppContext context = new WebAppContext();
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);
// @formatter:off
context.setConfigurations(new Configuration[] {
new AnnotationConfiguration(),
new WebXmlConfiguration(),
@ -121,7 +124,6 @@ public class WSServer
new MetaInfConfiguration(),
new FragmentConfiguration(),
new EnvConfiguration()});
// @formatter:on
return context;
}
@ -159,7 +161,7 @@ public class WSServer
public File getWebAppDir()
{
return this.contextDir;
return this.contextDir.toFile();
}
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()
{
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)
{
WebSocketPolicy clone = new WebSocketPolicy(behavior);
clone.idleTimeout = this.idleTimeout;
clone.maxTextMessageSize = this.maxTextMessageSize;
clone.maxTextMessageBufferSize = this.maxTextMessageBufferSize;
clone.maxBinaryMessageSize = this.maxBinaryMessageSize;
clone.maxBinaryMessageBufferSize = this.maxBinaryMessageBufferSize;
clone.inputBufferSize = this.inputBufferSize;
clone.asyncWriteTimeout = this.asyncWriteTimeout;
return clone;
return delegateAs(behavior);
}
public WebSocketPolicy delegateAs(WebSocketBehavior behavior)
{
if(behavior == this.behavior)
return this;
return new WebSocketPolicy.Delegated(this, behavior);
}
/**
@ -369,16 +389,152 @@ public class WebSocketPolicy
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("WebSocketPolicy@").append(Integer.toHexString(hashCode()));
builder.append("[behavior=").append(behavior);
builder.append(",maxTextMessageSize=").append(maxTextMessageSize);
builder.append(",maxTextMessageBufferSize=").append(maxTextMessageBufferSize);
builder.append(",maxBinaryMessageSize=").append(maxBinaryMessageSize);
builder.append(",maxBinaryMessageBufferSize=").append(maxBinaryMessageBufferSize);
builder.append(",asyncWriteTimeout=").append(asyncWriteTimeout);
builder.append(",idleTimeout=").append(idleTimeout);
builder.append(",inputBufferSize=").append(inputBufferSize);
builder.append(this.getClass().getSimpleName());
builder.append("@").append(Integer.toHexString(hashCode()));
builder.append("[behavior=").append(getBehavior());
builder.append(",maxTextMessageSize=").append(getMaxTextMessageSize());
builder.append(",maxTextMessageBufferSize=").append(getMaxTextMessageBufferSize());
builder.append(",maxBinaryMessageSize=").append(getMaxBinaryMessageSize());
builder.append(",maxBinaryMessageBufferSize=").append(getMaxTextMessageBufferSize());
builder.append(",asyncWriteTimeout=").append(getAsyncWriteTimeout());
builder.append(",idleTimeout=").append(getIdleTimeout());
builder.append(",inputBufferSize=").append(getInputBufferSize());
builder.append("]");
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.
this.policy = scope.getPolicy().clonePolicy(WebSocketBehavior.CLIENT);
this.policy = scope.getPolicy().delegateAs(WebSocketBehavior.CLIENT);
// Support Late Binding of Object Factory (for CDI)
this.objectFactorySupplier = () -> scope.getObjectFactory();
this.extensionRegistry = new WebSocketExtensionFactory(this);

View File

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