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
40import java.io.*;
41import java.net.URI;
42import jdk.incubator.http.HttpClient;
43import jdk.incubator.http.HttpRequest;
44import jdk.incubator.http.HttpResponse;
45import java.nio.charset.Charset;
46import java.nio.charset.StandardCharsets;
47import java.nio.file.Files;
48import java.nio.file.Path;
49import java.nio.file.Paths;
50import java.util.ArrayList;
51import java.util.Arrays;
52import java.util.List;
53import java.util.Optional;
54import java.util.concurrent.ExecutorService;
55import java.util.concurrent.Executors;
56import java.util.function.Supplier;
57import javax.net.ssl.SSLContext;
58import jdk.test.lib.util.FileUtils;
59import static java.nio.charset.StandardCharsets.*;
60import static java.nio.file.StandardOpenOption.*;
61import static jdk.incubator.http.HttpRequest.BodyProcessor.*;
62import static jdk.incubator.http.HttpResponse.BodyHandler.*;
63
64import org.testng.annotations.AfterTest;
65import org.testng.annotations.BeforeTest;
66import org.testng.annotations.DataProvider;
67import org.testng.annotations.Test;
68import static org.testng.Assert.*;
69
70public 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}
307