1 /*
2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 /*
25 * @test
26 * @build DummyWebSocketServer
27 * @run testng/othervm
28 * WebSocketTest
29 */
30
31 import org.testng.annotations.AfterTest;
32 import org.testng.annotations.DataProvider;
33 import org.testng.annotations.Test;
34
35 import java.io.IOException;
36 import java.net.http.WebSocket;
37 import java.nio.ByteBuffer;
38 import java.nio.charset.StandardCharsets;
39 import java.util.ArrayList;
40 import java.util.List;
41 import java.util.concurrent.CompletableFuture;
42 import java.util.concurrent.CompletionStage;
43 import java.util.concurrent.TimeUnit;
44 import java.util.concurrent.atomic.AtomicBoolean;
45 import java.util.function.Supplier;
46 import java.util.stream.Collectors;
47
48 import static java.net.http.HttpClient.Builder.NO_PROXY;
49 import static java.net.http.HttpClient.newBuilder;
50 import static java.net.http.WebSocket.NORMAL_CLOSURE;
51 import static org.testng.Assert.assertEquals;
52 import static org.testng.Assert.assertThrows;
53
54 public class WebSocketTest {
55
56 private static final Class<IllegalArgumentException> IAE = IllegalArgumentException.class;
57 private static final Class<IllegalStateException> ISE = IllegalStateException.class;
58 private static final Class<IOException> IOE = IOException.class;
59
60 /* shortcut */
61 private static void assertFails(Class<? extends Throwable> clazz,
62 CompletionStage<?> stage) {
63 Support.assertCompletesExceptionally(clazz, stage);
64 }
65
66 private DummyWebSocketServer server;
67 private WebSocket webSocket;
68
69 @AfterTest
70 public void cleanup() {
71 server.close();
72 webSocket.abort();
73 }
74
75 @Test
76 public void illegalArgument() throws IOException {
77 server = new DummyWebSocketServer();
78 server.open();
79 webSocket = newBuilder().proxy(NO_PROXY).build()
80 .newWebSocketBuilder()
81 .buildAsync(server.getURI(), new WebSocket.Listener() { })
82 .join();
83
84 assertFails(IAE, webSocket.sendPing(ByteBuffer.allocate(126)));
85 assertFails(IAE, webSocket.sendPing(ByteBuffer.allocate(127)));
86 assertFails(IAE, webSocket.sendPing(ByteBuffer.allocate(128)));
87 assertFails(IAE, webSocket.sendPing(ByteBuffer.allocate(129)));
88 assertFails(IAE, webSocket.sendPing(ByteBuffer.allocate(256)));
89
90 assertFails(IAE, webSocket.sendPong(ByteBuffer.allocate(126)));
91 assertFails(IAE, webSocket.sendPong(ByteBuffer.allocate(127)));
117 assertFails(IAE, webSocket.sendClose(1002, "a reason"));
118 assertFails(IAE, webSocket.sendClose(1003, "a reason"));
119 assertFails(IAE, webSocket.sendClose(1006, "a reason"));
120 assertFails(IAE, webSocket.sendClose(1007, "a reason"));
121 assertFails(IAE, webSocket.sendClose(1009, "a reason"));
122 assertFails(IAE, webSocket.sendClose(1010, "a reason"));
123 assertFails(IAE, webSocket.sendClose(1012, "a reason"));
124 assertFails(IAE, webSocket.sendClose(1013, "a reason"));
125 assertFails(IAE, webSocket.sendClose(1015, "a reason"));
126 assertFails(IAE, webSocket.sendClose(5000, "a reason"));
127 assertFails(IAE, webSocket.sendClose(32768, "a reason"));
128 assertFails(IAE, webSocket.sendClose(65535, "a reason"));
129 assertFails(IAE, webSocket.sendClose(65536, "a reason"));
130 assertFails(IAE, webSocket.sendClose(Integer.MAX_VALUE, "a reason"));
131 assertFails(IAE, webSocket.sendClose(Integer.MIN_VALUE, "a reason"));
132
133 assertThrows(IAE, () -> webSocket.request(Integer.MIN_VALUE));
134 assertThrows(IAE, () -> webSocket.request(Long.MIN_VALUE));
135 assertThrows(IAE, () -> webSocket.request(-1));
136 assertThrows(IAE, () -> webSocket.request(0));
137 }
138
139 @Test
140 public void partialBinaryThenText() throws IOException {
141 server = new DummyWebSocketServer();
142 server.open();
143 webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
144 .buildAsync(server.getURI(), new WebSocket.Listener() { })
145 .join();
146 webSocket.sendBinary(ByteBuffer.allocate(16), false).join();
147 assertFails(ISE, webSocket.sendText("text", false));
148 assertFails(ISE, webSocket.sendText("text", true));
149 // Pings & Pongs are fine
150 webSocket.sendPing(ByteBuffer.allocate(125)).join();
151 webSocket.sendPong(ByteBuffer.allocate(125)).join();
152 }
153
154 @Test
155 public void partialTextThenBinary() throws IOException {
156 server = new DummyWebSocketServer();
157 server.open();
158 webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
159 .buildAsync(server.getURI(), new WebSocket.Listener() { })
160 .join();
161
162 webSocket.sendText("text", false).join();
163 assertFails(ISE, webSocket.sendBinary(ByteBuffer.allocate(16), false));
164 assertFails(ISE, webSocket.sendBinary(ByteBuffer.allocate(16), true));
165 // Pings & Pongs are fine
166 webSocket.sendPing(ByteBuffer.allocate(125)).join();
167 webSocket.sendPong(ByteBuffer.allocate(125)).join();
168 }
169
170 @Test
171 public void sendMethodsThrowIOE1() throws IOException {
172 server = new DummyWebSocketServer();
173 server.open();
174 webSocket = newBuilder().proxy(NO_PROXY).build()
175 .newWebSocketBuilder()
176 .buildAsync(server.getURI(), new WebSocket.Listener() { })
177 .join();
178
179 webSocket.sendClose(NORMAL_CLOSURE, "ok").join();
180
181 assertFails(IOE, webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok"));
182
183 assertFails(IOE, webSocket.sendText("", true));
184 assertFails(IOE, webSocket.sendText("", false));
185 assertFails(IOE, webSocket.sendText("abc", true));
186 assertFails(IOE, webSocket.sendText("abc", false));
187 assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(0), true));
188 assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(0), false));
189 assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(1), true));
190 assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(1), false));
191
192 assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(125)));
193 assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(124)));
194 assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(1)));
195 assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(0)));
196
197 assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(125)));
198 assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(124)));
199 assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(1)));
200 assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(0)));
201 }
202
203 @DataProvider(name = "sequence")
204 public Object[][] data1() {
205 int[] CLOSE = {
206 0x81, 0x00, // ""
207 0x82, 0x00, // []
208 0x89, 0x00, // <PING>
209 0x8a, 0x00, // <PONG>
210 0x88, 0x00, // <CLOSE>
211 };
212 int[] ERROR = {
213 0x81, 0x00, // ""
214 0x82, 0x00, // []
215 0x89, 0x00, // <PING>
216 0x8a, 0x00, // <PONG>
217 0x8b, 0x00, // 0xB control frame (causes an error)
218 };
219 return new Object[][]{
220 {CLOSE, 1},
301 () -> super.onClose(webSocket, statusCode, reason));
302 }
303
304 @Override
305 public void onError(WebSocket webSocket, Throwable error) {
306 checkRunExclusively(() -> {
307 super.onError(webSocket, error);
308 return null;
309 });
310 }
311 };
312
313 webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
314 .buildAsync(server.getURI(), listener)
315 .join();
316
317
318 listener.invocations();
319 violation.complete(null); // won't affect if completed exceptionally
320 violation.join();
321 }
322
323 @Test
324 public void sendMethodsThrowIOE2() throws Exception {
325 server = Support.serverWithCannedData(0x88, 0x00);
326 server.open();
327 CompletableFuture<Void> onCloseCalled = new CompletableFuture<>();
328 CompletableFuture<Void> canClose = new CompletableFuture<>();
329
330 WebSocket.Listener listener = new WebSocket.Listener() {
331 @Override
332 public CompletionStage<?> onClose(WebSocket webSocket,
333 int statusCode,
334 String reason) {
335 System.out.printf("onClose(%s, '%s')%n", statusCode, reason);
336 onCloseCalled.complete(null);
337 return canClose;
338 }
339
340 @Override
355 assertFails(IOE, webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok"));
356
357 assertFails(IOE, webSocket.sendText("", true));
358 assertFails(IOE, webSocket.sendText("", false));
359 assertFails(IOE, webSocket.sendText("abc", true));
360 assertFails(IOE, webSocket.sendText("abc", false));
361 assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(0), true));
362 assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(0), false));
363 assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(1), true));
364 assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(1), false));
365
366 assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(125)));
367 assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(124)));
368 assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(1)));
369 assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(0)));
370
371 assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(125)));
372 assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(124)));
373 assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(1)));
374 assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(0)));
375 }
376
377 @Test
378 public void simpleAggregatingBinaryMessages() throws IOException {
379 List<byte[]> expected = List.of("alpha", "beta", "gamma", "delta")
380 .stream()
381 .map(s -> s.getBytes(StandardCharsets.US_ASCII))
382 .collect(Collectors.toList());
383 int[] binary = new int[]{
384 0x82, 0x05, 0x61, 0x6c, 0x70, 0x68, 0x61, // [alpha]
385 0x02, 0x02, 0x62, 0x65, // [be
386 0x80, 0x02, 0x74, 0x61, // ta]
387 0x02, 0x01, 0x67, // [g
388 0x00, 0x01, 0x61, // a
389 0x00, 0x00, //
390 0x00, 0x00, //
391 0x00, 0x01, 0x6d, // m
392 0x00, 0x01, 0x6d, // m
393 0x80, 0x01, 0x61, // a]
394 0x8a, 0x00, // <PONG>
395 0x02, 0x04, 0x64, 0x65, 0x6c, 0x74, // [delt
396 0x00, 0x01, 0x61, // a
397 0x80, 0x00, // ]
398 0x88, 0x00 // <CLOSE>
399 };
400 CompletableFuture<List<byte[]>> actual = new CompletableFuture<>();
401
402 server = Support.serverWithCannedData(binary);
403 server.open();
404
405 WebSocket.Listener listener = new WebSocket.Listener() {
406
407 List<byte[]> collectedBytes = new ArrayList<>();
408 ByteBuffer buffer = ByteBuffer.allocate(1024);
409
410 @Override
411 public CompletionStage<?> onBinary(WebSocket webSocket,
412 ByteBuffer message,
413 boolean last) {
414 System.out.printf("onBinary(%s, %s)%n", message, last);
415 webSocket.request(1);
416
417 append(message);
418 if (last) {
419 buffer.flip();
420 byte[] bytes = new byte[buffer.remaining()];
421 buffer.get(bytes);
422 buffer.clear();
423 processWholeBinary(bytes);
424 }
425 return null;
426 }
427
428 private void append(ByteBuffer message) {
429 if (buffer.remaining() < message.remaining()) {
430 assert message.remaining() > 0;
431 int cap = (buffer.capacity() + message.remaining()) * 2;
432 ByteBuffer b = ByteBuffer.allocate(cap);
433 b.put(buffer.flip());
434 buffer = b;
435 }
436 buffer.put(message);
437 }
438
439 private void processWholeBinary(byte[] bytes) {
440 String stringBytes = new String(bytes, StandardCharsets.UTF_8);
441 System.out.println("processWholeBinary: " + stringBytes);
442 collectedBytes.add(bytes);
443 }
444
445 @Override
446 public CompletionStage<?> onClose(WebSocket webSocket,
447 int statusCode,
448 String reason) {
449 actual.complete(collectedBytes);
450 return null;
451 }
452
453 @Override
454 public void onError(WebSocket webSocket, Throwable error) {
455 actual.completeExceptionally(error);
456 }
457 };
458
459 webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
460 .buildAsync(server.getURI(), listener)
461 .join();
462
463 List<byte[]> a = actual.join();
464 assertEquals(a, expected);
465 }
466
467 @Test
468 public void simpleAggregatingTextMessages() throws IOException {
469
470 List<String> expected = List.of("alpha", "beta", "gamma", "delta");
471
472 int[] binary = new int[]{
473 0x81, 0x05, 0x61, 0x6c, 0x70, 0x68, 0x61, // "alpha"
474 0x01, 0x02, 0x62, 0x65, // "be
475 0x80, 0x02, 0x74, 0x61, // ta"
476 0x01, 0x01, 0x67, // "g
477 0x00, 0x01, 0x61, // a
478 0x00, 0x00, //
479 0x00, 0x00, //
480 0x00, 0x01, 0x6d, // m
481 0x00, 0x01, 0x6d, // m
482 0x80, 0x01, 0x61, // a"
483 0x8a, 0x00, // <PONG>
484 0x01, 0x04, 0x64, 0x65, 0x6c, 0x74, // "delt
485 0x00, 0x01, 0x61, // a
486 0x80, 0x00, // "
487 0x88, 0x00 // <CLOSE>
488 };
489 CompletableFuture<List<String>> actual = new CompletableFuture<>();
490
491 server = Support.serverWithCannedData(binary);
492 server.open();
493
494 WebSocket.Listener listener = new WebSocket.Listener() {
495
496 List<String> collectedStrings = new ArrayList<>();
497 StringBuilder text = new StringBuilder();
498
499 @Override
500 public CompletionStage<?> onText(WebSocket webSocket,
501 CharSequence message,
502 boolean last) {
503 System.out.printf("onText(%s, %s)%n", message, last);
504 webSocket.request(1);
505 text.append(message);
506 if (last) {
507 String str = text.toString();
508 text.setLength(0);
509 processWholeText(str);
510 }
511 return null;
513
514 private void processWholeText(String string) {
515 System.out.println(string);
516 collectedStrings.add(string);
517 }
518
519 @Override
520 public CompletionStage<?> onClose(WebSocket webSocket,
521 int statusCode,
522 String reason) {
523 actual.complete(collectedStrings);
524 return null;
525 }
526
527 @Override
528 public void onError(WebSocket webSocket, Throwable error) {
529 actual.completeExceptionally(error);
530 }
531 };
532
533 webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
534 .buildAsync(server.getURI(), listener)
535 .join();
536
537 List<String> a = actual.join();
538 assertEquals(a, expected);
539 }
540
541 /*
542 * Exercises the scenario where requests for more messages are made prior to
543 * completing the returned CompletionStage instances.
544 */
545 @Test
546 public void aggregatingTextMessages() throws IOException {
547
548 List<String> expected = List.of("alpha", "beta", "gamma", "delta");
549
550 int[] binary = new int[]{
551 0x81, 0x05, 0x61, 0x6c, 0x70, 0x68, 0x61, // "alpha"
552 0x01, 0x02, 0x62, 0x65, // "be
553 0x80, 0x02, 0x74, 0x61, // ta"
554 0x01, 0x01, 0x67, // "g
555 0x00, 0x01, 0x61, // a
556 0x00, 0x00, //
557 0x00, 0x00, //
558 0x00, 0x01, 0x6d, // m
559 0x00, 0x01, 0x6d, // m
560 0x80, 0x01, 0x61, // a"
561 0x8a, 0x00, // <PONG>
562 0x01, 0x04, 0x64, 0x65, 0x6c, 0x74, // "delt
563 0x00, 0x01, 0x61, // a
564 0x80, 0x00, // "
565 0x88, 0x00 // <CLOSE>
566 };
567 CompletableFuture<List<String>> actual = new CompletableFuture<>();
568
569
570 server = Support.serverWithCannedData(binary);
571 server.open();
572
573 WebSocket.Listener listener = new WebSocket.Listener() {
574
575 List<CharSequence> parts = new ArrayList<>();
576 /*
577 * A CompletableFuture which will complete once the current
578 * message has been fully assembled. Until then the listener
579 * returns this instance for every call.
580 */
581 CompletableFuture<?> currentCf = new CompletableFuture<>();
582 List<String> collected = new ArrayList<>();
583
584 @Override
585 public CompletionStage<?> onText(WebSocket webSocket,
586 CharSequence message,
587 boolean last) {
588 parts.add(message);
589 if (!last) {
590 webSocket.request(1);
606 actual.complete(collected);
607 return null;
608 }
609
610 @Override
611 public void onError(WebSocket webSocket, Throwable error) {
612 actual.completeExceptionally(error);
613 }
614
615 public void processWholeMessage(List<CharSequence> data,
616 CompletableFuture<?> cf) {
617 StringBuilder b = new StringBuilder();
618 data.forEach(b::append);
619 String s = b.toString();
620 System.out.println(s);
621 cf.complete(null);
622 collected.add(s);
623 }
624 };
625
626 webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
627 .buildAsync(server.getURI(), listener)
628 .join();
629
630 List<String> a = actual.join();
631 assertEquals(a, expected);
632 }
633 }
|
1 /*
2 * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 /*
25 * @test
26 * @bug 8217429
27 * @build DummyWebSocketServer
28 * @run testng/othervm
29 * WebSocketTest
30 */
31
32 import org.testng.annotations.AfterTest;
33 import org.testng.annotations.DataProvider;
34 import org.testng.annotations.Test;
35
36 import java.io.IOException;
37 import java.net.Authenticator;
38 import java.net.PasswordAuthentication;
39 import java.net.http.HttpResponse;
40 import java.net.http.WebSocket;
41 import java.net.http.WebSocketHandshakeException;
42 import java.nio.ByteBuffer;
43 import java.nio.charset.StandardCharsets;
44 import java.util.ArrayList;
45 import java.util.Base64;
46 import java.util.List;
47 import java.util.concurrent.CompletableFuture;
48 import java.util.concurrent.CompletionException;
49 import java.util.concurrent.CompletionStage;
50 import java.util.concurrent.TimeUnit;
51 import java.util.concurrent.atomic.AtomicBoolean;
52 import java.util.function.Function;
53 import java.util.function.Supplier;
54 import java.util.stream.Collectors;
55
56 import static java.net.http.HttpClient.Builder.NO_PROXY;
57 import static java.net.http.HttpClient.newBuilder;
58 import static java.net.http.WebSocket.NORMAL_CLOSURE;
59 import static java.nio.charset.StandardCharsets.UTF_8;
60 import static org.testng.Assert.assertEquals;
61 import static org.testng.Assert.assertThrows;
62 import static org.testng.Assert.fail;
63
64 public class WebSocketTest {
65
66 private static final Class<IllegalArgumentException> IAE = IllegalArgumentException.class;
67 private static final Class<IllegalStateException> ISE = IllegalStateException.class;
68 private static final Class<IOException> IOE = IOException.class;
69
70 /* shortcut */
71 private static void assertFails(Class<? extends Throwable> clazz,
72 CompletionStage<?> stage) {
73 Support.assertCompletesExceptionally(clazz, stage);
74 }
75
76 private DummyWebSocketServer server;
77 private WebSocket webSocket;
78
79 @AfterTest
80 public void cleanup() {
81 System.out.println("AFTER TEST");
82 if (server != null)
83 server.close();
84 if (webSocket != null)
85 webSocket.abort();
86 }
87
88 @Test
89 public void illegalArgument() throws IOException {
90 server = new DummyWebSocketServer();
91 server.open();
92 webSocket = newBuilder().proxy(NO_PROXY).build()
93 .newWebSocketBuilder()
94 .buildAsync(server.getURI(), new WebSocket.Listener() { })
95 .join();
96
97 assertFails(IAE, webSocket.sendPing(ByteBuffer.allocate(126)));
98 assertFails(IAE, webSocket.sendPing(ByteBuffer.allocate(127)));
99 assertFails(IAE, webSocket.sendPing(ByteBuffer.allocate(128)));
100 assertFails(IAE, webSocket.sendPing(ByteBuffer.allocate(129)));
101 assertFails(IAE, webSocket.sendPing(ByteBuffer.allocate(256)));
102
103 assertFails(IAE, webSocket.sendPong(ByteBuffer.allocate(126)));
104 assertFails(IAE, webSocket.sendPong(ByteBuffer.allocate(127)));
130 assertFails(IAE, webSocket.sendClose(1002, "a reason"));
131 assertFails(IAE, webSocket.sendClose(1003, "a reason"));
132 assertFails(IAE, webSocket.sendClose(1006, "a reason"));
133 assertFails(IAE, webSocket.sendClose(1007, "a reason"));
134 assertFails(IAE, webSocket.sendClose(1009, "a reason"));
135 assertFails(IAE, webSocket.sendClose(1010, "a reason"));
136 assertFails(IAE, webSocket.sendClose(1012, "a reason"));
137 assertFails(IAE, webSocket.sendClose(1013, "a reason"));
138 assertFails(IAE, webSocket.sendClose(1015, "a reason"));
139 assertFails(IAE, webSocket.sendClose(5000, "a reason"));
140 assertFails(IAE, webSocket.sendClose(32768, "a reason"));
141 assertFails(IAE, webSocket.sendClose(65535, "a reason"));
142 assertFails(IAE, webSocket.sendClose(65536, "a reason"));
143 assertFails(IAE, webSocket.sendClose(Integer.MAX_VALUE, "a reason"));
144 assertFails(IAE, webSocket.sendClose(Integer.MIN_VALUE, "a reason"));
145
146 assertThrows(IAE, () -> webSocket.request(Integer.MIN_VALUE));
147 assertThrows(IAE, () -> webSocket.request(Long.MIN_VALUE));
148 assertThrows(IAE, () -> webSocket.request(-1));
149 assertThrows(IAE, () -> webSocket.request(0));
150
151 server.close();
152 }
153
154 @Test
155 public void partialBinaryThenText() throws IOException {
156 server = new DummyWebSocketServer();
157 server.open();
158 webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
159 .buildAsync(server.getURI(), new WebSocket.Listener() { })
160 .join();
161 webSocket.sendBinary(ByteBuffer.allocate(16), false).join();
162 assertFails(ISE, webSocket.sendText("text", false));
163 assertFails(ISE, webSocket.sendText("text", true));
164 // Pings & Pongs are fine
165 webSocket.sendPing(ByteBuffer.allocate(125)).join();
166 webSocket.sendPong(ByteBuffer.allocate(125)).join();
167 server.close();
168 }
169
170 @Test
171 public void partialTextThenBinary() throws IOException {
172 server = new DummyWebSocketServer();
173 server.open();
174 webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
175 .buildAsync(server.getURI(), new WebSocket.Listener() { })
176 .join();
177
178 webSocket.sendText("text", false).join();
179 assertFails(ISE, webSocket.sendBinary(ByteBuffer.allocate(16), false));
180 assertFails(ISE, webSocket.sendBinary(ByteBuffer.allocate(16), true));
181 // Pings & Pongs are fine
182 webSocket.sendPing(ByteBuffer.allocate(125)).join();
183 webSocket.sendPong(ByteBuffer.allocate(125)).join();
184 server.close();
185 }
186
187 @Test
188 public void sendMethodsThrowIOE1() throws IOException {
189 server = new DummyWebSocketServer();
190 server.open();
191 webSocket = newBuilder().proxy(NO_PROXY).build()
192 .newWebSocketBuilder()
193 .buildAsync(server.getURI(), new WebSocket.Listener() { })
194 .join();
195
196 webSocket.sendClose(NORMAL_CLOSURE, "ok").join();
197
198 assertFails(IOE, webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok"));
199
200 assertFails(IOE, webSocket.sendText("", true));
201 assertFails(IOE, webSocket.sendText("", false));
202 assertFails(IOE, webSocket.sendText("abc", true));
203 assertFails(IOE, webSocket.sendText("abc", false));
204 assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(0), true));
205 assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(0), false));
206 assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(1), true));
207 assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(1), false));
208
209 assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(125)));
210 assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(124)));
211 assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(1)));
212 assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(0)));
213
214 assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(125)));
215 assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(124)));
216 assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(1)));
217 assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(0)));
218
219 server.close();
220 }
221
222 @DataProvider(name = "sequence")
223 public Object[][] data1() {
224 int[] CLOSE = {
225 0x81, 0x00, // ""
226 0x82, 0x00, // []
227 0x89, 0x00, // <PING>
228 0x8a, 0x00, // <PONG>
229 0x88, 0x00, // <CLOSE>
230 };
231 int[] ERROR = {
232 0x81, 0x00, // ""
233 0x82, 0x00, // []
234 0x89, 0x00, // <PING>
235 0x8a, 0x00, // <PONG>
236 0x8b, 0x00, // 0xB control frame (causes an error)
237 };
238 return new Object[][]{
239 {CLOSE, 1},
320 () -> super.onClose(webSocket, statusCode, reason));
321 }
322
323 @Override
324 public void onError(WebSocket webSocket, Throwable error) {
325 checkRunExclusively(() -> {
326 super.onError(webSocket, error);
327 return null;
328 });
329 }
330 };
331
332 webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
333 .buildAsync(server.getURI(), listener)
334 .join();
335
336
337 listener.invocations();
338 violation.complete(null); // won't affect if completed exceptionally
339 violation.join();
340
341 server.close();
342 }
343
344 @Test
345 public void sendMethodsThrowIOE2() throws Exception {
346 server = Support.serverWithCannedData(0x88, 0x00);
347 server.open();
348 CompletableFuture<Void> onCloseCalled = new CompletableFuture<>();
349 CompletableFuture<Void> canClose = new CompletableFuture<>();
350
351 WebSocket.Listener listener = new WebSocket.Listener() {
352 @Override
353 public CompletionStage<?> onClose(WebSocket webSocket,
354 int statusCode,
355 String reason) {
356 System.out.printf("onClose(%s, '%s')%n", statusCode, reason);
357 onCloseCalled.complete(null);
358 return canClose;
359 }
360
361 @Override
376 assertFails(IOE, webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok"));
377
378 assertFails(IOE, webSocket.sendText("", true));
379 assertFails(IOE, webSocket.sendText("", false));
380 assertFails(IOE, webSocket.sendText("abc", true));
381 assertFails(IOE, webSocket.sendText("abc", false));
382 assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(0), true));
383 assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(0), false));
384 assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(1), true));
385 assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(1), false));
386
387 assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(125)));
388 assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(124)));
389 assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(1)));
390 assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(0)));
391
392 assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(125)));
393 assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(124)));
394 assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(1)));
395 assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(0)));
396
397 server.close();
398 }
399
400 // Used to verify a server requiring Authentication
401 private static final String USERNAME = "chegar";
402 private static final String PASSWORD = "a1b2c3";
403
404 static class WSAuthenticator extends Authenticator {
405 @Override
406 protected PasswordAuthentication getPasswordAuthentication() {
407 return new PasswordAuthentication(USERNAME, PASSWORD.toCharArray());
408 }
409 }
410
411 static final Function<int[],DummyWebSocketServer> SERVER_WITH_CANNED_DATA =
412 new Function<>() {
413 @Override public DummyWebSocketServer apply(int[] data) {
414 return Support.serverWithCannedData(data); }
415 @Override public String toString() { return "SERVER_WITH_CANNED_DATA"; }
416 };
417
418 static final Function<int[],DummyWebSocketServer> AUTH_SERVER_WITH_CANNED_DATA =
419 new Function<>() {
420 @Override public DummyWebSocketServer apply(int[] data) {
421 return Support.serverWithCannedDataAndAuthentication(USERNAME, PASSWORD, data); }
422 @Override public String toString() { return "AUTH_SERVER_WITH_CANNED_DATA"; }
423 };
424
425 @DataProvider(name = "servers")
426 public Object[][] servers() {
427 return new Object[][] {
428 { SERVER_WITH_CANNED_DATA },
429 { AUTH_SERVER_WITH_CANNED_DATA },
430 };
431 }
432
433 @Test(dataProvider = "servers")
434 public void simpleAggregatingBinaryMessages
435 (Function<int[],DummyWebSocketServer> serverSupplier)
436 throws IOException
437 {
438 List<byte[]> expected = List.of("alpha", "beta", "gamma", "delta")
439 .stream()
440 .map(s -> s.getBytes(StandardCharsets.US_ASCII))
441 .collect(Collectors.toList());
442 int[] binary = new int[]{
443 0x82, 0x05, 0x61, 0x6c, 0x70, 0x68, 0x61, // [alpha]
444 0x02, 0x02, 0x62, 0x65, // [be
445 0x80, 0x02, 0x74, 0x61, // ta]
446 0x02, 0x01, 0x67, // [g
447 0x00, 0x01, 0x61, // a
448 0x00, 0x00, //
449 0x00, 0x00, //
450 0x00, 0x01, 0x6d, // m
451 0x00, 0x01, 0x6d, // m
452 0x80, 0x01, 0x61, // a]
453 0x8a, 0x00, // <PONG>
454 0x02, 0x04, 0x64, 0x65, 0x6c, 0x74, // [delt
455 0x00, 0x01, 0x61, // a
456 0x80, 0x00, // ]
457 0x88, 0x00 // <CLOSE>
458 };
459 CompletableFuture<List<byte[]>> actual = new CompletableFuture<>();
460
461 server = serverSupplier.apply(binary);
462 server.open();
463
464 WebSocket.Listener listener = new WebSocket.Listener() {
465
466 List<byte[]> collectedBytes = new ArrayList<>();
467 ByteBuffer buffer = ByteBuffer.allocate(1024);
468
469 @Override
470 public CompletionStage<?> onBinary(WebSocket webSocket,
471 ByteBuffer message,
472 boolean last) {
473 System.out.printf("onBinary(%s, %s)%n", message, last);
474 webSocket.request(1);
475
476 append(message);
477 if (last) {
478 buffer.flip();
479 byte[] bytes = new byte[buffer.remaining()];
480 buffer.get(bytes);
481 buffer.clear();
482 processWholeBinary(bytes);
483 }
484 return null;
485 }
486
487 private void append(ByteBuffer message) {
488 if (buffer.remaining() < message.remaining()) {
489 assert message.remaining() > 0;
490 int cap = (buffer.capacity() + message.remaining()) * 2;
491 ByteBuffer b = ByteBuffer.allocate(cap);
492 b.put(buffer.flip());
493 buffer = b;
494 }
495 buffer.put(message);
496 }
497
498 private void processWholeBinary(byte[] bytes) {
499 String stringBytes = new String(bytes, UTF_8);
500 System.out.println("processWholeBinary: " + stringBytes);
501 collectedBytes.add(bytes);
502 }
503
504 @Override
505 public CompletionStage<?> onClose(WebSocket webSocket,
506 int statusCode,
507 String reason) {
508 actual.complete(collectedBytes);
509 return null;
510 }
511
512 @Override
513 public void onError(WebSocket webSocket, Throwable error) {
514 actual.completeExceptionally(error);
515 }
516 };
517
518 webSocket = newBuilder()
519 .proxy(NO_PROXY)
520 .authenticator(new WSAuthenticator())
521 .build().newWebSocketBuilder()
522 .buildAsync(server.getURI(), listener)
523 .join();
524
525 List<byte[]> a = actual.join();
526 assertEquals(a, expected);
527
528 server.close();
529 }
530
531 @Test(dataProvider = "servers")
532 public void simpleAggregatingTextMessages
533 (Function<int[],DummyWebSocketServer> serverSupplier)
534 throws IOException
535 {
536 List<String> expected = List.of("alpha", "beta", "gamma", "delta");
537
538 int[] binary = new int[]{
539 0x81, 0x05, 0x61, 0x6c, 0x70, 0x68, 0x61, // "alpha"
540 0x01, 0x02, 0x62, 0x65, // "be
541 0x80, 0x02, 0x74, 0x61, // ta"
542 0x01, 0x01, 0x67, // "g
543 0x00, 0x01, 0x61, // a
544 0x00, 0x00, //
545 0x00, 0x00, //
546 0x00, 0x01, 0x6d, // m
547 0x00, 0x01, 0x6d, // m
548 0x80, 0x01, 0x61, // a"
549 0x8a, 0x00, // <PONG>
550 0x01, 0x04, 0x64, 0x65, 0x6c, 0x74, // "delt
551 0x00, 0x01, 0x61, // a
552 0x80, 0x00, // "
553 0x88, 0x00 // <CLOSE>
554 };
555 CompletableFuture<List<String>> actual = new CompletableFuture<>();
556
557 server = serverSupplier.apply(binary);
558 server.open();
559
560 WebSocket.Listener listener = new WebSocket.Listener() {
561
562 List<String> collectedStrings = new ArrayList<>();
563 StringBuilder text = new StringBuilder();
564
565 @Override
566 public CompletionStage<?> onText(WebSocket webSocket,
567 CharSequence message,
568 boolean last) {
569 System.out.printf("onText(%s, %s)%n", message, last);
570 webSocket.request(1);
571 text.append(message);
572 if (last) {
573 String str = text.toString();
574 text.setLength(0);
575 processWholeText(str);
576 }
577 return null;
579
580 private void processWholeText(String string) {
581 System.out.println(string);
582 collectedStrings.add(string);
583 }
584
585 @Override
586 public CompletionStage<?> onClose(WebSocket webSocket,
587 int statusCode,
588 String reason) {
589 actual.complete(collectedStrings);
590 return null;
591 }
592
593 @Override
594 public void onError(WebSocket webSocket, Throwable error) {
595 actual.completeExceptionally(error);
596 }
597 };
598
599 webSocket = newBuilder()
600 .proxy(NO_PROXY)
601 .authenticator(new WSAuthenticator())
602 .build().newWebSocketBuilder()
603 .buildAsync(server.getURI(), listener)
604 .join();
605
606 List<String> a = actual.join();
607 assertEquals(a, expected);
608
609 server.close();
610 }
611
612 /*
613 * Exercises the scenario where requests for more messages are made prior to
614 * completing the returned CompletionStage instances.
615 */
616 @Test(dataProvider = "servers")
617 public void aggregatingTextMessages
618 (Function<int[],DummyWebSocketServer> serverSupplier)
619 throws IOException
620 {
621 List<String> expected = List.of("alpha", "beta", "gamma", "delta");
622
623 int[] binary = new int[]{
624 0x81, 0x05, 0x61, 0x6c, 0x70, 0x68, 0x61, // "alpha"
625 0x01, 0x02, 0x62, 0x65, // "be
626 0x80, 0x02, 0x74, 0x61, // ta"
627 0x01, 0x01, 0x67, // "g
628 0x00, 0x01, 0x61, // a
629 0x00, 0x00, //
630 0x00, 0x00, //
631 0x00, 0x01, 0x6d, // m
632 0x00, 0x01, 0x6d, // m
633 0x80, 0x01, 0x61, // a"
634 0x8a, 0x00, // <PONG>
635 0x01, 0x04, 0x64, 0x65, 0x6c, 0x74, // "delt
636 0x00, 0x01, 0x61, // a
637 0x80, 0x00, // "
638 0x88, 0x00 // <CLOSE>
639 };
640 CompletableFuture<List<String>> actual = new CompletableFuture<>();
641
642 server = serverSupplier.apply(binary);
643 server.open();
644
645 WebSocket.Listener listener = new WebSocket.Listener() {
646
647 List<CharSequence> parts = new ArrayList<>();
648 /*
649 * A CompletableFuture which will complete once the current
650 * message has been fully assembled. Until then the listener
651 * returns this instance for every call.
652 */
653 CompletableFuture<?> currentCf = new CompletableFuture<>();
654 List<String> collected = new ArrayList<>();
655
656 @Override
657 public CompletionStage<?> onText(WebSocket webSocket,
658 CharSequence message,
659 boolean last) {
660 parts.add(message);
661 if (!last) {
662 webSocket.request(1);
678 actual.complete(collected);
679 return null;
680 }
681
682 @Override
683 public void onError(WebSocket webSocket, Throwable error) {
684 actual.completeExceptionally(error);
685 }
686
687 public void processWholeMessage(List<CharSequence> data,
688 CompletableFuture<?> cf) {
689 StringBuilder b = new StringBuilder();
690 data.forEach(b::append);
691 String s = b.toString();
692 System.out.println(s);
693 cf.complete(null);
694 collected.add(s);
695 }
696 };
697
698 webSocket = newBuilder()
699 .proxy(NO_PROXY)
700 .authenticator(new WSAuthenticator())
701 .build().newWebSocketBuilder()
702 .buildAsync(server.getURI(), listener)
703 .join();
704
705 List<String> a = actual.join();
706 assertEquals(a, expected);
707
708 server.close();
709 }
710
711 // -- authentication specific tests
712
713 /*
714 * Ensures authentication succeeds when an Authenticator set on client builder.
715 */
716 @Test
717 public void clientAuthenticate() throws IOException {
718 try (var server = new DummyWebSocketServer(USERNAME, PASSWORD)){
719 server.open();
720
721 var webSocket = newBuilder()
722 .proxy(NO_PROXY)
723 .authenticator(new WSAuthenticator())
724 .build()
725 .newWebSocketBuilder()
726 .buildAsync(server.getURI(), new WebSocket.Listener() { })
727 .join();
728 }
729 }
730
731 /*
732 * Ensures authentication succeeds when an `Authorization` header is explicitly set.
733 */
734 @Test
735 public void explicitAuthenticate() throws IOException {
736 try (var server = new DummyWebSocketServer(USERNAME, PASSWORD)) {
737 server.open();
738
739 String hv = "Basic " + Base64.getEncoder().encodeToString(
740 (USERNAME + ":" + PASSWORD).getBytes(UTF_8));
741
742 var webSocket = newBuilder()
743 .proxy(NO_PROXY).build()
744 .newWebSocketBuilder()
745 .header("Authorization", hv)
746 .buildAsync(server.getURI(), new WebSocket.Listener() { })
747 .join();
748 }
749 }
750
751 /*
752 * Ensures authentication does not succeed when no authenticator is present.
753 */
754 @Test
755 public void failNoAuthenticator() throws IOException {
756 try (var server = new DummyWebSocketServer(USERNAME, PASSWORD)) {
757 server.open();
758
759 CompletableFuture<WebSocket> cf = newBuilder()
760 .proxy(NO_PROXY).build()
761 .newWebSocketBuilder()
762 .buildAsync(server.getURI(), new WebSocket.Listener() { });
763
764 try {
765 var webSocket = cf.join();
766 fail("Expected exception not thrown");
767 } catch (CompletionException expected) {
768 WebSocketHandshakeException e = (WebSocketHandshakeException)expected.getCause();
769 HttpResponse<?> response = e.getResponse();
770 assertEquals(response.statusCode(), 401);
771 }
772 }
773 }
774
775 /*
776 * Ensures authentication does not succeed when the authenticator presents
777 * unauthorized credentials.
778 */
779 @Test
780 public void failBadCredentials() throws IOException {
781 try (var server = new DummyWebSocketServer(USERNAME, PASSWORD)) {
782 server.open();
783
784 Authenticator authenticator = new Authenticator() {
785 @Override protected PasswordAuthentication getPasswordAuthentication() {
786 return new PasswordAuthentication("BAD"+USERNAME, "".toCharArray());
787 }
788 };
789
790 CompletableFuture<WebSocket> cf = newBuilder()
791 .proxy(NO_PROXY)
792 .authenticator(authenticator)
793 .build()
794 .newWebSocketBuilder()
795 .buildAsync(server.getURI(), new WebSocket.Listener() { });
796
797 try {
798 var webSocket = cf.join();
799 fail("Expected exception not thrown");
800 } catch (CompletionException expected) {
801 System.out.println("caught expected exception:" + expected);
802 }
803 }
804 }
805 }
|