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  * @summary Tests Exception detail message when too few response bytes are
27  *          received before a socket exception or eof.
28  * @library /lib/testlibrary
29  * @build jdk.testlibrary.SimpleSSLContext
30  * @run testng/othervm
31  *       -Djdk.httpclient.HttpClient.log=headers,errors,channel
32  *       ShortResponseBody
33  */
34 
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.OutputStream;
38 import java.io.UncheckedIOException;
39 import java.net.InetAddress;
40 import java.net.InetSocketAddress;
41 import java.net.ServerSocket;
42 import java.net.Socket;
43 import java.net.URI;
44 import java.net.http.HttpClient;
45 import java.net.http.HttpRequest;
46 import java.net.http.HttpRequest.BodyPublishers;
47 import java.net.http.HttpResponse;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.List;
51 import java.util.concurrent.ExecutionException;
52 import java.util.concurrent.Executor;
53 import java.util.concurrent.ExecutorService;
54 import java.util.concurrent.Executors;
55 import java.util.concurrent.ThreadFactory;
56 import java.util.concurrent.atomic.AtomicLong;
57 import java.util.stream.Stream;
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 javax.net.ssl.SSLContext;
64 import javax.net.ssl.SSLServerSocketFactory;
65 import javax.net.ssl.SSLParameters;
66 import javax.net.ssl.SSLSocket;
67 import static java.lang.System.out;
68 import static java.net.http.HttpClient.Builder.NO_PROXY;
69 import static java.net.http.HttpResponse.BodyHandlers.ofString;
70 import static java.nio.charset.StandardCharsets.US_ASCII;
71 import static java.util.stream.Collectors.toList;
72 import static org.testng.Assert.assertTrue;
73 import static org.testng.Assert.assertEquals;
74 import static org.testng.Assert.fail;
75 
76 public class ShortResponseBody {
77 
78     Server closeImmediatelyServer;
79     Server closeImmediatelyHttpsServer;
80     Server variableLengthServer;
81     Server variableLengthHttpsServer;
82     Server fixedLengthServer;
83 
84     String httpURIClsImed;
85     String httpsURIClsImed;
86     String httpURIVarLen;
87     String httpsURIVarLen;
88     String httpURIFixLen;
89 
90     SSLContext sslContext;
91     SSLParameters sslParameters;
92 
93     static final String EXPECTED_RESPONSE_BODY =
94             "<html><body><h1>Heading</h1><p>Some Text</p></body></html>";
95 
96     final static AtomicLong ids = new AtomicLong();
97     final ThreadFactory factory = new ThreadFactory() {
98         @Override
99         public Thread newThread(Runnable r) {
100             Thread thread = new Thread(r,  "HttpClient-Worker-" + ids.incrementAndGet());
101             thread.setDaemon(true);
102             return thread;
103         }
104     };
105     final ExecutorService service = Executors.newCachedThreadPool(factory);
106 
107     @DataProvider(name = "sanity")
sanity()108     public Object[][] sanity() {
109         return new Object[][]{
110             { httpURIVarLen  + "?length=all" },
111             { httpsURIVarLen + "?length=all" },
112             { httpURIFixLen  + "?length=all" },
113         };
114     }
115 
116     @Test(dataProvider = "sanity")
sanity(String url)117     void sanity(String url) throws Exception {
118         HttpClient client = newHttpClient();
119         HttpRequest request = HttpRequest.newBuilder(URI.create(url)).build();
120         HttpResponse<String> response = client.send(request, ofString());
121         String body = response.body();
122         assertEquals(body, EXPECTED_RESPONSE_BODY);
123         client.sendAsync(request, ofString())
124                 .thenApply(resp -> resp.body())
125                 .thenAccept(b -> assertEquals(b, EXPECTED_RESPONSE_BODY))
126                 .join();
127     }
128 
129     @DataProvider(name = "uris")
variants()130     public Object[][] variants() {
131         String[][] cases = new String[][] {
132             // The length query string is the total number of bytes in the reply,
133             // including headers, before the server closes the connection. The
134             // second arg is a partial-expected-detail message in the exception.
135             { httpURIVarLen + "?length=0",   "no bytes"     }, // EOF without receiving anything
136             { httpURIVarLen + "?length=1",   "status line"  }, // EOF during status-line
137             { httpURIVarLen + "?length=2",   "status line"  },
138             { httpURIVarLen + "?length=10",  "status line"  },
139             { httpURIVarLen + "?length=19",  "header"       }, // EOF during Content-Type header
140             { httpURIVarLen + "?length=30",  "header"       },
141             { httpURIVarLen + "?length=45",  "header"       },
142             { httpURIVarLen + "?length=48",  "header"       },
143             { httpURIVarLen + "?length=51",  "header"       },
144             { httpURIVarLen + "?length=98",  "header"       }, // EOF during Connection header
145             { httpURIVarLen + "?length=100", "header"       },
146             { httpURIVarLen + "?length=101", "header"       },
147             { httpURIVarLen + "?length=104", "header"       },
148             { httpURIVarLen + "?length=106", "chunked transfer encoding" }, // EOF during chunk header ( length )
149             { httpURIVarLen + "?length=110", "chunked transfer encoding" }, // EOF during chunk response body data
150 
151             { httpsURIVarLen + "?length=0",   "no bytes"    },
152             { httpsURIVarLen + "?length=1",   "status line" },
153             { httpsURIVarLen + "?length=2",   "status line" },
154             { httpsURIVarLen + "?length=10",  "status line" },
155             { httpsURIVarLen + "?length=19",  "header"      },
156             { httpsURIVarLen + "?length=30",  "header"      },
157             { httpsURIVarLen + "?length=45",  "header"      },
158             { httpsURIVarLen + "?length=48",  "header"      },
159             { httpsURIVarLen + "?length=51",  "header"      },
160             { httpsURIVarLen + "?length=98",  "header"      },
161             { httpsURIVarLen + "?length=100", "header"      },
162             { httpsURIVarLen + "?length=101", "header"      },
163             { httpsURIVarLen + "?length=104", "header"      },
164             { httpsURIVarLen + "?length=106", "chunked transfer encoding" },
165             { httpsURIVarLen + "?length=110", "chunked transfer encoding" },
166 
167             { httpURIFixLen + "?length=0",   "no bytes"    }, // EOF without receiving anything
168             { httpURIFixLen + "?length=1",   "status line" }, // EOF during status-line
169             { httpURIFixLen + "?length=2",   "status line" },
170             { httpURIFixLen + "?length=10",  "status line" },
171             { httpURIFixLen + "?length=19",  "header"      }, // EOF during Content-Type header
172             { httpURIFixLen + "?length=30",  "header"      },
173             { httpURIFixLen + "?length=45",  "header"      },
174             { httpURIFixLen + "?length=48",  "header"      },
175             { httpURIFixLen + "?length=51",  "header"      },
176             { httpURIFixLen + "?length=78",  "header"      }, // EOF during Connection header
177             { httpURIFixLen + "?length=79",  "header"      },
178             { httpURIFixLen + "?length=86",  "header"      },
179             { httpURIFixLen + "?length=104", "fixed content-length" }, // EOF during body
180             { httpURIFixLen + "?length=106", "fixed content-length" },
181             { httpURIFixLen + "?length=110", "fixed content-length" },
182 
183             // ## ADD https fixed
184 
185             { httpURIClsImed,  "no bytes"},
186             { httpsURIClsImed, "no bytes"},
187         };
188 
189         List<Object[]> list = new ArrayList<>();
190         Arrays.asList(cases).stream()
191                 .map(e -> new Object[] {e[0], e[1], true})  // reuse client
192                 .forEach(list::add);
193         Arrays.asList(cases).stream()
194                 .map(e -> new Object[] {e[0], e[1], false}) // do not reuse client
195                 .forEach(list::add);
196         return list.stream().toArray(Object[][]::new);
197     }
198 
199     static final int ITERATION_COUNT = 3;
200 
newHttpClient()201     HttpClient newHttpClient() {
202         return HttpClient.newBuilder()
203                 .proxy(NO_PROXY)
204                 .sslContext(sslContext)
205                 .sslParameters(sslParameters)
206                 .executor(service)
207                 .build();
208     }
209 
210     @Test(dataProvider = "uris")
testSynchronousGET(String url, String expectedMsg, boolean sameClient)211     void testSynchronousGET(String url, String expectedMsg, boolean sameClient)
212         throws Exception
213     {
214         out.print("---\n");
215         HttpClient client = null;
216         for (int i=0; i< ITERATION_COUNT; i++) {
217             if (!sameClient || client == null)
218                 client = newHttpClient();
219             HttpRequest request = HttpRequest.newBuilder(URI.create(url)).build();
220             try {
221                 HttpResponse<String> response = client.send(request, ofString());
222                 String body = response.body();
223                 out.println(response + ": " + body);
224                 fail("UNEXPECTED RESPONSE: " + response);
225             } catch (IOException ioe) {
226                 out.println("Caught expected exception:" + ioe);
227                 String msg = ioe.getMessage();
228                 assertTrue(msg.contains(expectedMsg), "exception msg:[" + msg + "]");
229                 // synchronous API must have the send method on the stack
230                 assertSendMethodOnStack(ioe);
231                 assertNoConnectionExpiredException(ioe);
232             }
233         }
234     }
235 
236     @Test(dataProvider = "uris")
testAsynchronousGET(String url, String expectedMsg, boolean sameClient)237     void testAsynchronousGET(String url, String expectedMsg, boolean sameClient)
238         throws Exception
239     {
240         out.print("---\n");
241         HttpClient client = null;
242         for (int i=0; i< ITERATION_COUNT; i++) {
243             if (!sameClient || client == null)
244                 client = newHttpClient();
245             HttpRequest request = HttpRequest.newBuilder(URI.create(url)).build();
246             try {
247                 HttpResponse<String> response = client.sendAsync(request, ofString()).get();
248                 String body = response.body();
249                 out.println(response + ": " + body);
250                 fail("UNEXPECTED RESPONSE: " + response);
251             } catch (ExecutionException ee) {
252                 if (ee.getCause() instanceof IOException) {
253                     IOException ioe = (IOException) ee.getCause();
254                     out.println("Caught expected exception:" + ioe);
255                     String msg = ioe.getMessage();
256                     assertTrue(msg.contains(expectedMsg), "exception msg:[" + msg + "]");
257                     assertNoConnectionExpiredException(ioe);
258                 } else {
259                     throw ee;
260                 }
261             }
262         }
263     }
264 
265     // can be used to prolong request body publication
266     static final class InfiniteInputStream extends InputStream {
267         int count = 0;
268         int k16 = 0;
269         @Override
read()270         public int read() throws IOException {
271             if (++count == 1) {
272                 System.out.println("Start sending 1 byte");
273             }
274             if (count > 16 * 1024) {
275                 k16++;
276                 System.out.println("... 16K sent.");
277                 count = count % (16 * 1024);
278             }
279             if (k16 > 128) {
280                 System.out.println("WARNING: InfiniteInputStream: " +
281                         "more than 128 16k buffers generated: returning EOF");
282                 return -1;
283             }
284             return 1;
285         }
286 
287         @Override
read(byte[] buf, int offset, int length)288         public int read(byte[] buf, int offset, int length) {
289             //int count = offset;
290             length = Math.max(0, Math.min(buf.length - offset, length));
291             //for (; count < length; count++)
292             //    buf[offset++] = 0x01;
293             //return count;
294             if (count == 0) {
295                 System.out.println("Start sending " + length);
296             } else if (count > 16 * 1024) {
297                 k16++;
298                 System.out.println("... 16K sent.");
299                 count = count % (16 * 1024);
300             }
301             if (k16 > 128) {
302                 System.out.println("WARNING: InfiniteInputStream: " +
303                         "more than 128 16k buffers generated: returning EOF");
304                 return -1;
305             }
306             count += length;
307             return length;
308         }
309     }
310 
311     // POST tests are racy in what may be received before writing may cause a
312     // broken pipe or reset exception, before all the received data can be read.
313     // Any message up to, and including, the "expected" error message can occur.
314     // Strictly ordered list, in order of possible occurrence.
315     static final List<String> MSGS_ORDER =
316             List.of("no bytes", "status line", "header");
317 
318 
319     @Test(dataProvider = "uris")
testSynchronousPOST(String url, String expectedMsg, boolean sameClient)320     void testSynchronousPOST(String url, String expectedMsg, boolean sameClient)
321         throws Exception
322     {
323         out.print("---\n");
324         HttpClient client = null;
325         for (int i=0; i< ITERATION_COUNT; i++) {
326             if (!sameClient || client == null)
327                 client = newHttpClient();
328             HttpRequest request = HttpRequest.newBuilder(URI.create(url))
329                     .POST(BodyPublishers.ofInputStream(() -> new InfiniteInputStream()))
330                     .build();
331             try {
332                 HttpResponse<String> response = client.send(request, ofString());
333                 String body = response.body();
334                 out.println(response + ": " + body);
335                 fail("UNEXPECTED RESPONSE: " + response);
336             } catch (IOException ioe) {
337                 out.println("Caught expected exception:" + ioe);
338                 String msg = ioe.getMessage();
339 
340                 List<String> expectedMessages = new ArrayList<>();
341                 expectedMessages.add(expectedMsg);
342                 MSGS_ORDER.stream().takeWhile(s -> !s.equals(expectedMsg))
343                                    .forEach(expectedMessages::add);
344 
345                 assertTrue(expectedMessages.stream().anyMatch(s -> msg.indexOf(s) != -1),
346                            "exception msg:[" + msg + "], not in [" + expectedMessages);
347                 // synchronous API must have the send method on the stack
348                 assertSendMethodOnStack(ioe);
349                 assertNoConnectionExpiredException(ioe);
350             }
351         }
352     }
353 
354     @Test(dataProvider = "uris")
testAsynchronousPOST(String url, String expectedMsg, boolean sameClient)355     void testAsynchronousPOST(String url, String expectedMsg, boolean sameClient)
356         throws Exception
357     {
358         out.print("---\n");
359         HttpClient client = null;
360         for (int i=0; i< ITERATION_COUNT; i++) {
361             if (!sameClient || client == null)
362                 client = newHttpClient();
363             HttpRequest request = HttpRequest.newBuilder(URI.create(url))
364                     .POST(BodyPublishers.ofInputStream(() -> new InfiniteInputStream()))
365                     .build();
366             try {
367                 HttpResponse<String> response = client.sendAsync(request, ofString()).get();
368                 String body = response.body();
369                 out.println(response + ": " + body);
370                 fail("UNEXPECTED RESPONSE: " + response);
371             } catch (ExecutionException ee) {
372                 if (ee.getCause() instanceof IOException) {
373                     IOException ioe = (IOException) ee.getCause();
374                     out.println("Caught expected exception:" + ioe);
375                     String msg = ioe.getMessage();
376 
377                     List<String> expectedMessages = new ArrayList<>();
378                     expectedMessages.add(expectedMsg);
379                     MSGS_ORDER.stream().takeWhile(s -> !s.equals(expectedMsg))
380                             .forEach(expectedMessages::add);
381 
382                     assertTrue(expectedMessages.stream().anyMatch(s -> msg.indexOf(s) != -1),
383                                "exception msg:[" + msg + "], not in [" + expectedMessages);
384                     assertNoConnectionExpiredException(ioe);
385                 } else {
386                     throw ee;
387                 }
388             }
389         }
390     }
391 
392     // Asserts that the "send" method appears in the stack of the given
393     // exception. The synchronous API must contain the send method on the stack.
assertSendMethodOnStack(IOException ioe)394     static void assertSendMethodOnStack(IOException ioe) {
395         final String cn = "jdk.internal.net.http.HttpClientImpl";
396         List<StackTraceElement> list = Stream.of(ioe.getStackTrace())
397                 .filter(ste -> ste.getClassName().equals(cn)
398                         && ste.getMethodName().equals("send"))
399                 .collect(toList());
400         if (list.size() != 1) {
401             ioe.printStackTrace(out);
402             fail(cn + ".send method not found in stack.");
403         }
404     }
405 
406     // Asserts that the implementation-specific ConnectionExpiredException does
407     // NOT appear anywhere in the exception or its causal chain.
assertNoConnectionExpiredException(IOException ioe)408     static void assertNoConnectionExpiredException(IOException ioe) {
409         Throwable throwable = ioe;
410         do {
411             String cn = throwable.getClass().getSimpleName();
412             if (cn.equals("ConnectionExpiredException")) {
413                 ioe.printStackTrace(out);
414                 fail("UNEXPECTED ConnectionExpiredException in:[" + ioe + "]");
415             }
416         } while ((throwable = throwable.getCause()) != null);
417     }
418 
419     // -- infra
420 
421     /**
422      * A server that, listens on a port, accepts new connections, and can be
423      * closed.
424      */
425     static abstract class Server extends Thread implements AutoCloseable {
426         protected final ServerSocket ss;
427         protected volatile boolean closed;
428 
Server(String name)429         Server(String name) throws IOException {
430             super(name);
431             ss = newServerSocket();
432             ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
433             this.start();
434         }
435 
newServerSocket()436         protected ServerSocket newServerSocket() throws IOException {
437             return new ServerSocket();
438         }
439 
getPort()440         public int getPort() { return ss.getLocalPort(); }
441 
442         @Override
close()443         public void close() {
444             if (closed)
445                 return;
446             closed = true;
447             try {
448                 ss.close();
449             } catch (IOException e) {
450                 throw new UncheckedIOException("Unexpected", e);
451             }
452         }
453     }
454 
455     /**
456      * A server that closes the connection immediately, without reading or writing.
457      */
458     static class PlainCloseImmediatelyServer extends Server {
PlainCloseImmediatelyServer()459         PlainCloseImmediatelyServer() throws IOException {
460             super("PlainCloseImmediatelyServer");
461         }
462 
PlainCloseImmediatelyServer(String name)463         protected PlainCloseImmediatelyServer(String name) throws IOException {
464             super(name);
465         }
466 
467         @Override
run()468         public void run() {
469             while (!closed) {
470                 try (Socket s = ss.accept()) {
471                     if (s instanceof SSLSocket) {
472                         ((SSLSocket)s).startHandshake();
473                     }
474                     out.println("Server: got connection, closing immediately ");
475                 } catch (IOException e) {
476                     if (!closed)
477                         throw new UncheckedIOException("Unexpected", e);
478                 }
479             }
480         }
481     }
482 
483     /**
484      * A server that closes the connection immediately, without reading or writing,
485      * after completing the SSL handshake.
486      */
487     static final class SSLCloseImmediatelyServer extends PlainCloseImmediatelyServer {
SSLCloseImmediatelyServer()488         SSLCloseImmediatelyServer() throws IOException {
489             super("SSLCloseImmediatelyServer");
490         }
491         @Override
newServerSocket()492         public ServerSocket newServerSocket() throws IOException {
493             return SSLServerSocketFactory.getDefault().createServerSocket();
494         }
495     }
496 
497     /**
498      * A server that replies with headers and a, possibly partial, reply, before
499      * closing the connection. The number of bytes of written ( header + body),
500      * is controllable through the "length" query string param in the requested
501      * URI.
502      */
503     static abstract class ReplyingServer extends Server {
504 
505         private final String name;
506 
ReplyingServer(String name)507         ReplyingServer(String name) throws IOException {
508             super(name);
509             this.name = name;
510         }
511 
response()512         abstract String response();
513 
514         @Override
run()515         public void run() {
516             while (!closed) {
517                 try (Socket s = ss.accept()) {
518                     out.print(name + ": got connection ");
519                     InputStream is = s.getInputStream();
520                     URI requestMethod = readRequestMethod(is);
521                     out.print(requestMethod + " ");
522                     URI uriPath = readRequestPath(is);
523                     out.println(uriPath);
524                     String headers = readRequestHeaders(is);
525 
526                     String query = uriPath.getRawQuery();
527                     if (query == null) {
528                         out.println("Request headers: [" + headers + "]");
529                     }
530                     assert query != null : "null query for uriPath: " + uriPath;
531                     String qv = query.split("=")[1];
532                     int len;
533                     if (qv.equals("all")) {
534                         len = response().getBytes(US_ASCII).length;
535                     } else {
536                         len = Integer.parseInt(query.split("=")[1]);
537                     }
538 
539                     OutputStream os = s.getOutputStream();
540                     out.println(name + ": writing " + len  + " bytes");
541                     byte[] responseBytes = response().getBytes(US_ASCII);
542                     for (int i = 0; i< len; i++) {
543                         os.write(responseBytes[i]);
544                         os.flush();
545                     }
546                 } catch (IOException e) {
547                     if (!closed)
548                         throw new UncheckedIOException("Unexpected", e);
549                 }
550             }
551         }
552 
553         static final byte[] requestEnd = new byte[] { '\r', '\n', '\r', '\n' };
554 
555         // Read the request method
readRequestMethod(InputStream is)556         static URI readRequestMethod(InputStream is) throws IOException {
557             StringBuilder sb = new StringBuilder();
558             int r;
559             while ((r = is.read()) != -1 && r != 0x20) {
560                 sb.append((char)r);
561             }
562             return URI.create(sb.toString());
563         }
564 
565         // Read the request URI path
readRequestPath(InputStream is)566         static URI readRequestPath(InputStream is) throws IOException {
567             StringBuilder sb = new StringBuilder();
568             int r;
569             while ((r = is.read()) != -1 && r != 0x20) {
570                 sb.append((char)r);
571             }
572             return URI.create(sb.toString());
573         }
574 
575         // Read until the end of a HTTP request headers
readRequestHeaders(InputStream is)576         static String readRequestHeaders(InputStream is) throws IOException {
577             int requestEndCount = 0, r;
578             StringBuilder sb = new StringBuilder();
579             while ((r = is.read()) != -1) {
580                 sb.append((char) r);
581                 if (r == requestEnd[requestEndCount]) {
582                     requestEndCount++;
583                     if (requestEndCount == 4) {
584                         break;
585                     }
586                 } else {
587                     requestEndCount = 0;
588                 }
589             }
590             return sb.toString();
591         }
592     }
593 
594     /** A server that issues a, possibly-partial, chunked reply. */
595     static class PlainVariableLengthServer extends ReplyingServer {
596 
597         static final String CHUNKED_RESPONSE_BODY =
598                 "6\r\n"+ "<html>\r\n" +
599                 "6\r\n"+ "<body>\r\n" +
600                 "10\r\n"+ "<h1>Heading</h1>\r\n" +
601                 "10\r\n"+ "<p>Some Text</p>\r\n" +
602                 "7\r\n"+ "</body>\r\n" +
603                 "7\r\n"+ "</html>\r\n" +
604                 "0\r\n"+ "\r\n";
605 
606         static final String RESPONSE_HEADERS =
607                 "HTTP/1.1 200 OK\r\n" +
608                 "Content-Type: text/html; charset=utf-8\r\n" +
609                 "Transfer-Encoding: chunked\r\n" +
610                 "Connection: close\r\n\r\n";
611 
612         static final String RESPONSE = RESPONSE_HEADERS + CHUNKED_RESPONSE_BODY;
613 
PlainVariableLengthServer()614         PlainVariableLengthServer() throws IOException {
615             super("PlainVariableLengthServer");
616         }
617 
PlainVariableLengthServer(String name)618         protected PlainVariableLengthServer(String name) throws IOException {
619             super(name);
620         }
621 
622         @Override
response( )623         String response( ) { return RESPONSE; }
624     }
625 
626     /** A server that issues a, possibly-partial, chunked reply over SSL. */
627     static final class SSLVariableLengthServer extends PlainVariableLengthServer {
SSLVariableLengthServer()628         SSLVariableLengthServer() throws IOException {
629             super("SSLVariableLengthServer");
630         }
631         @Override
newServerSocket()632         public ServerSocket newServerSocket() throws IOException {
633             return SSLServerSocketFactory.getDefault().createServerSocket();
634         }
635     }
636 
637     /** A server that issues a fixed-length reply. */
638     static final class FixedLengthServer extends ReplyingServer {
639 
640         static final String RESPONSE_BODY = EXPECTED_RESPONSE_BODY;
641 
642         static final String RESPONSE_HEADERS =
643                 "HTTP/1.1 200 OK\r\n" +
644                 "Content-Type: text/html; charset=utf-8\r\n" +
645                 "Content-Length: " + RESPONSE_BODY.length() + "\r\n" +
646                 "Connection: close\r\n\r\n";
647 
648         static final String RESPONSE = RESPONSE_HEADERS + RESPONSE_BODY;
649 
FixedLengthServer()650         FixedLengthServer() throws IOException {
651             super("FixedLengthServer");
652         }
653 
654         @Override
response( )655         String response( ) { return RESPONSE; }
656     }
657 
serverAuthority(Server server)658     static String serverAuthority(Server server) {
659         return InetAddress.getLoopbackAddress().getHostName() + ":"
660                 + server.getPort();
661     }
662 
663     @BeforeTest
setup()664     public void setup() throws Exception {
665         sslContext = new SimpleSSLContext().get();
666         if (sslContext == null)
667             throw new AssertionError("Unexpected null sslContext");
668         SSLContext.setDefault(sslContext);
669 
670         sslParameters = new SSLParameters();
671         sslParameters.setProtocols(new String[] {"TLSv1.2"});
672 
673         closeImmediatelyServer = new PlainCloseImmediatelyServer();
674         httpURIClsImed = "http://" + serverAuthority(closeImmediatelyServer)
675                 + "/http1/closeImmediately/foo";
676 
677         closeImmediatelyHttpsServer = new SSLCloseImmediatelyServer();
678         httpsURIClsImed = "https://" + serverAuthority(closeImmediatelyHttpsServer)
679                 + "/https1/closeImmediately/foo";
680 
681         variableLengthServer = new PlainVariableLengthServer();
682         httpURIVarLen = "http://" + serverAuthority(variableLengthServer)
683                 + "/http1/variable/bar";
684 
685         variableLengthHttpsServer = new SSLVariableLengthServer();
686         httpsURIVarLen = "https://" + serverAuthority(variableLengthHttpsServer)
687                 + "/https1/variable/bar";
688 
689         fixedLengthServer = new FixedLengthServer();
690         httpURIFixLen = "http://" + serverAuthority(fixedLengthServer)
691                 + "/http1/fixed/baz";
692     }
693 
694     @AfterTest
teardown()695     public void teardown() throws Exception {
696         closeImmediatelyServer.close();
697         closeImmediatelyHttpsServer.close();
698         variableLengthServer.close();
699         variableLengthHttpsServer.close();
700         fixedLengthServer.close();
701     }
702 }
703