test/jdk/java/net/httpclient/PlainProxyConnectionTest.java
changeset 58055 734f7711f87c
child 58365 73950479184b
equal deleted inserted replaced
58054:ee230ad8cfef 58055:734f7711f87c
       
     1 /*
       
     2  * Copyright (c) 2019, 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 import com.sun.net.httpserver.HttpContext;
       
    25 import com.sun.net.httpserver.HttpExchange;
       
    26 import com.sun.net.httpserver.HttpHandler;
       
    27 import com.sun.net.httpserver.HttpServer;
       
    28 import java.io.IOException;
       
    29 import java.io.InputStream;
       
    30 import java.net.HttpURLConnection;
       
    31 import java.net.InetAddress;
       
    32 import java.net.InetSocketAddress;
       
    33 import java.net.Proxy;
       
    34 import java.net.ProxySelector;
       
    35 import java.net.SocketAddress;
       
    36 import java.net.URI;
       
    37 import java.net.URISyntaxException;
       
    38 import java.nio.charset.StandardCharsets;
       
    39 import java.security.NoSuchAlgorithmException;
       
    40 import java.util.List;
       
    41 import java.util.Set;
       
    42 import java.util.concurrent.ConcurrentLinkedQueue;
       
    43 import java.net.http.HttpClient;
       
    44 import java.net.http.HttpRequest;
       
    45 import java.net.http.HttpResponse;
       
    46 import java.util.stream.Collectors;
       
    47 
       
    48 /**
       
    49  * @test
       
    50  * @bug 8230526
       
    51  * @summary Verifies that PlainProxyConnections are cached and reused properly. We do this by
       
    52  *          verifying that the remote address of the HTTP exchange (on the fake proxy server)
       
    53  *          is always the same InetSocketAddress.
       
    54  * @modules jdk.httpserver
       
    55  * @run main/othervm PlainProxyConnectionTest
       
    56  * @author danielfuchs
       
    57  */
       
    58 public class PlainProxyConnectionTest {
       
    59 
       
    60     static final String RESPONSE = "<html><body><p>Hello World!</body></html>";
       
    61     static final String PATH = "/foo/";
       
    62     static final ConcurrentLinkedQueue<InetSocketAddress> connections = new ConcurrentLinkedQueue<>();
       
    63 
       
    64     // For convenience the server is used both as a plain server and as a plain proxy.
       
    65     // When used as a proxy, it serves the request itself instead of forwarding it
       
    66     // to the requested server.
       
    67     static HttpServer createHttpsServer() throws IOException, NoSuchAlgorithmException {
       
    68         HttpServer server = com.sun.net.httpserver.HttpServer.create();
       
    69         HttpContext context = server.createContext(PATH);
       
    70         context.setHandler(new HttpHandler() {
       
    71             @Override
       
    72             public void handle(HttpExchange he) throws IOException {
       
    73                 connections.add(he.getRemoteAddress());
       
    74                 he.getResponseHeaders().add("encoding", "UTF-8");
       
    75                 byte[] bytes = RESPONSE.getBytes(StandardCharsets.UTF_8);
       
    76                 he.sendResponseHeaders(200, bytes.length > 0 ? bytes.length : -1);
       
    77                 he.getResponseBody().write(bytes);
       
    78                 he.close();
       
    79             }
       
    80         });
       
    81 
       
    82         InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
       
    83         server.bind(addr, 0);
       
    84         return server;
       
    85     }
       
    86 
       
    87     public static void main(String[] args)
       
    88             throws IOException,
       
    89             URISyntaxException,
       
    90             NoSuchAlgorithmException,
       
    91             InterruptedException
       
    92     {
       
    93         HttpServer server = createHttpsServer();
       
    94         server.start();
       
    95         try {
       
    96             test(server, HttpClient.Version.HTTP_1_1);
       
    97             test(server, HttpClient.Version.HTTP_2);
       
    98         } finally {
       
    99             server.stop(0);
       
   100             System.out.println("Server stopped");
       
   101         }
       
   102     }
       
   103 
       
   104     /**
       
   105      * A Proxy Selector that wraps a ProxySelector.of(), and counts the number
       
   106      * of times its select method has been invoked. This can be used to ensure
       
   107      * that the Proxy Selector is invoked only once per HttpClient.sendXXX
       
   108      * invocation.
       
   109      */
       
   110     static class CountingProxySelector extends ProxySelector {
       
   111         private final ProxySelector proxySelector;
       
   112         private volatile int count; // 0
       
   113         private CountingProxySelector(InetSocketAddress proxyAddress) {
       
   114             proxySelector = ProxySelector.of(proxyAddress);
       
   115         }
       
   116 
       
   117         public static CountingProxySelector of(InetSocketAddress proxyAddress) {
       
   118             return new CountingProxySelector(proxyAddress);
       
   119         }
       
   120 
       
   121         int count() { return count; }
       
   122 
       
   123         @Override
       
   124         public List<Proxy> select(URI uri) {
       
   125             count++;
       
   126             return proxySelector.select(uri);
       
   127         }
       
   128 
       
   129         @Override
       
   130         public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
       
   131             proxySelector.connectFailed(uri, sa, ioe);
       
   132         }
       
   133     }
       
   134 
       
   135     // The sanity test sends request to the server, and through the proxy,
       
   136     // using the legacy HttpURLConnection to verify that server and proxy
       
   137     // work as expected.
       
   138     private static void performSanityTest(HttpServer server, URI uri, URI proxiedURI)
       
   139         throws IOException {
       
   140         connections.clear();
       
   141         System.out.println("Verifying communication with server");
       
   142         try (InputStream is = uri.toURL().openConnection().getInputStream()) {
       
   143             String resp = new String(is.readAllBytes(), StandardCharsets.UTF_8);
       
   144             System.out.println(resp);
       
   145             if (!RESPONSE.equals(resp)) {
       
   146                 throw new AssertionError("Unexpected response from server");
       
   147             }
       
   148         }
       
   149         System.out.println("Communication with server OK");
       
   150         int count = connections.size();
       
   151         if (count != 1) {
       
   152             System.err.println("Unexpected connection count: " + count);
       
   153             System.err.println("Connections: " + connections);
       
   154             throw new AssertionError("Expected only one connection: " + connections);
       
   155         }
       
   156         try {
       
   157             System.out.println("Pretending the server is a proxy...");
       
   158             Proxy p = new Proxy(Proxy.Type.HTTP,
       
   159                     InetSocketAddress.createUnresolved(
       
   160                             server.getAddress().getAddress().getHostAddress(),
       
   161                             server.getAddress().getPort()));
       
   162             System.out.println("Verifying communication with proxy");
       
   163             HttpURLConnection conn = (HttpURLConnection) proxiedURI.toURL().openConnection(p);
       
   164             try (InputStream is = conn.getInputStream()) {
       
   165                 String resp = new String(is.readAllBytes(), StandardCharsets.UTF_8);
       
   166                 System.out.println(resp);
       
   167                 if (!RESPONSE.equals(resp)) {
       
   168                     throw new AssertionError("Unexpected response from proxy");
       
   169                 }
       
   170             }
       
   171             count = connections.size();
       
   172             if (count != 2) {
       
   173                 System.err.println("Unexpected connection count: " + count);
       
   174                 System.err.println("Connections: " + connections);
       
   175                 throw new AssertionError("Expected two connection: " + connections);
       
   176             }
       
   177             System.out.println("Communication with proxy OK");
       
   178         } finally {
       
   179             connections.clear();
       
   180         }
       
   181     }
       
   182 
       
   183     public static void test(HttpServer server, HttpClient.Version version)
       
   184             throws IOException,
       
   185             URISyntaxException,
       
   186             InterruptedException {
       
   187         connections.clear();
       
   188         System.out.println("\n===== Testing with " + version);
       
   189         System.out.println("Server is: " + server.getAddress().toString());
       
   190         URI uri = new URI("http", null,
       
   191                 server.getAddress().getAddress().getHostAddress(),
       
   192                 server.getAddress().getPort(), PATH + "x",
       
   193                 null, null);
       
   194         URI proxiedURI = new URI("http://some.host.that.does.not.exist:4242" + PATH + "x");
       
   195 
       
   196         performSanityTest(server, uri, proxiedURI);
       
   197 
       
   198         try {
       
   199             connections.clear();
       
   200             System.out.println("\nReal test begins here.");
       
   201             System.out.println("Setting up request with HttpClient for version: "
       
   202                     + version.name());
       
   203             // This will force the HTTP client to see the server as a proxy,
       
   204             // and to (re)use a PlainProxyConnection to send the request
       
   205             // to the fake `proxiedURI` at
       
   206             // http://some.host.that.does.not.exist:4242/foo/x
       
   207             //
       
   208             CountingProxySelector ps = CountingProxySelector.of(
       
   209                     InetSocketAddress.createUnresolved(
       
   210                             server.getAddress().getAddress().getHostAddress(),
       
   211                             server.getAddress().getPort()));
       
   212             HttpClient client = HttpClient.newBuilder()
       
   213                     .version(version)
       
   214                     .proxy(ps)
       
   215                     .build();
       
   216             HttpRequest request = HttpRequest.newBuilder()
       
   217                     .uri(proxiedURI)
       
   218                     .GET()
       
   219                     .build();
       
   220 
       
   221             System.out.println("Sending request with HttpClient: " + request);
       
   222             HttpResponse<String> response
       
   223                     = client.send(request, HttpResponse.BodyHandlers.ofString());
       
   224             System.out.println("Got response");
       
   225             String resp = response.body();
       
   226             System.out.println("Received: " + resp);
       
   227             if (!RESPONSE.equals(resp)) {
       
   228                 throw new AssertionError("Unexpected response");
       
   229             }
       
   230             if (ps.count() > 1) {
       
   231                 throw new AssertionError("CountingProxySelector. Expected 1, got " + ps.count());
       
   232             }
       
   233             int count = connections.size();
       
   234             if (count != 1) {
       
   235                 System.err.println("Unexpected connection count: " + count);
       
   236                 System.err.println("Connections: " + connections);
       
   237                 throw new AssertionError("Expected only one connection: " + connections);
       
   238             }
       
   239             for (int i = 2; i < 5; i++) {
       
   240                 System.out.println("Sending next request (" + i + ") with HttpClient: " + request);
       
   241                 response = client.send(request, HttpResponse.BodyHandlers.ofString());
       
   242                 System.out.println("Got response");
       
   243                 resp = response.body();
       
   244                 System.out.println("Received: " + resp);
       
   245                 if (!RESPONSE.equals(resp)) {
       
   246                     throw new AssertionError("Unexpected response");
       
   247                 }
       
   248                 if (ps.count() > i) {
       
   249                     throw new AssertionError("CountingProxySelector. Expected "
       
   250                             + i + ", got " + ps.count());
       
   251                 }
       
   252                 count = connections.size();
       
   253                 if (count != i) {
       
   254                     System.err.println("Unexpected connection count: " + count);
       
   255                     System.err.println("Connections: " + connections);
       
   256                     throw new AssertionError("Expected " + i + ": " + connections);
       
   257                 }
       
   258             }
       
   259             Set<InetSocketAddress> remote = connections.stream().distinct().collect(Collectors.toSet());
       
   260             count = remote.size();
       
   261             if (count != 1) {
       
   262                 System.err.println("Unexpected connection count: " + count);
       
   263                 System.err.println("Connections: " + remote);
       
   264                 throw new AssertionError("Expected only one connection: " + remote);
       
   265             } else {
       
   266                 System.out.println("PASSED: Proxy received only one connection from: " + remote);
       
   267             }
       
   268         } finally {
       
   269             connections.clear();
       
   270         }
       
   271     }
       
   272 }