38 import java.net.ServerSocket; |
45 import java.net.ServerSocket; |
39 import java.net.Socket; |
46 import java.net.Socket; |
40 import java.net.URI; |
47 import java.net.URI; |
41 import java.net.http.HttpClient; |
48 import java.net.http.HttpClient; |
42 import java.net.http.HttpRequest; |
49 import java.net.http.HttpRequest; |
|
50 import java.net.http.HttpRequest.BodyPublishers; |
43 import java.net.http.HttpResponse; |
51 import java.net.http.HttpResponse; |
44 import java.util.ArrayList; |
52 import java.util.ArrayList; |
45 import java.util.Arrays; |
53 import java.util.Arrays; |
46 import java.util.List; |
54 import java.util.List; |
47 import java.util.concurrent.ExecutionException; |
55 import java.util.concurrent.ExecutionException; |
48 import java.util.stream.Stream; |
56 import java.util.stream.Stream; |
|
57 import jdk.testlibrary.SimpleSSLContext; |
49 import org.testng.annotations.AfterTest; |
58 import org.testng.annotations.AfterTest; |
50 import org.testng.annotations.BeforeTest; |
59 import org.testng.annotations.BeforeTest; |
51 import org.testng.annotations.DataProvider; |
60 import org.testng.annotations.DataProvider; |
52 import org.testng.annotations.Test; |
61 import org.testng.annotations.Test; |
|
62 import javax.net.ssl.SSLContext; |
|
63 import javax.net.ssl.SSLServerSocketFactory; |
|
64 import javax.net.ssl.SSLSocket; |
53 import static java.lang.System.out; |
65 import static java.lang.System.out; |
54 import static java.net.http.HttpClient.Builder.NO_PROXY; |
66 import static java.net.http.HttpClient.Builder.NO_PROXY; |
55 import static java.net.http.HttpResponse.BodyHandlers.ofString; |
67 import static java.net.http.HttpResponse.BodyHandlers.ofString; |
56 import static java.nio.charset.StandardCharsets.US_ASCII; |
68 import static java.nio.charset.StandardCharsets.US_ASCII; |
57 import static java.util.stream.Collectors.toList; |
69 import static java.util.stream.Collectors.toList; |
60 import static org.testng.Assert.fail; |
72 import static org.testng.Assert.fail; |
61 |
73 |
62 public class ShortResponseBody { |
74 public class ShortResponseBody { |
63 |
75 |
64 Server closeImmediatelyServer; |
76 Server closeImmediatelyServer; |
|
77 Server closeImmediatelyHttpsServer; |
65 Server variableLengthServer; |
78 Server variableLengthServer; |
|
79 Server variableLengthHttpsServer; |
66 Server fixedLengthServer; |
80 Server fixedLengthServer; |
67 |
81 |
68 String httpURIClsImed; |
82 String httpURIClsImed; |
|
83 String httpsURIClsImed; |
69 String httpURIVarLen; |
84 String httpURIVarLen; |
|
85 String httpsURIVarLen; |
70 String httpURIFixLen; |
86 String httpURIFixLen; |
|
87 |
|
88 SSLContext sslContext; |
71 |
89 |
72 static final String EXPECTED_RESPONSE_BODY = |
90 static final String EXPECTED_RESPONSE_BODY = |
73 "<html><body><h1>Heading</h1><p>Some Text</p></body></html>"; |
91 "<html><body><h1>Heading</h1><p>Some Text</p></body></html>"; |
74 |
92 |
75 @DataProvider(name = "sanity") |
93 @DataProvider(name = "sanity") |
76 public Object[][] sanity() { |
94 public Object[][] sanity() { |
77 return new Object[][]{ |
95 return new Object[][]{ |
78 { httpURIVarLen + "?length=all" }, |
96 { httpURIVarLen + "?length=all" }, |
79 { httpURIFixLen + "?length=all" }, |
97 { httpsURIVarLen + "?length=all" }, |
|
98 { httpURIFixLen + "?length=all" }, |
80 }; |
99 }; |
81 } |
100 } |
82 |
101 |
83 @Test(dataProvider = "sanity") |
102 @Test(dataProvider = "sanity") |
84 void sanity(String url) throws Exception { |
103 void sanity(String url) throws Exception { |
85 HttpClient client = HttpClient.newBuilder().build(); |
104 HttpClient client = HttpClient.newBuilder() |
|
105 .proxy(NO_PROXY) |
|
106 .sslContext(sslContext) |
|
107 .build(); |
86 HttpRequest request = HttpRequest.newBuilder(URI.create(url)).build(); |
108 HttpRequest request = HttpRequest.newBuilder(URI.create(url)).build(); |
87 HttpResponse<String> response = client.send(request, ofString()); |
109 HttpResponse<String> response = client.send(request, ofString()); |
88 String body = response.body(); |
110 String body = response.body(); |
89 assertEquals(body, EXPECTED_RESPONSE_BODY); |
111 assertEquals(body, EXPECTED_RESPONSE_BODY); |
90 client.sendAsync(request, ofString()) |
112 client.sendAsync(request, ofString()) |
97 public Object[][] variants() { |
119 public Object[][] variants() { |
98 String[][] cases = new String[][] { |
120 String[][] cases = new String[][] { |
99 // The length query string is the total number of bytes in the reply, |
121 // The length query string is the total number of bytes in the reply, |
100 // including headers, before the server closes the connection. The |
122 // including headers, before the server closes the connection. The |
101 // second arg is a partial-expected-detail message in the exception. |
123 // second arg is a partial-expected-detail message in the exception. |
102 { httpURIVarLen + "?length=0", "no bytes" }, // EOF without receiving anything |
124 { httpURIVarLen + "?length=0", "no bytes" }, // EOF without receiving anything |
103 { httpURIVarLen + "?length=1", "status line" }, // EOF during status-line |
125 { httpURIVarLen + "?length=1", "status line" }, // EOF during status-line |
104 { httpURIVarLen + "?length=2", "status line" }, |
126 { httpURIVarLen + "?length=2", "status line" }, |
105 { httpURIVarLen + "?length=10", "status line" }, |
127 { httpURIVarLen + "?length=10", "status line" }, |
106 { httpURIVarLen + "?length=19", "header" }, // EOF during Content-Type header |
128 { httpURIVarLen + "?length=19", "header" }, // EOF during Content-Type header |
107 { httpURIVarLen + "?length=30", "header" }, |
129 { httpURIVarLen + "?length=30", "header" }, |
108 { httpURIVarLen + "?length=45", "header" }, |
130 { httpURIVarLen + "?length=45", "header" }, |
109 { httpURIVarLen + "?length=48", "header" }, |
131 { httpURIVarLen + "?length=48", "header" }, |
110 { httpURIVarLen + "?length=51", "header" }, |
132 { httpURIVarLen + "?length=51", "header" }, |
111 { httpURIVarLen + "?length=98", "header" }, // EOF during Connection header |
133 { httpURIVarLen + "?length=98", "header" }, // EOF during Connection header |
112 { httpURIVarLen + "?length=100", "header" }, |
134 { httpURIVarLen + "?length=100", "header" }, |
113 { httpURIVarLen + "?length=101", "header" }, |
135 { httpURIVarLen + "?length=101", "header" }, |
114 { httpURIVarLen + "?length=104", "header" }, |
136 { httpURIVarLen + "?length=104", "header" }, |
115 { httpURIVarLen + "?length=106", "chunked transfer encoding" }, // EOF during chunk header ( length ) |
137 { httpURIVarLen + "?length=106", "chunked transfer encoding" }, // EOF during chunk header ( length ) |
116 { httpURIVarLen + "?length=110", "chunked transfer encoding" }, // EOF during chunk response body data |
138 { httpURIVarLen + "?length=110", "chunked transfer encoding" }, // EOF during chunk response body data |
|
139 |
|
140 { httpsURIVarLen + "?length=0", "no bytes" }, |
|
141 { httpsURIVarLen + "?length=1", "status line" }, |
|
142 { httpsURIVarLen + "?length=2", "status line" }, |
|
143 { httpsURIVarLen + "?length=10", "status line" }, |
|
144 { httpsURIVarLen + "?length=19", "header" }, |
|
145 { httpsURIVarLen + "?length=30", "header" }, |
|
146 { httpsURIVarLen + "?length=45", "header" }, |
|
147 { httpsURIVarLen + "?length=48", "header" }, |
|
148 { httpsURIVarLen + "?length=51", "header" }, |
|
149 { httpsURIVarLen + "?length=98", "header" }, |
|
150 { httpsURIVarLen + "?length=100", "header" }, |
|
151 { httpsURIVarLen + "?length=101", "header" }, |
|
152 { httpsURIVarLen + "?length=104", "header" }, |
|
153 { httpsURIVarLen + "?length=106", "chunked transfer encoding" }, |
|
154 { httpsURIVarLen + "?length=110", "chunked transfer encoding" }, |
117 |
155 |
118 { httpURIFixLen + "?length=0", "no bytes" }, // EOF without receiving anything |
156 { httpURIFixLen + "?length=0", "no bytes" }, // EOF without receiving anything |
119 { httpURIFixLen + "?length=1", "status line" }, // EOF during status-line |
157 { httpURIFixLen + "?length=1", "status line" }, // EOF during status-line |
120 { httpURIFixLen + "?length=2", "status line" }, |
158 { httpURIFixLen + "?length=2", "status line" }, |
121 { httpURIFixLen + "?length=10", "status line" }, |
159 { httpURIFixLen + "?length=10", "status line" }, |
129 { httpURIFixLen + "?length=86", "header" }, |
167 { httpURIFixLen + "?length=86", "header" }, |
130 { httpURIFixLen + "?length=104", "fixed content-length" }, // EOF during body |
168 { httpURIFixLen + "?length=104", "fixed content-length" }, // EOF during body |
131 { httpURIFixLen + "?length=106", "fixed content-length" }, |
169 { httpURIFixLen + "?length=106", "fixed content-length" }, |
132 { httpURIFixLen + "?length=110", "fixed content-length" }, |
170 { httpURIFixLen + "?length=110", "fixed content-length" }, |
133 |
171 |
|
172 // ## ADD https fixed |
|
173 |
134 { httpURIClsImed, "no bytes"}, |
174 { httpURIClsImed, "no bytes"}, |
|
175 { httpsURIClsImed, "no bytes"}, |
135 }; |
176 }; |
136 |
177 |
137 List<Object[]> list = new ArrayList<>(); |
178 List<Object[]> list = new ArrayList<>(); |
138 Arrays.asList(cases).stream() |
179 Arrays.asList(cases).stream() |
139 .map(e -> new Object[] {e[0], e[1], true}) // reuse client |
180 .map(e -> new Object[] {e[0], e[1], true}) // reuse client |
207 public int read() throws IOException { |
254 public int read() throws IOException { |
208 return 1; |
255 return 1; |
209 } |
256 } |
210 } |
257 } |
211 |
258 |
|
259 // POST tests are racy in what may be received before writing may cause a |
|
260 // broken pipe or reset exception, before all the received data can be read. |
|
261 // Any message up to, and including, the "expected" error message can occur. |
|
262 // Strictly ordered list, in order of possible occurrence. |
|
263 static final List<String> MSGS_ORDER = |
|
264 List.of("no bytes", "status line", "header"); |
|
265 |
|
266 |
212 @Test(dataProvider = "uris") |
267 @Test(dataProvider = "uris") |
213 void testSynchronousPOST(String url, String unused, boolean sameClient) |
268 void testSynchronousPOST(String url, String expectedMsg, boolean sameClient) |
214 throws Exception |
269 throws Exception |
215 { |
270 { |
216 out.print("---\n"); |
271 out.print("---\n"); |
217 HttpClient client = null; |
272 HttpClient client = null; |
218 for (int i=0; i< ITERATION_COUNT; i++) { |
273 for (int i=0; i< ITERATION_COUNT; i++) { |
219 if (!sameClient || client == null) |
274 if (!sameClient || client == null) |
220 client = HttpClient.newBuilder().proxy(NO_PROXY).build(); |
275 client = HttpClient.newBuilder() |
|
276 .proxy(NO_PROXY) |
|
277 .sslContext(sslContext) |
|
278 .build(); |
221 HttpRequest request = HttpRequest.newBuilder(URI.create(url)) |
279 HttpRequest request = HttpRequest.newBuilder(URI.create(url)) |
222 .POST(HttpRequest.BodyPublishers.ofInputStream(() -> new InfiniteInputStream())) |
280 .POST(BodyPublishers.ofInputStream(() -> new InfiniteInputStream())) |
223 .build(); |
281 .build(); |
224 try { |
282 try { |
225 HttpResponse<String> response = client.send(request, ofString()); |
283 HttpResponse<String> response = client.send(request, ofString()); |
226 String body = response.body(); |
284 String body = response.body(); |
227 out.println(response + ": " + body); |
285 out.println(response + ": " + body); |
228 fail("UNEXPECTED RESPONSE: " + response); |
286 fail("UNEXPECTED RESPONSE: " + response); |
229 } catch (IOException ioe) { |
287 } catch (IOException ioe) { |
230 out.println("Caught expected exception:" + ioe); |
288 out.println("Caught expected exception:" + ioe); |
231 String msg = ioe.getMessage(); |
289 String msg = ioe.getMessage(); |
232 // "incomplete" since the chunked request body is not completely sent |
290 |
233 assertTrue(msg.contains("incomplete"), "exception msg:[" + msg + "]"); |
291 List<String> expectedMessages = new ArrayList<>(); |
|
292 expectedMessages.add(expectedMsg); |
|
293 MSGS_ORDER.stream().takeWhile(s -> !s.equals(expectedMsg)) |
|
294 .forEach(expectedMessages::add); |
|
295 |
|
296 assertTrue(expectedMessages.stream().anyMatch(s -> msg.indexOf(s) != -1), |
|
297 "exception msg:[" + msg + "], not in [" + expectedMessages); |
234 // synchronous API must have the send method on the stack |
298 // synchronous API must have the send method on the stack |
235 assertSendMethodOnStack(ioe); |
299 assertSendMethodOnStack(ioe); |
236 assertNoConnectionExpiredException(ioe); |
300 assertNoConnectionExpiredException(ioe); |
237 } |
301 } |
238 } |
302 } |
239 } |
303 } |
240 |
304 |
241 @Test(dataProvider = "uris") |
305 @Test(dataProvider = "uris") |
242 void testAsynchronousPOST(String url, String unused, boolean sameClient) |
306 void testAsynchronousPOST(String url, String expectedMsg, boolean sameClient) |
243 throws Exception |
307 throws Exception |
244 { |
308 { |
245 out.print("---\n"); |
309 out.print("---\n"); |
246 HttpClient client = null; |
310 HttpClient client = null; |
247 for (int i=0; i< ITERATION_COUNT; i++) { |
311 for (int i=0; i< ITERATION_COUNT; i++) { |
248 if (!sameClient || client == null) |
312 if (!sameClient || client == null) |
249 client = HttpClient.newBuilder().proxy(NO_PROXY).build(); |
313 client = HttpClient.newBuilder() |
|
314 .proxy(NO_PROXY) |
|
315 .sslContext(sslContext) |
|
316 .build(); |
250 HttpRequest request = HttpRequest.newBuilder(URI.create(url)) |
317 HttpRequest request = HttpRequest.newBuilder(URI.create(url)) |
251 .POST(HttpRequest.BodyPublishers.ofInputStream(() -> new InfiniteInputStream())) |
318 .POST(BodyPublishers.ofInputStream(() -> new InfiniteInputStream())) |
252 .build(); |
319 .build(); |
253 try { |
320 try { |
254 HttpResponse<String> response = client.sendAsync(request, ofString()).get(); |
321 HttpResponse<String> response = client.sendAsync(request, ofString()).get(); |
255 String body = response.body(); |
322 String body = response.body(); |
256 out.println(response + ": " + body); |
323 out.println(response + ": " + body); |
330 } |
407 } |
331 |
408 |
332 /** |
409 /** |
333 * A server that closes the connection immediately, without reading or writing. |
410 * A server that closes the connection immediately, without reading or writing. |
334 */ |
411 */ |
335 static final class CloseImmediatelyServer extends Server { |
412 static class PlainCloseImmediatelyServer extends Server { |
336 CloseImmediatelyServer() throws IOException { |
413 PlainCloseImmediatelyServer() throws IOException { |
337 super("CloseImmediateServer"); |
414 super("PlainCloseImmediatelyServer"); |
|
415 } |
|
416 |
|
417 protected PlainCloseImmediatelyServer(String name) throws IOException { |
|
418 super(name); |
338 } |
419 } |
339 |
420 |
340 @Override |
421 @Override |
341 public void run() { |
422 public void run() { |
342 while (!closed) { |
423 while (!closed) { |
343 try (Socket s = ss.accept()) { |
424 try (Socket s = ss.accept()) { |
|
425 if (s instanceof SSLSocket) { |
|
426 ((SSLSocket)s).startHandshake(); |
|
427 } |
344 out.println("Server: got connection, closing immediately "); |
428 out.println("Server: got connection, closing immediately "); |
345 } catch (IOException e) { |
429 } catch (IOException e) { |
346 if (!closed) |
430 if (!closed) |
347 throw new UncheckedIOException("Unexpected", e); |
431 throw new UncheckedIOException("Unexpected", e); |
348 } |
432 } |
349 } |
433 } |
|
434 } |
|
435 } |
|
436 |
|
437 /** |
|
438 * A server that closes the connection immediately, without reading or writing, |
|
439 * after completing the SSL handshake. |
|
440 */ |
|
441 static final class SSLCloseImmediatelyServer extends PlainCloseImmediatelyServer { |
|
442 SSLCloseImmediatelyServer() throws IOException { |
|
443 super("SSLCloseImmediatelyServer"); |
|
444 } |
|
445 @Override |
|
446 public ServerSocket newServerSocket() throws IOException { |
|
447 return SSLServerSocketFactory.getDefault().createServerSocket(); |
350 } |
448 } |
351 } |
449 } |
352 |
450 |
353 /** |
451 /** |
354 * A server that replies with headers and a, possibly partial, reply, before |
452 * A server that replies with headers and a, possibly partial, reply, before |
458 "Transfer-Encoding: chunked\r\n" + |
557 "Transfer-Encoding: chunked\r\n" + |
459 "Connection: close\r\n\r\n"; |
558 "Connection: close\r\n\r\n"; |
460 |
559 |
461 static final String RESPONSE = RESPONSE_HEADERS + CHUNKED_RESPONSE_BODY; |
560 static final String RESPONSE = RESPONSE_HEADERS + CHUNKED_RESPONSE_BODY; |
462 |
561 |
463 VariableLengthServer() throws IOException { |
562 PlainVariableLengthServer() throws IOException { |
464 super("VariableLengthServer"); |
563 super("PlainVariableLengthServer"); |
|
564 } |
|
565 |
|
566 protected PlainVariableLengthServer(String name) throws IOException { |
|
567 super(name); |
465 } |
568 } |
466 |
569 |
467 @Override |
570 @Override |
468 String response( ) { return RESPONSE; } |
571 String response( ) { return RESPONSE; } |
|
572 } |
|
573 |
|
574 /** A server that issues a, possibly-partial, chunked reply over SSL. */ |
|
575 static final class SSLVariableLengthServer extends PlainVariableLengthServer { |
|
576 SSLVariableLengthServer() throws IOException { |
|
577 super("SSLVariableLengthServer"); |
|
578 } |
|
579 @Override |
|
580 public ServerSocket newServerSocket() throws IOException { |
|
581 return SSLServerSocketFactory.getDefault().createServerSocket(); |
|
582 } |
469 } |
583 } |
470 |
584 |
471 /** A server that issues a fixed-length reply. */ |
585 /** A server that issues a fixed-length reply. */ |
472 static final class FixedLengthServer extends ReplyingServer { |
586 static final class FixedLengthServer extends ReplyingServer { |
473 |
587 |
494 + server.getPort(); |
608 + server.getPort(); |
495 } |
609 } |
496 |
610 |
497 @BeforeTest |
611 @BeforeTest |
498 public void setup() throws Exception { |
612 public void setup() throws Exception { |
499 closeImmediatelyServer = new CloseImmediatelyServer(); |
613 sslContext = new SimpleSSLContext().get(); |
|
614 if (sslContext == null) |
|
615 throw new AssertionError("Unexpected null sslContext"); |
|
616 SSLContext.setDefault(sslContext); |
|
617 |
|
618 closeImmediatelyServer = new PlainCloseImmediatelyServer(); |
500 httpURIClsImed = "http://" + serverAuthority(closeImmediatelyServer) |
619 httpURIClsImed = "http://" + serverAuthority(closeImmediatelyServer) |
501 + "/http1/closeImmediately/foo"; |
620 + "/http1/closeImmediately/foo"; |
502 |
621 |
503 variableLengthServer = new VariableLengthServer(); |
622 closeImmediatelyHttpsServer = new SSLCloseImmediatelyServer(); |
|
623 httpsURIClsImed = "https://" + serverAuthority(closeImmediatelyHttpsServer) |
|
624 + "/https1/closeImmediately/foo"; |
|
625 |
|
626 variableLengthServer = new PlainVariableLengthServer(); |
504 httpURIVarLen = "http://" + serverAuthority(variableLengthServer) |
627 httpURIVarLen = "http://" + serverAuthority(variableLengthServer) |
505 + "/http1/variable/bar"; |
628 + "/http1/variable/bar"; |
|
629 |
|
630 variableLengthHttpsServer = new SSLVariableLengthServer(); |
|
631 httpsURIVarLen = "https://" + serverAuthority(variableLengthHttpsServer) |
|
632 + "/https1/variable/bar"; |
506 |
633 |
507 fixedLengthServer = new FixedLengthServer(); |
634 fixedLengthServer = new FixedLengthServer(); |
508 httpURIFixLen = "http://" + serverAuthority(fixedLengthServer) |
635 httpURIFixLen = "http://" + serverAuthority(fixedLengthServer) |
509 + "/http1/fixed/baz"; |
636 + "/http1/fixed/baz"; |
510 } |
637 } |
511 |
638 |
512 @AfterTest |
639 @AfterTest |
513 public void teardown() throws Exception { |
640 public void teardown() throws Exception { |
514 closeImmediatelyServer.close(); |
641 closeImmediatelyServer.close(); |
|
642 closeImmediatelyHttpsServer.close(); |
515 variableLengthServer.close(); |
643 variableLengthServer.close(); |
|
644 variableLengthHttpsServer.close(); |
516 fixedLengthServer.close(); |
645 fixedLengthServer.close(); |
517 } |
646 } |
518 } |
647 } |