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