test/jdk/java/net/httpclient/http2/ServerPushWithDiffTypes.java
branchhttp-client-branch
changeset 56010 782b2f2d1e76
child 56034 43b531ed872b
equal deleted inserted replaced
56009:cf8792f51dee 56010:782b2f2d1e76
       
     1 /*
       
     2  * Copyright (c) 2018, 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
       
    26  * @library /lib/testlibrary server
       
    27  * @build jdk.testlibrary.SimpleSSLContext
       
    28  * @modules java.base/sun.net.www.http
       
    29  *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
       
    30  *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
       
    31  *          jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
       
    32  * @run testng/othervm -Djdk.internal.httpclient.debug=true -Djdk.httpclient.HttpClient.log=errors,requests,responses ServerPushWithDiffTypes
       
    33  */
       
    34 
       
    35 import java.io.*;
       
    36 import java.net.*;
       
    37 import java.nio.ByteBuffer;
       
    38 import java.nio.charset.StandardCharsets;
       
    39 import java.nio.file.*;
       
    40 import jdk.incubator.http.*;
       
    41 import jdk.incubator.http.HttpResponse.BodyHandler;
       
    42 import jdk.incubator.http.HttpResponse.PushPromiseHandler;
       
    43 import jdk.incubator.http.HttpResponse.BodySubscriber;
       
    44 import java.util.*;
       
    45 import java.util.concurrent.*;
       
    46 import jdk.incubator.http.internal.common.HttpHeadersImpl;
       
    47 import org.testng.annotations.Test;
       
    48 import static java.nio.charset.StandardCharsets.UTF_8;
       
    49 
       
    50 public class ServerPushWithDiffTypes {
       
    51 
       
    52     static Map<String,String> PUSH_PROMISES = Map.of(
       
    53         "/x/y/z/1", "the first push promise body",
       
    54         "/x/y/z/2", "the second push promise body",
       
    55         "/x/y/z/3", "the third push promise body",
       
    56         "/x/y/z/4", "the fourth push promise body",
       
    57         "/x/y/z/5", "the fifth push promise body",
       
    58         "/x/y/z/6", "the sixth push promise body",
       
    59         "/x/y/z/7", "the seventh push promise body",
       
    60         "/x/y/z/8", "the eight push promise body",
       
    61         "/x/y/z/9", "the ninth push promise body"
       
    62     );
       
    63 
       
    64     @Test
       
    65     public static void test() throws Exception {
       
    66         Http2TestServer server = null;
       
    67         try {
       
    68             server = new Http2TestServer(false, 0);
       
    69             Http2Handler handler = new ServerPushHandler("the main response body",
       
    70                                                          PUSH_PROMISES);
       
    71             server.addHandler(handler, "/");
       
    72             server.start();
       
    73             int port = server.getAddress().getPort();
       
    74             System.err.println("Server listening on port " + port);
       
    75 
       
    76             HttpClient client = HttpClient.newHttpClient();
       
    77             // use multi-level path
       
    78             URI uri = new URI("http://127.0.0.1:" + port + "/foo/a/b/c");
       
    79             HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
       
    80 
       
    81             ConcurrentMap<HttpRequest,CompletableFuture<HttpResponse<BodyAndType<?>>>> results = new ConcurrentHashMap<>();
       
    82             PushPromiseHandler<BodyAndType<?>> bh = PushPromiseHandler.withPushPromises(
       
    83                 (pushRequest) -> new BodyAndTypeHandler(pushRequest), results);
       
    84 
       
    85             CompletableFuture<HttpResponse<BodyAndType<?>>> cf = client.sendAsync(request, new BodyAndTypeHandler(request), bh);
       
    86             results.put(request, cf);
       
    87             cf.join();
       
    88             System.err.println("CHEGAR: results.size: " + results.size());
       
    89 
       
    90             if (results.size() != PUSH_PROMISES.size() + 1)
       
    91                 throw new RuntimeException("Some results missing, expected:"
       
    92                         + (PUSH_PROMISES.size() + 1) + ", got:" + results.size());
       
    93 
       
    94             for (HttpRequest r : results.keySet()) {
       
    95                 URI u = r.uri();
       
    96                 BodyAndType<?> body = results.get(r).get().body();
       
    97                 String result;
       
    98                 // convert all body types to String for easier comparison
       
    99                 if (body.type() == String.class) {
       
   100                     result = (String)body.getBody();
       
   101                 } else if (body.type() == byte[].class) {
       
   102                     byte[] bytes = (byte[])body.getBody();
       
   103                     result = new String(bytes, UTF_8);
       
   104                 } else if (Path.class.isAssignableFrom(body.type())) {
       
   105                     Path path = (Path)body.getBody();
       
   106                     result = new String(Files.readAllBytes(path), UTF_8);
       
   107                 } else {
       
   108                     throw new AssertionError("Unknown:" + body.type());
       
   109                 }
       
   110 
       
   111                 System.err.printf("%s -> %s\n", u.toString(), result.toString());
       
   112                 String expected = PUSH_PROMISES.get(r.uri().getPath());
       
   113                 if (expected == null)
       
   114                     expected = "the main response body";
       
   115                 System.err.println("For " + r + ", got [" + result + "], expected [" + expected +"]");
       
   116                 if (!result.equals(expected)) {
       
   117                     throw new RuntimeException("For " + r + ", got [" + result + "], expected [" + expected +"]");
       
   118                 }
       
   119             }
       
   120         } finally {
       
   121             server.stop();
       
   122         }
       
   123     }
       
   124 
       
   125     static interface BodyAndType<T> {
       
   126         Class<T> type();
       
   127         T getBody();
       
   128     }
       
   129 
       
   130     static final Path WORK_DIR = Paths.get(".");
       
   131 
       
   132     static class BodyAndTypeHandler implements BodyHandler<BodyAndType<?>> {
       
   133         int count;
       
   134         final HttpRequest request;
       
   135 
       
   136         BodyAndTypeHandler(HttpRequest request) {
       
   137             this.request = request;
       
   138         }
       
   139 
       
   140         @Override
       
   141         public HttpResponse.BodySubscriber<BodyAndType<?>> apply(int statusCode,
       
   142                                                                  HttpHeaders responseHeaders) {
       
   143             int whichType = count++ % 3;  // real world may base this on the request metadata
       
   144             switch (whichType) {
       
   145                 case 0: // String
       
   146                     return new BodyAndTypeSubscriber(BodySubscriber.asString(StandardCharsets.UTF_8));
       
   147                 case 1: // byte[]
       
   148                     return new BodyAndTypeSubscriber(BodySubscriber.asByteArray());
       
   149                 case 2: // Path
       
   150                     URI u = request.uri();
       
   151                     Path path = Paths.get(WORK_DIR.toString(), u.getPath());
       
   152                     try {
       
   153                         Files.createDirectories(path.getParent());
       
   154                     } catch (IOException ee) {
       
   155                         throw new UncheckedIOException(ee);
       
   156                     }
       
   157                     return new BodyAndTypeSubscriber(BodySubscriber.asFile(path));
       
   158                 default:
       
   159                     throw new AssertionError("Unexpected " + whichType);
       
   160             }
       
   161         }
       
   162     }
       
   163 
       
   164     static class BodyAndTypeSubscriber<T> implements HttpResponse.BodySubscriber<BodyAndType<T>> {
       
   165 
       
   166         private static class BodyAndTypeImpl<T> implements BodyAndType<T> {
       
   167             private final Class<T> type;
       
   168             private final T body;
       
   169             public BodyAndTypeImpl(Class<T> type, T body) { this.type = type; this.body = body; }
       
   170             @Override public Class<T> type() { return type; }
       
   171             @Override public T getBody() { return body; }
       
   172         }
       
   173 
       
   174         private final BodySubscriber<?> bodySubscriber;
       
   175         private final CompletableFuture<BodyAndType<T>> cf;
       
   176 
       
   177         BodyAndTypeSubscriber(BodySubscriber bodySubscriber) {
       
   178             this.bodySubscriber = bodySubscriber;
       
   179             cf = new CompletableFuture<>();
       
   180             bodySubscriber.getBody().whenComplete((r,t) -> cf.complete(new BodyAndTypeImpl(r.getClass(), r)));
       
   181         }
       
   182 
       
   183         @Override
       
   184         public void onSubscribe(Flow.Subscription subscription) {
       
   185             bodySubscriber.onSubscribe(subscription);
       
   186         }
       
   187 
       
   188         @Override
       
   189         public void onNext(List<ByteBuffer> item) {
       
   190             bodySubscriber.onNext(item);
       
   191         }
       
   192 
       
   193         @Override
       
   194         public void onError(Throwable throwable) {
       
   195             bodySubscriber.onError(throwable);
       
   196             cf.completeExceptionally(throwable);
       
   197         }
       
   198 
       
   199         @Override
       
   200         public void onComplete() {
       
   201             bodySubscriber.onComplete();
       
   202         }
       
   203 
       
   204         @Override
       
   205         public CompletionStage<BodyAndType<T>> getBody() {
       
   206             return cf;
       
   207         }
       
   208     }
       
   209 
       
   210     // --- server push handler ---
       
   211     static class ServerPushHandler implements Http2Handler {
       
   212 
       
   213         private final String mainResponseBody;
       
   214         private final Map<String,String> promises;
       
   215 
       
   216         public ServerPushHandler(String mainResponseBody, Map<String,String> promises) throws Exception {
       
   217             Objects.requireNonNull(promises);
       
   218             this.mainResponseBody = mainResponseBody;
       
   219             this.promises = promises;
       
   220         }
       
   221 
       
   222         public void handle(Http2TestExchange exchange) throws IOException {
       
   223             System.err.println("Server: handle " + exchange);
       
   224             try (InputStream is = exchange.getRequestBody()) {
       
   225                 is.readAllBytes();
       
   226             }
       
   227 
       
   228             if (exchange.serverPushAllowed()) {
       
   229                 pushPromises(exchange);
       
   230             }
       
   231 
       
   232             // response data for the main response
       
   233             try (OutputStream os = exchange.getResponseBody()) {
       
   234                 byte[] bytes = mainResponseBody.getBytes(UTF_8);
       
   235                 exchange.sendResponseHeaders(200, bytes.length);
       
   236                 os.write(bytes);
       
   237             }
       
   238         }
       
   239 
       
   240         private void pushPromises(Http2TestExchange exchange) throws IOException {
       
   241             URI requestURI = exchange.getRequestURI();
       
   242             for (Map.Entry<String,String> promise : promises.entrySet()) {
       
   243                 URI uri = requestURI.resolve(promise.getKey());
       
   244                 InputStream is = new ByteArrayInputStream(promise.getValue().getBytes(UTF_8));
       
   245                 HttpHeadersImpl headers = new HttpHeadersImpl();
       
   246                 headers.addHeader("X-Promise-"+promise.getKey(), promise.getKey()); // todo: add some check on headers, maybe
       
   247                 exchange.serverPush(uri, headers, is);
       
   248             }
       
   249             System.err.println("Server: All pushes sent");
       
   250         }
       
   251     }
       
   252 }