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 * @build DummyWebSocketServer 27 * @run testng/othervm 28 * -Djdk.internal.httpclient.websocket.debug=true 29 * Abort 30 */ 31 32 import org.testng.annotations.Test; 33 34 import java.io.IOException; 35 import java.net.ProtocolException; 36 import java.net.http.WebSocket; 37 import java.nio.ByteBuffer; 38 import java.util.Arrays; 39 import java.util.List; 40 import java.util.concurrent.CompletableFuture; 41 import java.util.concurrent.CompletionStage; 42 import java.util.concurrent.TimeUnit; 43 import java.util.concurrent.TimeoutException; 44 45 import static java.net.http.HttpClient.newHttpClient; 46 import static java.net.http.WebSocket.NORMAL_CLOSURE; 47 import static org.testng.Assert.assertEquals; 48 import static org.testng.Assert.assertThrows; 49 import static org.testng.Assert.assertTrue; 50 import static org.testng.Assert.fail; 51 52 public class Abort { 53 54 private static final Class<NullPointerException> NPE = NullPointerException.class; 55 private static final Class<IllegalArgumentException> IAE = IllegalArgumentException.class; 56 private static final Class<IOException> IOE = IOException.class; 57 58 59 @Test onOpenThenAbort()60 public void onOpenThenAbort() throws Exception { 61 int[] bytes = new int[]{ 62 0x88, 0x00, // opcode=close 63 }; 64 try (var server = Support.serverWithCannedData(bytes)) { 65 server.open(); 66 // messages are available 67 MockListener listener = new MockListener() { 68 @Override 69 protected void onOpen0(WebSocket webSocket) { 70 // unbounded request 71 webSocket.request(Long.MAX_VALUE); 72 webSocket.abort(); 73 } 74 }; 75 var webSocket = newHttpClient().newWebSocketBuilder() 76 .buildAsync(server.getURI(), listener) 77 .join(); 78 try { 79 TimeUnit.SECONDS.sleep(5); 80 List<MockListener.Invocation> inv = listener.invocationsSoFar(); 81 // no more invocations after onOpen as WebSocket was aborted 82 assertEquals(inv, List.of(MockListener.Invocation.onOpen(webSocket))); 83 } finally { 84 webSocket.abort(); 85 } 86 } 87 } 88 89 @Test onOpenThenOnTextThenAbort()90 public void onOpenThenOnTextThenAbort() throws Exception { 91 int[] bytes = new int[]{ 92 0x81, 0x00, // opcode=text, fin=true 93 0x88, 0x00, // opcode=close 94 }; 95 try (var server = Support.serverWithCannedData(bytes)) { 96 server.open(); 97 MockListener listener = new MockListener() { 98 @Override 99 protected void onOpen0(WebSocket webSocket) { 100 // unbounded request 101 webSocket.request(Long.MAX_VALUE); 102 } 103 104 @Override 105 protected CompletionStage<?> onText0(WebSocket webSocket, 106 CharSequence message, 107 boolean last) { 108 webSocket.abort(); 109 return super.onText0(webSocket, message, last); 110 } 111 }; 112 var webSocket = newHttpClient().newWebSocketBuilder() 113 .buildAsync(server.getURI(), listener) 114 .join(); 115 try { 116 TimeUnit.SECONDS.sleep(5); 117 List<MockListener.Invocation> inv = listener.invocationsSoFar(); 118 // no more invocations after onOpen, onBinary as WebSocket was aborted 119 List<MockListener.Invocation> expected = List.of( 120 MockListener.Invocation.onOpen(webSocket), 121 MockListener.Invocation.onText(webSocket, "", true)); 122 assertEquals(inv, expected); 123 } finally { 124 webSocket.abort(); 125 } 126 } 127 } 128 129 @Test onOpenThenOnBinaryThenAbort()130 public void onOpenThenOnBinaryThenAbort() throws Exception { 131 int[] bytes = new int[]{ 132 0x82, 0x00, // opcode=binary, fin=true 133 0x88, 0x00, // opcode=close 134 }; 135 try (var server = Support.serverWithCannedData(bytes)) { 136 server.open(); 137 MockListener listener = new MockListener() { 138 @Override 139 protected void onOpen0(WebSocket webSocket) { 140 // unbounded request 141 webSocket.request(Long.MAX_VALUE); 142 } 143 144 @Override 145 protected CompletionStage<?> onBinary0(WebSocket webSocket, 146 ByteBuffer message, 147 boolean last) { 148 webSocket.abort(); 149 return super.onBinary0(webSocket, message, last); 150 } 151 }; 152 var webSocket = newHttpClient().newWebSocketBuilder() 153 .buildAsync(server.getURI(), listener) 154 .join(); 155 try { 156 TimeUnit.SECONDS.sleep(5); 157 List<MockListener.Invocation> inv = listener.invocationsSoFar(); 158 // no more invocations after onOpen, onBinary as WebSocket was aborted 159 List<MockListener.Invocation> expected = List.of( 160 MockListener.Invocation.onOpen(webSocket), 161 MockListener.Invocation.onBinary(webSocket, ByteBuffer.allocate(0), true)); 162 assertEquals(inv, expected); 163 } finally { 164 webSocket.abort(); 165 } 166 } 167 } 168 169 @Test onOpenThenOnPingThenAbort()170 public void onOpenThenOnPingThenAbort() throws Exception { 171 int[] bytes = { 172 0x89, 0x00, // opcode=ping 173 0x88, 0x00, // opcode=close 174 }; 175 try (var server = Support.serverWithCannedData(bytes)) { 176 server.open(); 177 MockListener listener = new MockListener() { 178 @Override 179 protected void onOpen0(WebSocket webSocket) { 180 // unbounded request 181 webSocket.request(Long.MAX_VALUE); 182 } 183 184 @Override 185 protected CompletionStage<?> onPing0(WebSocket webSocket, 186 ByteBuffer message) { 187 webSocket.abort(); 188 return super.onPing0(webSocket, message); 189 } 190 }; 191 var webSocket = newHttpClient().newWebSocketBuilder() 192 .buildAsync(server.getURI(), listener) 193 .join(); 194 try { 195 TimeUnit.SECONDS.sleep(5); 196 List<MockListener.Invocation> inv = listener.invocationsSoFar(); 197 // no more invocations after onOpen, onPing as WebSocket was aborted 198 List<MockListener.Invocation> expected = List.of( 199 MockListener.Invocation.onOpen(webSocket), 200 MockListener.Invocation.onPing(webSocket, ByteBuffer.allocate(0))); 201 assertEquals(inv, expected); 202 } finally { 203 webSocket.abort(); 204 } 205 } 206 } 207 208 @Test onOpenThenOnPongThenAbort()209 public void onOpenThenOnPongThenAbort() throws Exception { 210 int[] bytes = { 211 0x8a, 0x00, // opcode=pong 212 0x88, 0x00, // opcode=close 213 }; 214 try (var server = Support.serverWithCannedData(bytes)) { 215 server.open(); 216 MockListener listener = new MockListener() { 217 @Override 218 protected void onOpen0(WebSocket webSocket) { 219 // unbounded request 220 webSocket.request(Long.MAX_VALUE); 221 } 222 223 @Override 224 protected CompletionStage<?> onPong0(WebSocket webSocket, 225 ByteBuffer message) { 226 webSocket.abort(); 227 return super.onPong0(webSocket, message); 228 } 229 }; 230 var webSocket = newHttpClient().newWebSocketBuilder() 231 .buildAsync(server.getURI(), listener) 232 .join(); 233 try { 234 TimeUnit.SECONDS.sleep(5); 235 List<MockListener.Invocation> inv = listener.invocationsSoFar(); 236 // no more invocations after onOpen, onPong as WebSocket was aborted 237 List<MockListener.Invocation> expected = List.of( 238 MockListener.Invocation.onOpen(webSocket), 239 MockListener.Invocation.onPong(webSocket, ByteBuffer.allocate(0))); 240 assertEquals(inv, expected); 241 } finally { 242 webSocket.abort(); 243 } 244 } 245 } 246 247 @Test onOpenThenOnCloseThenAbort()248 public void onOpenThenOnCloseThenAbort() throws Exception { 249 int[] bytes = { 250 0x88, 0x00, // opcode=close 251 0x8a, 0x00, // opcode=pong 252 }; 253 try (var server = Support.serverWithCannedData(bytes)) { 254 server.open(); 255 MockListener listener = new MockListener() { 256 @Override 257 protected void onOpen0(WebSocket webSocket) { 258 // unbounded request 259 webSocket.request(Long.MAX_VALUE); 260 } 261 262 @Override 263 protected CompletionStage<?> onClose0(WebSocket webSocket, 264 int statusCode, 265 String reason) { 266 webSocket.abort(); 267 return super.onClose0(webSocket, statusCode, reason); 268 } 269 }; 270 var webSocket = newHttpClient().newWebSocketBuilder() 271 .buildAsync(server.getURI(), listener) 272 .join(); 273 try { 274 TimeUnit.SECONDS.sleep(5); 275 List<MockListener.Invocation> inv = listener.invocationsSoFar(); 276 // no more invocations after onOpen, onClose 277 List<MockListener.Invocation> expected = List.of( 278 MockListener.Invocation.onOpen(webSocket), 279 MockListener.Invocation.onClose(webSocket, 1005, "")); 280 assertEquals(inv, expected); 281 } finally { 282 webSocket.abort(); 283 } 284 } 285 } 286 287 @Test onOpenThenOnErrorThenAbort()288 public void onOpenThenOnErrorThenAbort() throws Exception { 289 // A header of 128 bytes long Ping (which is a protocol error) 290 int[] badPingHeader = new int[]{0x89, 0x7e, 0x00, 0x80}; 291 int[] closeMessage = new int[]{0x88, 0x00}; 292 int[] bytes = new int[badPingHeader.length + 128 + closeMessage.length]; 293 System.arraycopy(badPingHeader, 0, bytes, 0, badPingHeader.length); 294 System.arraycopy(closeMessage, 0, bytes, badPingHeader.length + 128, closeMessage.length); 295 try (var server = Support.serverWithCannedData(bytes)) { 296 server.open(); 297 MockListener listener = new MockListener() { 298 @Override 299 protected void onOpen0(WebSocket webSocket) { 300 // unbounded request 301 webSocket.request(Long.MAX_VALUE); 302 } 303 304 @Override 305 protected void onError0(WebSocket webSocket, Throwable error) { 306 webSocket.abort(); 307 super.onError0(webSocket, error); 308 } 309 }; 310 var webSocket = newHttpClient().newWebSocketBuilder() 311 .buildAsync(server.getURI(), listener) 312 .join(); 313 try { 314 TimeUnit.SECONDS.sleep(5); 315 List<MockListener.Invocation> inv = listener.invocationsSoFar(); 316 // no more invocations after onOpen, onError 317 List<MockListener.Invocation> expected = List.of( 318 MockListener.Invocation.onOpen(webSocket), 319 MockListener.Invocation.onError(webSocket, ProtocolException.class)); 320 System.out.println("actual invocations:" + Arrays.toString(inv.toArray())); 321 assertEquals(inv, expected); 322 } finally { 323 webSocket.abort(); 324 } 325 } 326 } 327 328 @Test immediateAbort()329 public void immediateAbort() throws Exception { 330 CompletableFuture<Void> messageReceived = new CompletableFuture<>(); 331 WebSocket.Listener listener = new WebSocket.Listener() { 332 333 @Override 334 public void onOpen(WebSocket webSocket) { 335 /* no initial request */ 336 } 337 338 @Override 339 public CompletionStage<?> onText(WebSocket webSocket, 340 CharSequence message, 341 boolean last) { 342 messageReceived.complete(null); 343 return null; 344 } 345 346 @Override 347 public CompletionStage<?> onBinary(WebSocket webSocket, 348 ByteBuffer message, 349 boolean last) { 350 messageReceived.complete(null); 351 return null; 352 } 353 354 @Override 355 public CompletionStage<?> onPing(WebSocket webSocket, 356 ByteBuffer message) { 357 messageReceived.complete(null); 358 return null; 359 } 360 361 @Override 362 public CompletionStage<?> onPong(WebSocket webSocket, 363 ByteBuffer message) { 364 messageReceived.complete(null); 365 return null; 366 } 367 368 @Override 369 public CompletionStage<?> onClose(WebSocket webSocket, 370 int statusCode, 371 String reason) { 372 messageReceived.complete(null); 373 return null; 374 } 375 }; 376 377 int[] bytes = new int[]{ 378 0x82, 0x00, // opcode=binary, fin=true 379 0x88, 0x00, // opcode=close 380 }; 381 try (var server = Support.serverWithCannedData(bytes)) { 382 server.open(); 383 384 WebSocket ws = newHttpClient() 385 .newWebSocketBuilder() 386 .buildAsync(server.getURI(), listener) 387 .join(); 388 try { 389 for (int i = 0; i < 3; i++) { 390 System.out.printf("iteration #%s%n", i); 391 // after the first abort() each consecutive one must be a no-op, 392 // moreover, query methods should continue to return consistent 393 // values 394 for (int j = 0; j < 3; j++) { 395 System.out.printf("abort #%s%n", j); 396 ws.abort(); 397 assertTrue(ws.isInputClosed()); 398 assertTrue(ws.isOutputClosed()); 399 assertEquals(ws.getSubprotocol(), ""); 400 } 401 // at this point valid requests MUST be a no-op: 402 for (int j = 0; j < 3; j++) { 403 System.out.printf("request #%s%n", j); 404 ws.request(1); 405 ws.request(2); 406 ws.request(8); 407 ws.request(Integer.MAX_VALUE); 408 ws.request(Long.MAX_VALUE); 409 // invalid requests MUST throw IAE: 410 assertThrows(IAE, () -> ws.request(Integer.MIN_VALUE)); 411 assertThrows(IAE, () -> ws.request(Long.MIN_VALUE)); 412 assertThrows(IAE, () -> ws.request(-1)); 413 assertThrows(IAE, () -> ws.request(0)); 414 } 415 } 416 // even though there is a bunch of messages readily available on the 417 // wire we shouldn't have received any of them as we aborted before 418 // the first request 419 try { 420 messageReceived.get(5, TimeUnit.SECONDS); 421 fail(); 422 } catch (TimeoutException expected) { 423 System.out.println("Finished waiting"); 424 } 425 for (int i = 0; i < 3; i++) { 426 System.out.printf("send #%s%n", i); 427 Support.assertFails(IOE, ws.sendText("text!", false)); 428 Support.assertFails(IOE, ws.sendText("text!", true)); 429 Support.assertFails(IOE, ws.sendBinary(ByteBuffer.allocate(16), false)); 430 Support.assertFails(IOE, ws.sendBinary(ByteBuffer.allocate(16), true)); 431 Support.assertFails(IOE, ws.sendPing(ByteBuffer.allocate(16))); 432 Support.assertFails(IOE, ws.sendPong(ByteBuffer.allocate(16))); 433 Support.assertFails(IOE, ws.sendClose(NORMAL_CLOSURE, "a reason")); 434 assertThrows(NPE, () -> ws.sendText(null, false)); 435 assertThrows(NPE, () -> ws.sendText(null, true)); 436 assertThrows(NPE, () -> ws.sendBinary(null, false)); 437 assertThrows(NPE, () -> ws.sendBinary(null, true)); 438 assertThrows(NPE, () -> ws.sendPing(null)); 439 assertThrows(NPE, () -> ws.sendPong(null)); 440 assertThrows(NPE, () -> ws.sendClose(NORMAL_CLOSURE, null)); 441 } 442 } finally { 443 ws.abort(); 444 } 445 } 446 } 447 } 448