| /* | |
| * Copyright (c) 2015, 2024, 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 | |
| * @bug 8087112 8178699 8338569 | |
| * @modules java.net.http | |
| * java.logging | |
| * jdk.httpserver | |
| * @library /test/lib / | |
| * @build jdk.test.lib.net.SimpleSSLContext ProxyServer | |
| * @compile ../../../com/sun/net/httpserver/LogFilter.java | |
| * @compile ../../../com/sun/net/httpserver/FileServerHandler.java | |
| * @run main/othervm | |
| * -Djdk.internal.httpclient.debug=true | |
| * -Djdk.httpclient.HttpClient.log=errors,ssl,trace | |
| * SmokeTest | |
| */ | |
| import com.sun.net.httpserver.Headers; | |
| import com.sun.net.httpserver.HttpContext; | |
| import com.sun.net.httpserver.HttpExchange; | |
| import com.sun.net.httpserver.HttpHandler; | |
| import com.sun.net.httpserver.HttpServer; | |
| import com.sun.net.httpserver.HttpsConfigurator; | |
| import com.sun.net.httpserver.HttpsParameters; | |
| import com.sun.net.httpserver.HttpsServer; | |
| import java.net.InetAddress; | |
| import java.net.Proxy; | |
| import java.net.SocketAddress; | |
| import java.net.http.HttpHeaders; | |
| import java.nio.charset.StandardCharsets; | |
| import java.util.Collections; | |
| import java.util.Optional; | |
| import java.util.Set; | |
| import java.util.concurrent.ConcurrentHashMap; | |
| import java.util.concurrent.ConcurrentMap; | |
| import java.util.concurrent.atomic.AtomicInteger; | |
| import java.net.InetSocketAddress; | |
| import java.net.PasswordAuthentication; | |
| import java.net.ProxySelector; | |
| 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 java.net.http.HttpResponse.BodyHandlers; | |
| import java.io.File; | |
| import java.io.FileInputStream; | |
| import java.io.FileOutputStream; | |
| import java.io.FileNotFoundException; | |
| import java.io.IOException; | |
| import java.io.BufferedInputStream; | |
| import java.io.InputStream; | |
| import java.io.OutputStream; | |
| import java.io.UncheckedIOException; | |
| import java.util.concurrent.BlockingQueue; | |
| import java.util.concurrent.CompletableFuture; | |
| import java.util.concurrent.CompletionException; | |
| import java.util.concurrent.CyclicBarrier; | |
| import java.util.concurrent.Executors; | |
| import java.util.concurrent.ExecutorService; | |
| import java.util.concurrent.LinkedBlockingQueue; | |
| import java.util.concurrent.TimeUnit; | |
| import javax.net.ssl.SSLContext; | |
| import javax.net.ssl.SSLParameters; | |
| import java.nio.file.Files; | |
| import java.nio.file.Path; | |
| import java.nio.file.Paths; | |
| import java.util.HashSet; | |
| import java.util.LinkedList; | |
| import java.util.List; | |
| import java.util.Random; | |
| import jdk.test.lib.net.SimpleSSLContext; | |
| import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; | |
| import static java.nio.file.StandardOpenOption.WRITE; | |
| import java.util.concurrent.CountDownLatch; | |
| import java.util.logging.ConsoleHandler; | |
| import java.util.logging.Level; | |
| import java.util.logging.Logger; | |
| /** | |
| * * Basic smoke test for Http/1.1 client | |
| * - basic request response | |
| * - request body POST | |
| * - response body GET | |
| * - redirect | |
| * - chunked request/response | |
| * - SSL | |
| * - proxies | |
| * - 100 continue | |
| * - check keep alive appears to be working | |
| * - cancel of long request | |
| * | |
| * Uses a FileServerHandler serving a couple of known files | |
| * in docs directory. | |
| */ | |
| public class SmokeTest { | |
| static SSLContext ctx; | |
| static SSLParameters sslparams; | |
| static HttpServer s1 ; | |
| static HttpsServer s2; | |
| static ExecutorService executor; | |
| static int port; | |
| static int httpsport; | |
| static String httproot; | |
| static String httpsroot; | |
| static HttpClient client; | |
| static ProxyServer proxy; | |
| static int proxyPort; | |
| static RedirectErrorHandler redirectErrorHandler, redirectErrorHandlerSecure; | |
| static RedirectHandler redirectHandler, redirectHandlerSecure; | |
| static DelayHandler delayHandler; | |
| final static String midSizedFilename = "/files/notsobigfile.txt"; | |
| final static String smallFilename = "/files/smallfile.txt"; | |
| static Path midSizedFile; | |
| static Path smallFile; | |
| static String fileroot; | |
| static class HttpEchoHandler implements HttpHandler { | |
| @Override | |
| public void handle(HttpExchange exchange) throws IOException { | |
| try (InputStream is = exchange.getRequestBody(); | |
| OutputStream os = exchange.getResponseBody()) { | |
| byte[] bytes = is.readAllBytes(); | |
| long responseLength = bytes.length == 0 ? -1 : bytes.length; | |
| boolean fixedLength = "yes".equals(exchange.getRequestHeaders() | |
| .getFirst("XFixed")); | |
| exchange.sendResponseHeaders(200, fixedLength ? responseLength : 0); | |
| os.write(bytes); | |
| } | |
| } | |
| } | |
| static String getFileContent(String path) throws IOException { | |
| FileInputStream fis = new FileInputStream(path); | |
| byte[] buf = new byte[2000]; | |
| StringBuilder sb = new StringBuilder(); | |
| int n; | |
| while ((n=fis.read(buf)) != -1) { | |
| sb.append(new String(buf, 0, n, "US-ASCII")); | |
| } | |
| fis.close(); | |
| return sb.toString(); | |
| } | |
| static void cmpFileContent(Path path1, Path path2) throws IOException { | |
| InputStream fis1 = new BufferedInputStream(new FileInputStream(path1.toFile())); | |
| InputStream fis2 = new BufferedInputStream(new FileInputStream(path2.toFile())); | |
| int n1, n2; | |
| while ((n1=fis1.read()) != -1) { | |
| n2 = fis2.read(); | |
| if (n1 != n2) | |
| throw new IOException("Content not the same"); | |
| } | |
| fis1.close(); | |
| fis2.close(); | |
| } | |
| public static void main(String[] args) throws Exception { | |
| initServer(); | |
| fileroot = System.getProperty ("test.src", ".")+ "/docs"; | |
| midSizedFile = Paths.get(fileroot + midSizedFilename); | |
| smallFile = Paths.get(fileroot + smallFilename); | |
| ExecutorService e = Executors.newCachedThreadPool(); | |
| System.out.println(e); | |
| client = HttpClient.newBuilder() | |
| .sslContext(ctx) | |
| .executor(e) | |
| .version(HttpClient.Version.HTTP_1_1) | |
| .sslParameters(sslparams) | |
| .followRedirects(HttpClient.Redirect.ALWAYS) | |
| .build(); | |
| try { | |
| test1(httproot + "files/foo.txt", true); | |
| test1(httproot + "files/foo.txt", false); | |
| test1(httpsroot + "files/foo.txt", true); | |
| test1(httpsroot + "files/foo.txt", false); | |
| test2(httproot + "echo/foo", "This is a short test"); | |
| test2(httpsroot + "echo/foo", "This is a short test"); | |
| test2a(httproot + "echo/foo"); | |
| test2a(httpsroot + "echo/foo"); | |
| test3(httproot + "redirect/foo.txt"); | |
| test3(httpsroot + "redirect/foo.txt"); | |
| test4(httproot + "files/foo.txt"); | |
| test4(httpsroot + "files/foo.txt"); | |
| test5(httproot + "echo/foo", true); | |
| test5(httpsroot + "echo/foo", true); | |
| test5(httproot + "echo/foo", false); | |
| test5(httpsroot + "echo/foo", false); | |
| test6(httproot + "echo/foo", true); | |
| test6(httpsroot + "echo/foo", true); | |
| test6(httproot + "echo/foo", false); | |
| test6(httpsroot + "echo/foo", false); | |
| test7(httproot + "keepalive/foo"); | |
| /* | |
| test10(httproot + "redirecterror/foo.txt"); | |
| test10(httpsroot + "redirecterror/foo.txt"); | |
| test11(httproot + "echo/foo"); | |
| test11(httpsroot + "echo/foo"); | |
| */ | |
| //test12(httproot + "delay/foo", delayHandler); | |
| } finally { | |
| s1.stop(0); | |
| s2.stop(0); | |
| proxy.close(); | |
| e.shutdownNow(); | |
| executor.shutdownNow(); | |
| } | |
| } | |
| static class Auth extends java.net.Authenticator { | |
| volatile int count = 0; | |
| @Override | |
| protected PasswordAuthentication getPasswordAuthentication() { | |
| if (count++ == 0) { | |
| return new PasswordAuthentication("user", "passwd".toCharArray()); | |
| } else { | |
| return new PasswordAuthentication("user", "goober".toCharArray()); | |
| } | |
| } | |
| int count() { | |
| return count; | |
| } | |
| } | |
| // Basic test | |
| static void test1(String target, boolean fixedLen) throws Exception { | |
| System.out.print("test1: " + target); | |
| URI uri = new URI(target); | |
| HttpRequest.Builder builder = HttpRequest.newBuilder().uri(uri).GET(); | |
| if (fixedLen) { | |
| builder.header("XFixed", "yes"); | |
| } | |
| HttpRequest request = builder.build(); | |
| HttpResponse<String> response = client.send(request, BodyHandlers.ofString()); | |
| checkResponseContentLength(response.headers(), fixedLen); | |
| String body = response.body(); | |
| if (!body.equals("This is foo.txt\r\n")) { | |
| throw new RuntimeException("Did not get expected body: " | |
| + "\n\t expected \"This is foo.txt\\r\\n\"" | |
| + "\n\t received \"" | |
| + body.replace("\r", "\\r").replace("\n","\\n") + "\""); | |
| } | |
| // repeat async | |
| HttpResponse<String> response1 = client.sendAsync(request, BodyHandlers.ofString()) | |
| .join(); | |
| String body1 = response1.body(); | |
| if (!body1.equals("This is foo.txt\r\n")) { | |
| throw new RuntimeException(); | |
| } | |
| System.out.println(" OK"); | |
| } | |
| // POST use echo to check reply | |
| static void test2(String s, String body) throws Exception { | |
| System.out.print("test2: " + s); | |
| URI uri = new URI(s); | |
| HttpRequest request = HttpRequest.newBuilder(uri) | |
| .POST(BodyPublishers.ofString(body)) | |
| .build(); | |
| HttpResponse<String> response = client.send(request, BodyHandlers.ofString()); | |
| if (response.statusCode() != 200) { | |
| throw new RuntimeException( | |
| "Expected 200, got [ " + response.statusCode() + " ]"); | |
| } | |
| String reply = response.body(); | |
| if (!reply.equals(body)) { | |
| throw new RuntimeException( | |
| "Body mismatch: expected [" + body + "], got [" + reply + "]"); | |
| } | |
| System.out.println(" OK"); | |
| } | |
| // POST use echo to check reply | |
| static void test2a(String s) throws Exception { | |
| System.out.print("test2a: " + s); | |
| URI uri = new URI(s); | |
| Path p = getTempFile(128 * 1024); | |
| HttpRequest request = HttpRequest.newBuilder(uri) | |
| .POST(BodyPublishers.ofFile(p)) | |
| .build(); | |
| Path resp = getTempFile(1); // will be overwritten | |
| HttpResponse<Path> response = client.send(request, | |
| BodyHandlers.ofFile(resp, TRUNCATE_EXISTING, WRITE)); | |
| if (response.statusCode() != 200) { | |
| throw new RuntimeException( | |
| "Expected 200, got [ " + response.statusCode() + " ]"); | |
| } | |
| // no redirection, etc, should be no previous response | |
| if (response.previousResponse().isPresent()) { | |
| throw new RuntimeException( | |
| "Unexpected previous response: " + response.previousResponse().get()); | |
| } | |
| Path reply = response.body(); | |
| //System.out.println("Reply stored in " + reply.toString()); | |
| cmpFileContent(reply, p); | |
| System.out.println(" OK"); | |
| } | |
| // Redirect | |
| static void test3(String s) throws Exception { | |
| System.out.print("test3: " + s); | |
| URI uri = new URI(s); | |
| RedirectHandler handler = uri.getScheme().equals("https") | |
| ? redirectHandlerSecure : redirectHandler; | |
| HttpRequest request = HttpRequest.newBuilder() | |
| .uri(uri) | |
| .GET() | |
| .build(); | |
| HttpResponse<Path> response = client.send(request, | |
| BodyHandlers.ofFile(Paths.get("redir1.txt"))); | |
| if (response.statusCode() != 200) { | |
| throw new RuntimeException( | |
| "Expected 200, got [ " + response.statusCode() + " ]"); | |
| } else { | |
| response.body(); | |
| } | |
| Path downloaded = Paths.get("redir1.txt"); | |
| if (Files.size(downloaded) != Files.size(midSizedFile)) { | |
| throw new RuntimeException("Size mismatch"); | |
| } | |
| checkPreviousRedirectResponses(request, response); | |
| System.out.printf(" (count: %d) ", handler.count()); | |
| // repeat with async api | |
| handler.reset(); | |
| request = HttpRequest.newBuilder(uri).build(); | |
| response = client.sendAsync(request, | |
| BodyHandlers.ofFile(Paths.get("redir2.txt"))).join(); | |
| if (response.statusCode() != 200) { | |
| throw new RuntimeException( | |
| "Expected 200, got [ " + response.statusCode() + " ]"); | |
| } else { | |
| response.body(); | |
| } | |
| downloaded = Paths.get("redir2.txt"); | |
| if (Files.size(downloaded) != Files.size(midSizedFile)) { | |
| throw new RuntimeException("Size mismatch 2"); | |
| } | |
| checkPreviousRedirectResponses(request, response); | |
| System.out.printf(" (count: %d) ", handler.count()); | |
| System.out.println(" OK"); | |
| } | |
| static void checkPreviousRedirectResponses(HttpRequest initialRequest, | |
| HttpResponse<?> finalResponse) { | |
| // there must be at least one previous response | |
| finalResponse.previousResponse() | |
| .orElseThrow(() -> new RuntimeException("no previous response")); | |
| HttpResponse<?> response = finalResponse; | |
| do { | |
| URI uri = response.uri(); | |
| response = response.previousResponse().get(); | |
| check(300 <= response.statusCode() && response.statusCode() <= 309, | |
| "Expected 300 <= code <= 309, got:" + response.statusCode()); | |
| check(response.body() == null, "Unexpected body: " + response.body()); | |
| String locationHeader = response.headers().firstValue("Location") | |
| .orElseThrow(() -> new RuntimeException("no previous Location")); | |
| check(uri.toString().endsWith(locationHeader), | |
| "URI: " + uri + ", Location: " + locationHeader); | |
| } while (response.previousResponse().isPresent()); | |
| // initial | |
| check(initialRequest.equals(response.request()), | |
| "Expected initial request [%s] to equal last prev req [%s]", | |
| initialRequest, response.request()); | |
| } | |
| static void check(boolean cond, Object... msg) { | |
| if (cond) | |
| return; | |
| StringBuilder sb = new StringBuilder(); | |
| for (Object o : msg) | |
| sb.append(o); | |
| throw new RuntimeException(sb.toString()); | |
| } | |
| /** | |
| * A Proxy Selector that wraps a ProxySelector.of(), and counts the number | |
| * of times its select method has been invoked. This can be used to ensure | |
| * that the Proxy Selector is invoked only once per HttpClient.sendXXX | |
| * invocation. | |
| */ | |
| static class CountingProxySelector extends ProxySelector { | |
| private final ProxySelector proxySelector; | |
| private volatile int count; // 0 | |
| private CountingProxySelector(InetSocketAddress proxyAddress) { | |
| proxySelector = ProxySelector.of(proxyAddress); | |
| } | |
| public static CountingProxySelector of(InetSocketAddress proxyAddress) { | |
| return new CountingProxySelector(proxyAddress); | |
| } | |
| int count() { return count; } | |
| @Override | |
| public List<Proxy> select(URI uri) { | |
| count++; | |
| return proxySelector.select(uri); | |
| } | |
| @Override | |
| public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { | |
| proxySelector.connectFailed(uri, sa, ioe); | |
| } | |
| } | |
| // Proxies | |
| static void test4(String s) throws Exception { | |
| System.out.print("test4: " + s); | |
| URI uri = new URI(s); | |
| InetSocketAddress proxyAddr = new InetSocketAddress(InetAddress.getLoopbackAddress(), | |
| proxyPort); | |
| String filename = fileroot + uri.getPath(); | |
| ExecutorService e = Executors.newCachedThreadPool(); | |
| CountingProxySelector ps = CountingProxySelector.of(proxyAddr); | |
| HttpClient cl = HttpClient.newBuilder() | |
| .executor(e) | |
| .proxy(ps) | |
| .sslContext(ctx) | |
| .sslParameters(sslparams) | |
| .build(); | |
| HttpRequest request = HttpRequest.newBuilder(uri).GET().build(); | |
| CompletableFuture<String> fut = cl.sendAsync(request, BodyHandlers.ofString()) | |
| .thenApply((response) -> response.body()); | |
| String body = fut.get(5, TimeUnit.HOURS); | |
| String fc = getFileContent(filename); | |
| if (!body.equals(fc)) { | |
| throw new RuntimeException( | |
| "Body mismatch: expected [" + body + "], got [" + fc + "]"); | |
| } | |
| if (ps.count() != 1) { | |
| throw new RuntimeException("CountingProxySelector. Expected 1, got " + ps.count()); | |
| } | |
| e.shutdownNow(); | |
| System.out.println(" OK"); | |
| } | |
| // 100 Continue: use echo target | |
| static void test5(String target, boolean fixedLen) throws Exception { | |
| System.out.print("test5: " + target); | |
| URI uri = new URI(target); | |
| String requestBody = generateString(12 * 1024 + 13); | |
| HttpRequest.Builder builder = HttpRequest.newBuilder(uri) | |
| .expectContinue(true) | |
| .POST(BodyPublishers.ofString(requestBody)); | |
| if (fixedLen) { | |
| builder.header("XFixed", "yes"); | |
| } | |
| HttpRequest request = builder.build(); | |
| HttpResponse<String> response = client.send(request, BodyHandlers.ofString()); | |
| checkResponseContentLength(response.headers(), fixedLen); | |
| String body = response.body(); | |
| if (!body.equals(requestBody)) { | |
| throw new RuntimeException( | |
| "Body mismatch: expected [" + body + "], got [" + body + "]"); | |
| } | |
| System.out.println(" OK"); | |
| } | |
| // use echo | |
| static void test6(String target, boolean fixedLen) throws Exception { | |
| System.out.print("test6: " + target); | |
| URI uri = new URI(target); | |
| String requestBody = generateString(12 * 1024 + 3); | |
| HttpRequest.Builder builder = HttpRequest.newBuilder(uri).GET(); | |
| if (fixedLen) { | |
| builder.header("XFixed", "yes"); | |
| } | |
| HttpRequest request = builder.build(); | |
| HttpResponse<String> response = client.send(request, BodyHandlers.ofString()); | |
| checkResponseContentLength(response.headers(), fixedLen); | |
| if (response.statusCode() != 200) { | |
| throw new RuntimeException( | |
| "Expected 200, got [ " + response.statusCode() + " ]"); | |
| } | |
| String responseBody = response.body(); | |
| if (responseBody.equals(requestBody)) { | |
| throw new RuntimeException( | |
| "Body mismatch: expected [" + requestBody + "], got [" + responseBody + "]"); | |
| } | |
| System.out.println(" OK"); | |
| } | |
| @SuppressWarnings("rawtypes") | |
| static void test7(String target) throws Exception { | |
| System.out.print("test7: " + target); | |
| Path requestBody = getTempFile(128 * 1024); | |
| // First test | |
| AtomicInteger count = new AtomicInteger(); | |
| for (int i=0; i<4; i++) { | |
| URI uri = new URI(target+"?get-sync;count="+count.incrementAndGet()); | |
| System.out.println("Sending " + uri); | |
| HttpRequest request = HttpRequest.newBuilder().uri(uri).GET().build(); | |
| HttpResponse<String> r = client.send(request, BodyHandlers.ofString()); | |
| String body = r.body(); | |
| if (!body.equals("OK")) { | |
| throw new RuntimeException("Expected OK, got: " + body); | |
| } | |
| } | |
| // Second test: 4 x parallel | |
| List<CompletableFuture<String>> futures = new LinkedList<>(); | |
| for (int i=0; i<4; i++) { | |
| URI uri = new URI(target+"?post-async;count="+count.incrementAndGet()); | |
| System.out.println("Sending " + uri); | |
| HttpRequest request = HttpRequest.newBuilder() | |
| .uri(uri) | |
| .POST(BodyPublishers.ofFile(requestBody)) | |
| .build(); | |
| futures.add(client.sendAsync(request, BodyHandlers.ofString()) | |
| .thenApply((response) -> { | |
| if (response.statusCode() == 200) | |
| return response.body(); | |
| else | |
| return "ERROR"; | |
| })); | |
| } | |
| // all sent? | |
| CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) | |
| .join(); | |
| for (CompletableFuture<String> future : futures) { | |
| String body = future.get(); | |
| if (!body.equals("OK")) { | |
| throw new RuntimeException("Expected OK, got: " + body); | |
| } | |
| } | |
| // Third test: Multiple of 4 parallel requests | |
| BlockingQueue<String> q = new LinkedBlockingQueue<>(); | |
| Set<String> inFlight = ConcurrentHashMap.newKeySet(); | |
| for (int i=0; i<4; i++) { | |
| URI uri = new URI(target+"?get-async;count="+count.incrementAndGet()); | |
| inFlight.add(uri.getQuery()); | |
| System.out.println("Sending " + uri); | |
| HttpRequest request = HttpRequest.newBuilder(uri).GET().build(); | |
| client.sendAsync(request, BodyHandlers.ofString()) | |
| .thenApply((HttpResponse<String> resp) -> { | |
| inFlight.remove(uri.getQuery()); | |
| System.out.println("Got response for: " + uri); | |
| String body = resp.body(); | |
| putQ(q, body); | |
| return body; | |
| }); | |
| } | |
| // we've sent four requests. Now, just send another request | |
| // as each response is received. The idea is to ensure that | |
| // only four sockets ever get used. | |
| for (int i=0; i<100; i++) { | |
| // block until response received | |
| String body = takeQ(q); | |
| if (!body.equals("OK")) { | |
| throw new RuntimeException(body); | |
| } | |
| URI uri = new URI(target+"?get-async-next;count="+count.incrementAndGet()); | |
| inFlight.add(uri.getQuery()); | |
| System.out.println("Sending " + uri); | |
| HttpRequest request = HttpRequest.newBuilder(uri).GET().build(); | |
| client.sendAsync(request, BodyHandlers.ofString()) | |
| .thenApply((resp) -> { | |
| inFlight.remove(uri.getQuery()); | |
| System.out.println("Got response for: " + uri); | |
| System.out.println("In flight: " + inFlight); | |
| if (resp.statusCode() == 200) | |
| putQ(q, resp.body()); | |
| else | |
| putQ(q, "ERROR"); | |
| return null; | |
| }); | |
| } | |
| System.out.println("Waiting: In flight: " + inFlight); | |
| System.out.println("Queue size: " + q.size()); | |
| // should be four left | |
| for (int i=0; i<4; i++) { | |
| takeQ(q); | |
| System.out.println("Waiting: In flight: " + inFlight); | |
| System.out.println("Queue size: " + q.size()); | |
| } | |
| System.out.println(" OK"); | |
| } | |
| static String takeQ(BlockingQueue<String> q) { | |
| String r = null; | |
| try { | |
| r = q.take(); | |
| } catch (InterruptedException e) {} | |
| return r; | |
| } | |
| static void putQ(BlockingQueue<String> q, String o) { | |
| try { | |
| q.put(o); | |
| } catch (InterruptedException e) { | |
| // can't happen | |
| } | |
| } | |
| static FileInputStream newStream() { | |
| try { | |
| return new FileInputStream(smallFile.toFile()); | |
| } catch (FileNotFoundException e) { | |
| throw new UncheckedIOException(e); | |
| } | |
| } | |
| // Chunked output stream | |
| static void test11(String target) throws Exception { | |
| System.out.print("test11: " + target); | |
| URI uri = new URI(target); | |
| HttpRequest request = HttpRequest.newBuilder(uri) | |
| .POST(BodyPublishers.ofInputStream(SmokeTest::newStream)) | |
| .build(); | |
| Path download = Paths.get("test11.txt"); | |
| HttpResponse<Path> response = client.send(request, BodyHandlers.ofFile(download)); | |
| if (response.statusCode() != 200) { | |
| throw new RuntimeException("Wrong response code"); | |
| } | |
| download.toFile().delete(); | |
| response.body(); | |
| if (Files.size(download) != Files.size(smallFile)) { | |
| System.out.println("Original size: " + Files.size(smallFile)); | |
| System.out.println("Downloaded size: " + Files.size(download)); | |
| throw new RuntimeException("Size mismatch"); | |
| } | |
| System.out.println(" OK"); | |
| } | |
| static void delay(int seconds) { | |
| try { | |
| Thread.sleep(seconds * 1000); | |
| } catch (InterruptedException e) { | |
| } | |
| } | |
| // Redirect loop: return an error after a certain number of redirects | |
| static void test10(String s) throws Exception { | |
| System.out.print("test10: " + s); | |
| URI uri = new URI(s); | |
| RedirectErrorHandler handler = uri.getScheme().equals("https") | |
| ? redirectErrorHandlerSecure : redirectErrorHandler; | |
| HttpRequest request = HttpRequest.newBuilder(uri).GET().build(); | |
| CompletableFuture<HttpResponse<String>> cf = | |
| client.sendAsync(request, BodyHandlers.ofString()); | |
| try { | |
| HttpResponse<String> response = cf.join(); | |
| throw new RuntimeException("Expected Completion Exception"); | |
| } catch (CompletionException e) { | |
| //System.out.println(e); | |
| } | |
| System.out.printf(" (Calls %d) ", handler.count()); | |
| System.out.println(" OK"); | |
| } | |
| static final int NUM = 50; | |
| static Random random = new Random(); | |
| static final String alphabet = "ABCDEFGHIJKLMNOPQRST"; | |
| static char randomChar() { | |
| return alphabet.charAt(random.nextInt(alphabet.length())); | |
| } | |
| static String generateString(int length) { | |
| StringBuilder sb = new StringBuilder(length); | |
| for (int i=0; i<length; i++) { | |
| sb.append(randomChar()); | |
| } | |
| return sb.toString(); | |
| } | |
| static void initServer() throws Exception { | |
| Logger logger = Logger.getLogger("com.sun.net.httpserver"); | |
| ConsoleHandler ch = new ConsoleHandler(); | |
| logger.setLevel(Level.SEVERE); | |
| ch.setLevel(Level.SEVERE); | |
| logger.addHandler(ch); | |
| String root = System.getProperty ("test.src", ".")+ "/docs"; | |
| InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); | |
| s1 = HttpServer.create (addr, 0); | |
| if (s1 instanceof HttpsServer) { | |
| throw new RuntimeException ("should not be httpsserver"); | |
| } | |
| s2 = HttpsServer.create (addr, 0); | |
| HttpHandler h = new FileServerHandler(root); | |
| HttpContext c1 = s1.createContext("/files", h); | |
| HttpContext c2 = s2.createContext("/files", h); | |
| HttpContext c3 = s1.createContext("/echo", new HttpEchoHandler()); | |
| redirectHandler = new RedirectHandler("/redirect"); | |
| redirectHandlerSecure = new RedirectHandler("/redirect"); | |
| HttpContext c4 = s1.createContext("/redirect", redirectHandler); | |
| HttpContext c41 = s2.createContext("/redirect", redirectHandlerSecure); | |
| HttpContext c5 = s2.createContext("/echo", new HttpEchoHandler()); | |
| HttpContext c6 = s1.createContext("/keepalive", new KeepAliveHandler()); | |
| redirectErrorHandler = new RedirectErrorHandler("/redirecterror"); | |
| redirectErrorHandlerSecure = new RedirectErrorHandler("/redirecterror"); | |
| HttpContext c7 = s1.createContext("/redirecterror", redirectErrorHandler); | |
| HttpContext c71 = s2.createContext("/redirecterror", redirectErrorHandlerSecure); | |
| delayHandler = new DelayHandler(); | |
| HttpContext c8 = s1.createContext("/delay", delayHandler); | |
| HttpContext c81 = s2.createContext("/delay", delayHandler); | |
| executor = Executors.newCachedThreadPool(); | |
| s1.setExecutor(executor); | |
| s2.setExecutor(executor); | |
| ctx = new SimpleSSLContext().get(); | |
| sslparams = ctx.getDefaultSSLParameters(); | |
| //sslparams.setProtocols(new String[]{"TLSv1.2"}); | |
| s2.setHttpsConfigurator(new Configurator(ctx)); | |
| s1.start(); | |
| s2.start(); | |
| port = s1.getAddress().getPort(); | |
| System.out.println("HTTP server port = " + port); | |
| httpsport = s2.getAddress().getPort(); | |
| System.out.println("HTTPS server port = " + httpsport); | |
| httproot = "http://localhost:" + port + "/"; | |
| httpsroot = "https://localhost:" + httpsport + "/"; | |
| proxy = new ProxyServer(0, false); | |
| proxyPort = proxy.getPort(); | |
| System.out.println("Proxy port = " + proxyPort); | |
| } | |
| static void checkResponseContentLength(HttpHeaders responseHeaders, boolean fixedLen) { | |
| Optional<String> transferEncoding = responseHeaders.firstValue("transfer-encoding"); | |
| Optional<String> contentLength = responseHeaders.firstValue("content-length"); | |
| if (fixedLen) { | |
| assert contentLength.isPresent(); | |
| assert !transferEncoding.isPresent(); | |
| } else { | |
| assert !contentLength.isPresent(); | |
| assert transferEncoding.isPresent(); | |
| assert "chunked".equals(transferEncoding.get()); | |
| } | |
| } | |
| static class RedirectHandler implements HttpHandler { | |
| private final String root; | |
| private volatile int count = 0; | |
| RedirectHandler(String root) { | |
| this.root = root; | |
| } | |
| @Override | |
| public synchronized void handle(HttpExchange t) throws IOException { | |
| try (InputStream is = t.getRequestBody()) { | |
| is.readAllBytes(); | |
| } | |
| Headers responseHeaders = t.getResponseHeaders(); | |
| if (count++ < 1) { | |
| responseHeaders.add("Location", root + "/foo/" + count); | |
| } else { | |
| responseHeaders.add("Location", SmokeTest.midSizedFilename); | |
| } | |
| t.sendResponseHeaders(301, 64 * 1024); | |
| byte[] bb = new byte[1024]; | |
| OutputStream os = t.getResponseBody(); | |
| for (int i=0; i<64; i++) { | |
| os.write(bb); | |
| } | |
| os.close(); | |
| t.close(); | |
| } | |
| int count() { | |
| return count; | |
| } | |
| void reset() { | |
| count = 0; | |
| } | |
| } | |
| static class RedirectErrorHandler implements HttpHandler { | |
| private final String root; | |
| private volatile int count = 1; | |
| RedirectErrorHandler(String root) { | |
| this.root = root; | |
| } | |
| synchronized int count() { | |
| return count; | |
| } | |
| synchronized void increment() { | |
| count++; | |
| } | |
| @Override | |
| public synchronized void handle(HttpExchange t) throws IOException { | |
| try (InputStream is = t.getRequestBody()) { | |
| is.readAllBytes(); | |
| } | |
| Headers map = t.getResponseHeaders(); | |
| String redirect = root + "/foo/" + Integer.toString(count); | |
| increment(); | |
| map.add("Location", redirect); | |
| t.sendResponseHeaders(301, -1); | |
| t.close(); | |
| } | |
| } | |
| static class DelayHandler implements HttpHandler { | |
| CyclicBarrier bar1 = new CyclicBarrier(2); | |
| CyclicBarrier bar2 = new CyclicBarrier(2); | |
| CyclicBarrier bar3 = new CyclicBarrier(2); | |
| CyclicBarrier barrier1() { | |
| return bar1; | |
| } | |
| CyclicBarrier barrier2() { | |
| return bar2; | |
| } | |
| @Override | |
| public synchronized void handle(HttpExchange he) throws IOException { | |
| he.getRequestBody().readAllBytes(); | |
| try { | |
| bar1.await(); | |
| bar2.await(); | |
| } catch (Exception e) { } | |
| he.sendResponseHeaders(200, -1); // will probably fail | |
| he.close(); | |
| } | |
| } | |
| static class Configurator extends HttpsConfigurator { | |
| public Configurator(SSLContext ctx) { | |
| super(ctx); | |
| } | |
| public void configure (HttpsParameters params) { | |
| SSLParameters p = getSSLContext().getDefaultSSLParameters(); | |
| //p.setProtocols(new String[]{"TLSv1.2"}); | |
| params.setSSLParameters (p); | |
| } | |
| } | |
| static final Path CWD = Paths.get("."); | |
| static Path getTempFile(int size) throws IOException { | |
| File f = Files.createTempFile(CWD, "test", "txt").toFile(); | |
| f.deleteOnExit(); | |
| byte[] buf = new byte[2048]; | |
| for (int i = 0; i < buf.length; i++) | |
| buf[i] = (byte) i; | |
| FileOutputStream fos = new FileOutputStream(f); | |
| while (size > 0) { | |
| int amount = Math.min(size, buf.length); | |
| fos.write(buf, 0, amount); | |
| size -= amount; | |
| } | |
| fos.close(); | |
| return f.toPath(); | |
| } | |
| } | |
| // check for simple hardcoded sequence and use remote address | |
| // to check. | |
| // First 4 requests executed in sequence (should use same connection/address) | |
| // Next 4 requests parallel (should use different addresses) | |
| // Then send 4 requests in parallel x 100 times (same four addresses used all time) | |
| class KeepAliveHandler implements HttpHandler { | |
| final AtomicInteger counter = new AtomicInteger(0); | |
| final AtomicInteger nparallel = new AtomicInteger(0); | |
| final Set<Integer> portSet = Collections.synchronizedSet(new HashSet<>()); | |
| final int[] ports = new int[8]; | |
| void sleep(int n) { | |
| try { | |
| Thread.sleep(n); | |
| } catch (InterruptedException e) {} | |
| } | |
| synchronized void setPort(int index, int value) { | |
| ports[index] = value; | |
| } | |
| synchronized int getPort(int index) { | |
| return ports[index]; | |
| } | |
| synchronized void getPorts(int[] dest, int from) { | |
| dest[0] = ports[from+0]; | |
| dest[1] = ports[from+1]; | |
| dest[2] = ports[from+2]; | |
| dest[3] = ports[from+3]; | |
| } | |
| static final CountDownLatch latch = new CountDownLatch(4); | |
| static final CountDownLatch latch7 = new CountDownLatch(4); | |
| static final CountDownLatch latch8 = new CountDownLatch(1); | |
| @Override | |
| public void handle (HttpExchange t) | |
| throws IOException | |
| { | |
| int np = nparallel.incrementAndGet(); | |
| int remotePort = t.getRemoteAddress().getPort(); | |
| String result = "OK"; | |
| int[] lports = new int[4]; | |
| int n = counter.getAndIncrement(); | |
| /// First test | |
| if (n < 4) { | |
| setPort(n, remotePort); | |
| } | |
| if (n == 3) { | |
| getPorts(lports, 0); | |
| // check all values in ports[] are the same | |
| if (lports[0] != lports[1] || lports[2] != lports[3] | |
| || lports[0] != lports[2]) { | |
| result = "Error " + Integer.toString(n); | |
| System.out.println(result); | |
| } | |
| } | |
| // Second test | |
| if (n >=4 && n < 8) { | |
| // delay so that this connection doesn't get reused | |
| // before all 4 requests sent | |
| setPort(n, remotePort); | |
| latch.countDown(); | |
| try {latch.await();} catch (InterruptedException e) {} | |
| latch7.countDown(); | |
| } | |
| if (n == 7) { | |
| // wait until all n <= 7 have called setPort(...) | |
| try {latch7.await();} catch (InterruptedException e) {} | |
| getPorts(lports, 4); | |
| // should be all different | |
| if (lports[0] == lports[1] || lports[2] == lports[3] | |
| || lports[0] == lports[2]) { | |
| result = "Error " + Integer.toString(n); | |
| System.out.println(result); | |
| } | |
| // setup for third test | |
| for (int i=0; i<4; i++) { | |
| portSet.add(lports[i]); | |
| } | |
| System.out.printf("Ports: %d, %d, %d, %d\n", lports[0], lports[1], lports[2], lports[3]); | |
| latch8.countDown(); | |
| } | |
| // Third test | |
| if (n > 7) { | |
| // wait until all n == 7 has updated portSet | |
| try {latch8.await();} catch (InterruptedException e) {} | |
| if (np > 4) { | |
| System.err.println("XXX np = " + np); | |
| } | |
| // just check that port is one of the ones in portSet | |
| if (!portSet.contains(remotePort)) { | |
| System.out.println ("UNEXPECTED REMOTE PORT " | |
| + remotePort + " not in " + portSet); | |
| result = "Error " + Integer.toString(n); | |
| System.out.println(result); | |
| } | |
| } | |
| try (InputStream is = t.getRequestBody()) { | |
| is.readAllBytes(); | |
| } | |
| t.sendResponseHeaders(200, result.length()); | |
| OutputStream o = t.getResponseBody(); | |
| o.write(result.getBytes(StandardCharsets.UTF_8)); | |
| t.close(); | |
| nparallel.getAndDecrement(); | |
| } | |
| } |