1 /*
   2  * Copyright (c) 2015, 2017, 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 @bug 8087112
  26  * @modules jdk.incubator.httpclient
  27  *          java.logging
  28  *          jdk.httpserver
  29  * @library /lib/testlibrary/ /test/lib
  30  * @compile ../../../com/sun/net/httpserver/LogFilter.java
  31  * @compile ../../../com/sun/net/httpserver/FileServerHandler.java
  32  * @build jdk.test.lib.util.FileUtils
  33  * @build jdk.test.lib.Utils
  34  * @build jdk.test.lib.Asserts
  35  * @build jdk.test.lib.JDKToolFinder
  36  * @build jdk.test.lib.JDKToolLauncher
  37  * @build jdk.test.lib.Platform
  38  * @build jdk.test.lib.process.*
  39  * @build LightWeightHttpServer
  40  * @build jdk.testlibrary.SimpleSSLContext
  41  * @run testng/othervm RequestBodyTest
  42  */
  43 
  44 import java.io.*;
  45 import java.net.URI;
  46 import jdk.incubator.http.HttpClient;
  47 import jdk.incubator.http.HttpRequest;
  48 import jdk.incubator.http.HttpResponse;
  49 import java.nio.charset.Charset;
  50 import java.nio.charset.StandardCharsets;
  51 import java.nio.file.Files;
  52 import java.nio.file.Path;
  53 import java.nio.file.Paths;
  54 import java.util.ArrayList;
  55 import java.util.Arrays;
  56 import java.util.List;
  57 import java.util.Optional;
  58 import java.util.concurrent.ExecutorService;
  59 import java.util.concurrent.Executors;
  60 import java.util.function.Supplier;
  61 import javax.net.ssl.SSLContext;
  62 import jdk.test.lib.util.FileUtils;
  63 import static java.nio.charset.StandardCharsets.*;
  64 import static java.nio.file.StandardOpenOption.*;
  65 import static jdk.incubator.http.HttpRequest.BodyProcessor.*;
  66 import static jdk.incubator.http.HttpResponse.BodyHandler.*;
  67 
  68 import org.testng.annotations.AfterTest;
  69 import org.testng.annotations.BeforeTest;
  70 import org.testng.annotations.DataProvider;
  71 import org.testng.annotations.Test;
  72 import static org.testng.Assert.*;
  73 
  74 public class RequestBodyTest {
  75 
  76     static final String fileroot = System.getProperty("test.src") + "/docs";
  77     static final String midSizedFilename = "/files/notsobigfile.txt";
  78     static final String smallFilename = "/files/smallfile.txt";
  79 
  80     HttpClient client;
  81     ExecutorService exec = Executors.newCachedThreadPool();
  82     String httpURI;
  83     String httpsURI;
  84 
  85     enum RequestBody {
  86         BYTE_ARRAY,
  87         BYTE_ARRAY_OFFSET,
  88         BYTE_ARRAYS,
  89         FILE,
  90         INPUTSTREAM,
  91         STRING,
  92         STRING_WITH_CHARSET
  93     }
  94 
  95     enum ResponseBody {
  96         BYTE_ARRAY,
  97         BYTE_ARRAY_CONSUMER,
  98         DISCARD,
  99         FILE,
 100         FILE_WITH_OPTION,
 101         STRING,
 102         STRING_WITH_CHARSET,
 103     }
 104 
 105     @BeforeTest
 106     public void setup() throws Exception {
 107         LightWeightHttpServer.initServer();
 108         httpURI = LightWeightHttpServer.httproot + "echo/foo";
 109         httpsURI = LightWeightHttpServer.httpsroot + "echo/foo";
 110 
 111         SSLContext ctx = LightWeightHttpServer.ctx;
 112         client = HttpClient.newBuilder()
 113                            .sslContext(ctx)
 114                            .version(HttpClient.Version.HTTP_1_1)
 115                            .followRedirects(HttpClient.Redirect.ALWAYS)
 116                            .executor(exec)
 117                            .build();
 118     }
 119 
 120     @AfterTest
 121     public void teardown() throws Exception {
 122         exec.shutdownNow();
 123         LightWeightHttpServer.stop();
 124     }
 125 
 126     @DataProvider
 127     public Object[][] exchanges() throws Exception {
 128         List<Object[]> values = new ArrayList<>();
 129 
 130         for (boolean async : new boolean[] { false, true })
 131             for (String uri : new String[] { httpURI, httpsURI })
 132                 for (String file : new String[] { smallFilename, midSizedFilename })
 133                     for (RequestBody requestBodyType : RequestBody.values())
 134                         for (ResponseBody responseBodyType : ResponseBody.values())
 135                             values.add(new Object[]
 136                                 {uri, requestBodyType, responseBodyType, file, async});
 137 
 138         return values.stream().toArray(Object[][]::new);
 139     }
 140 
 141     @Test(dataProvider = "exchanges")
 142     void exchange(String target,
 143                   RequestBody requestBodyType,
 144                   ResponseBody responseBodyType,
 145                   String file,
 146                   boolean async)
 147         throws Exception
 148     {
 149         Path filePath = Paths.get(fileroot + file);
 150         URI uri = new URI(target);
 151 
 152         HttpRequest request = createRequest(uri, requestBodyType, filePath);
 153 
 154         checkResponse(client, request, requestBodyType, responseBodyType, filePath, async);
 155     }
 156 
 157     static final int DEFAULT_OFFSET = 10;
 158     static final int DEFAULT_LENGTH = 1000;
 159 
 160     HttpRequest createRequest(URI uri,
 161                               RequestBody requestBodyType,
 162                               Path file)
 163         throws IOException
 164     {
 165         HttpRequest.Builder rb =  HttpRequest.newBuilder(uri);
 166 
 167         String filename = file.toFile().getAbsolutePath();
 168         byte[] fileAsBytes = getFileBytes(filename);
 169         String fileAsString = new String(fileAsBytes, UTF_8);
 170 
 171         switch (requestBodyType) {
 172             case BYTE_ARRAY:
 173                 rb.POST(fromByteArray(fileAsBytes));
 174                 break;
 175             case BYTE_ARRAY_OFFSET:
 176                 rb.POST(fromByteArray(fileAsBytes, DEFAULT_OFFSET, DEFAULT_LENGTH));
 177                 break;
 178             case BYTE_ARRAYS:
 179                 Iterable<byte[]> iterable = Arrays.asList(fileAsBytes);
 180                 rb.POST(fromByteArrays(iterable));
 181                 break;
 182             case FILE:
 183                 rb.POST(fromFile(file));
 184                 break;
 185             case INPUTSTREAM:
 186                 rb.POST(fromInputStream(fileInputStreamSupplier(file)));
 187                 break;
 188             case STRING:
 189                 rb.POST(fromString(fileAsString));
 190                 break;
 191             case STRING_WITH_CHARSET:
 192                 rb.POST(fromString(new String(fileAsBytes), Charset.defaultCharset()));
 193                 break;
 194             default:
 195                 throw new AssertionError("Unknown request body:" + requestBodyType);
 196         }
 197         return rb.build();
 198     }
 199 
 200     void checkResponse(HttpClient client,
 201                        HttpRequest request,
 202                        RequestBody requestBodyType,
 203                        ResponseBody responseBodyType,
 204                        Path file,
 205                        boolean async)
 206         throws InterruptedException, IOException
 207     {
 208         String filename = file.toFile().getAbsolutePath();
 209         byte[] fileAsBytes = getFileBytes(filename);
 210         if (requestBodyType == RequestBody.BYTE_ARRAY_OFFSET) {
 211             // Truncate the expected response body, if only a portion was sent
 212             fileAsBytes = Arrays.copyOfRange(fileAsBytes,
 213                                              DEFAULT_OFFSET,
 214                                              DEFAULT_OFFSET + DEFAULT_LENGTH);
 215         }
 216         String fileAsString = new String(fileAsBytes, UTF_8);
 217         Path tempFile = Paths.get("RequestBodyTest.tmp");
 218         FileUtils.deleteFileIfExistsWithRetry(tempFile);
 219 
 220         switch (responseBodyType) {
 221             case BYTE_ARRAY:
 222                 HttpResponse<byte[]> bar = getResponse(client, request, asByteArray(), async);
 223                 assertEquals(bar.statusCode(), 200);
 224                 assertEquals(bar.body(), fileAsBytes);
 225                 break;
 226             case BYTE_ARRAY_CONSUMER:
 227                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 228                 HttpResponse<Void> v = getResponse(client, request,
 229                         asByteArrayConsumer(o -> consumerBytes(o, baos) ), async);
 230                 byte[] ba = baos.toByteArray();
 231                 assertEquals(v.statusCode(), 200);
 232                 assertEquals(ba, fileAsBytes);
 233                 break;
 234             case DISCARD:
 235                 Object o = new Object();
 236                 HttpResponse<Object> or = getResponse(client, request, discard(o), async);
 237                 assertEquals(or.statusCode(), 200);
 238                 assertSame(or.body(), o);
 239                 break;
 240             case FILE:
 241                 HttpResponse<Path> fr = getResponse(client, request, asFile(tempFile), async);
 242                 assertEquals(fr.statusCode(), 200);
 243                 assertEquals(Files.size(tempFile), fileAsString.length());
 244                 assertEquals(Files.readAllBytes(tempFile), fileAsBytes);
 245                 break;
 246             case FILE_WITH_OPTION:
 247                 fr = getResponse(client, request, asFile(tempFile, CREATE_NEW, WRITE), async);
 248                 assertEquals(fr.statusCode(), 200);
 249                 assertEquals(Files.size(tempFile), fileAsString.length());
 250                 assertEquals(Files.readAllBytes(tempFile), fileAsBytes);
 251                 break;
 252             case STRING:
 253                 HttpResponse<String> sr = getResponse(client, request, asString(), async);
 254                 assertEquals(sr.statusCode(), 200);
 255                 assertEquals(sr.body(), fileAsString);
 256                 break;
 257             case STRING_WITH_CHARSET:
 258                 HttpResponse<String> r = getResponse(client, request, asString(StandardCharsets.UTF_8), async);
 259                 assertEquals(r.statusCode(), 200);
 260                 assertEquals(r.body(), fileAsString);
 261                 break;
 262             default:
 263                 throw new AssertionError("Unknown response body:" + responseBodyType);
 264         }
 265     }
 266 
 267     static <T> HttpResponse<T> getResponse(HttpClient client,
 268                                            HttpRequest request,
 269                                            HttpResponse.BodyHandler<T> handler,
 270                                            boolean async)
 271         throws InterruptedException, IOException
 272     {
 273         if (!async)
 274             return client.send(request, handler);
 275         else
 276             return client.sendAsync(request, handler).join();
 277     }
 278 
 279     static byte[] getFileBytes(String path) throws IOException {
 280         try (FileInputStream fis = new FileInputStream(path);
 281              BufferedInputStream bis = new BufferedInputStream(fis);
 282              ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
 283             bis.transferTo(baos);
 284             return baos.toByteArray();
 285         }
 286     }
 287 
 288     static Supplier<FileInputStream> fileInputStreamSupplier(Path f) {
 289         return new Supplier<>() {
 290             Path file = f;
 291             @Override
 292             public FileInputStream get() {
 293                 try {
 294                     return new FileInputStream(file.toFile());
 295                 } catch (FileNotFoundException x) {
 296                     throw new UncheckedIOException(x);
 297                 }
 298             }
 299         };
 300     }
 301 
 302     static void consumerBytes(Optional<byte[]> bytes, ByteArrayOutputStream baos) {
 303         try {
 304             if (bytes.isPresent())
 305                 baos.write(bytes.get());
 306         } catch (IOException x) {
 307             throw new UncheckedIOException(x);
 308         }
 309     }
 310 }