--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/AutomaticPong.java Mon Mar 19 21:04:01 2018 +0000
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @build DummyWebSocketServer
+ * @run testng/othervm
+ * -Djdk.internal.httpclient.websocket.debug=true
+ * AutomaticPong
+ */
+
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import static java.net.http.HttpClient.newHttpClient;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+public class AutomaticPong {
+
+ private WebSocket webSocket;
+ private DummyWebSocketServer server;
+
+ @AfterTest
+ public void cleanup() {
+ server.close();
+ webSocket.abort();
+ }
+
+ /*
+ * The sendClose method has been invoked and a Ping comes from the server.
+ * Naturally, the client cannot reply with a Pong (the output has been
+ * closed). However, this MUST not be treated as an error.
+ * At this stage the server either has received or pretty soon will receive
+ * the Close message sent by the sendClose. Thus, the server will know the
+ * client cannot send further messages and it's up to the server to decide
+ * how to react on the corresponding Pong not being received.
+ */
+ @Test
+ public void sendCloseThenAutomaticPong() throws IOException {
+ int[] bytes = {
+ 0x89, 0x00, // ping
+ 0x89, 0x06, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x3f, // ping hello?
+ 0x88, 0x00, // close
+ };
+ server = Support.serverWithCannedData(bytes);
+ server.open();
+ MockListener listener = new MockListener() {
+ @Override
+ protected void onOpen0(WebSocket webSocket) {
+ /* request nothing */
+ }
+ };
+ webSocket = newHttpClient()
+ .newWebSocketBuilder()
+ .buildAsync(server.getURI(), listener)
+ .join();
+
+ webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok").join();
+ // now request all messages available
+ webSocket.request(Long.MAX_VALUE);
+ List<MockListener.Invocation> actual = listener.invocations();
+ ByteBuffer hello = ByteBuffer.wrap("hello?".getBytes(StandardCharsets.UTF_8));
+ ByteBuffer empty = ByteBuffer.allocate(0);
+ List<MockListener.Invocation> expected = List.of(
+ MockListener.Invocation.onOpen(webSocket),
+ MockListener.Invocation.onPing(webSocket, empty),
+ MockListener.Invocation.onPing(webSocket, hello),
+ MockListener.Invocation.onClose(webSocket, 1005, "")
+ );
+ assertEquals(actual, expected);
+ }
+
+ /*
+ * The server sends a number of contiguous Ping messages. The client replies
+ * to these messages automatically. According to RFC 6455 a WebSocket client
+ * is free to reply only to the most recent Pings.
+ *
+ * What is checked here is that
+ *
+ * a) the order of Pong replies corresponds to the Pings received,
+ * b) the last Pong corresponds to the last Ping
+ * c) there are no unrelated Pongs
+ */
+ @Test(dataProvider = "nPings")
+ public void automaticPongs(int nPings) throws Exception {
+ // big enough to not bother with resize
+ ByteBuffer buffer = ByteBuffer.allocate(65536);
+ Frame.HeaderWriter w = new Frame.HeaderWriter();
+ for (int i = 0; i < nPings; i++) {
+ w.fin(true)
+ .opcode(Frame.Opcode.PING)
+ .noMask()
+ .payloadLen(4)
+ .write(buffer);
+ buffer.putInt(i);
+ }
+ w.fin(true)
+ .opcode(Frame.Opcode.CLOSE)
+ .noMask()
+ .payloadLen(2)
+ .write(buffer);
+ buffer.putChar((char) 1000);
+ buffer.flip();
+ server = Support.serverWithCannedData(buffer.array());
+ server.open();
+ MockListener listener = new MockListener();
+ webSocket = newHttpClient()
+ .newWebSocketBuilder()
+ .buildAsync(server.getURI(), listener)
+ .join();
+ List<MockListener.Invocation> inv = listener.invocations();
+ assertEquals(inv.size(), nPings + 2); // onOpen + onClose + n*onPing
+
+ ByteBuffer data = server.read();
+ Frame.Reader reader = new Frame.Reader();
+
+ Frame.Consumer consumer = new Frame.Consumer() {
+
+ ByteBuffer number = ByteBuffer.allocate(4);
+ Frame.Masker masker = new Frame.Masker();
+ int i = -1;
+ boolean closed;
+
+ @Override
+ public void fin(boolean value) { assertTrue(value); }
+
+ @Override
+ public void rsv1(boolean value) { assertFalse(value); }
+
+ @Override
+ public void rsv2(boolean value) { assertFalse(value); }
+
+ @Override
+ public void rsv3(boolean value) { assertFalse(value); }
+
+ @Override
+ public void opcode(Frame.Opcode value) {
+ if (value == Frame.Opcode.CLOSE) {
+ closed = true;
+ return;
+ }
+ assertEquals(value, Frame.Opcode.PONG);
+ }
+
+ @Override
+ public void mask(boolean value) { assertTrue(value); }
+
+ @Override
+ public void payloadLen(long value) {
+ if (!closed)
+ assertEquals(value, 4);
+ }
+
+ @Override
+ public void maskingKey(int value) {
+ masker.mask(value);
+ }
+
+ @Override
+ public void payloadData(ByteBuffer src) {
+ masker.transferMasking(src, number);
+ if (closed) {
+ return;
+ }
+ number.flip();
+ int n = number.getInt();
+ System.out.printf("pong number=%s%n", n);
+ number.clear();
+ if (i >= n) {
+ fail(String.format("i=%s, n=%s", i, n));
+ }
+ i = n;
+ }
+
+ @Override
+ public void endFrame() { }
+ };
+ while (data.hasRemaining()) {
+ reader.readFrame(data, consumer);
+ }
+ }
+
+
+ @DataProvider(name = "nPings")
+ public Object[][] nPings() {
+ return new Object[][]{{1}, {2}, {4}, {8}, {9}, {1023}};
+ }
+}