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 import java.io.*;
 25 import java.net.URI;
 26 import java.net.http.HttpClient;
 27 import java.net.http.HttpRequest;
 28 import java.net.http.HttpResponse;
 29 import java.net.http.HttpRequest.BodyPublishers;
 30 import java.net.http.HttpResponse.BodyHandler;
 31 import java.net.http.HttpResponse.BodyHandlers;
 32 import java.nio.charset.Charset;
 33 import java.nio.charset.StandardCharsets;
 34 import java.nio.file.Files;
 35 import java.nio.file.Path;
 36 import java.nio.file.Paths;
 37 import java.security.AccessController;
 38 import java.security.PrivilegedActionException;
 39 import java.security.PrivilegedExceptionAction;
 40 import java.util.ArrayList;
 41 import java.util.Arrays;
 42 import java.util.List;
 43 import java.util.Optional;
 44 import java.util.concurrent.ConcurrentHashMap;
 45 import java.util.function.Consumer;
 46 import java.util.function.Supplier;
 47 import javax.net.ssl.SSLContext;
 48 import jdk.test.lib.util.FileUtils;
 49 import static java.lang.System.out;
 50 import static java.nio.charset.StandardCharsets.*;
 51 import static java.nio.file.StandardOpenOption.*;
 52 
 53 import org.testng.annotations.AfterTest;
 54 import org.testng.annotations.BeforeTest;
 55 import org.testng.annotations.DataProvider;
 56 import org.testng.annotations.Test;
 57 import static org.testng.Assert.*;
 58 
 59 /*
 60  * @test
 61  * @bug 8087112
 62  * @modules java.net.http/jdk.internal.net.http.common
 63  *          java.logging
 64  *          jdk.httpserver
 65  * @library /test/lib /test/jdk/java/net/httpclient/lib
 66  * @compile ../../../com/sun/net/httpserver/LogFilter.java
 67  * @compile ../../../com/sun/net/httpserver/EchoHandler.java
 68  * @compile ../../../com/sun/net/httpserver/FileServerHandler.java
 69  * @build jdk.test.lib.net.SimpleSSLContext jdk.httpclient.test.lib.common.TestServerConfigurator
 70  * @build LightWeightHttpServer
 71  * @build jdk.test.lib.Platform
 72  * @build jdk.test.lib.util.FileUtils
 73  * @run testng/othervm RequestBodyTest
 74  * @run testng/othervm/java.security.policy=RequestBodyTest.policy RequestBodyTest
 75  */
 76 public class RequestBodyTest {
 77 
 78     static final String fileroot = System.getProperty("test.src", ".") + "/docs";
 79     static final String midSizedFilename = "/files/notsobigfile.txt";
 80     static final String smallFilename = "/files/smallfile.txt";
 81     final ConcurrentHashMap<String,Throwable> failures = new ConcurrentHashMap<>();
 82 
 83     HttpClient client;
 84     String httpURI;
 85     String httpsURI;
 86 
 87     enum RequestBody {
 88         BYTE_ARRAY,
 89         BYTE_ARRAY_OFFSET,
 90         BYTE_ARRAYS,
 91         FILE,
 92         INPUTSTREAM,
 93         STRING,
 94         STRING_WITH_CHARSET
 95     }
 96 
 97     enum ResponseBody {
 98         BYTE_ARRAY,
 99         BYTE_ARRAY_CONSUMER,
100         DISCARD,
101         FILE,
102         FILE_WITH_OPTION,
103         STRING,
104         STRING_WITH_CHARSET,
105     }
106 
107     @BeforeTest
108     public void setup() throws Exception {
109         LightWeightHttpServer.initServer();
110         httpURI = LightWeightHttpServer.httproot + "echo/foo";
111         httpsURI = LightWeightHttpServer.httpsroot + "echo/foo";
112 
113         SSLContext ctx = LightWeightHttpServer.ctx;
114         client = HttpClient.newBuilder()
115                            .sslContext(ctx)
116                            .version(HttpClient.Version.HTTP_1_1)
117                            .followRedirects(HttpClient.Redirect.ALWAYS)
118                            .build();
119     }
120 
121     @AfterTest
122     public void teardown() throws Exception {
123         try {
124             LightWeightHttpServer.stop();
125         } finally {
126             System.out.println("RequestBodyTest: " + failures.size() + " failures");
127             int i = 0;
128             for (String key: failures.keySet()) {
129                 System.out.println("test" + key + " failed: " + failures.get(key));
130                 failures.get(key).printStackTrace(System.out);
131                 if (i++ > 3) {
132                    System.out.println("..... other failures not printed ....");
133                    break;
134                 }
135             }
136         }
137     }
138 
139     @DataProvider
140     public Object[][] exchanges() throws Exception {
141         List<Object[]> values = new ArrayList<>();
142 
143         for (boolean async : new boolean[] { false, true })
144             for (String uri : new String[] { httpURI, httpsURI })
145                 for (String file : new String[] { smallFilename, midSizedFilename })
146                     for (RequestBody requestBodyType : RequestBody.values())
147                         for (ResponseBody responseBodyType : ResponseBody.values())
148                             for (boolean bufferResponseBody : new boolean[] { false, true })
149                                 values.add(new Object[]
150                                     {uri, requestBodyType, responseBodyType, file, async, bufferResponseBody});
151 
152         return values.stream().toArray(Object[][]::new);
153     }
154 
155     @Test(dataProvider = "exchanges")
156     void exchange(String target,
157                   RequestBody requestBodyType,
158                   ResponseBody responseBodyType,
159                   String file,
160                   boolean async,
161                   boolean bufferResponseBody)
162         throws Exception
163     {
164         try {
165             Path filePath = Paths.get(fileroot + file);
166             URI uri = new URI(target);
167 
168             HttpRequest request = createRequest(uri, requestBodyType, filePath);
169 
170             checkResponse(client, request, requestBodyType, responseBodyType, filePath, async, bufferResponseBody);
171         } catch (Exception | Error x) {
172             Object[] params = new Object[] {
173                 target, requestBodyType, responseBodyType,
174                 file, "async=" + async, "buffer=" + bufferResponseBody
175             };
176             failures.put(java.util.Arrays.toString(params), x);
177             throw x;
178         }
179     }
180 
181     static final int DEFAULT_OFFSET = 10;
182     static final int DEFAULT_LENGTH_FACTOR = 4/5;
183 
184     HttpRequest createRequest(URI uri,
185                               RequestBody requestBodyType,
186                               Path file)
187         throws IOException
188     {
189         HttpRequest.Builder rb =  HttpRequest.newBuilder(uri);
190 
191         String filename = file.toFile().getAbsolutePath();
192         byte[] fileAsBytes = getFileBytes(filename);
193         String fileAsString = new String(fileAsBytes, UTF_8);
194 
195         switch (requestBodyType) {
196             case BYTE_ARRAY:
197                 rb.POST(BodyPublishers.ofByteArray(fileAsBytes));
198                 break;
199             case BYTE_ARRAY_OFFSET:
200                 rb.POST(BodyPublishers.ofByteArray(fileAsBytes,
201                         DEFAULT_OFFSET,
202                         fileAsBytes.length * DEFAULT_LENGTH_FACTOR));
203                 break;
204             case BYTE_ARRAYS:
205                 Iterable<byte[]> iterable = Arrays.asList(fileAsBytes);
206                 rb.POST(BodyPublishers.ofByteArrays(iterable));
207                 break;
208             case FILE:
209                 rb.POST(BodyPublishers.ofFile(file));
210                 break;
211             case INPUTSTREAM:
212                 rb.POST(BodyPublishers.ofInputStream(fileInputStreamSupplier(file)));
213                 break;
214             case STRING:
215                 rb.POST(BodyPublishers.ofString(fileAsString));
216                 break;
217             case STRING_WITH_CHARSET:
218                 rb.POST(BodyPublishers.ofString(new String(fileAsBytes), Charset.defaultCharset()));
219                 break;
220             default:
221                 throw new AssertionError("Unknown request body:" + requestBodyType);
222         }
223         return rb.build();
224     }
225 
226     void checkResponse(HttpClient client,
227                        HttpRequest request,
228                        RequestBody requestBodyType,
229                        ResponseBody responseBodyType,
230                        Path file,
231                        boolean async,
232                        boolean bufferResponseBody)
233         throws InterruptedException, IOException
234     {
235         String filename = file.toFile().getAbsolutePath();
236         byte[] fileAsBytes = getFileBytes(filename);
237         if (requestBodyType == RequestBody.BYTE_ARRAY_OFFSET) {
238             // Truncate the expected response body, if only a portion was sent
239             int length = DEFAULT_OFFSET + (fileAsBytes.length * DEFAULT_LENGTH_FACTOR);
240             fileAsBytes = Arrays.copyOfRange(fileAsBytes, DEFAULT_OFFSET, length);
241         }
242         String fileAsString = new String(fileAsBytes, UTF_8);
243         Path tempFile = Paths.get("RequestBodyTest.tmp");
244         FileUtils.deleteFileIfExistsWithRetry(tempFile);
245 
246         switch (responseBodyType) {
247             case BYTE_ARRAY:
248                 BodyHandler<byte[]> bh = BodyHandlers.ofByteArray();
249                 if (bufferResponseBody) bh = BodyHandlers.buffering(bh, 50);
250                 HttpResponse<byte[]> bar = getResponse(client, request, bh, async);
251                 assertEquals(bar.statusCode(), 200);
252                 assertEquals(bar.body(), fileAsBytes);
253                 break;
254             case BYTE_ARRAY_CONSUMER:
255                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
256                 Consumer<Optional<byte[]>> consumer = o -> consumerBytes(o, baos);
257                 BodyHandler<Void> bh1 = BodyHandlers.ofByteArrayConsumer(consumer);
258                 if (bufferResponseBody) bh1 = BodyHandlers.buffering(bh1, 49);
259                 HttpResponse<Void> v = getResponse(client, request, bh1, async);
260                 byte[] ba = baos.toByteArray();
261                 assertEquals(v.statusCode(), 200);
262                 assertEquals(ba, fileAsBytes);
263                 break;
264             case DISCARD:
265                 Object o = new Object();
266                 BodyHandler<Object> bh2 = BodyHandlers.replacing(o);
267                 if (bufferResponseBody) bh2 = BodyHandlers.buffering(bh2, 51);
268                 HttpResponse<Object> or = getResponse(client, request, bh2, async);
269                 assertEquals(or.statusCode(), 200);
270                 assertSame(or.body(), o);
271                 break;
272             case FILE:
273                 BodyHandler<Path> bh3 = BodyHandlers.ofFile(tempFile);
274                 if (bufferResponseBody) bh3 = BodyHandlers.buffering(bh3, 48);
275                 HttpResponse<Path> fr = getResponse(client, request, bh3, async);
276                 assertEquals(fr.statusCode(), 200);
277                 assertEquals(Files.size(tempFile), fileAsString.length());
278                 assertEquals(Files.readAllBytes(tempFile), fileAsBytes);
279                 break;
280             case FILE_WITH_OPTION:
281                 BodyHandler<Path> bh4 = BodyHandlers.ofFile(tempFile, CREATE_NEW, WRITE);
282                 if (bufferResponseBody) bh4 = BodyHandlers.buffering(bh4, 52);
283                 fr = getResponse(client, request, bh4, async);
284                 assertEquals(fr.statusCode(), 200);
285                 assertEquals(Files.size(tempFile), fileAsString.length());
286                 assertEquals(Files.readAllBytes(tempFile), fileAsBytes);
287                 break;
288             case STRING:
289                 BodyHandler<String> bh5 = BodyHandlers.ofString();
290                 if(bufferResponseBody) bh5 = BodyHandlers.buffering(bh5, 47);
291                 HttpResponse<String> sr = getResponse(client, request, bh5, async);
292                 assertEquals(sr.statusCode(), 200);
293                 assertEquals(sr.body(), fileAsString);
294                 break;
295             case STRING_WITH_CHARSET:
296                 BodyHandler<String> bh6 = BodyHandlers.ofString(StandardCharsets.UTF_8);
297                 if (bufferResponseBody) bh6 = BodyHandlers.buffering(bh6, 53);
298                 HttpResponse<String> r = getResponse(client, request, bh6, async);
299                 assertEquals(r.statusCode(), 200);
300                 assertEquals(r.body(), fileAsString);
301                 break;
302             default:
303                 throw new AssertionError("Unknown response body:" + responseBodyType);
304         }
305     }
306 
307     static <T> HttpResponse<T> getResponse(HttpClient client,
308                                            HttpRequest request,
309                                            HttpResponse.BodyHandler<T> handler,
310                                            boolean async)
311         throws InterruptedException, IOException
312     {
313         if (!async)
314             return client.send(request, handler);
315         else
316             return client.sendAsync(request, handler).join();
317     }
318 
319     static byte[] getFileBytes(String path) throws IOException {
320         try (FileInputStream fis = new FileInputStream(path);
321              BufferedInputStream bis = new BufferedInputStream(fis);
322              ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
323             bis.transferTo(baos);
324             return baos.toByteArray();
325         }
326     }
327 
328     static Supplier<FileInputStream> fileInputStreamSupplier(Path f) {
329         return new Supplier<>() {
330             Path file = f;
331             @Override
332             public FileInputStream get() {
333                 try {
334                     PrivilegedExceptionAction<FileInputStream> pa =
335                             () -> new FileInputStream(file.toFile());
336                     return AccessController.doPrivileged(pa);
337                 } catch (PrivilegedActionException x) {
338                     throw new UncheckedIOException((IOException)x.getCause());
339                 }
340             }
341         };
342     }
343 
344     static void consumerBytes(Optional<byte[]> bytes, ByteArrayOutputStream baos) {
345         try {
346             if (bytes.isPresent())
347                 baos.write(bytes.get());
348         } catch (IOException x) {
349             throw new UncheckedIOException(x);
350         }
351     }
352 
353     // ---
354 
355     /* Main entry point for standalone testing of the main functional test. */
356     public static void main(String... args) throws Exception {
357         RequestBodyTest t = new RequestBodyTest();
358         t.setup();
359         int count = 0;
360         try {
361             for (Object[] objs : t.exchanges()) {
362                 count++;
363                 out.printf("********* iteration: %d %s %s %s %s %s %s *********%n",
364                            count, objs[0], objs[1], objs[2], objs[3], objs[4], objs[5]);
365                 t.exchange((String) objs[0],
366                            (RequestBody) objs[1],
367                            (ResponseBody) objs[2],
368                            (String) objs[3],
369                            (boolean) objs[4],
370                            (boolean) objs[5]);
371             }
372         } finally {
373             t.teardown();
374         }
375     }
376 }