19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
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 |
20 * or visit www.oracle.com if you need additional information or have any |
21 * questions. |
21 * questions. |
22 */ |
22 */ |
23 |
23 |
24 import java.io.*; |
24 import java.io.IOException; |
25 import jdk.incubator.http.HttpClient; |
25 import java.io.InputStream; |
26 import jdk.incubator.http.HttpResponse; |
26 import java.io.OutputStream; |
27 import jdk.incubator.http.HttpRequest; |
27 import java.io.UncheckedIOException; |
28 import java.net.ServerSocket; |
28 import java.net.ServerSocket; |
29 import java.net.Socket; |
29 import java.net.Socket; |
30 import java.net.URI; |
30 import java.net.URI; |
31 import java.nio.file.Files; |
31 import java.nio.file.Files; |
32 import java.nio.file.Path; |
32 import java.nio.file.Path; |
33 import java.nio.file.Paths; |
33 import java.nio.file.Paths; |
34 import java.nio.ByteBuffer; |
34 import java.nio.ByteBuffer; |
|
35 import java.util.ArrayList; |
|
36 import java.util.List; |
35 import java.util.concurrent.CompletableFuture; |
37 import java.util.concurrent.CompletableFuture; |
36 import java.util.concurrent.Executor; |
|
37 import java.util.concurrent.ExecutorService; |
|
38 import java.util.concurrent.ExecutionException; |
38 import java.util.concurrent.ExecutionException; |
39 import java.util.concurrent.Flow; |
39 import java.util.concurrent.Flow; |
40 import java.util.concurrent.TimeoutException; |
40 import java.util.concurrent.TimeoutException; |
41 import java.util.concurrent.TimeUnit; |
41 import java.util.concurrent.TimeUnit; |
42 import static java.lang.System.out; |
42 import java.util.function.Supplier; |
|
43 import jdk.incubator.http.HttpClient; |
|
44 import jdk.incubator.http.HttpResponse; |
|
45 import jdk.incubator.http.HttpRequest; |
|
46 import jdk.incubator.http.HttpTimeoutException; |
|
47 |
|
48 import static java.lang.System.err; |
43 import static java.nio.charset.StandardCharsets.US_ASCII; |
49 import static java.nio.charset.StandardCharsets.US_ASCII; |
44 import static jdk.incubator.http.HttpResponse.BodyHandler.discard; |
50 import static jdk.incubator.http.HttpResponse.BodyHandler.discard; |
45 import static java.nio.charset.StandardCharsets.UTF_8; |
51 import static java.nio.charset.StandardCharsets.UTF_8; |
46 |
52 |
47 /** |
53 /** |
48 * @test |
54 * @test |
49 * @bug 8151441 |
55 * @bug 8151441 |
50 * @summary Request body of incorrect (larger or smaller) sizes than that |
56 * @summary Request body of incorrect (larger or smaller) sizes than that |
51 * reported by the body processor |
57 * reported by the body publisher |
52 * @run main/othervm ShortRequestBody |
58 * @run main/othervm ShortRequestBody |
53 */ |
59 */ |
54 |
60 |
55 public class ShortRequestBody { |
61 public class ShortRequestBody { |
56 |
62 |
57 static final Path testSrc = Paths.get(System.getProperty("test.src", ".")); |
63 static final Path testSrc = Paths.get(System.getProperty("test.src", ".")); |
58 static volatile HttpClient staticDefaultClient; |
|
59 |
|
60 static HttpClient defaultClient() { |
|
61 if (staticDefaultClient == null) { |
|
62 synchronized (ShortRequestBody.class) { |
|
63 staticDefaultClient = HttpClient.newHttpClient(); |
|
64 } |
|
65 } |
|
66 return staticDefaultClient; |
|
67 } |
|
68 |
64 |
69 // Some body types ( sources ) for testing. |
65 // Some body types ( sources ) for testing. |
70 static final String STRING_BODY = "Hello world"; |
66 static final String STRING_BODY = "Hello world"; |
71 static final byte[] BYTE_ARRAY_BODY = new byte[] { |
67 static final byte[] BYTE_ARRAY_BODY = new byte[] { |
72 (byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE }; |
68 (byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE }; |
99 |
95 |
100 @Override |
96 @Override |
101 public long contentLength() { return contentLength; /* may be wrong! */ } |
97 public long contentLength() { return contentLength; /* may be wrong! */ } |
102 } |
98 } |
103 |
99 |
104 // Request body processors that may generate a different number of actual |
100 // Request body Publishers that may generate a different number of actual |
105 // bytes to that of what is reported through their {@code contentLength}. |
101 // bytes to that of what is reported through their {@code contentLength}. |
106 |
102 |
107 static class StringRequestBody extends AbstractDelegateRequestBody { |
103 static class StringRequestBody extends AbstractDelegateRequestBody { |
108 StringRequestBody(String body, int additionalLength) { |
104 StringRequestBody(String body, int additionalLength) { |
109 super(HttpRequest.BodyProcessor.fromString(body), |
105 super(HttpRequest.BodyPublisher.fromString(body), |
110 body.getBytes(UTF_8).length + additionalLength); |
106 body.getBytes(UTF_8).length + additionalLength); |
111 } |
107 } |
112 } |
108 } |
113 |
109 |
114 static class ByteArrayRequestBody extends AbstractDelegateRequestBody { |
110 static class ByteArrayRequestBody extends AbstractDelegateRequestBody { |
115 ByteArrayRequestBody(byte[] body, int additionalLength) { |
111 ByteArrayRequestBody(byte[] body, int additionalLength) { |
116 super(HttpRequest.BodyProcessor.fromByteArray(body), |
112 super(HttpRequest.BodyPublisher.fromByteArray(body), |
117 body.length + additionalLength); |
113 body.length + additionalLength); |
118 } |
114 } |
119 } |
115 } |
120 |
116 |
121 static class FileRequestBody extends AbstractDelegateRequestBody { |
117 static class FileRequestBody extends AbstractDelegateRequestBody { |
122 FileRequestBody(Path path, int additionalLength) throws IOException { |
118 FileRequestBody(Path path, int additionalLength) throws IOException { |
123 super(HttpRequest.BodyProcessor.fromFile(path), |
119 super(HttpRequest.BodyPublisher.fromFile(path), |
124 Files.size(path) + additionalLength); |
120 Files.size(path) + additionalLength); |
125 } |
121 } |
126 } |
122 } |
127 |
123 |
128 // --- |
124 // --- |
129 |
125 |
130 public static void main(String[] args) throws Exception { |
126 public static void main(String[] args) throws Exception { |
|
127 HttpClient sharedClient = HttpClient.newHttpClient(); |
|
128 List<Supplier<HttpClient>> clientSuppliers = new ArrayList<>(); |
|
129 clientSuppliers.add(() -> HttpClient.newHttpClient()); |
|
130 clientSuppliers.add(() -> sharedClient); |
|
131 |
131 try (Server server = new Server()) { |
132 try (Server server = new Server()) { |
132 URI uri = new URI("http://127.0.0.1:" + server.getPort() + "/"); |
133 for (Supplier<HttpClient> cs : clientSuppliers) { |
133 |
134 err.println("\n---- next supplier ----\n"); |
134 // sanity |
135 URI uri = new URI("http://127.0.0.1:" + server.getPort() + "/"); |
135 success(uri, new StringRequestBody(STRING_BODY, 0)); |
136 |
136 success(uri, new ByteArrayRequestBody(BYTE_ARRAY_BODY, 0)); |
137 // sanity ( 6 requests to keep client and server offsets easy to workout ) |
137 success(uri, new FileRequestBody(FILE_BODY, 0)); |
138 success(cs, uri, new StringRequestBody(STRING_BODY, 0)); |
138 |
139 success(cs, uri, new ByteArrayRequestBody(BYTE_ARRAY_BODY, 0)); |
139 for (int i=1; i< BODY_OFFSETS.length; i++) { |
140 success(cs, uri, new FileRequestBody(FILE_BODY, 0)); |
140 failureBlocking(uri, new StringRequestBody(STRING_BODY, BODY_OFFSETS[i])); |
141 success(cs, uri, new StringRequestBody(STRING_BODY, 0)); |
141 failureBlocking(uri, new ByteArrayRequestBody(BYTE_ARRAY_BODY, BODY_OFFSETS[i])); |
142 success(cs, uri, new ByteArrayRequestBody(BYTE_ARRAY_BODY, 0)); |
142 failureBlocking(uri, new FileRequestBody(FILE_BODY, BODY_OFFSETS[i])); |
143 success(cs, uri, new FileRequestBody(FILE_BODY, 0)); |
143 |
144 |
144 failureNonBlocking(uri, new StringRequestBody(STRING_BODY, BODY_OFFSETS[i])); |
145 for (int i = 1; i < BODY_OFFSETS.length; i++) { |
145 failureNonBlocking(uri, new ByteArrayRequestBody(BYTE_ARRAY_BODY, BODY_OFFSETS[i])); |
146 failureBlocking(cs, uri, new StringRequestBody(STRING_BODY, BODY_OFFSETS[i])); |
146 failureNonBlocking(uri, new FileRequestBody(FILE_BODY, BODY_OFFSETS[i])); |
147 failureBlocking(cs, uri, new ByteArrayRequestBody(BYTE_ARRAY_BODY, BODY_OFFSETS[i])); |
|
148 failureBlocking(cs, uri, new FileRequestBody(FILE_BODY, BODY_OFFSETS[i])); |
|
149 |
|
150 failureNonBlocking(cs, uri, new StringRequestBody(STRING_BODY, BODY_OFFSETS[i])); |
|
151 failureNonBlocking(cs, uri, new ByteArrayRequestBody(BYTE_ARRAY_BODY, BODY_OFFSETS[i])); |
|
152 failureNonBlocking(cs, uri, new FileRequestBody(FILE_BODY, BODY_OFFSETS[i])); |
|
153 } |
147 } |
154 } |
148 } finally { |
155 } |
149 Executor def = defaultClient().executor(); |
156 } |
150 if (def instanceof ExecutorService) { |
157 |
151 ((ExecutorService)def).shutdownNow(); |
158 static void success(Supplier<HttpClient> clientSupplier, |
152 } |
159 URI uri, |
153 } |
160 HttpRequest.BodyPublisher publisher) |
154 } |
|
155 |
|
156 static void success(URI uri, HttpRequest.BodyProcessor processor) |
|
157 throws Exception |
161 throws Exception |
158 { |
162 { |
159 CompletableFuture<HttpResponse<Void>> cf; |
163 CompletableFuture<HttpResponse<Void>> cf; |
160 HttpRequest request = HttpRequest.newBuilder(uri) |
164 HttpRequest request = HttpRequest.newBuilder(uri) |
161 .POST(processor) |
165 .POST(publisher) |
162 .build(); |
166 .build(); |
163 cf = defaultClient().sendAsync(request, discard(null)); |
167 cf = clientSupplier.get().sendAsync(request, discard(null)); |
164 |
168 |
165 HttpResponse<Void> resp = cf.get(30, TimeUnit.SECONDS); |
169 HttpResponse<Void> resp = cf.get(30, TimeUnit.SECONDS); |
166 out.println("Response code: " + resp.statusCode()); |
170 err.println("Response code: " + resp.statusCode()); |
167 check(resp.statusCode() == 200, "Expected 200, got ", resp.statusCode()); |
171 check(resp.statusCode() == 200, "Expected 200, got ", resp.statusCode()); |
168 } |
172 } |
169 |
173 |
170 static void failureNonBlocking(URI uri, HttpRequest.BodyProcessor processor) |
174 static void failureNonBlocking(Supplier<HttpClient> clientSupplier, |
|
175 URI uri, |
|
176 HttpRequest.BodyPublisher publisher) |
171 throws Exception |
177 throws Exception |
172 { |
178 { |
173 CompletableFuture<HttpResponse<Void>> cf; |
179 CompletableFuture<HttpResponse<Void>> cf; |
174 HttpRequest request = HttpRequest.newBuilder(uri) |
180 HttpRequest request = HttpRequest.newBuilder(uri) |
175 .POST(processor) |
181 .POST(publisher) |
176 .build(); |
182 .build(); |
177 cf = defaultClient().sendAsync(request, discard(null)); |
183 cf = clientSupplier.get().sendAsync(request, discard(null)); |
178 |
184 |
179 try { |
185 try { |
180 HttpResponse<Void> r = cf.get(30, TimeUnit.SECONDS); |
186 HttpResponse<Void> r = cf.get(30, TimeUnit.SECONDS); |
181 throw new RuntimeException("Unexpected response: " + r.statusCode()); |
187 throw new RuntimeException("Unexpected response: " + r.statusCode()); |
182 } catch (TimeoutException x) { |
188 } catch (TimeoutException x) { |
183 throw new RuntimeException("Unexpected timeout", x); |
189 throw new RuntimeException("Unexpected timeout", x); |
184 } catch (ExecutionException expected) { |
190 } catch (ExecutionException expected) { |
185 out.println("Caught expected: " + expected); |
191 err.println("Caught expected: " + expected); |
186 check(expected.getCause() instanceof IOException, |
192 Throwable t = expected.getCause(); |
|
193 check(t instanceof IOException, |
187 "Expected cause IOException, but got: ", expected.getCause()); |
194 "Expected cause IOException, but got: ", expected.getCause()); |
188 } |
195 String msg = t.getMessage(); |
189 } |
196 check(msg.contains("Too many") || msg.contains("Too few"), |
190 |
197 "Expected Too many|Too few, got: ", t); |
191 static void failureBlocking(URI uri, HttpRequest.BodyProcessor processor) |
198 } |
|
199 } |
|
200 |
|
201 static void failureBlocking(Supplier<HttpClient> clientSupplier, |
|
202 URI uri, |
|
203 HttpRequest.BodyPublisher publisher) |
192 throws Exception |
204 throws Exception |
193 { |
205 { |
194 HttpRequest request = HttpRequest.newBuilder(uri) |
206 HttpRequest request = HttpRequest.newBuilder(uri) |
195 .POST(processor) |
207 .POST(publisher) |
196 .build(); |
208 .build(); |
197 try { |
209 try { |
198 HttpResponse<Void> r = defaultClient().send(request, discard(null)); |
210 HttpResponse<Void> r = clientSupplier.get().send(request, discard(null)); |
199 throw new RuntimeException("Unexpected response: " + r.statusCode()); |
211 throw new RuntimeException("Unexpected response: " + r.statusCode()); |
|
212 } catch (HttpTimeoutException x) { |
|
213 throw new RuntimeException("Unexpected timeout", x); |
200 } catch (IOException expected) { |
214 } catch (IOException expected) { |
201 out.println("Caught expected: " + expected); |
215 err.println("Caught expected: " + expected); |
|
216 String msg = expected.getMessage(); |
|
217 check(msg.contains("Too many") || msg.contains("Too few"), |
|
218 "Expected Too many|Too few, got: ", expected); |
202 } |
219 } |
203 } |
220 } |
204 |
221 |
205 static class Server extends Thread implements AutoCloseable { |
222 static class Server extends Thread implements AutoCloseable { |
206 |
223 |
223 int count = 0; |
240 int count = 0; |
224 int offset = 0; |
241 int offset = 0; |
225 |
242 |
226 while (!closed) { |
243 while (!closed) { |
227 try (Socket s = ss.accept()) { |
244 try (Socket s = ss.accept()) { |
|
245 err.println("Server: got connection"); |
228 InputStream is = s.getInputStream(); |
246 InputStream is = s.getInputStream(); |
229 readRequestHeaders(is); |
247 readRequestHeaders(is); |
230 byte[] ba = new byte[1024]; |
248 byte[] ba = new byte[1024]; |
231 |
249 |
232 int length = BODY_LENGTHS[count % 3]; |
250 int length = BODY_LENGTHS[count % 3]; |
233 length += BODY_OFFSETS[offset]; |
251 length += BODY_OFFSETS[offset]; |
234 |
252 err.println("Server: count=" + count + ", offset=" + offset); |
235 is.readNBytes(ba, 0, length); |
253 err.println("Server: expecting " +length+ " bytes"); |
236 |
254 int read = is.readNBytes(ba, 0, length); |
237 OutputStream os = s.getOutputStream(); |
255 err.println("Server: actually read " + read + " bytes"); |
238 os.write(RESPONSE.getBytes(US_ASCII)); |
256 |
|
257 // Update the counts before replying, to prevent the |
|
258 // client-side racing reset with this thread. |
239 count++; |
259 count++; |
240 if (count % 6 == 0) // 6 is the number of failure requests per offset |
260 if (count % 6 == 0) // 6 is the number of failure requests per offset |
241 offset++; |
261 offset++; |
|
262 if (count % 42 == 0) { |
|
263 count = 0; // reset, for second iteration |
|
264 offset = 0; |
|
265 } |
|
266 |
|
267 if (read < length) { |
|
268 // no need to reply, client has already closed |
|
269 // ensure closed |
|
270 if (is.read() != -1) |
|
271 new AssertionError("Unexpected read"); |
|
272 } else { |
|
273 OutputStream os = s.getOutputStream(); |
|
274 err.println("Server: writing " |
|
275 + RESPONSE.getBytes(US_ASCII).length + " bytes"); |
|
276 os.write(RESPONSE.getBytes(US_ASCII)); |
|
277 } |
|
278 |
242 } catch (IOException e) { |
279 } catch (IOException e) { |
243 if (!closed) |
280 if (!closed) |
244 System.out.println("Unexpected" + e); |
281 System.out.println("Unexpected" + e); |
245 } |
282 } |
246 } |
283 } |