| /* | |
| * Copyright (c) 2018, 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. | |
| */ | |
| /* | |
| * @test | |
| * @summary Ensure that the POST method is retied when the property is set. | |
| * @run testng/othervm -Djdk.httpclient.enableAllMethodRetry RetryPost | |
| * @run testng/othervm -Djdk.httpclient.enableAllMethodRetry=true RetryPost | |
| */ | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.io.OutputStream; | |
| import java.io.UncheckedIOException; | |
| import java.net.InetAddress; | |
| import java.net.InetSocketAddress; | |
| import java.net.ServerSocket; | |
| import java.net.Socket; | |
| import java.net.URI; | |
| import java.net.http.HttpClient; | |
| import java.net.http.HttpRequest; | |
| import java.net.http.HttpRequest.BodyPublishers; | |
| import java.net.http.HttpResponse; | |
| import org.testng.annotations.AfterTest; | |
| import org.testng.annotations.BeforeTest; | |
| import org.testng.annotations.DataProvider; | |
| import org.testng.annotations.Test; | |
| import static java.lang.System.out; | |
| import static java.net.http.HttpClient.Builder.NO_PROXY; | |
| import static java.net.http.HttpResponse.BodyHandlers.ofString; | |
| import static java.nio.charset.StandardCharsets.US_ASCII; | |
| import static org.testng.Assert.assertEquals; | |
| public class RetryPost { | |
| FixedLengthServer fixedLengthServer; | |
| String httpURIFixLen; | |
| static final String RESPONSE_BODY = | |
| "You use a glass mirror to see your face: you use works of art to see your soul."; | |
| @DataProvider(name = "uris") | |
| public Object[][] variants() { | |
| return new Object[][] { | |
| { httpURIFixLen, true }, | |
| { httpURIFixLen, false }, | |
| }; | |
| } | |
| static final int ITERATION_COUNT = 3; | |
| static final String REQUEST_BODY = "Body"; | |
| @Test(dataProvider = "uris") | |
| void testSynchronousPOST(String url, boolean sameClient) throws Exception { | |
| out.print("---\n"); | |
| HttpClient client = null; | |
| for (int i=0; i< ITERATION_COUNT; i++) { | |
| if (!sameClient || client == null) | |
| client = HttpClient.newBuilder().proxy(NO_PROXY).build(); | |
| HttpRequest request = HttpRequest.newBuilder(URI.create(url)) | |
| .POST(BodyPublishers.ofString(REQUEST_BODY)) | |
| .build(); | |
| HttpResponse<String> response = client.send(request, ofString()); | |
| String body = response.body(); | |
| out.println(response + ": " + body); | |
| assertEquals(response.statusCode(), 200); | |
| assertEquals(body, RESPONSE_BODY); | |
| } | |
| } | |
| @Test(dataProvider = "uris") | |
| void testAsynchronousPOST(String url, boolean sameClient) { | |
| out.print("---\n"); | |
| HttpClient client = null; | |
| for (int i=0; i< ITERATION_COUNT; i++) { | |
| if (!sameClient || client == null) | |
| client = HttpClient.newBuilder().proxy(NO_PROXY).build(); | |
| HttpRequest request = HttpRequest.newBuilder(URI.create(url)) | |
| .POST(BodyPublishers.ofString(REQUEST_BODY)) | |
| .build(); | |
| client.sendAsync(request, ofString()) | |
| .thenApply(r -> { out.println(r + ": " + r.body()); return r; }) | |
| .thenApply(r -> { assertEquals(r.statusCode(), 200); return r; }) | |
| .thenApply(HttpResponse::body) | |
| .thenAccept(b -> assertEquals(b, RESPONSE_BODY)) | |
| .join(); | |
| } | |
| } | |
| /** | |
| * A server that issues a valid fixed-length reply on even requests, and | |
| * immediately closes the connection on odd requests ( tick-tock ). | |
| */ | |
| static class FixedLengthServer extends Thread implements AutoCloseable { | |
| static final String RESPONSE_HEADERS = | |
| "HTTP/1.1 200 OK\r\n" + | |
| "Content-Type: text/html; charset=utf-8\r\n" + | |
| "Content-Length: " + RESPONSE_BODY.length() + "\r\n" + | |
| "Connection: close\r\n\r\n"; | |
| static final String RESPONSE = RESPONSE_HEADERS + RESPONSE_BODY; | |
| private final ServerSocket ss; | |
| private volatile boolean closed; | |
| private int invocationTimes; | |
| FixedLengthServer() throws IOException { | |
| super("FixedLengthServer"); | |
| ss = new ServerSocket(); | |
| ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); | |
| this.start(); | |
| } | |
| public int getPort() { return ss.getLocalPort(); } | |
| @Override | |
| public void run() { | |
| while (!closed) { | |
| try (Socket s = ss.accept()) { | |
| invocationTimes++; | |
| out.print("FixedLengthServer: got connection "); | |
| if ((invocationTimes & 0x1) == 0x1) { | |
| out.println(" closing immediately"); | |
| s.close(); | |
| continue; | |
| } | |
| InputStream is = s.getInputStream(); | |
| URI requestMethod = readRequestMethod(is); | |
| out.print(requestMethod + " "); | |
| URI uriPath = readRequestPath(is); | |
| out.println(uriPath); | |
| readRequestHeaders(is); | |
| byte[] body = is.readNBytes(4); | |
| assert body.length == REQUEST_BODY.length() : | |
| "Unexpected request body " + body.length; | |
| OutputStream os = s.getOutputStream(); | |
| out.println("Server: writing response bytes"); | |
| byte[] responseBytes = RESPONSE.getBytes(US_ASCII); | |
| os.write(responseBytes); | |
| } catch (IOException e) { | |
| if (!closed) | |
| throw new UncheckedIOException("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 the request method | |
| static URI readRequestMethod(InputStream is) throws IOException { | |
| StringBuilder sb = new StringBuilder(); | |
| int r; | |
| while ((r = is.read()) != -1 && r != 0x20) { | |
| sb.append((char)r); | |
| } | |
| return URI.create(sb.toString()); | |
| } | |
| // Read the request URI path | |
| static URI readRequestPath(InputStream is) throws IOException { | |
| StringBuilder sb = new StringBuilder(); | |
| int r; | |
| while ((r = is.read()) != -1 && r != 0x20) { | |
| sb.append((char)r); | |
| } | |
| return URI.create(sb.toString()); | |
| } | |
| // 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 String serverAuthority(FixedLengthServer server) { | |
| return InetAddress.getLoopbackAddress().getHostName() + ":" | |
| + server.getPort(); | |
| } | |
| @BeforeTest | |
| public void setup() throws Exception { | |
| fixedLengthServer = new FixedLengthServer(); | |
| httpURIFixLen = "http://" + serverAuthority(fixedLengthServer) | |
| + "/http1/fixed/baz"; | |
| } | |
| @AfterTest | |
| public void teardown() throws Exception { | |
| fixedLengthServer.close(); | |
| } | |
| } |