1 /*
   2  * Copyright (c) 2017, 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  * @summary Tests response body subscribers's onComplete is not invoked before onSubscribe
  27  * @library /lib/testlibrary http2/server
  28  * @build jdk.testlibrary.SimpleSSLContext
  29  * @modules java.base/sun.net.www.http
  30  *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
  31  *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
  32  *          jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
  33  * @run testng/othervm CustomResponseSubscriber
  34  */
  35 
  36 import java.io.IOException;
  37 import java.io.InputStream;
  38 import java.net.InetSocketAddress;
  39 import java.net.URI;
  40 import java.nio.ByteBuffer;
  41 import java.util.List;
  42 import java.util.concurrent.CompletionStage;
  43 import java.util.concurrent.Executor;
  44 import java.util.concurrent.Executors;
  45 import java.util.concurrent.Flow;
  46 import com.sun.net.httpserver.HttpExchange;
  47 import com.sun.net.httpserver.HttpHandler;
  48 import com.sun.net.httpserver.HttpServer;
  49 import com.sun.net.httpserver.HttpsConfigurator;
  50 import com.sun.net.httpserver.HttpsServer;
  51 import jdk.incubator.http.HttpClient;
  52 import jdk.incubator.http.HttpHeaders;
  53 import jdk.incubator.http.HttpRequest;
  54 import jdk.incubator.http.HttpResponse;
  55 import jdk.incubator.http.HttpResponse.BodyHandler;
  56 import  jdk.incubator.http.HttpResponse.BodySubscriber;
  57 import javax.net.ssl.SSLContext;
  58 import jdk.testlibrary.SimpleSSLContext;
  59 import org.testng.annotations.AfterTest;
  60 import org.testng.annotations.BeforeTest;
  61 import org.testng.annotations.DataProvider;
  62 import org.testng.annotations.Test;
  63 import static java.lang.System.out;
  64 import static java.nio.charset.StandardCharsets.UTF_8;
  65 import static jdk.incubator.http.HttpResponse.BodySubscriber.asString;
  66 import static org.testng.Assert.assertEquals;
  67 import static org.testng.Assert.assertTrue;
  68 
  69 public class CustomResponseSubscriber {
  70 
  71     SSLContext sslContext;
  72     HttpServer httpTestServer;         // HTTP/1.1    [ 4 servers ]
  73     HttpsServer httpsTestServer;       // HTTPS/1.1
  74     Http2TestServer http2TestServer;   // HTTP/2 ( h2c )
  75     Http2TestServer https2TestServer;  // HTTP/2 ( h2  )
  76     String httpURI_fixed;
  77     String httpURI_chunk;
  78     String httpsURI_fixed;
  79     String httpsURI_chunk;
  80     String http2URI_fixed;
  81     String http2URI_chunk;
  82     String https2URI_fixed;
  83     String https2URI_chunk;
  84 
  85     static final int ITERATION_COUNT = 10;
  86     // a shared executor helps reduce the amount of threads created by the test
  87     static final Executor executor = Executors.newCachedThreadPool();
  88 
  89     @DataProvider(name = "variants")
  90     public Object[][] variants() {
  91         return new Object[][]{
  92                 { httpURI_fixed,    false },
  93                 { httpURI_chunk,    false },
  94                 { httpsURI_fixed,   false },
  95                 { httpsURI_chunk,   false },
  96                 { http2URI_fixed,   false },
  97                 { http2URI_chunk,   false },
  98                 { https2URI_fixed,  false,},
  99                 { https2URI_chunk,  false },
 100 
 101                 { httpURI_fixed,    true },
 102                 { httpURI_chunk,    true },
 103                 { httpsURI_fixed,   true },
 104                 { httpsURI_chunk,   true },
 105                 { http2URI_fixed,   true },
 106                 { http2URI_chunk,   true },
 107                 { https2URI_fixed,  true,},
 108                 { https2URI_chunk,  true },
 109         };
 110     }
 111 
 112     HttpClient newHttpClient() {
 113         return HttpClient.newBuilder()
 114                          .executor(executor)
 115                          .sslContext(sslContext)
 116                          .build();
 117     }
 118 
 119     @Test(dataProvider = "variants")
 120     public void testAsString(String uri, boolean sameClient) throws Exception {
 121         HttpClient client = null;
 122         for (int i=0; i< ITERATION_COUNT; i++) {
 123             if (!sameClient || client == null)
 124                 client = newHttpClient();
 125 
 126             HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
 127                                          .build();
 128             BodyHandler<String> handler = new CRSBodyHandler();
 129             HttpResponse<String> response = client.send(req, handler);
 130             String body = response.body();
 131             assertEquals(body, "");
 132         }
 133     }
 134 
 135     static class CRSBodyHandler implements BodyHandler<String> {
 136         @Override
 137         public BodySubscriber<String> apply(int statusCode, HttpHeaders responseHeaders) {
 138             assertEquals(statusCode, 200);
 139             return new CRSBodySubscriber();
 140         }
 141     }
 142 
 143     static class CRSBodySubscriber implements BodySubscriber<String> {
 144         private final BodySubscriber<String> asString = asString(UTF_8);
 145         volatile boolean onSubscribeCalled;
 146 
 147         @Override
 148         public void onSubscribe(Flow.Subscription subscription) {
 149             //out.println("onSubscribe ");
 150             onSubscribeCalled = true;
 151             asString.onSubscribe(subscription);
 152         }
 153 
 154         @Override
 155         public void onNext(List<ByteBuffer> item) {
 156            // out.println("onNext " + item);
 157             assertTrue(onSubscribeCalled);
 158             asString.onNext(item);
 159         }
 160 
 161         @Override
 162         public void onError(Throwable throwable) {
 163             //out.println("onError");
 164             assertTrue(onSubscribeCalled);
 165             asString.onError(throwable);
 166         }
 167 
 168         @Override
 169         public void onComplete() {
 170             //out.println("onComplete");
 171             assertTrue(onSubscribeCalled, "onComplete called before onSubscribe");
 172             asString.onComplete();
 173         }
 174 
 175         @Override
 176         public CompletionStage<String> getBody() {
 177             return asString.getBody();
 178         }
 179     }
 180 
 181 
 182     @BeforeTest
 183     public void setup() throws Exception {
 184         sslContext = new SimpleSSLContext().get();
 185         if (sslContext == null)
 186             throw new AssertionError("Unexpected null sslContext");
 187 
 188         // HTTP/1.1
 189         HttpHandler h1_fixedLengthHandler = new HTTP1_FixedLengthHandler();
 190         HttpHandler h1_chunkHandler = new HTTP1_ChunkedHandler();
 191         InetSocketAddress sa = new InetSocketAddress("localhost", 0);
 192         httpTestServer = HttpServer.create(sa, 0);
 193         httpTestServer.createContext("/http1/fixed", h1_fixedLengthHandler);
 194         httpTestServer.createContext("/http1/chunk", h1_chunkHandler);
 195         httpURI_fixed = "http://127.0.0.1:" + httpTestServer.getAddress().getPort() + "/http1/fixed";
 196         httpURI_chunk = "http://127.0.0.1:" + httpTestServer.getAddress().getPort() + "/http1/chunk";
 197 
 198         httpsTestServer = HttpsServer.create(sa, 0);
 199         httpsTestServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
 200         httpsTestServer.createContext("/https1/fixed", h1_fixedLengthHandler);
 201         httpsTestServer.createContext("/https1/chunk", h1_chunkHandler);
 202         httpsURI_fixed = "https://127.0.0.1:" + httpsTestServer.getAddress().getPort() + "/https1/fixed";
 203         httpsURI_chunk = "https://127.0.0.1:" + httpsTestServer.getAddress().getPort() + "/https1/chunk";
 204 
 205         // HTTP/2
 206         Http2Handler h2_fixedLengthHandler = new HTTP2_FixedLengthHandler();
 207         Http2Handler h2_chunkedHandler = new HTTP2_VariableHandler();
 208 
 209         http2TestServer = new Http2TestServer("127.0.0.1", false, 0);
 210         http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");
 211         http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");
 212         int port = http2TestServer.getAddress().getPort();
 213         http2URI_fixed = "http://127.0.0.1:" + port + "/http2/fixed";
 214         http2URI_chunk = "http://127.0.0.1:" + port + "/http2/chunk";
 215 
 216         https2TestServer = new Http2TestServer("127.0.0.1", true, 0);
 217         https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");
 218         https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");
 219         port = https2TestServer.getAddress().getPort();
 220         https2URI_fixed = "https://127.0.0.1:" + port + "/https2/fixed";
 221         https2URI_chunk = "https://127.0.0.1:" + port + "/https2/chunk";
 222 
 223         httpTestServer.start();
 224         httpsTestServer.start();
 225         http2TestServer.start();
 226         https2TestServer.start();
 227     }
 228 
 229     @AfterTest
 230     public void teardown() throws Exception {
 231         httpTestServer.stop(0);
 232         httpsTestServer.stop(0);
 233         http2TestServer.stop();
 234         https2TestServer.stop();
 235     }
 236 
 237     static class HTTP1_FixedLengthHandler implements HttpHandler {
 238         @Override
 239         public void handle(HttpExchange t) throws IOException {
 240             out.println("HTTP1_FixedLengthHandler received request to " + t.getRequestURI());
 241             try (InputStream is = t.getRequestBody()) {
 242                 is.readAllBytes();
 243             }
 244             t.sendResponseHeaders(200, -1);  //no body
 245         }
 246     }
 247 
 248     static class HTTP1_ChunkedHandler implements HttpHandler {
 249         @Override
 250         public void handle(HttpExchange t) throws IOException {
 251             out.println("HTTP1_ChunkedHandler received request to " + t.getRequestURI());
 252             try (InputStream is = t.getRequestBody()) {
 253                 is.readAllBytes();
 254             }
 255             t.sendResponseHeaders(200, 0); // chunked
 256             t.getResponseBody().close();   // no body
 257         }
 258     }
 259 
 260     static class HTTP2_FixedLengthHandler implements Http2Handler {
 261         @Override
 262         public void handle(Http2TestExchange t) throws IOException {
 263             out.println("HTTP2_FixedLengthHandler received request to " + t.getRequestURI());
 264             try (InputStream is = t.getRequestBody()) {
 265                 is.readAllBytes();
 266             }
 267             t.sendResponseHeaders(200, 0);
 268             t.getResponseBody().close();
 269         }
 270     }
 271 
 272     static class HTTP2_VariableHandler implements Http2Handler {
 273         @Override
 274         public void handle(Http2TestExchange t) throws IOException {
 275             out.println("HTTP2_VariableHandler received request to " + t.getRequestURI());
 276             try (InputStream is = t.getRequestBody()) {
 277                 is.readAllBytes();
 278             }
 279             t.sendResponseHeaders(200, -1); // variable
 280             t.getResponseBody().close();  //no body
 281         }
 282     }
 283 }