diff --git a/jetty-websocket/javax-websocket-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java b/jetty-websocket/javax-websocket-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java index 033cc270aa2..8356e064163 100644 --- a/jetty-websocket/javax-websocket-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java +++ b/jetty-websocket/javax-websocket-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java @@ -545,13 +545,7 @@ public class JavaxWebSocketFrameHandler implements FrameHandler public void onPing(Frame frame, Callback callback) { - ByteBuffer payload = BufferUtil.EMPTY_BUFFER; - - if (frame.hasPayload()) - { - payload = ByteBuffer.allocate(frame.getPayloadLength()); - BufferUtil.put(frame.getPayload(), payload); - } + ByteBuffer payload = BufferUtil.copy(frame.getPayload()); coreSession.sendFrame(new Frame(OpCode.PONG).setPayload(payload), Callback.NOOP, false); callback.succeeded(); } diff --git a/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/ContainerDefaultConfigurator.java b/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/ContainerDefaultConfigurator.java index 9906438cc3f..4a5ac1bb7e6 100644 --- a/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/ContainerDefaultConfigurator.java +++ b/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/ContainerDefaultConfigurator.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.websocket.javax.server.config; +import java.util.ArrayList; import java.util.List; import java.util.ServiceLoader; import javax.websocket.Extension; @@ -28,6 +29,7 @@ import javax.websocket.server.ServerEndpointConfig.Configurator; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.core.ExtensionConfig; /** * The "Container Default Configurator" per the JSR-356 spec. @@ -78,7 +80,16 @@ public final class ContainerDefaultConfigurator extends Configurator @Override public List getNegotiatedExtensions(List installed, List requested) { - return requested; + List negotiatedExtensions = new ArrayList<>(); + for (Extension ext : requested) + { + // Only choose the first extension if multiple with the same name. + long matches = negotiatedExtensions.stream().filter(e -> e.getName().equals(ext.getName())).count(); + if (matches == 0) + negotiatedExtensions.add(ext); + } + + return negotiatedExtensions; } @Override diff --git a/jetty-websocket/javax-websocket-tests/fuzzingclient.json b/jetty-websocket/javax-websocket-tests/fuzzingclient.json new file mode 100644 index 00000000000..fbe1ce7e85e --- /dev/null +++ b/jetty-websocket/javax-websocket-tests/fuzzingclient.json @@ -0,0 +1,18 @@ +{ + "options": { + "failByDrop": false + }, + "outdir": "./target/reports/servers", + "servers": [ + { + "agent": "Jetty-10.0.0-SNAPSHOT", + "url": "ws://127.0.0.1:9001", + "options": { + "version": 18 + } + } + ], + "cases": ["*"], + "exclude-cases": [], + "exclude-agent-cases": {} +} diff --git a/jetty-websocket/javax-websocket-tests/fuzzingserver.json b/jetty-websocket/javax-websocket-tests/fuzzingserver.json new file mode 100644 index 00000000000..ade31281937 --- /dev/null +++ b/jetty-websocket/javax-websocket-tests/fuzzingserver.json @@ -0,0 +1,10 @@ +{ + "options": { + "failByDrop": false + }, + "url": "ws://127.0.0.1:9001", + "outdir": "./target/reports/clients", + "cases": ["*"], + "exclude-cases": [], + "exclude-agent-cases": {} +} diff --git a/jetty-websocket/javax-websocket-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/EventSocket.java b/jetty-websocket/javax-websocket-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/EventSocket.java index 86828f5ac72..45d373eed81 100644 --- a/jetty-websocket/javax-websocket-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/EventSocket.java +++ b/jetty-websocket/javax-websocket-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/EventSocket.java @@ -52,28 +52,32 @@ public class EventSocket public void onOpen(Session session) { this.session = session; - LOG.info("{} onOpen(): {}", toString(), session); + if (LOG.isDebugEnabled()) + LOG.debug("{} onOpen(): {}", toString(), session); openLatch.countDown(); } @OnMessage public void onMessage(String message) throws IOException { - LOG.info("{} onMessage(): {}", toString(), message); + if (LOG.isDebugEnabled()) + LOG.debug("{} onMessage(): {}", toString(), message); messageQueue.offer(message); } @OnClose public void onClose(CloseReason reason) { - LOG.info("{} onClose(): {}", toString(), reason); + if (LOG.isDebugEnabled()) + LOG.debug("{} onClose(): {}", toString(), reason); closeLatch.countDown(); } @OnError public void onError(Throwable cause) { - LOG.info("{} onError(): {}", toString(), cause); + if (LOG.isDebugEnabled()) + LOG.debug("{} onError(): {}", toString(), cause); error = cause; } } diff --git a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/autobahn/JavaxAutobahnClient.java b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/autobahn/JavaxAutobahnClient.java new file mode 100644 index 00000000000..f722737e4af --- /dev/null +++ b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/autobahn/JavaxAutobahnClient.java @@ -0,0 +1,200 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.javax.tests.autobahn; + +import java.net.URI; +import java.util.concurrent.TimeUnit; +import javax.websocket.CloseReason; + +import org.eclipse.jetty.util.Jetty; +import org.eclipse.jetty.util.UrlEncoded; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.javax.client.JavaxWebSocketClientContainer; +import org.eclipse.jetty.websocket.javax.tests.EventSocket; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * WebSocket Client for use with autobahn websocket testsuite (wstest). + *

+ * Installing Autobahn: + *

+ *
+ *    # For Debian / Ubuntu
+ *    $ sudo apt-get install python python-dev python-twisted
+ *    $ sudo apt-get install python-pip
+ *    $ sudo pip install autobahntestsuite
+ *
+ *    # For Fedora / Redhat
+ *    $ sudo yum install python python-dev python-pip twisted
+ *    $ sudo yum install libffi-devel
+ *    $ sudo pip install autobahntestsuite
+ * 
+ *

+ * Upgrading an existing installation of autobahntestsuite + *

+ *
+ *     $ sudo pip install -U autobahntestsuite
+ * 
+ *

+ * Running Autobahn Fuzzing Server (which you run this client implementation against): + *

+ *
+ *     # Change to javax-websocket-tests directory first.
+ *     $ cd jetty-websocket/javax-websocket-tests/
+ *     $ wstest --mode=fuzzingserver --spec=fuzzingserver.json
+ *
+ *     # Report output is configured (in the fuzzingserver.json) at location:
+ *     $ ls target/reports/clients/
+ * 
+ */ +public class JavaxAutobahnClient +{ + public static void main(String[] args) + { + String hostname = "localhost"; + int port = 9001; + + if (args.length > 0) + hostname = args[0]; + if (args.length > 1) + port = Integer.parseInt(args[1]); + + // Optional case numbers + // NOTE: these are url query parameter case numbers (whole integers, eg "6"), not the case ids (eg "7.3.1") + int[] caseNumbers = null; + if (args.length > 2) + { + int offset = 2; + caseNumbers = new int[args.length - offset]; + for (int i = offset; i < args.length; i++) + { + caseNumbers[i - offset] = Integer.parseInt(args[i]); + } + } + + JavaxAutobahnClient client = null; + try + { + String userAgent = "JettyWebsocketClient/" + Jetty.VERSION; + client = new JavaxAutobahnClient(hostname, port, userAgent); + + LOG.info("Running test suite..."); + LOG.info("Using Fuzzing Server: {}:{}", hostname, port); + LOG.info("User Agent: {}", userAgent); + + if (caseNumbers == null) + { + int caseCount = client.getCaseCount(); + LOG.info("Will run all {} cases ...", caseCount); + for (int caseNum = 1; caseNum <= caseCount; caseNum++) + { + LOG.info("Running case {} (of {}) ...", caseNum, caseCount); + client.runCaseByNumber(caseNum); + } + } + else + { + LOG.info("Will run %d cases ...", caseNumbers.length); + for (int caseNum : caseNumbers) + { + client.runCaseByNumber(caseNum); + } + } + LOG.info("All test cases executed."); + client.updateReports(); + } + catch (Throwable t) + { + LOG.warn("Test Failed", t); + } + finally + { + if (client != null) + client.stop(); + } + } + + private static final Logger LOG = Log.getLogger(JavaxAutobahnClient.class); + private URI baseWebsocketUri; + private JavaxWebSocketClientContainer clientContainer; + private String userAgent; + + public JavaxAutobahnClient(String hostname, int port, String userAgent) throws Exception + { + this.userAgent = userAgent; + this.baseWebsocketUri = new URI("ws://" + hostname + ":" + port); + this.clientContainer = new JavaxWebSocketClientContainer(); + clientContainer.start(); + } + + public void stop() + { + LifeCycle.stop(clientContainer); + } + + public int getCaseCount() + { + URI wsUri = baseWebsocketUri.resolve("/getCaseCount"); + EventSocket onCaseCount = new EventSocket(); + + try + { + clientContainer.connectToServer(onCaseCount, wsUri); + String msg = onCaseCount.messageQueue.poll(10, TimeUnit.SECONDS); + onCaseCount.session.close(new CloseReason(CloseReason.CloseCodes.GOING_AWAY, null)); + assertTrue(onCaseCount.closeLatch.await(2, TimeUnit.SECONDS)); + assertNotNull(msg); + return Integer.decode(msg); + } + catch (Throwable t) + { + throw new IllegalStateException("Unable to get Case Count", t); + } + } + + public void runCaseByNumber(int caseNumber) throws Exception + { + URI wsUri = baseWebsocketUri.resolve("/runCase?case=" + caseNumber + "&agent=" + UrlEncoded.encodeString(userAgent)); + LOG.info("test uri: {}", wsUri); + + JavaxAutobahnSocket echoHandler = new JavaxAutobahnSocket(); + clientContainer.connectToServer(echoHandler, wsUri); + + // Wait up to 5 min as some of the tests can take a while + if (!echoHandler.closeLatch.await(5, TimeUnit.MINUTES)) + { + LOG.warn("could not close {}, closing session", echoHandler); + echoHandler.session.close(new CloseReason(CloseReason.CloseCodes.GOING_AWAY, null)); + } + } + + public void updateReports() throws Exception + { + URI wsUri = baseWebsocketUri.resolve("/updateReports?agent=" + UrlEncoded.encodeString(userAgent)); + EventSocket onUpdateReports = new EventSocket(); + clientContainer.connectToServer(onUpdateReports, wsUri); + assertTrue(onUpdateReports.closeLatch.await(15, TimeUnit.SECONDS)); + LOG.info("Reports updated."); + LOG.info("Test suite finished!"); + } +} diff --git a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/autobahn/JavaxAutobahnServer.java b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/autobahn/JavaxAutobahnServer.java new file mode 100644 index 00000000000..543d6812794 --- /dev/null +++ b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/autobahn/JavaxAutobahnServer.java @@ -0,0 +1,82 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.javax.tests.autobahn; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer; + +/** + * WebSocket Server for use with autobahn websocket testsuite (wstest). + *

+ * Installing Autobahn: + *

+ *
+ *    # For Debian / Ubuntu
+ *    $ sudo apt-get install python python-dev python-twisted
+ *    $ sudo apt-get install python-pip
+ *    $ sudo pip install autobahntestsuite
+ *
+ *    # For Fedora / Redhat
+ *    $ sudo yum install python python-dev python-pip twisted
+ *    $ sudo yum install libffi-devel
+ *    $ sudo pip install autobahntestsuite
+ * 
+ *

+ * Upgrading an existing installation of autobahntestsuite + *

+ *
+ *     $ sudo pip install -U autobahntestsuite
+ * 
+ *

+ * Running Autobahn Fuzzing Client (against this server implementation): + *

+ *
+ *     # Change to javax-websocket-tests directory first.
+ *     $ cd jetty-websocket/javax-websocket-tests/
+ *     $ wstest --mode=fuzzingclient --spec=fuzzingclient.json
+ *
+ *     # Report output is configured (in the fuzzingclient.json) at location:
+ *     $ ls target/reports/servers/
+ * 
+ */ +public class JavaxAutobahnServer +{ + public static void main(String[] args) throws Exception + { + int port = 9001; // same port as found in fuzzing-client.json + if (args != null && args.length > 0) + port = Integer.parseInt(args[0]); + + Server server = new Server(port); + ServerConnector connector = new ServerConnector(server); + connector.setIdleTimeout(10000); + server.addConnector(connector); + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + server.setHandler(context); + + JavaxWebSocketServletContainerInitializer.configure(context, (servletContext, container)-> + container.addEndpoint(JavaxAutobahnSocket.class)); + + server.start(); + server.join(); + } +} diff --git a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/autobahn/JavaxAutobahnSocket.java b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/autobahn/JavaxAutobahnSocket.java new file mode 100644 index 00000000000..be746df58b3 --- /dev/null +++ b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/autobahn/JavaxAutobahnSocket.java @@ -0,0 +1,76 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.javax.tests.autobahn; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import javax.websocket.ClientEndpoint; +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +@ClientEndpoint +@ServerEndpoint("/") +public class JavaxAutobahnSocket +{ + private static final Logger LOG = Log.getLogger(JavaxAutobahnSocket.class); + + public Session session; + public CountDownLatch closeLatch = new CountDownLatch(1); + + @OnOpen + public void onConnect(Session session) + { + this.session = session; + session.setMaxTextMessageBufferSize(Integer.MAX_VALUE); + session.setMaxBinaryMessageBufferSize(Integer.MAX_VALUE); + } + + @OnMessage + public void onText(String message) throws IOException + { + session.getBasicRemote().sendText(message); + } + + @OnMessage + public void onBinary(ByteBuffer message) throws IOException + { + session.getBasicRemote().sendBinary(message); + } + + @OnError + public void onError(Throwable t) + { + if (LOG.isDebugEnabled()) + LOG.debug("onError()", t); + } + + @OnClose + public void onClose() + { + closeLatch.countDown(); + } +} diff --git a/jetty-websocket/jetty-websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java b/jetty-websocket/jetty-websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java index 8b089fdbcba..f043b864cb6 100644 --- a/jetty-websocket/jetty-websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java +++ b/jetty-websocket/jetty-websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java @@ -84,6 +84,20 @@ public interface WebSocketPolicy */ long getMaxTextMessageSize(); + /** + * The maximum payload size of any WebSocket Frame which can be received. + * + * @return the maximum size of a WebSocket Frame. + */ + long getMaxFrameSize(); + + /** + * If true, frames are automatically fragmented to respect the maximum frame size. + * + * @return whether to automatically fragment incoming WebSocket Frames. + */ + boolean isAutoFragment(); + /** * The duration that a websocket may be idle before being closed by the implementation * @@ -123,4 +137,21 @@ public interface WebSocketPolicy * @param size the maximum allowed size of a text message. */ void setMaxTextMessageSize(long size); + + /** + * The maximum payload size of any WebSocket Frame which can be received. + *

+ * WebSocket Frames over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} + *

+ * + * @param maxFrameSize the maximum allowed size of a WebSocket Frame. + */ + void setMaxFrameSize(long maxFrameSize); + + /** + * If set to true, frames are automatically fragmented to respect the maximum frame size. + * + * @param autoFragment whether to automatically fragment incoming WebSocket Frames. + */ + void setAutoFragment(boolean autoFragment); } diff --git a/jetty-websocket/jetty-websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketMessage.java b/jetty-websocket/jetty-websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketMessage.java index 00483883708..064fce15d83 100644 --- a/jetty-websocket/jetty-websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketMessage.java +++ b/jetty-websocket/jetty-websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketMessage.java @@ -45,6 +45,8 @@ import org.eclipse.jetty.websocket.api.Session; *

* Binary Message Versions *

    + *
  1. {@code public void methodName(ByteBuffer message)}
  2. + *
  3. public void methodName({@link Session} session, ByteBuffer message)
  4. *
  5. {@code public void methodName(byte buf[], int offset, int length)}
  6. *
  7. public void methodName({@link Session} session, byte buf[], int offset, int length)
  8. *
  9. {@code public void methodName(InputStream stream)}
  10. diff --git a/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java b/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java index fd07a8c0eb3..c37ff27ecb9 100644 --- a/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java +++ b/jetty-websocket/jetty-websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java @@ -237,6 +237,18 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketPoli return configurationCustomizer.getMaxTextMessageSize(); } + @Override + public long getMaxFrameSize() + { + return configurationCustomizer.getMaxFrameSize(); + } + + @Override + public boolean isAutoFragment() + { + return configurationCustomizer.isAutoFragment(); + } + @Override public void setIdleTimeout(Duration duration) { @@ -268,6 +280,18 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketPoli configurationCustomizer.setMaxTextMessageSize(size); } + @Override + public void setMaxFrameSize(long maxFrameSize) + { + configurationCustomizer.setMaxFrameSize(maxFrameSize); + } + + @Override + public void setAutoFragment(boolean autoFragment) + { + configurationCustomizer.setAutoFragment(autoFragment); + } + public SocketAddress getBindAddress() { return getHttpClient().getBindAddress(); diff --git a/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java b/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java index 6ce9a1fe59f..9a2ccaa23e7 100644 --- a/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java +++ b/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java @@ -29,6 +29,7 @@ import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.BatchMode; import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.eclipse.jetty.websocket.api.UpgradeResponse; +import org.eclipse.jetty.websocket.api.WriteCallback; import org.eclipse.jetty.websocket.common.invoke.InvalidSignatureException; import org.eclipse.jetty.websocket.core.BadPayloadException; import org.eclipse.jetty.websocket.core.CloseException; @@ -359,10 +360,8 @@ public class JettyWebSocketFrameHandler implements FrameHandler else { // Automatically respond - Frame pong = new Frame(OpCode.PONG); - if (frame.hasPayload()) - pong.setPayload(frame.getPayload()); - getSession().getRemote().getCoreSession().sendFrame(pong, Callback.NOOP, false); + ByteBuffer payload = BufferUtil.copy(frame.getPayload()); + getSession().getRemote().sendPong(payload, WriteCallback.NOOP); } callback.succeeded(); } diff --git a/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java b/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java index e136ac32562..80a5fcc2493 100644 --- a/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java +++ b/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java @@ -117,6 +117,18 @@ public class WebSocketSession extends AbstractLifeCycle implements Session, Susp return coreSession.getMaxTextMessageSize(); } + @Override + public long getMaxFrameSize() + { + return coreSession.getMaxFrameSize(); + } + + @Override + public boolean isAutoFragment() + { + return coreSession.isAutoFragment(); + } + @Override public void setIdleTimeout(Duration duration) { @@ -147,6 +159,18 @@ public class WebSocketSession extends AbstractLifeCycle implements Session, Susp coreSession.setMaxTextMessageSize(size); } + @Override + public void setMaxFrameSize(long maxFrameSize) + { + coreSession.setMaxFrameSize(maxFrameSize); + } + + @Override + public void setAutoFragment(boolean autoFragment) + { + coreSession.setAutoFragment(autoFragment); + } + @Override public String getProtocolVersion() { diff --git a/jetty-websocket/jetty-websocket-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingMessageCapture.java b/jetty-websocket/jetty-websocket-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingMessageCapture.java index 5955296793f..76164af7236 100644 --- a/jetty-websocket/jetty-websocket-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingMessageCapture.java +++ b/jetty-websocket/jetty-websocket-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingMessageCapture.java @@ -295,6 +295,18 @@ public class OutgoingMessageCapture extends FrameHandler.CoreSession.Empty imple return maxMessageSize; } + @Override + public long getMaxFrameSize() + { + return 0; + } + + @Override + public boolean isAutoFragment() + { + return false; + } + @Override public void setIdleTimeout(Duration duration) { @@ -319,6 +331,16 @@ public class OutgoingMessageCapture extends FrameHandler.CoreSession.Empty imple public void setMaxTextMessageSize(long size) { } + + @Override + public void setMaxFrameSize(long maxFrameSize) + { + } + + @Override + public void setAutoFragment(boolean autoFragment) + { + } }; } } diff --git a/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java index b6b7938f11f..cb2b886e879 100644 --- a/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java +++ b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java @@ -206,6 +206,18 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements return customizer.getMaxTextMessageSize(); } + @Override + public long getMaxFrameSize() + { + return customizer.getMaxFrameSize(); + } + + @Override + public boolean isAutoFragment() + { + return customizer.isAutoFragment(); + } + @Override public void setIdleTimeout(Duration duration) { @@ -235,4 +247,16 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements { customizer.setMaxTextMessageSize(size); } + + @Override + public void setMaxFrameSize(long maxFrameSize) + { + customizer.setMaxFrameSize(maxFrameSize); + } + + @Override + public void setAutoFragment(boolean autoFragment) + { + customizer.setAutoFragment(autoFragment); + } } diff --git a/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServletFactory.java b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServletFactory.java index 0c424b494d0..15c313eddc9 100644 --- a/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServletFactory.java +++ b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServletFactory.java @@ -22,7 +22,6 @@ import java.time.Duration; import java.util.Set; import org.eclipse.jetty.http.pathmap.PathSpec; -import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.WebSocketBehavior; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; @@ -50,44 +49,25 @@ public class JettyWebSocketServletFactory implements WebSocketPolicy return WebSocketBehavior.SERVER; } - /** - * If true, frames are automatically fragmented to respect the maximum frame size. - * - * @return whether to automatically fragment incoming WebSocket Frames. - */ + @Override public boolean isAutoFragment() { return factory.isAutoFragment(); } - /** - * If set to true, frames are automatically fragmented to respect the maximum frame size. - * - * @param autoFragment whether to automatically fragment incoming WebSocket Frames. - */ + @Override public void setAutoFragment(boolean autoFragment) { factory.setAutoFragment(autoFragment); } - /** - * The maximum payload size of any WebSocket Frame which can be received. - * - * @return the maximum size of a WebSocket Frame. - */ + @Override public long getMaxFrameSize() { return factory.getMaxFrameSize(); } - /** - * The maximum payload size of any WebSocket Frame which can be received. - *

    - * WebSocket Frames over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} - *

    - * - * @param maxFrameSize the maximum allowed size of a WebSocket Frame. - */ + @Override public void setMaxFrameSize(long maxFrameSize) { factory.setMaxFrameSize(maxFrameSize); diff --git a/jetty-websocket/jetty-websocket-tests/fuzzingclient.json b/jetty-websocket/jetty-websocket-tests/fuzzingclient.json new file mode 100644 index 00000000000..fbe1ce7e85e --- /dev/null +++ b/jetty-websocket/jetty-websocket-tests/fuzzingclient.json @@ -0,0 +1,18 @@ +{ + "options": { + "failByDrop": false + }, + "outdir": "./target/reports/servers", + "servers": [ + { + "agent": "Jetty-10.0.0-SNAPSHOT", + "url": "ws://127.0.0.1:9001", + "options": { + "version": 18 + } + } + ], + "cases": ["*"], + "exclude-cases": [], + "exclude-agent-cases": {} +} diff --git a/jetty-websocket/jetty-websocket-tests/fuzzingserver.json b/jetty-websocket/jetty-websocket-tests/fuzzingserver.json new file mode 100644 index 00000000000..ade31281937 --- /dev/null +++ b/jetty-websocket/jetty-websocket-tests/fuzzingserver.json @@ -0,0 +1,10 @@ +{ + "options": { + "failByDrop": false + }, + "url": "ws://127.0.0.1:9001", + "outdir": "./target/reports/clients", + "cases": ["*"], + "exclude-cases": [], + "exclude-agent-cases": {} +} diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/EventSocket.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/EventSocket.java index ae660da1c09..cafa1e41044 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/EventSocket.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/EventSocket.java @@ -57,14 +57,16 @@ public class EventSocket { this.session = session; behavior = session.getPolicy().getBehavior().name(); - LOG.info("{} onOpen(): {}", toString(), session); + if (LOG.isDebugEnabled()) + LOG.debug("{} onOpen(): {}", toString(), session); openLatch.countDown(); } @OnWebSocketMessage public void onMessage(String message) throws IOException { - LOG.info("{} onMessage(): {}", toString(), message); + if (LOG.isDebugEnabled()) + LOG.debug("{} onMessage(): {}", toString(), message); messageQueue.offer(message); } @@ -72,14 +74,16 @@ public class EventSocket public void onMessage(byte buf[], int offset, int len) { ByteBuffer message = ByteBuffer.wrap(buf, offset, len); - LOG.info("{} onMessage(): {}", toString(), message); + if (LOG.isDebugEnabled()) + LOG.debug("{} onMessage(): {}", toString(), message); binaryMessageQueue.offer(message); } @OnWebSocketClose public void onClose(int statusCode, String reason) { - LOG.info("{} onClose(): {}:{}", toString(), statusCode, reason); + if (LOG.isDebugEnabled()) + LOG.debug("{} onClose(): {}:{}", toString(), statusCode, reason); this.statusCode = statusCode; this.reason = reason; closeLatch.countDown(); @@ -88,7 +92,8 @@ public class EventSocket @OnWebSocketError public void onError(Throwable cause) { - LOG.info("{} onError(): {}", toString(), cause); + if (LOG.isDebugEnabled()) + LOG.debug("{} onError(): {}", toString(), cause); error = cause; errorLatch.countDown(); } diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/autobahn/JettyAutobahnClient.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/autobahn/JettyAutobahnClient.java new file mode 100644 index 00000000000..166860784f4 --- /dev/null +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/autobahn/JettyAutobahnClient.java @@ -0,0 +1,227 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests.autobahn; + +import java.io.IOException; +import java.net.URI; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jetty.util.Jetty; +import org.eclipse.jetty.util.UrlEncoded; +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.client.WebSocketClient; +import org.eclipse.jetty.websocket.tests.EchoSocket; +import org.eclipse.jetty.websocket.tests.EventSocket; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * WebSocket Client for use with autobahn websocket testsuite (wstest). + *

    + * Installing Autobahn: + *

    + *
    + *    # For Debian / Ubuntu
    + *    $ sudo apt-get install python python-dev python-twisted
    + *    $ sudo apt-get install python-pip
    + *    $ sudo pip install autobahntestsuite
    + *
    + *    # For Fedora / Redhat
    + *    $ sudo yum install python python-dev python-pip twisted
    + *    $ sudo yum install libffi-devel
    + *    $ sudo pip install autobahntestsuite
    + * 
    + *

    + * Upgrading an existing installation of autobahntestsuite + *

    + *
    + *     $ sudo pip install -U autobahntestsuite
    + * 
    + *

    + * Running Autobahn Fuzzing Server (which you run this client implementation against): + *

    + *
    + *     # Change to jetty-websocket-tests directory first.
    + *     $ cd jetty-websocket/jetty-websocket-tests/
    + *     $ wstest --mode=fuzzingserver --spec=fuzzingserver.json
    + *
    + *     # Report output is configured (in the fuzzingserver.json) at location:
    + *     $ ls target/reports/clients/
    + * 
    + */ +public class JettyAutobahnClient +{ + public static void main(String[] args) + { + String hostname = "localhost"; + int port = 9001; + + if (args.length > 0) + hostname = args[0]; + if (args.length > 1) + port = Integer.parseInt(args[1]); + + // Optional case numbers + // NOTE: these are url query parameter case numbers (whole integers, eg "6"), not the case ids (eg "7.3.1") + int[] caseNumbers = null; + if (args.length > 2) + { + int offset = 2; + caseNumbers = new int[args.length - offset]; + for (int i = offset; i < args.length; i++) + { + caseNumbers[i - offset] = Integer.parseInt(args[i]); + } + } + + JettyAutobahnClient client = null; + try + { + String userAgent = "JettyWebsocketClient/" + Jetty.VERSION; + client = new JettyAutobahnClient(hostname, port, userAgent); + + LOG.info("Running test suite..."); + LOG.info("Using Fuzzing Server: {}:{}", hostname, port); + LOG.info("User Agent: {}", userAgent); + + if (caseNumbers == null) + { + int caseCount = client.getCaseCount(); + LOG.info("Will run all {} cases ...", caseCount); + for (int caseNum = 1; caseNum <= caseCount; caseNum++) + { + LOG.info("Running case {} (of {}) ...", caseNum, caseCount); + client.runCaseByNumber(caseNum); + } + } + else + { + LOG.info("Will run %d cases ...", caseNumbers.length); + for (int caseNum : caseNumbers) + { + client.runCaseByNumber(caseNum); + } + } + LOG.info("All test cases executed."); + client.updateReports(); + } + catch (Throwable t) + { + LOG.warn("Test Failed", t); + } + finally + { + if (client != null) + client.shutdown(); + } + } + + private static final Logger LOG = Log.getLogger(JettyAutobahnClient.class); + private URI baseWebsocketUri; + private WebSocketClient client; + private String userAgent; + + public JettyAutobahnClient(String hostname, int port, String userAgent) throws Exception + { + this.userAgent = userAgent; + this.baseWebsocketUri = new URI("ws://" + hostname + ":" + port); + this.client = new WebSocketClient(); + this.client.start(); + } + + public int getCaseCount() throws IOException, InterruptedException + { + URI wsUri = baseWebsocketUri.resolve("/getCaseCount"); + EventSocket onCaseCount = new EventSocket(); + CompletableFuture response = client.connect(onCaseCount, wsUri); + + if (waitForUpgrade(wsUri, response)) + { + String msg = onCaseCount.messageQueue.poll(10, TimeUnit.SECONDS); + onCaseCount.session.close(StatusCode.SHUTDOWN, null); + assertTrue(onCaseCount.closeLatch.await(2, TimeUnit.SECONDS)); + assertNotNull(msg); + return Integer.decode(msg); + } + throw new IllegalStateException("Unable to get Case Count"); + } + + public void runCaseByNumber(int caseNumber) throws IOException, InterruptedException + { + URI wsUri = baseWebsocketUri.resolve("/runCase?case=" + caseNumber + "&agent=" + UrlEncoded.encodeString(userAgent)); + LOG.info("test uri: {}", wsUri); + + EchoSocket echoHandler = new JettyAutobahnSocket(); + Future response = client.connect(echoHandler, wsUri); + if (waitForUpgrade(wsUri, response)) + { + // Wait up to 5 min as some of the tests can take a while + if (!echoHandler.closeLatch.await(5, TimeUnit.MINUTES)) + { + LOG.warn("could not close {}, aborting session", echoHandler); + echoHandler.session.disconnect(); + } + } + } + + public void shutdown() + { + try + { + client.stop(); + } + catch (Exception e) + { + LOG.warn("Unable to stop WebSocketClient", e); + } + } + + public void updateReports() throws IOException, InterruptedException, ExecutionException, TimeoutException + { + URI wsUri = baseWebsocketUri.resolve("/updateReports?agent=" + UrlEncoded.encodeString(userAgent)); + EventSocket onUpdateReports = new EventSocket(); + Future response = client.connect(onUpdateReports, wsUri); + response.get(5, TimeUnit.SECONDS); + assertTrue(onUpdateReports.closeLatch.await(15, TimeUnit.SECONDS)); + LOG.info("Reports updated."); + LOG.info("Test suite finished!"); + } + + private boolean waitForUpgrade(URI wsUri, Future response) + { + try + { + response.get(10, TimeUnit.SECONDS); + return true; + } + catch (Throwable t) + { + LOG.warn("Unable to connect to: " + wsUri, t); + return false; + } + } +} diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/autobahn/JettyAutobahnServer.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/autobahn/JettyAutobahnServer.java new file mode 100644 index 00000000000..bc5eb25c26a --- /dev/null +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/autobahn/JettyAutobahnServer.java @@ -0,0 +1,82 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests.autobahn; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; + +/** + * WebSocket Server for use with autobahn websocket testsuite (wstest). + *

    + * Installing Autobahn: + *

    + *
    + *    # For Debian / Ubuntu
    + *    $ sudo apt-get install python python-dev python-twisted
    + *    $ sudo apt-get install python-pip
    + *    $ sudo pip install autobahntestsuite
    + *
    + *    # For Fedora / Redhat
    + *    $ sudo yum install python python-dev python-pip twisted
    + *    $ sudo yum install libffi-devel
    + *    $ sudo pip install autobahntestsuite
    + * 
    + *

    + * Upgrading an existing installation of autobahntestsuite + *

    + *
    + *     $ sudo pip install -U autobahntestsuite
    + * 
    + *

    + * Running Autobahn Fuzzing Client (against this server implementation): + *

    + *
    + *     # Change to jetty-websocket-tests directory first.
    + *     $ cd jetty-websocket/jetty-websocket-tests/
    + *     $ wstest --mode=fuzzingclient --spec=fuzzingclient.json
    + *
    + *     # Report output is configured (in the fuzzingclient.json) at location:
    + *     $ ls target/reports/servers/
    + * 
    + */ +public class JettyAutobahnServer +{ + public static void main(String[] args) throws Exception + { + int port = 9001; // same port as found in fuzzing-client.json + if (args != null && args.length > 0) + port = Integer.parseInt(args[0]); + + Server server = new Server(port); + ServerConnector connector = new ServerConnector(server); + connector.setIdleTimeout(10000); + server.addConnector(connector); + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + server.setHandler(context); + + JettyWebSocketServletContainerInitializer.configure(context, (servletContext, container)-> + container.addMapping("/", (req, resp) -> new JettyAutobahnSocket())); + + server.start(); + server.join(); + } +} diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/autobahn/JettyAutobahnSocket.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/autobahn/JettyAutobahnSocket.java new file mode 100644 index 00000000000..d1d99cf659a --- /dev/null +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/autobahn/JettyAutobahnSocket.java @@ -0,0 +1,37 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests.autobahn; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.core.WebSocketConstants; +import org.eclipse.jetty.websocket.tests.EchoSocket; + +@WebSocket +public class JettyAutobahnSocket extends EchoSocket +{ + @Override + public void onOpen(Session session) + { + super.onOpen(session); + session.setMaxTextMessageSize(Long.MAX_VALUE); + session.setMaxBinaryMessageSize(Long.MAX_VALUE); + session.setMaxFrameSize(WebSocketConstants.DEFAULT_MAX_FRAME_SIZE*2); + } +} diff --git a/jetty-websocket/websocket-core/pom.xml b/jetty-websocket/websocket-core/pom.xml index 1e3067f593b..60879dcaa46 100644 --- a/jetty-websocket/websocket-core/pom.xml +++ b/jetty-websocket/websocket-core/pom.xml @@ -141,7 +141,7 @@ - org.eclipse.jetty.websocket.core.autobahn.AutobahnWebSocketServer + org.eclipse.jetty.websocket.core.autobahn.CoreAutobahnServer diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java index 848724c5e05..ce78b1cf86f 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java @@ -48,6 +48,7 @@ import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -104,6 +105,7 @@ public class WebSocketNegotiationTest extends WebSocketTester break; case "test": + case "testExtensionThatDoesNotExist": case "testInvalidExtensionParameter": case "testAcceptTwoExtensionsOfSameName": case "testInvalidUpgradeRequest": @@ -239,6 +241,23 @@ public class WebSocketNegotiationTest extends WebSocketTester assertNull(extensionHeader.get(5, TimeUnit.SECONDS)); } + @Test + public void testExtensionThatDoesNotExist() throws Exception + { + Socket client = new Socket(); + client.connect(new InetSocketAddress("127.0.0.1", server.getLocalPort())); + + HttpFields httpFields = newUpgradeRequest("nonExistentExtensionName"); + String upgradeRequest = "GET / HTTP/1.1\r\n" + httpFields; + client.getOutputStream().write(upgradeRequest.getBytes(StandardCharsets.ISO_8859_1)); + String response = getUpgradeResponse(client.getInputStream()); + + assertThat(response, startsWith("HTTP/1.1 101 Switching Protocols")); + assertThat(response, containsString("Sec-WebSocket-Protocol: test")); + assertThat(response, containsString("Sec-WebSocket-Accept: +WahVcVmeMLKQUMm0fvPrjSjwzI=")); + assertThat(response, not(containsString(HttpHeader.SEC_WEBSOCKET_EXTENSIONS.asString()))); + } + @Test public void testAcceptTwoExtensionsOfSameName() throws Exception { diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/AutobahnWebSocketClient.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnClient.java similarity index 95% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/AutobahnWebSocketClient.java rename to jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnClient.java index 62291bb8684..6503abf72ea 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/AutobahnWebSocketClient.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnClient.java @@ -62,7 +62,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; * Running Autobahn Fuzzing Server (which you run this client implementation against): *

    *
    - *     # Change to websocket-core
    + *     # Change to websocket-core first
      *     $ cd jetty-websocket/websocket-core
      *     $ wstest --mode=fuzzingserver --spec=fuzzingserver.json
      *
    @@ -70,7 +70,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
      *     $ ls target/reports/clients/
      * 
    */ -public class AutobahnWebSocketClient +public class CoreAutobahnClient { public static void main(String[] args) { @@ -95,11 +95,11 @@ public class AutobahnWebSocketClient } } - AutobahnWebSocketClient client = null; + CoreAutobahnClient client = null; try { String userAgent = "JettyWebsocketClient/" + Jetty.VERSION; - client = new AutobahnWebSocketClient(hostname, port, userAgent); + client = new CoreAutobahnClient(hostname, port, userAgent); LOG.info("Running test suite..."); LOG.info("Using Fuzzing Server: {}:{}", hostname, port); @@ -137,12 +137,12 @@ public class AutobahnWebSocketClient } } - private static final Logger LOG = Log.getLogger(AutobahnWebSocketClient.class); + private static final Logger LOG = Log.getLogger(CoreAutobahnClient.class); private URI baseWebsocketUri; private WebSocketCoreClient client; private String userAgent; - public AutobahnWebSocketClient(String hostname, int port, String userAgent) throws Exception + public CoreAutobahnClient(String hostname, int port, String userAgent) throws Exception { this.userAgent = userAgent; this.baseWebsocketUri = new URI("ws://" + hostname + ":" + port); diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/AutobahnWebSocketServer.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnServer.java similarity index 98% rename from jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/AutobahnWebSocketServer.java rename to jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnServer.java index 9f1978534d1..93ba4f58346 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/AutobahnWebSocketServer.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnServer.java @@ -57,7 +57,7 @@ import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler; * $ ls target/reports/servers/ * */ -public class AutobahnWebSocketServer +public class CoreAutobahnServer { public static void main(String[] args) throws Exception { diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java index 15ac69ba78e..af915587669 100644 --- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java +++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/ServletUpgradeResponse.java @@ -176,7 +176,7 @@ public class ServletUpgradeResponse if (matches < 1) throw new IllegalArgumentException("Extension not a requested extension"); - matches = negotiation.getNegotiatedExtensions().stream().filter(e -> e.getName().equals(config.getName())).count(); + matches = configs.stream().filter(e -> e.getName().equals(config.getName())).count(); if (matches > 1) throw new IllegalArgumentException("Multiple extensions of the same name"); }