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