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 * @bug 8201186 27 * @summary Tests an asynchronous BodySubscriber that completes 28 * immediately with a Publisher<List<ByteBuffer>> whose 29 * subscriber issues bad requests 30 * @library /lib/testlibrary http2/server 31 * @build jdk.testlibrary.SimpleSSLContext ReferenceTracker 32 * @modules java.base/sun.net.www.http 33 * java.net.http/jdk.internal.net.http.common 34 * java.net.http/jdk.internal.net.http.frame 35 * java.net.http/jdk.internal.net.http.hpack 36 * @run testng/othervm InvalidSubscriptionRequest 37 */ 38 39 import com.sun.net.httpserver.HttpServer; 40 import com.sun.net.httpserver.HttpsConfigurator; 41 import com.sun.net.httpserver.HttpsServer; 42 import jdk.testlibrary.SimpleSSLContext; 43 import org.testng.annotations.AfterTest; 44 import org.testng.annotations.BeforeTest; 45 import org.testng.annotations.DataProvider; 46 import org.testng.annotations.Test; 47 48 import javax.net.ssl.SSLContext; 49 import java.io.IOException; 50 import java.io.InputStream; 51 import java.io.OutputStream; 52 import java.net.InetAddress; 53 import java.net.InetSocketAddress; 54 import java.net.URI; 55 import java.net.http.HttpClient; 56 import java.net.http.HttpRequest; 57 import java.net.http.HttpResponse; 58 import java.net.http.HttpResponse.BodyHandler; 59 import java.net.http.HttpResponse.BodyHandlers; 60 import java.net.http.HttpResponse.BodySubscriber; 61 import java.net.http.HttpResponse.BodySubscribers; 62 import java.nio.ByteBuffer; 63 import java.util.List; 64 import java.util.concurrent.CompletableFuture; 65 import java.util.concurrent.CompletionException; 66 import java.util.concurrent.CompletionStage; 67 import java.util.concurrent.ExecutionException; 68 import java.util.concurrent.Executor; 69 import java.util.concurrent.Executors; 70 import java.util.concurrent.Flow; 71 import java.util.concurrent.Flow.Publisher; 72 import java.util.function.Supplier; 73 74 import static java.lang.System.out; 75 import static java.nio.charset.StandardCharsets.UTF_8; 76 import static org.testng.Assert.assertEquals; 77 78 public class InvalidSubscriptionRequest implements HttpServerAdapters { 79 80 SSLContext sslContext; 81 HttpTestServer httpTestServer; // HTTP/1.1 [ 4 servers ] 82 HttpTestServer httpsTestServer; // HTTPS/1.1 83 HttpTestServer http2TestServer; // HTTP/2 ( h2c ) 84 HttpTestServer https2TestServer; // HTTP/2 ( h2 ) 85 String httpURI_fixed; 86 String httpURI_chunk; 87 String httpsURI_fixed; 88 String httpsURI_chunk; 89 String http2URI_fixed; 90 String http2URI_chunk; 91 String https2URI_fixed; 92 String https2URI_chunk; 93 94 static final int ITERATION_COUNT = 3; 95 // a shared executor helps reduce the amount of threads created by the test 96 static final Executor executor = Executors.newCachedThreadPool(); 97 98 interface BHS extends Supplier<BodyHandler<Publisher<List<ByteBuffer>>>> { of(BHS impl, String name)99 static BHS of(BHS impl, String name) { 100 return new BHSImpl(impl, name); 101 } 102 } 103 104 static final class BHSImpl implements BHS { 105 final BHS supplier; 106 final String name; BHSImpl(BHS impl, String name)107 BHSImpl(BHS impl, String name) { 108 this.supplier = impl; 109 this.name = name; 110 } 111 @Override toString()112 public String toString() { 113 return name; 114 } 115 116 @Override get()117 public BodyHandler<Publisher<List<ByteBuffer>>> get() { 118 return supplier.get(); 119 } 120 } 121 122 static final Supplier<BodyHandler<Publisher<List<ByteBuffer>>>> OF_PUBLISHER_API = 123 BHS.of(BodyHandlers::ofPublisher, "BodyHandlers::ofPublisher"); 124 125 @DataProvider(name = "variants") variants()126 public Object[][] variants() { 127 return new Object[][]{ 128 { httpURI_fixed, false, OF_PUBLISHER_API }, 129 { httpURI_chunk, false, OF_PUBLISHER_API }, 130 { httpsURI_fixed, false, OF_PUBLISHER_API }, 131 { httpsURI_chunk, false, OF_PUBLISHER_API }, 132 { http2URI_fixed, false, OF_PUBLISHER_API }, 133 { http2URI_chunk, false, OF_PUBLISHER_API }, 134 { https2URI_fixed, false, OF_PUBLISHER_API }, 135 { https2URI_chunk, false, OF_PUBLISHER_API }, 136 137 { httpURI_fixed, true, OF_PUBLISHER_API }, 138 { httpURI_chunk, true, OF_PUBLISHER_API }, 139 { httpsURI_fixed, true, OF_PUBLISHER_API }, 140 { httpsURI_chunk, true, OF_PUBLISHER_API }, 141 { http2URI_fixed, true, OF_PUBLISHER_API }, 142 { http2URI_chunk, true, OF_PUBLISHER_API }, 143 { https2URI_fixed, true, OF_PUBLISHER_API }, 144 { https2URI_chunk, true, OF_PUBLISHER_API }, 145 }; 146 } 147 148 final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE; newHttpClient()149 HttpClient newHttpClient() { 150 return TRACKER.track(HttpClient.newBuilder() 151 .proxy(HttpClient.Builder.NO_PROXY) 152 .executor(executor) 153 .sslContext(sslContext) 154 .build()); 155 } 156 157 @Test(dataProvider = "variants") testNoBody(String uri, boolean sameClient, BHS handlers)158 public void testNoBody(String uri, boolean sameClient, BHS handlers) throws Exception { 159 HttpClient client = null; 160 for (int i=0; i< ITERATION_COUNT; i++) { 161 if (!sameClient || client == null) 162 client = newHttpClient(); 163 164 HttpRequest req = HttpRequest.newBuilder(URI.create(uri)) 165 .build(); 166 BodyHandler<Publisher<List<ByteBuffer>>> handler = handlers.get(); 167 HttpResponse<Publisher<List<ByteBuffer>>> response = client.send(req, handler); 168 // We can reuse our BodySubscribers implementations to subscribe to the 169 // Publisher<List<ByteBuffer>> 170 BodySubscriber<String> ofString = new BadBodySubscriber<>(BodySubscribers.ofString(UTF_8)); 171 // get the Publisher<List<ByteBuffer>> and 172 // subscribe to it. 173 response.body().subscribe(ofString); 174 // Get the final result and compare it with the expected body 175 try { 176 String body = ofString.getBody().toCompletableFuture().get(); 177 assertEquals(body, ""); 178 if (uri.endsWith("/chunk") 179 && response.version() == HttpClient.Version.HTTP_1_1) { 180 // with /fixed and 0 length 181 // there's no need for any call to request() 182 throw new RuntimeException("Expected IAE not thrown"); 183 } 184 } catch (Exception x) { 185 Throwable cause = x; 186 if (x instanceof CompletionException || x instanceof ExecutionException) { 187 cause = x.getCause(); 188 } 189 if (cause instanceof IllegalArgumentException) { 190 System.out.println("Got expected exception: " + cause); 191 } else throw x; 192 } 193 } 194 } 195 196 @Test(dataProvider = "variants") testNoBodyAsync(String uri, boolean sameClient, BHS handlers)197 public void testNoBodyAsync(String uri, boolean sameClient, BHS handlers) throws Exception { 198 HttpClient client = null; 199 for (int i=0; i< ITERATION_COUNT; i++) { 200 if (!sameClient || client == null) 201 client = newHttpClient(); 202 203 HttpRequest req = HttpRequest.newBuilder(URI.create(uri)) 204 .build(); 205 BodyHandler<Publisher<List<ByteBuffer>>> handler = handlers.get(); 206 // We can reuse our BodySubscribers implementations to subscribe to the 207 // Publisher<List<ByteBuffer>> 208 BodySubscriber<String> ofString = 209 new BadBodySubscriber<>(BodySubscribers.ofString(UTF_8)); 210 CompletableFuture<HttpResponse<Publisher<List<ByteBuffer>>>> response = 211 client.sendAsync(req, handler); 212 CompletableFuture<String> result = response.thenCompose( 213 (responsePublisher) -> { 214 // get the Publisher<List<ByteBuffer>> and 215 // subscribe to it. 216 responsePublisher.body().subscribe(ofString); 217 return ofString.getBody(); 218 }); 219 try { 220 // Get the final result and compare it with the expected body 221 assertEquals(result.get(), ""); 222 if (uri.endsWith("/chunk") 223 && response.get().version() == HttpClient.Version.HTTP_1_1) { 224 // with /fixed and 0 length 225 // there's no need for any call to request() 226 throw new RuntimeException("Expected IAE not thrown"); 227 } 228 } catch (Exception x) { 229 Throwable cause = x; 230 if (x instanceof CompletionException || x instanceof ExecutionException) { 231 cause = x.getCause(); 232 } 233 if (cause instanceof IllegalArgumentException) { 234 System.out.println("Got expected exception: " + cause); 235 } else throw x; 236 } 237 } 238 } 239 240 @Test(dataProvider = "variants") testAsString(String uri, boolean sameClient, BHS handlers)241 public void testAsString(String uri, boolean sameClient, BHS handlers) throws Exception { 242 HttpClient client = null; 243 for (int i=0; i< ITERATION_COUNT; i++) { 244 if (!sameClient || client == null) 245 client = newHttpClient(); 246 247 HttpRequest req = HttpRequest.newBuilder(URI.create(uri+"/withBody")) 248 .build(); 249 BodyHandler<Publisher<List<ByteBuffer>>> handler = handlers.get(); 250 HttpResponse<Publisher<List<ByteBuffer>>> response = client.send(req, handler); 251 // We can reuse our BodySubscribers implementations to subscribe to the 252 // Publisher<List<ByteBuffer>> 253 BodySubscriber<String> ofString = new BadBodySubscriber<>( 254 BodySubscribers.ofString(UTF_8)); 255 // get the Publisher<List<ByteBuffer>> and 256 // subscribe to it. 257 response.body().subscribe(ofString); 258 // Get the final result and compare it with the expected body 259 try { 260 String body = ofString.getBody().toCompletableFuture().get(); 261 assertEquals(body, WITH_BODY); 262 throw new RuntimeException("Expected IAE not thrown"); 263 } catch (Exception x) { 264 Throwable cause = x; 265 if (x instanceof CompletionException || x instanceof ExecutionException) { 266 cause = x.getCause(); 267 } 268 if (cause instanceof IllegalArgumentException) { 269 System.out.println("Got expected exception: " + cause); 270 } else throw x; 271 } 272 } 273 } 274 275 @Test(dataProvider = "variants") testAsStringAsync(String uri, boolean sameClient, BHS handlers)276 public void testAsStringAsync(String uri, boolean sameClient, BHS handlers) throws Exception { 277 HttpClient client = null; 278 for (int i=0; i< ITERATION_COUNT; i++) { 279 if (!sameClient || client == null) 280 client = newHttpClient(); 281 282 HttpRequest req = HttpRequest.newBuilder(URI.create(uri+"/withBody")) 283 .build(); 284 BodyHandler<Publisher<List<ByteBuffer>>> handler = handlers.get(); 285 // We can reuse our BodySubscribers implementations to subscribe to the 286 // Publisher<List<ByteBuffer>> 287 BodySubscriber<String> ofString = 288 new BadBodySubscriber<>(BodySubscribers.ofString(UTF_8)); 289 CompletableFuture<String> result = client.sendAsync(req, handler) 290 .thenCompose((responsePublisher) -> { 291 // get the Publisher<List<ByteBuffer>> and 292 // subscribe to it. 293 responsePublisher.body().subscribe(ofString); 294 return ofString.getBody(); 295 }); 296 // Get the final result and compare it with the expected body 297 try { 298 String body = result.get(); 299 assertEquals(body, WITH_BODY); 300 throw new RuntimeException("Expected IAE not thrown"); 301 } catch (Exception x) { 302 Throwable cause = x; 303 if (x instanceof CompletionException || x instanceof ExecutionException) { 304 cause = x.getCause(); 305 } 306 if (cause instanceof IllegalArgumentException) { 307 System.out.println("Got expected exception: " + cause); 308 } else throw x; 309 } 310 } 311 } 312 313 static final class BadSubscription implements Flow.Subscription { 314 Flow.Subscription subscription; 315 Executor executor; BadSubscription(Flow.Subscription subscription)316 BadSubscription(Flow.Subscription subscription) { 317 this.subscription = subscription; 318 } 319 320 @Override request(long n)321 public void request(long n) { 322 if (executor == null) { 323 subscription.request(-n); 324 } else { 325 executor.execute(() -> subscription.request(-n)); 326 } 327 } 328 329 @Override cancel()330 public void cancel() { 331 subscription.cancel(); 332 } 333 } 334 335 static final class BadBodySubscriber<T> implements BodySubscriber<T> { 336 final BodySubscriber<T> subscriber; BadBodySubscriber(BodySubscriber<T> subscriber)337 BadBodySubscriber(BodySubscriber<T> subscriber) { 338 this.subscriber = subscriber; 339 } 340 341 @Override getBody()342 public CompletionStage<T> getBody() { 343 return subscriber.getBody(); 344 } 345 346 @Override onSubscribe(Flow.Subscription subscription)347 public void onSubscribe(Flow.Subscription subscription) { 348 System.out.println("Subscription is: " + subscription); 349 subscriber.onSubscribe(new BadSubscription(subscription)); 350 } 351 352 @Override onNext(List<ByteBuffer> item)353 public void onNext(List<ByteBuffer> item) { 354 subscriber.onNext(item); 355 } 356 357 @Override onError(Throwable throwable)358 public void onError(Throwable throwable) { 359 subscriber.onError(throwable); 360 } 361 362 @Override onComplete()363 public void onComplete() { 364 subscriber.onComplete(); 365 } 366 } 367 serverAuthority(HttpServer server)368 static String serverAuthority(HttpServer server) { 369 return InetAddress.getLoopbackAddress().getHostName() + ":" 370 + server.getAddress().getPort(); 371 } 372 373 @BeforeTest setup()374 public void setup() throws Exception { 375 sslContext = new SimpleSSLContext().get(); 376 if (sslContext == null) 377 throw new AssertionError("Unexpected null sslContext"); 378 379 // HTTP/1.1 380 HttpTestHandler h1_fixedLengthHandler = new HTTP_FixedLengthHandler(); 381 HttpTestHandler h1_chunkHandler = new HTTP_VariableLengthHandler(); 382 InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); 383 httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0)); 384 httpTestServer.addHandler( h1_fixedLengthHandler, "/http1/fixed"); 385 httpTestServer.addHandler(h1_chunkHandler,"/http1/chunk"); 386 httpURI_fixed = "http://" + httpTestServer.serverAuthority() + "/http1/fixed"; 387 httpURI_chunk = "http://" + httpTestServer.serverAuthority() + "/http1/chunk"; 388 389 HttpsServer httpsServer = HttpsServer.create(sa, 0); 390 httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext)); 391 httpsTestServer = HttpTestServer.of(httpsServer); 392 httpsTestServer.addHandler(h1_fixedLengthHandler, "/https1/fixed"); 393 httpsTestServer.addHandler(h1_chunkHandler, "/https1/chunk"); 394 httpsURI_fixed = "https://" + httpsTestServer.serverAuthority() + "/https1/fixed"; 395 httpsURI_chunk = "https://" + httpsTestServer.serverAuthority() + "/https1/chunk"; 396 397 // HTTP/2 398 HttpTestHandler h2_fixedLengthHandler = new HTTP_FixedLengthHandler(); 399 HttpTestHandler h2_chunkedHandler = new HTTP_VariableLengthHandler(); 400 401 http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0)); 402 http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed"); 403 http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk"); 404 http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed"; 405 http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk"; 406 407 https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, sslContext)); 408 https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed"); 409 https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk"); 410 https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed"; 411 https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk"; 412 413 httpTestServer.start(); 414 httpsTestServer.start(); 415 http2TestServer.start(); 416 https2TestServer.start(); 417 } 418 419 @AfterTest teardown()420 public void teardown() throws Exception { 421 AssertionError fail = TRACKER.check(500); 422 try { 423 httpTestServer.stop(); 424 httpsTestServer.stop(); 425 http2TestServer.stop(); 426 https2TestServer.stop(); 427 } finally { 428 if (fail != null) { 429 throw fail; 430 } 431 } 432 } 433 434 static final String WITH_BODY = "Lorem ipsum dolor sit amet, consectetur" + 435 " adipiscing elit, sed do eiusmod tempor incididunt ut labore et" + 436 " dolore magna aliqua. Ut enim ad minim veniam, quis nostrud" + 437 " exercitation ullamco laboris nisi ut aliquip ex ea" + 438 " commodo consequat. Duis aute irure dolor in reprehenderit in " + 439 "voluptate velit esse cillum dolore eu fugiat nulla pariatur." + 440 " Excepteur sint occaecat cupidatat non proident, sunt in culpa qui" + 441 " officia deserunt mollit anim id est laborum."; 442 443 static class HTTP_FixedLengthHandler implements HttpTestHandler { 444 @Override handle(HttpTestExchange t)445 public void handle(HttpTestExchange t) throws IOException { 446 out.println("HTTP_FixedLengthHandler received request to " + t.getRequestURI()); 447 try (InputStream is = t.getRequestBody()) { 448 is.readAllBytes(); 449 } 450 if (t.getRequestURI().getPath().endsWith("/withBody")) { 451 byte[] bytes = WITH_BODY.getBytes(UTF_8); 452 t.sendResponseHeaders(200, bytes.length); // body 453 try (OutputStream os = t.getResponseBody()) { 454 os.write(bytes); 455 } 456 } else { 457 t.sendResponseHeaders(200, 0); //no body 458 } 459 } 460 } 461 462 static class HTTP_VariableLengthHandler implements HttpTestHandler { 463 @Override handle(HttpTestExchange t)464 public void handle(HttpTestExchange t) throws IOException { 465 out.println("HTTP_VariableLengthHandler received request to " + t.getRequestURI()); 466 try (InputStream is = t.getRequestBody()) { 467 is.readAllBytes(); 468 } 469 t.sendResponseHeaders(200, -1); //chunked or variable 470 if (t.getRequestURI().getPath().endsWith("/withBody")) { 471 byte[] bytes = WITH_BODY.getBytes(UTF_8); 472 try (OutputStream os = t.getResponseBody()) { 473 int chunkLen = bytes.length/10; 474 if (chunkLen == 0) { 475 os.write(bytes); 476 } else { 477 int count = 0; 478 for (int i=0; i<10; i++) { 479 os.write(bytes, count, chunkLen); 480 os.flush(); 481 count += chunkLen; 482 } 483 os.write(bytes, count, bytes.length % chunkLen); 484 count += bytes.length % chunkLen; 485 assert count == bytes.length; 486 } 487 } 488 } else { 489 t.getResponseBody().close(); // no body 490 } 491 } 492 } 493 } 494