1 /*
2  * Copyright (c) 2015, 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 8087112 8159814
27  * @library /lib/testlibrary server
28  * @build jdk.testlibrary.SimpleSSLContext
29  * @modules java.base/sun.net.www.http
30  *          java.net.http/jdk.internal.net.http.common
31  *          java.net.http/jdk.internal.net.http.frame
32  *          java.net.http/jdk.internal.net.http.hpack
33  * @run testng/othervm
34  *      -Djdk.httpclient.HttpClient.log=errors,requests,responses
35  *      ServerPush
36  */
37 
38 import java.io.*;
39 import java.net.*;
40 import java.nio.ByteBuffer;
41 import java.nio.file.*;
42 import java.net.http.*;
43 import java.net.http.HttpResponse.BodyHandler;
44 import java.net.http.HttpResponse.BodyHandlers;
45 import java.net.http.HttpResponse.BodySubscribers;
46 import java.net.http.HttpResponse.PushPromiseHandler;
47 import java.util.*;
48 import java.util.concurrent.*;
49 import java.util.function.Consumer;
50 import org.testng.annotations.AfterTest;
51 import org.testng.annotations.BeforeTest;
52 import org.testng.annotations.Test;
53 import static java.nio.charset.StandardCharsets.UTF_8;
54 import static org.testng.Assert.*;
55 
56 public class ServerPush {
57 
58     static final int LOOPS = 13;
59     static final int FILE_SIZE = 512 * 1024 + 343;
60 
61     static Path tempFile;
62 
63     Http2TestServer server;
64     URI uri;
65 
66     @BeforeTest
setup()67     public void setup() throws Exception {
68         tempFile = TestUtil.getAFile(FILE_SIZE);
69         server = new Http2TestServer(false, 0);
70         server.addHandler(new PushHandler(tempFile, LOOPS), "/");
71         System.out.println("Using temp file:" + tempFile);
72 
73         System.err.println("Server listening on port " + server.getAddress().getPort());
74         server.start();
75         int port = server.getAddress().getPort();
76         uri = new URI("http://localhost:" + port + "/foo/a/b/c");
77     }
78 
79     @AfterTest
teardown()80     public void teardown() {
81         server.stop();
82     }
83 
84     // Test 1 - custom written push promise handler, everything as a String
85     @Test
testTypeString()86     public void testTypeString() throws Exception {
87         String tempFileAsString = new String(Files.readAllBytes(tempFile), UTF_8);
88         ConcurrentMap<HttpRequest, CompletableFuture<HttpResponse<String>>>
89                 resultMap = new ConcurrentHashMap<>();
90 
91         PushPromiseHandler<String> pph = (initial, pushRequest, acceptor) -> {
92             BodyHandler<String> s = BodyHandlers.ofString(UTF_8);
93             CompletableFuture<HttpResponse<String>> cf = acceptor.apply(s);
94             resultMap.put(pushRequest, cf);
95         };
96 
97         HttpClient client = HttpClient.newHttpClient();
98         HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
99         CompletableFuture<HttpResponse<String>> cf =
100                 client.sendAsync(request, BodyHandlers.ofString(UTF_8), pph);
101         cf.join();
102         resultMap.put(request, cf);
103         System.err.println("results.size: " + resultMap.size());
104         for (HttpRequest r : resultMap.keySet()) {
105             HttpResponse<String> response = resultMap.get(r).join();
106             assertEquals(response.statusCode(), 200);
107             assertEquals(response.body(), tempFileAsString);
108         }
109         assertEquals(resultMap.size(), LOOPS + 1);
110     }
111 
112     // Test 2 - of(...) populating the given Map, everything as a String
113     @Test
testTypeStringOfMap()114     public void testTypeStringOfMap() throws Exception {
115         String tempFileAsString = new String(Files.readAllBytes(tempFile), UTF_8);
116         ConcurrentMap<HttpRequest, CompletableFuture<HttpResponse<String>>>
117                 resultMap = new ConcurrentHashMap<>();
118 
119         PushPromiseHandler<String> pph =
120                 PushPromiseHandler.of(pushPromise -> BodyHandlers.ofString(UTF_8),
121                                       resultMap);
122 
123         HttpClient client = HttpClient.newHttpClient();
124         HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
125         CompletableFuture<HttpResponse<String>> cf =
126                 client.sendAsync(request, BodyHandlers.ofString(UTF_8), pph);
127         cf.join();
128         resultMap.put(request, cf);
129         System.err.println("results.size: " + resultMap.size());
130         for (HttpRequest r : resultMap.keySet()) {
131             HttpResponse<String> response = resultMap.get(r).join();
132             assertEquals(response.statusCode(), 200);
133             assertEquals(response.body(), tempFileAsString);
134         }
135         assertEquals(resultMap.size(), LOOPS + 1);
136     }
137 
138     // --- Path ---
139 
140     static final Path dir = Paths.get(".", "serverPush");
requestToPath(HttpRequest req)141     static BodyHandler<Path> requestToPath(HttpRequest req) {
142         URI u = req.uri();
143         Path path = Paths.get(dir.toString(), u.getPath());
144         try {
145             Files.createDirectories(path.getParent());
146         } catch (IOException ee) {
147             throw new UncheckedIOException(ee);
148         }
149         return BodyHandlers.ofFile(path);
150     }
151 
152     // Test 3 - custom written push promise handler, everything as a Path
153     @Test
testTypePath()154     public void testTypePath() throws Exception {
155         String tempFileAsString = new String(Files.readAllBytes(tempFile), UTF_8);
156         ConcurrentMap<HttpRequest, CompletableFuture<HttpResponse<Path>>> resultsMap
157                 = new ConcurrentHashMap<>();
158 
159         PushPromiseHandler<Path> pushPromiseHandler = (initial, pushRequest, acceptor) -> {
160             BodyHandler<Path> pp = requestToPath(pushRequest);
161             CompletableFuture<HttpResponse<Path>> cf = acceptor.apply(pp);
162             resultsMap.put(pushRequest, cf);
163         };
164 
165         HttpClient client = HttpClient.newHttpClient();
166         HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
167         CompletableFuture<HttpResponse<Path>> cf =
168                 client.sendAsync(request, requestToPath(request), pushPromiseHandler);
169         cf.join();
170         resultsMap.put(request, cf);
171 
172         for (HttpRequest r : resultsMap.keySet()) {
173             HttpResponse<Path> response = resultsMap.get(r).join();
174             assertEquals(response.statusCode(), 200);
175             String fileAsString = new String(Files.readAllBytes(response.body()), UTF_8);
176             assertEquals(fileAsString, tempFileAsString);
177         }
178         assertEquals(resultsMap.size(),  LOOPS + 1);
179     }
180 
181     // Test 4 - of(...) populating the given Map, everything as a Path
182     @Test
testTypePathOfMap()183     public void testTypePathOfMap() throws Exception {
184         String tempFileAsString = new String(Files.readAllBytes(tempFile), UTF_8);
185         ConcurrentMap<HttpRequest, CompletableFuture<HttpResponse<Path>>> resultsMap
186                 = new ConcurrentHashMap<>();
187 
188         PushPromiseHandler<Path> pushPromiseHandler =
189                 PushPromiseHandler.of(pushRequest -> requestToPath(pushRequest),
190                         resultsMap);
191 
192         HttpClient client = HttpClient.newHttpClient();
193         HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
194         CompletableFuture<HttpResponse<Path>> cf =
195                 client.sendAsync(request, requestToPath(request), pushPromiseHandler);
196         cf.join();
197         resultsMap.put(request, cf);
198 
199         for (HttpRequest r : resultsMap.keySet()) {
200             HttpResponse<Path> response = resultsMap.get(r).join();
201             assertEquals(response.statusCode(), 200);
202             String fileAsString = new String(Files.readAllBytes(response.body()), UTF_8);
203             assertEquals(fileAsString, tempFileAsString);
204         }
205         assertEquals(resultsMap.size(),  LOOPS + 1);
206     }
207 
208     // ---  Consumer<byte[]> ---
209 
210     static class ByteArrayConsumer implements Consumer<Optional<byte[]>> {
211         volatile List<byte[]> listByteArrays = new ArrayList<>();
212         volatile byte[] accumulatedBytes;
213 
getAccumulatedBytes()214         public byte[] getAccumulatedBytes() { return accumulatedBytes; }
215 
216         @Override
accept(Optional<byte[]> optionalBytes)217         public void accept(Optional<byte[]> optionalBytes) {
218             assert accumulatedBytes == null;
219             if (!optionalBytes.isPresent()) {
220                 int size = listByteArrays.stream().mapToInt(ba -> ba.length).sum();
221                 ByteBuffer bb = ByteBuffer.allocate(size);
222                 listByteArrays.stream().forEach(ba -> bb.put(ba));
223                 accumulatedBytes = bb.array();
224             } else {
225                 listByteArrays.add(optionalBytes.get());
226             }
227         }
228     }
229 
230     // Test 5 - custom written handler, everything as a consumer of optional byte[]
231     @Test
testTypeByteArrayConsumer()232     public void testTypeByteArrayConsumer() throws Exception {
233         String tempFileAsString = new String(Files.readAllBytes(tempFile), UTF_8);
234         ConcurrentMap<HttpRequest, CompletableFuture<HttpResponse<Void>>> resultsMap
235                 = new ConcurrentHashMap<>();
236         Map<HttpRequest,ByteArrayConsumer> byteArrayConsumerMap
237                 = new ConcurrentHashMap<>();
238 
239         HttpClient client = HttpClient.newHttpClient();
240         HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
241         ByteArrayConsumer bac = new ByteArrayConsumer();
242         byteArrayConsumerMap.put(request, bac);
243 
244         PushPromiseHandler<Void> pushPromiseHandler = (initial, pushRequest, acceptor) -> {
245             CompletableFuture<HttpResponse<Void>> cf = acceptor.apply(
246                     (info) -> {
247                         ByteArrayConsumer bc = new ByteArrayConsumer();
248                         byteArrayConsumerMap.put(pushRequest, bc);
249                         return BodySubscribers.ofByteArrayConsumer(bc); } );
250             resultsMap.put(pushRequest, cf);
251         };
252 
253         CompletableFuture<HttpResponse<Void>> cf =
254                 client.sendAsync(request, BodyHandlers.ofByteArrayConsumer(bac), pushPromiseHandler);
255         cf.join();
256         resultsMap.put(request, cf);
257 
258         for (HttpRequest r : resultsMap.keySet()) {
259             HttpResponse<Void> response = resultsMap.get(r).join();
260             assertEquals(response.statusCode(), 200);
261             byte[] ba = byteArrayConsumerMap.get(r).getAccumulatedBytes();
262             String result = new String(ba, UTF_8);
263             assertEquals(result, tempFileAsString);
264         }
265         assertEquals(resultsMap.size(), LOOPS + 1);
266     }
267 
268     // Test 6 - of(...) populating the given Map, everything as a consumer of optional byte[]
269     @Test
testTypeByteArrayConsumerOfMap()270     public void testTypeByteArrayConsumerOfMap() throws Exception {
271         String tempFileAsString = new String(Files.readAllBytes(tempFile), UTF_8);
272         ConcurrentMap<HttpRequest, CompletableFuture<HttpResponse<Void>>> resultsMap
273                 = new ConcurrentHashMap<>();
274         Map<HttpRequest,ByteArrayConsumer> byteArrayConsumerMap
275                 = new ConcurrentHashMap<>();
276 
277         HttpClient client = HttpClient.newHttpClient();
278         HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
279         ByteArrayConsumer bac = new ByteArrayConsumer();
280         byteArrayConsumerMap.put(request, bac);
281 
282         PushPromiseHandler<Void> pushPromiseHandler =
283                 PushPromiseHandler.of(
284                         pushRequest -> {
285                             ByteArrayConsumer bc = new ByteArrayConsumer();
286                             byteArrayConsumerMap.put(pushRequest, bc);
287                             return BodyHandlers.ofByteArrayConsumer(bc);
288                         },
289                         resultsMap);
290 
291         CompletableFuture<HttpResponse<Void>> cf =
292                 client.sendAsync(request, BodyHandlers.ofByteArrayConsumer(bac), pushPromiseHandler);
293         cf.join();
294         resultsMap.put(request, cf);
295 
296         for (HttpRequest r : resultsMap.keySet()) {
297             HttpResponse<Void> response = resultsMap.get(r).join();
298             assertEquals(response.statusCode(), 200);
299             byte[] ba = byteArrayConsumerMap.get(r).getAccumulatedBytes();
300             String result = new String(ba, UTF_8);
301             assertEquals(result, tempFileAsString);
302         }
303         assertEquals(resultsMap.size(), LOOPS + 1);
304     }
305 }
306