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 * @library /lib/testlibrary server 27 * @build jdk.testlibrary.SimpleSSLContext 28 * @modules java.base/sun.net.www.http 29 * jdk.incubator.httpclient/jdk.incubator.http.internal.common 30 * jdk.incubator.httpclient/jdk.incubator.http.internal.frame 31 * jdk.incubator.httpclient/jdk.incubator.http.internal.hpack 32 * @run testng/othervm -Djdk.internal.httpclient.debug=true -Djdk.httpclient.HttpClient.log=errors,requests,responses,trace ImplicitPushCancel 33 */ 34 35 import java.io.ByteArrayInputStream; 36 import java.io.IOException; 37 import java.io.InputStream; 38 import java.io.OutputStream; 39 import java.net.URI; 40 import java.util.Map; 41 import java.util.Objects; 42 import java.util.Optional; 43 import jdk.incubator.http.HttpClient; 44 import jdk.incubator.http.HttpRequest; 45 import jdk.incubator.http.HttpResponse; 46 import jdk.incubator.http.HttpResponse.BodyHandler; 47 import jdk.incubator.http.MultiMapResult; 48 import jdk.incubator.http.internal.common.HttpHeadersImpl; 49 import org.testng.annotations.AfterTest; 50 import org.testng.annotations.BeforeTest; 51 import org.testng.annotations.Test; 52 import static java.nio.charset.StandardCharsets.UTF_8; 53 import static org.testng.Assert.assertEquals; 54 55 public class ImplicitPushCancel { 56 57 static Map<String,String> PUSH_PROMISES = Map.of( 58 "/x/y/z/1", "the first push promise body", 59 "/x/y/z/2", "the second push promise body", 60 "/x/y/z/3", "the third push promise body", 61 "/x/y/z/4", "the fourth push promise body", 62 "/x/y/z/5", "the fifth push promise body", 63 "/x/y/z/6", "the sixth push promise body", 64 "/x/y/z/7", "the seventh push promise body", 65 "/x/y/z/8", "the eight push promise body", 66 "/x/y/z/9", "the ninth push promise body" 67 ); 68 static final String MAIN_RESPONSE_BODY = "the main response body"; 69 70 Http2TestServer server; 71 URI uri; 72 73 @BeforeTest 74 public void setup() throws Exception { 75 server = new Http2TestServer(false, 0); 76 Http2Handler handler = new ServerPushHandler(MAIN_RESPONSE_BODY, 77 PUSH_PROMISES); 78 server.addHandler(handler, "/"); 79 server.start(); 80 int port = server.getAddress().getPort(); 81 System.err.println("Server listening on port " + port); 82 uri = new URI("http://127.0.0.1:" + port + "/foo/a/b/c"); 83 } 84 85 @AfterTest 86 public void teardown() { 87 server.stop(); 88 } 89 90 static final <T> HttpResponse<T> assert200ResponseCode(HttpResponse<T> response) { 91 assertEquals(response.statusCode(), 200); 92 return response; 93 } 94 95 /* 96 * With a handler not capable of accepting push promises, then all push 97 * promises should be rejected / cancelled, without interfering with the 98 * main response. 99 */ 100 @Test 101 public void test() throws Exception { 102 HttpClient client = HttpClient.newHttpClient(); 103 104 client.sendAsync(HttpRequest.newBuilder(uri).build(), BodyHandler.asString()) 105 .thenApply(ImplicitPushCancel::assert200ResponseCode) 106 .thenApply(HttpResponse::body) 107 .thenAccept(body -> body.equals(MAIN_RESPONSE_BODY)) 108 .join(); 109 110 MultiMapResult<String> map = client.sendAsync( 111 HttpRequest.newBuilder(uri).build(), 112 HttpResponse.MultiSubscriber.asMap( 113 (req) -> Optional.of(HttpResponse.BodyHandler.asString())) 114 ).join(); 115 116 map.entrySet().stream().forEach(e -> System.out.println(e.getKey() + ":" + e.getValue().join().body())); 117 118 map.entrySet().stream().forEach(entry -> { 119 HttpRequest request = entry.getKey(); 120 HttpResponse<String> response = entry.getValue().join(); 121 assertEquals(response.statusCode(), 200); 122 if (PUSH_PROMISES.containsKey(request.uri().getPath())) { 123 assertEquals(response.body(), PUSH_PROMISES.get(request.uri().getPath())); 124 } else { 125 assertEquals(response.body(), MAIN_RESPONSE_BODY); 126 } 127 128 } ); 129 } 130 131 // --- server push handler --- 132 static class ServerPushHandler implements Http2Handler { 133 134 private final String mainResponseBody; 135 private final Map<String,String> promises; 136 137 public ServerPushHandler(String mainResponseBody, 138 Map<String,String> promises) 139 throws Exception 140 { 141 Objects.requireNonNull(promises); 142 this.mainResponseBody = mainResponseBody; 143 this.promises = promises; 144 } 145 146 public void handle(Http2TestExchange exchange) throws IOException { 147 System.err.println("Server: handle " + exchange); 148 try (InputStream is = exchange.getRequestBody()) { 149 is.readAllBytes(); 150 } 151 152 if (exchange.serverPushAllowed()) { 153 pushPromises(exchange); 154 } 155 156 // response data for the main response 157 try (OutputStream os = exchange.getResponseBody()) { 158 byte[] bytes = mainResponseBody.getBytes(UTF_8); 159 exchange.sendResponseHeaders(200, bytes.length); 160 os.write(bytes); 161 } 162 } 163 164 private void pushPromises(Http2TestExchange exchange) throws IOException { 165 URI requestURI = exchange.getRequestURI(); 166 for (Map.Entry<String,String> promise : promises.entrySet()) { 167 URI uri = requestURI.resolve(promise.getKey()); 168 InputStream is = new ByteArrayInputStream(promise.getValue().getBytes(UTF_8)); 169 HttpHeadersImpl headers = new HttpHeadersImpl(); 170 exchange.serverPush(uri, headers, is); 171 } 172 System.err.println("Server: All pushes sent"); 173 } 174 } 175 } 176