1 /*
  2  * Copyright (c) 2018, 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 onNext's Lists are unmodifiable,
 27  *          and that the buffers are read-only
 28  * @library /test/lib /test/jdk/java/net/httpclient/lib
 29  * @build jdk.test.lib.net.SimpleSSLContext jdk.httpclient.test.lib.http2.Http2TestServer
 30  *        jdk.httpclient.test.lib.common.TestServerConfigurator
 31  * @run testng/othervm ImmutableFlowItems
 32  */
 33 
 34 import java.io.IOException;
 35 import java.io.InputStream;
 36 import java.io.OutputStream;
 37 import java.net.InetAddress;
 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.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 import javax.net.ssl.SSLContext;
 56 
 57 import jdk.httpclient.test.lib.common.TestServerConfigurator;
 58 import jdk.httpclient.test.lib.http2.Http2TestServer;
 59 import jdk.httpclient.test.lib.http2.Http2TestExchange;
 60 import jdk.httpclient.test.lib.http2.Http2Handler;
 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.*;
 69 
 70 public class ImmutableFlowItems {
 71 
 72     SSLContext sslContext;
 73     HttpServer httpTestServer;         // HTTP/1.1    [ 4 servers ]
 74     HttpsServer httpsTestServer;       // HTTPS/1.1
 75     Http2TestServer http2TestServer;   // HTTP/2 ( h2c )
 76     Http2TestServer https2TestServer;  // HTTP/2 ( h2  )
 77     String httpURI_fixed;
 78     String httpURI_chunk;
 79     String httpsURI_fixed;
 80     String httpsURI_chunk;
 81     String http2URI_fixed;
 82     String http2URI_chunk;
 83     String https2URI_fixed;
 84     String https2URI_chunk;
 85 
 86     @DataProvider(name = "variants")
 87     public Object[][] variants() {
 88         return new Object[][]{
 89                 { httpURI_fixed   },
 90                 { httpURI_chunk   },
 91                 { httpsURI_fixed  },
 92                 { httpsURI_chunk  },
 93                 { http2URI_fixed  },
 94                 { http2URI_chunk  },
 95                 { https2URI_fixed },
 96                 { https2URI_chunk },
 97         };
 98     }
 99 
100     static final String BODY = "You'll never plough a field by turning it over in your mind.";
101 
102     HttpClient newHttpClient() {
103         return HttpClient.newBuilder()
104                 .sslContext(sslContext)
105                 .build();
106     }
107 
108     @Test(dataProvider = "variants")
109     public void testAsString(String uri) throws Exception {
110         HttpClient client = newHttpClient();
111 
112         HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
113                 .build();
114 
115         BodyHandler<String> handler = new CRSBodyHandler();
116         client.sendAsync(req, handler)
117                 .thenApply(HttpResponse::body)
118                 .thenAccept(body -> assertEquals(body, BODY))
119                 .join();
120     }
121 
122     static class CRSBodyHandler implements BodyHandler<String> {
123         @Override
124         public BodySubscriber<String> apply(HttpResponse.ResponseInfo rinfo) {
125             assertEquals(rinfo.statusCode(), 200);
126             return new CRSBodySubscriber();
127         }
128     }
129 
130     static class CRSBodySubscriber implements BodySubscriber<String> {
131         private final BodySubscriber<String> ofString = BodySubscribers.ofString(UTF_8);
132 
133         @Override
134         public void onSubscribe(Flow.Subscription subscription) {
135             ofString.onSubscribe(subscription);
136         }
137 
138         @Override
139         public void onNext(List<ByteBuffer> item) {
140             assertUnmodifiableList(item);
141             long c = item.stream().filter(ByteBuffer::isReadOnly).count();
142             assertEquals(c, item.size(), "Unexpected writable buffer in: " +item);
143             ofString.onNext(item);
144         }
145 
146         @Override
147         public void onError(Throwable throwable) {
148             ofString.onError(throwable);
149         }
150 
151         @Override
152         public void onComplete() {
153             ofString.onComplete();
154         }
155 
156         @Override
157         public CompletionStage<String> getBody() {
158             return ofString.getBody();
159         }
160     }
161 
162     static final Class<UnsupportedOperationException> UOE = UnsupportedOperationException.class;
163 
164     static void assertUnmodifiableList(List<ByteBuffer> list) {
165         assertNotNull(list);
166         ByteBuffer b = list.get(0);
167         assertNotNull(b);
168         assertThrows(UOE, () -> list.add(ByteBuffer.wrap(new byte[0])));
169         assertThrows(UOE, () -> list.remove(b));
170     }
171 
172     static String serverAuthority(HttpServer server) {
173         return InetAddress.getLoopbackAddress().getHostName() + ":"
174                 + server.getAddress().getPort();
175     }
176 
177     @BeforeTest
178     public void setup() throws Exception {
179         sslContext = new SimpleSSLContext().get();
180         if (sslContext == null)
181             throw new AssertionError("Unexpected null sslContext");
182 
183         // HTTP/1.1
184         HttpHandler h1_fixedLengthHandler = new HTTP1_FixedLengthHandler();
185         HttpHandler h1_chunkHandler = new HTTP1_ChunkedHandler();
186         InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
187         httpTestServer = HttpServer.create(sa, 0);
188         httpTestServer.createContext("/http1/fixed", h1_fixedLengthHandler);
189         httpTestServer.createContext("/http1/chunk", h1_chunkHandler);
190         httpURI_fixed = "http://" + serverAuthority(httpTestServer) + "/http1/fixed";
191         httpURI_chunk = "http://" + serverAuthority(httpTestServer) + "/http1/chunk";
192 
193         httpsTestServer = HttpsServer.create(sa, 0);
194         httpsTestServer.setHttpsConfigurator(new TestServerConfigurator(sa.getAddress(), sslContext));
195         httpsTestServer.createContext("/https1/fixed", h1_fixedLengthHandler);
196         httpsTestServer.createContext("/https1/chunk", h1_chunkHandler);
197         httpsURI_fixed = "https://" + serverAuthority(httpsTestServer) + "/https1/fixed";
198         httpsURI_chunk = "https://" + serverAuthority(httpsTestServer) + "/https1/chunk";
199 
200         // HTTP/2
201         Http2Handler h2_fixedLengthHandler = new HTTP2_FixedLengthHandler();
202         Http2Handler h2_chunkedHandler = new HTTP2_VariableHandler();
203 
204         http2TestServer = new Http2TestServer("localhost", false, 0);
205         http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");
206         http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");
207         http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed";
208         http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk";
209 
210         https2TestServer = new Http2TestServer("localhost", true, sslContext);
211         https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");
212         https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");
213         https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed";
214         https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk";
215 
216         httpTestServer.start();
217         httpsTestServer.start();
218         http2TestServer.start();
219         https2TestServer.start();
220     }
221 
222     @AfterTest
223     public void teardown() throws Exception {
224         httpTestServer.stop(0);
225         httpsTestServer.stop(0);
226         http2TestServer.stop();
227         https2TestServer.stop();
228     }
229 
230     static class HTTP1_FixedLengthHandler implements HttpHandler {
231         @Override
232         public void handle(HttpExchange t) throws IOException {
233             out.println("HTTP1_FixedLengthHandler received request to " + t.getRequestURI());
234             try (InputStream is = t.getRequestBody();
235                  OutputStream os = t.getResponseBody()) {
236                 is.readAllBytes();
237                 byte[] bytes = BODY.getBytes(UTF_8);
238                 t.sendResponseHeaders(200, bytes.length);
239                 os.write(bytes);
240             }
241         }
242     }
243 
244     static class HTTP1_ChunkedHandler implements HttpHandler {
245         @Override
246         public void handle(HttpExchange t) throws IOException {
247             out.println("HTTP1_ChunkedHandler received request to " + t.getRequestURI());
248             try (InputStream is = t.getRequestBody();
249                  OutputStream os = t.getResponseBody()) {
250                 is.readAllBytes();
251                 byte[] bytes = BODY.getBytes(UTF_8);
252                 t.sendResponseHeaders(200, 0);
253                 os.write(bytes);
254             }
255         }
256     }
257 
258     static class HTTP2_FixedLengthHandler implements Http2Handler {
259         @Override
260         public void handle(Http2TestExchange t) throws IOException {
261             out.println("HTTP2_FixedLengthHandler received request to " + t.getRequestURI());
262             try (InputStream is = t.getRequestBody();
263                  OutputStream os = t.getResponseBody()) {
264                 is.readAllBytes();
265                 byte[] bytes = BODY.getBytes(UTF_8);
266                 t.sendResponseHeaders(200, bytes.length);
267                 os.write(bytes);
268             }
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                  OutputStream os = t.getResponseBody()) {
278                 is.readAllBytes();
279                 byte[] bytes = BODY.getBytes(UTF_8);
280                 t.sendResponseHeaders(200, 0); // variable
281                 os.write(bytes);
282             }
283         }
284     }
285 }