diff -r 4ebc2e2fb97c -r 71c04702a3d5 test/jdk/java/net/httpclient/ShortRequestBody.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/java/net/httpclient/ShortRequestBody.java Tue Sep 12 19:03:39 2017 +0200 @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.*; +import jdk.incubator.http.HttpClient; +import jdk.incubator.http.HttpResponse; +import jdk.incubator.http.HttpRequest; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Flow; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.TimeUnit; +import static java.lang.System.out; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static jdk.incubator.http.HttpResponse.BodyHandler.discard; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * @test + * @bug 8151441 + * @summary Request body of incorrect (larger or smaller) sizes than that + * reported by the body processor + * @run main/othervm ShortRequestBody + */ + +public class ShortRequestBody { + + static final Path testSrc = Paths.get(System.getProperty("test.src", ".")); + static volatile HttpClient staticDefaultClient; + + static HttpClient defaultClient() { + if (staticDefaultClient == null) { + synchronized (ShortRequestBody.class) { + staticDefaultClient = HttpClient.newHttpClient(); + } + } + return staticDefaultClient; + } + + // Some body types ( sources ) for testing. + static final String STRING_BODY = "Hello world"; + static final byte[] BYTE_ARRAY_BODY = new byte[] { + (byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE }; + static final Path FILE_BODY = testSrc.resolve("docs").resolve("files").resolve("foo.txt"); + + // Body lengths and offsets ( amount to be wrong by ), to make coordination + // between client and server easier. + static final int[] BODY_LENGTHS = new int[] { STRING_BODY.length(), + BYTE_ARRAY_BODY.length, + fileSize(FILE_BODY) }; + static final int[] BODY_OFFSETS = new int[] { 0, +1, -1, +2, -2, +3, -3 }; + + // A delegating body processor. Subtypes will have a concrete body type. + static abstract class AbstractDelegateRequestBody + implements HttpRequest.BodyProcessor { + + final HttpRequest.BodyProcessor delegate; + final long contentLength; + + AbstractDelegateRequestBody(HttpRequest.BodyProcessor delegate, + long contentLength) { + this.delegate = delegate; + this.contentLength = contentLength; + } + + @Override + public void subscribe(Flow.Subscriber subscriber) { + delegate.subscribe(subscriber); + } + + @Override + public long contentLength() { return contentLength; /* may be wrong! */ } + } + + // Request body processors that may generate a different number of actual + // bytes to that of what is reported through their {@code contentLength}. + + static class StringRequestBody extends AbstractDelegateRequestBody { + StringRequestBody(String body, int additionalLength) { + super(HttpRequest.BodyProcessor.fromString(body), + body.getBytes(UTF_8).length + additionalLength); + } + } + + static class ByteArrayRequestBody extends AbstractDelegateRequestBody { + ByteArrayRequestBody(byte[] body, int additionalLength) { + super(HttpRequest.BodyProcessor.fromByteArray(body), + body.length + additionalLength); + } + } + + static class FileRequestBody extends AbstractDelegateRequestBody { + FileRequestBody(Path path, int additionalLength) throws IOException { + super(HttpRequest.BodyProcessor.fromFile(path), + Files.size(path) + additionalLength); + } + } + + // --- + + public static void main(String[] args) throws Exception { + try (Server server = new Server()) { + URI uri = new URI("http://127.0.0.1:" + server.getPort() + "/"); + + // sanity + success(uri, new StringRequestBody(STRING_BODY, 0)); + success(uri, new ByteArrayRequestBody(BYTE_ARRAY_BODY, 0)); + success(uri, new FileRequestBody(FILE_BODY, 0)); + + for (int i=1; i< BODY_OFFSETS.length; i++) { + failureBlocking(uri, new StringRequestBody(STRING_BODY, BODY_OFFSETS[i])); + failureBlocking(uri, new ByteArrayRequestBody(BYTE_ARRAY_BODY, BODY_OFFSETS[i])); + failureBlocking(uri, new FileRequestBody(FILE_BODY, BODY_OFFSETS[i])); + + failureNonBlocking(uri, new StringRequestBody(STRING_BODY, BODY_OFFSETS[i])); + failureNonBlocking(uri, new ByteArrayRequestBody(BYTE_ARRAY_BODY, BODY_OFFSETS[i])); + failureNonBlocking(uri, new FileRequestBody(FILE_BODY, BODY_OFFSETS[i])); + } + } finally { + Executor def = defaultClient().executor(); + if (def instanceof ExecutorService) { + ((ExecutorService)def).shutdownNow(); + } + } + } + + static void success(URI uri, HttpRequest.BodyProcessor processor) + throws Exception + { + CompletableFuture> cf; + HttpRequest request = HttpRequest.newBuilder(uri) + .POST(processor) + .build(); + cf = defaultClient().sendAsync(request, discard(null)); + + HttpResponse resp = cf.get(30, TimeUnit.SECONDS); + out.println("Response code: " + resp.statusCode()); + check(resp.statusCode() == 200, "Expected 200, got ", resp.statusCode()); + } + + static void failureNonBlocking(URI uri, HttpRequest.BodyProcessor processor) + throws Exception + { + CompletableFuture> cf; + HttpRequest request = HttpRequest.newBuilder(uri) + .POST(processor) + .build(); + cf = defaultClient().sendAsync(request, discard(null)); + + try { + HttpResponse r = cf.get(30, TimeUnit.SECONDS); + throw new RuntimeException("Unexpected response: " + r.statusCode()); + } catch (TimeoutException x) { + throw new RuntimeException("Unexpected timeout", x); + } catch (ExecutionException expected) { + out.println("Caught expected: " + expected); + check(expected.getCause() instanceof IOException, + "Expected cause IOException, but got: ", expected.getCause()); + } + } + + static void failureBlocking(URI uri, HttpRequest.BodyProcessor processor) + throws Exception + { + HttpRequest request = HttpRequest.newBuilder(uri) + .POST(processor) + .build(); + try { + HttpResponse r = defaultClient().send(request, discard(null)); + throw new RuntimeException("Unexpected response: " + r.statusCode()); + } catch (IOException expected) { + out.println("Caught expected: " + expected); + } + } + + static class Server extends Thread implements AutoCloseable { + + static String RESPONSE = "HTTP/1.1 200 OK\r\n" + + "Connection: close\r\n"+ + "Content-length: 0\r\n\r\n"; + + private final ServerSocket ss; + private volatile boolean closed; + + Server() throws IOException { + super("Test-Server"); + ss = new ServerSocket(0); this.start(); + } + + int getPort() { return ss.getLocalPort(); } + + @Override + public void run() { + int count = 0; + int offset = 0; + + while (!closed) { + try (Socket s = ss.accept()) { + InputStream is = s.getInputStream(); + readRequestHeaders(is); + byte[] ba = new byte[1024]; + + int length = BODY_LENGTHS[count % 3]; + length += BODY_OFFSETS[offset]; + + is.readNBytes(ba, 0, length); + + OutputStream os = s.getOutputStream(); + os.write(RESPONSE.getBytes(US_ASCII)); + count++; + if (count % 6 == 0) // 6 is the number of failure requests per offset + offset++; + } catch (IOException e) { + if (!closed) + System.out.println("Unexpected" + e); + } + } + } + + @Override + public void close() { + if (closed) + return; + closed = true; + try { + ss.close(); + } catch (IOException e) { + throw new UncheckedIOException("Unexpected", e); + } + } + } + + static final byte[] requestEnd = new byte[] {'\r', '\n', '\r', '\n' }; + + // Read until the end of a HTTP request headers + static void readRequestHeaders(InputStream is) throws IOException { + int requestEndCount = 0, r; + while ((r = is.read()) != -1) { + if (r == requestEnd[requestEndCount]) { + requestEndCount++; + if (requestEndCount == 4) { + break; + } + } else { + requestEndCount = 0; + } + } + } + + static int fileSize(Path p) { + try { return (int) Files.size(p); } + catch (IOException x) { throw new UncheckedIOException(x); } + } + + static boolean check(boolean cond, Object... failedArgs) { + if (cond) + return true; + // We are going to fail... + StringBuilder sb = new StringBuilder(); + for (Object o : failedArgs) + sb.append(o); + throw new RuntimeException(sb.toString()); + } +}