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