43 import java.util.concurrent.CompletableFuture; |
41 import java.util.concurrent.CompletableFuture; |
44 import java.util.concurrent.CompletionStage; |
42 import java.util.concurrent.CompletionStage; |
45 import java.util.concurrent.TimeUnit; |
43 import java.util.concurrent.TimeUnit; |
46 import java.util.concurrent.TimeoutException; |
44 import java.util.concurrent.TimeoutException; |
47 import java.util.stream.Collectors; |
45 import java.util.stream.Collectors; |
48 |
46 import org.testng.annotations.DataProvider; |
|
47 import org.testng.annotations.Test; |
49 import static java.net.http.HttpClient.newHttpClient; |
48 import static java.net.http.HttpClient.newHttpClient; |
50 import static java.net.http.WebSocket.NORMAL_CLOSURE; |
49 import static java.net.http.WebSocket.NORMAL_CLOSURE; |
51 import static org.testng.Assert.assertEquals; |
50 import static org.testng.Assert.assertEquals; |
52 import static org.testng.Assert.assertFalse; |
51 import static org.testng.Assert.assertFalse; |
53 import static org.testng.Assert.assertThrows; |
52 import static org.testng.Assert.assertThrows; |
54 import static org.testng.Assert.assertTrue; |
53 import static org.testng.Assert.assertTrue; |
55 import static org.testng.Assert.fail; |
54 import static org.testng.Assert.fail; |
56 |
55 |
57 public class WebSocketTest { |
56 public class WebSocketTest { |
58 |
57 |
59 private static final Class<NullPointerException> NPE = NullPointerException.class; |
|
60 private static final Class<IllegalArgumentException> IAE = IllegalArgumentException.class; |
58 private static final Class<IllegalArgumentException> IAE = IllegalArgumentException.class; |
61 private static final Class<IllegalStateException> ISE = IllegalStateException.class; |
59 private static final Class<IllegalStateException> ISE = IllegalStateException.class; |
62 private static final Class<IOException> IOE = IOException.class; |
60 private static final Class<IOException> IOE = IOException.class; |
63 |
|
64 /* |
|
65 * Examines WebSocket behaviour after a call to abort() |
|
66 */ |
|
67 @Test |
|
68 public void immediateAbort() throws Exception { |
|
69 try (DummyWebSocketServer server = serverWithCannedData(0x81, 0x00, 0x88, 0x00)) { |
|
70 server.open(); |
|
71 CompletableFuture<Void> messageReceived = new CompletableFuture<>(); |
|
72 WebSocket.Listener listener = new WebSocket.Listener() { |
|
73 |
|
74 @Override |
|
75 public void onOpen(WebSocket webSocket) { |
|
76 /* no initial request */ |
|
77 } |
|
78 |
|
79 @Override |
|
80 public CompletionStage<?> onText(WebSocket webSocket, |
|
81 CharSequence message, |
|
82 WebSocket.MessagePart part) { |
|
83 messageReceived.complete(null); |
|
84 return null; |
|
85 } |
|
86 |
|
87 @Override |
|
88 public CompletionStage<?> onBinary(WebSocket webSocket, |
|
89 ByteBuffer message, |
|
90 WebSocket.MessagePart part) { |
|
91 messageReceived.complete(null); |
|
92 return null; |
|
93 } |
|
94 |
|
95 @Override |
|
96 public CompletionStage<?> onPing(WebSocket webSocket, |
|
97 ByteBuffer message) { |
|
98 messageReceived.complete(null); |
|
99 return null; |
|
100 } |
|
101 |
|
102 @Override |
|
103 public CompletionStage<?> onPong(WebSocket webSocket, |
|
104 ByteBuffer message) { |
|
105 messageReceived.complete(null); |
|
106 return null; |
|
107 } |
|
108 |
|
109 @Override |
|
110 public CompletionStage<?> onClose(WebSocket webSocket, |
|
111 int statusCode, |
|
112 String reason) { |
|
113 messageReceived.complete(null); |
|
114 return null; |
|
115 } |
|
116 }; |
|
117 |
|
118 WebSocket ws = newHttpClient() |
|
119 .newWebSocketBuilder() |
|
120 .buildAsync(server.getURI(), listener) |
|
121 .join(); |
|
122 for (int i = 0; i < 3; i++) { |
|
123 System.out.printf("iteration #%s%n", i); |
|
124 // after the first abort() each consecutive one must be a no-op, |
|
125 // moreover, query methods should continue to return consistent, |
|
126 // permanent values |
|
127 for (int j = 0; j < 3; j++) { |
|
128 System.out.printf("abort #%s%n", j); |
|
129 ws.abort(); |
|
130 assertTrue(ws.isInputClosed()); |
|
131 assertTrue(ws.isOutputClosed()); |
|
132 assertEquals(ws.getSubprotocol(), ""); |
|
133 } |
|
134 // at this point valid requests MUST be a no-op: |
|
135 for (int j = 0; j < 3; j++) { |
|
136 System.out.printf("request #%s%n", j); |
|
137 ws.request(1); |
|
138 ws.request(2); |
|
139 ws.request(8); |
|
140 ws.request(Integer.MAX_VALUE); |
|
141 ws.request(Long.MAX_VALUE); |
|
142 // invalid requests MUST throw IAE: |
|
143 assertThrows(IAE, () -> ws.request(Integer.MIN_VALUE)); |
|
144 assertThrows(IAE, () -> ws.request(Long.MIN_VALUE)); |
|
145 assertThrows(IAE, () -> ws.request(-1)); |
|
146 assertThrows(IAE, () -> ws.request(0)); |
|
147 } |
|
148 } |
|
149 // even though there is a bunch of messages readily available on the |
|
150 // wire we shouldn't have received any of them as we aborted before |
|
151 // the first request |
|
152 try { |
|
153 messageReceived.get(10, TimeUnit.SECONDS); |
|
154 fail(); |
|
155 } catch (TimeoutException expected) { |
|
156 System.out.println("Finished waiting"); |
|
157 } |
|
158 for (int i = 0; i < 3; i++) { |
|
159 System.out.printf("send #%s%n", i); |
|
160 assertFails(IOE, ws.sendText("text!", false)); |
|
161 assertFails(IOE, ws.sendText("text!", true)); |
|
162 assertFails(IOE, ws.sendBinary(ByteBuffer.allocate(16), false)); |
|
163 assertFails(IOE, ws.sendBinary(ByteBuffer.allocate(16), true)); |
|
164 assertFails(IOE, ws.sendPing(ByteBuffer.allocate(16))); |
|
165 assertFails(IOE, ws.sendPong(ByteBuffer.allocate(16))); |
|
166 assertFails(IOE, ws.sendClose(NORMAL_CLOSURE, "a reason")); |
|
167 assertThrows(NPE, () -> ws.sendText(null, false)); |
|
168 assertThrows(NPE, () -> ws.sendText(null, true)); |
|
169 assertThrows(NPE, () -> ws.sendBinary(null, false)); |
|
170 assertThrows(NPE, () -> ws.sendBinary(null, true)); |
|
171 assertThrows(NPE, () -> ws.sendPing(null)); |
|
172 assertThrows(NPE, () -> ws.sendPong(null)); |
|
173 assertThrows(NPE, () -> ws.sendClose(NORMAL_CLOSURE, null)); |
|
174 } |
|
175 } |
|
176 } |
|
177 |
61 |
178 /* shortcut */ |
62 /* shortcut */ |
179 private static void assertFails(Class<? extends Throwable> clazz, |
63 private static void assertFails(Class<? extends Throwable> clazz, |
180 CompletionStage<?> stage) { |
64 CompletionStage<?> stage) { |
181 Support.assertCompletesExceptionally(clazz, stage); |
65 Support.assertCompletesExceptionally(clazz, stage); |
197 ByteBuffer closeMessage = ByteBuffer.wrap(copy); |
81 ByteBuffer closeMessage = ByteBuffer.wrap(copy); |
198 channel.write(closeMessage); |
82 channel.write(closeMessage); |
199 super.serve(channel); |
83 super.serve(channel); |
200 } |
84 } |
201 }; |
85 }; |
202 } |
|
203 |
|
204 @Test |
|
205 public void sendMethodsThrowNPE() throws IOException { |
|
206 try (DummyWebSocketServer server = new DummyWebSocketServer()) { |
|
207 server.open(); |
|
208 WebSocket ws = newHttpClient() |
|
209 .newWebSocketBuilder() |
|
210 .buildAsync(server.getURI(), new WebSocket.Listener() { }) |
|
211 .join(); |
|
212 |
|
213 assertThrows(NPE, () -> ws.sendText(null, false)); |
|
214 assertThrows(NPE, () -> ws.sendText(null, true)); |
|
215 assertThrows(NPE, () -> ws.sendBinary(null, false)); |
|
216 assertThrows(NPE, () -> ws.sendBinary(null, true)); |
|
217 assertThrows(NPE, () -> ws.sendPing(null)); |
|
218 assertThrows(NPE, () -> ws.sendPong(null)); |
|
219 assertThrows(NPE, () -> ws.sendClose(NORMAL_CLOSURE, null)); |
|
220 |
|
221 ws.abort(); |
|
222 |
|
223 assertThrows(NPE, () -> ws.sendText(null, false)); |
|
224 assertThrows(NPE, () -> ws.sendText(null, true)); |
|
225 assertThrows(NPE, () -> ws.sendBinary(null, false)); |
|
226 assertThrows(NPE, () -> ws.sendBinary(null, true)); |
|
227 assertThrows(NPE, () -> ws.sendPing(null)); |
|
228 assertThrows(NPE, () -> ws.sendPong(null)); |
|
229 assertThrows(NPE, () -> ws.sendClose(NORMAL_CLOSURE, null)); |
|
230 } |
|
231 } |
|
232 |
|
233 // TODO: request in onClose/onError |
|
234 // TODO: throw exception in onClose/onError |
|
235 // TODO: exception is thrown from request() |
|
236 |
|
237 @Test |
|
238 public void sendCloseCompleted() throws IOException { |
|
239 try (DummyWebSocketServer server = new DummyWebSocketServer()) { |
|
240 server.open(); |
|
241 WebSocket ws = newHttpClient() |
|
242 .newWebSocketBuilder() |
|
243 .buildAsync(server.getURI(), new WebSocket.Listener() { }) |
|
244 .join(); |
|
245 ws.sendClose(NORMAL_CLOSURE, "").join(); |
|
246 assertTrue(ws.isOutputClosed()); |
|
247 assertEquals(ws.getSubprotocol(), ""); |
|
248 ws.request(1); // No exceptions must be thrown |
|
249 } |
|
250 } |
|
251 |
|
252 @Test |
|
253 public void sendClosePending() throws Exception { |
|
254 try (DummyWebSocketServer server = notReadingServer()) { |
|
255 server.open(); |
|
256 WebSocket ws = newHttpClient() |
|
257 .newWebSocketBuilder() |
|
258 .buildAsync(server.getURI(), new WebSocket.Listener() { }) |
|
259 .join(); |
|
260 try { |
|
261 ByteBuffer data = ByteBuffer.allocate(65536); |
|
262 for (int i = 0; ; i++) { // fill up the send buffer |
|
263 System.out.printf("begin cycle #%s at %s%n", |
|
264 i, System.currentTimeMillis()); |
|
265 try { |
|
266 ws.sendBinary(data, true).get(10, TimeUnit.SECONDS); |
|
267 data.clear(); |
|
268 } catch (TimeoutException e) { |
|
269 break; |
|
270 } finally { |
|
271 System.out.printf("end cycle #%s at %s%n", |
|
272 i, System.currentTimeMillis()); |
|
273 } |
|
274 } |
|
275 CompletableFuture<WebSocket> cf = ws.sendClose(NORMAL_CLOSURE, ""); |
|
276 // The output closes even if the Close message has not been sent |
|
277 assertFalse(cf.isDone()); |
|
278 assertTrue(ws.isOutputClosed()); |
|
279 assertEquals(ws.getSubprotocol(), ""); |
|
280 } finally { |
|
281 ws.abort(); |
|
282 } |
|
283 } |
|
284 } |
86 } |
285 |
87 |
286 /* |
88 /* |
287 * This server does not read from the wire, allowing its client to fill up |
89 * This server does not read from the wire, allowing its client to fill up |
288 * their send buffer. Used to test scenarios with outstanding send |
90 * their send buffer. Used to test scenarios with outstanding send |
299 } |
101 } |
300 } |
102 } |
301 }; |
103 }; |
302 } |
104 } |
303 |
105 |
304 @Test |
|
305 public void abortPendingSendBinary() throws Exception { |
|
306 try (DummyWebSocketServer server = notReadingServer()) { |
|
307 server.open(); |
|
308 WebSocket ws = newHttpClient() |
|
309 .newWebSocketBuilder() |
|
310 .buildAsync(server.getURI(), new WebSocket.Listener() { }) |
|
311 .join(); |
|
312 ByteBuffer data = ByteBuffer.allocate(65536); |
|
313 CompletableFuture<WebSocket> cf = null; |
|
314 for (int i = 0; ; i++) { // fill up the send buffer |
|
315 System.out.printf("begin cycle #%s at %s%n", |
|
316 i, System.currentTimeMillis()); |
|
317 try { |
|
318 cf = ws.sendBinary(data, true); |
|
319 cf.get(10, TimeUnit.SECONDS); |
|
320 data.clear(); |
|
321 } catch (TimeoutException e) { |
|
322 break; |
|
323 } finally { |
|
324 System.out.printf("end cycle #%s at %s%n", |
|
325 i, System.currentTimeMillis()); |
|
326 } |
|
327 } |
|
328 ws.abort(); |
|
329 assertTrue(ws.isOutputClosed()); |
|
330 assertTrue(ws.isInputClosed()); |
|
331 assertFails(IOException.class, cf); |
|
332 } |
|
333 } |
|
334 |
|
335 @Test |
|
336 public void abortPendingSendText() throws Exception { |
|
337 try (DummyWebSocketServer server = notReadingServer()) { |
|
338 server.open(); |
|
339 WebSocket ws = newHttpClient() |
|
340 .newWebSocketBuilder() |
|
341 .buildAsync(server.getURI(), new WebSocket.Listener() { }) |
|
342 .join(); |
|
343 String data = stringWith2NBytes(32768); |
|
344 CompletableFuture<WebSocket> cf = null; |
|
345 for (int i = 0; ; i++) { // fill up the send buffer |
|
346 System.out.printf("begin cycle #%s at %s%n", |
|
347 i, System.currentTimeMillis()); |
|
348 try { |
|
349 cf = ws.sendText(data, true); |
|
350 cf.get(10, TimeUnit.SECONDS); |
|
351 } catch (TimeoutException e) { |
|
352 break; |
|
353 } finally { |
|
354 System.out.printf("end cycle #%s at %s%n", |
|
355 i, System.currentTimeMillis()); |
|
356 } |
|
357 } |
|
358 ws.abort(); |
|
359 assertTrue(ws.isOutputClosed()); |
|
360 assertTrue(ws.isInputClosed()); |
|
361 assertFails(IOException.class, cf); |
|
362 } |
|
363 } |
|
364 |
|
365 private static String stringWith2NBytes(int n) { |
106 private static String stringWith2NBytes(int n) { |
366 // -- Russian Alphabet (33 characters, 2 bytes per char) -- |
107 // -- Russian Alphabet (33 characters, 2 bytes per char) -- |
367 char[] abc = { |
108 char[] abc = { |
368 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0401, 0x0416, |
109 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0401, 0x0416, |
369 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, |
110 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, |
377 sb.append(abc[j]); |
118 sb.append(abc[j]); |
378 } |
119 } |
379 String s = sb.toString(); |
120 String s = sb.toString(); |
380 assert s.length() == n && s.getBytes(StandardCharsets.UTF_8).length == 2 * n; |
121 assert s.length() == n && s.getBytes(StandardCharsets.UTF_8).length == 2 * n; |
381 return s; |
122 return s; |
382 } |
|
383 |
|
384 @Test |
|
385 public void sendCloseTimeout() throws Exception { |
|
386 try (DummyWebSocketServer server = notReadingServer()) { |
|
387 server.open(); |
|
388 WebSocket ws = newHttpClient() |
|
389 .newWebSocketBuilder() |
|
390 .buildAsync(server.getURI(), new WebSocket.Listener() { }) |
|
391 .join(); |
|
392 String data = stringWith2NBytes(32768); |
|
393 CompletableFuture<WebSocket> cf = null; |
|
394 for (int i = 0; ; i++) { // fill up the send buffer |
|
395 System.out.printf("begin cycle #%s at %s%n", |
|
396 i, System.currentTimeMillis()); |
|
397 try { |
|
398 cf = ws.sendText(data, true); |
|
399 cf.get(10, TimeUnit.SECONDS); |
|
400 } catch (TimeoutException e) { |
|
401 break; |
|
402 } finally { |
|
403 System.out.printf("end cycle #%s at %s%n", |
|
404 i, System.currentTimeMillis()); |
|
405 } |
|
406 } |
|
407 long before = System.currentTimeMillis(); |
|
408 assertFails(IOException.class, |
|
409 ws.sendClose(WebSocket.NORMAL_CLOSURE, "ok")); |
|
410 long after = System.currentTimeMillis(); |
|
411 // default timeout should be 30 seconds |
|
412 long elapsed = after - before; |
|
413 System.out.printf("Elapsed %s ms%n", elapsed); |
|
414 assertTrue(elapsed >= 29_000, String.valueOf(elapsed)); |
|
415 assertTrue(ws.isOutputClosed()); |
|
416 assertTrue(ws.isInputClosed()); |
|
417 assertFails(IOException.class, cf); |
|
418 } |
|
419 } |
123 } |
420 |
124 |
421 @Test |
125 @Test |
422 public void testIllegalArgument() throws IOException { |
126 public void testIllegalArgument() throws IOException { |
423 try (DummyWebSocketServer server = new DummyWebSocketServer()) { |
127 try (DummyWebSocketServer server = new DummyWebSocketServer()) { |