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.*; |
25 import java.net.http.*; |
25 import jdk.incubator.http.HttpClient; |
26 import java.net.*; |
26 import jdk.incubator.http.HttpResponse; |
27 import java.util.concurrent.*; |
27 import jdk.incubator.http.HttpRequest; |
|
28 import java.net.ServerSocket; |
|
29 import java.net.Socket; |
|
30 import java.net.URI; |
|
31 import java.nio.file.Files; |
|
32 import java.nio.file.Path; |
|
33 import java.nio.file.Paths; |
28 import java.nio.ByteBuffer; |
34 import java.nio.ByteBuffer; |
29 import java.util.function.LongConsumer; |
35 import java.util.concurrent.CompletableFuture; |
|
36 import java.util.concurrent.Executor; |
|
37 import java.util.concurrent.ExecutorService; |
|
38 import java.util.concurrent.ExecutionException; |
|
39 import java.util.concurrent.Flow; |
|
40 import java.util.concurrent.TimeoutException; |
|
41 import java.util.concurrent.TimeUnit; |
|
42 import static java.lang.System.out; |
|
43 import static java.nio.charset.StandardCharsets.US_ASCII; |
|
44 import static jdk.incubator.http.HttpResponse.BodyHandler.discard; |
|
45 import static java.nio.charset.StandardCharsets.UTF_8; |
30 |
46 |
31 /** |
47 /** |
32 * @test |
48 * @test |
33 * @bug 8151441 |
49 * @bug 8151441 |
34 * @run main/othervm/timeout=10 ShortRequestBody |
50 * @summary Request body of incorrect (larger or smaller) sizes than that |
|
51 * reported by the body processor |
|
52 * @run main/othervm ShortRequestBody |
35 */ |
53 */ |
36 |
54 |
37 /** |
|
38 * Exception was not being thrown |
|
39 */ |
|
40 public class ShortRequestBody { |
55 public class ShortRequestBody { |
41 |
56 |
42 static Server server; |
57 static final Path testSrc = Paths.get(System.getProperty("test.src", ".")); |
43 static String reqbody = "Hello world"; |
58 static volatile HttpClient staticDefaultClient; |
44 |
59 |
45 static String response = "HTTP/1.1 200 OK\r\nContent-length: 0\r\n\r\n"; |
60 static HttpClient defaultClient() { |
46 |
61 if (staticDefaultClient == null) { |
47 static class RequestBody implements HttpRequest.BodyProcessor { |
62 synchronized (ShortRequestBody.class) { |
48 public long onRequestStart(HttpRequest hr, LongConsumer flowController) { |
63 staticDefaultClient = HttpClient.newHttpClient(); |
49 return reqbody.length() + 1; // wrong! |
64 } |
50 } |
65 } |
51 |
66 return staticDefaultClient; |
52 public boolean onRequestBodyChunk(ByteBuffer buf) throws IOException { |
67 } |
53 byte[] b = reqbody.getBytes(); |
68 |
54 buf.put(b); |
69 // Some body types ( sources ) for testing. |
|
70 static final String STRING_BODY = "Hello world"; |
|
71 static final byte[] BYTE_ARRAY_BODY = new byte[] { |
|
72 (byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE }; |
|
73 static final Path FILE_BODY = testSrc.resolve("docs").resolve("files").resolve("foo.txt"); |
|
74 |
|
75 // Body lengths and offsets ( amount to be wrong by ), to make coordination |
|
76 // between client and server easier. |
|
77 static final int[] BODY_LENGTHS = new int[] { STRING_BODY.length(), |
|
78 BYTE_ARRAY_BODY.length, |
|
79 fileSize(FILE_BODY) }; |
|
80 static final int[] BODY_OFFSETS = new int[] { 0, +1, -1, +2, -2, +3, -3 }; |
|
81 |
|
82 // A delegating body processor. Subtypes will have a concrete body type. |
|
83 static abstract class AbstractDelegateRequestBody |
|
84 implements HttpRequest.BodyProcessor { |
|
85 |
|
86 final HttpRequest.BodyProcessor delegate; |
|
87 final long contentLength; |
|
88 |
|
89 AbstractDelegateRequestBody(HttpRequest.BodyProcessor delegate, |
|
90 long contentLength) { |
|
91 this.delegate = delegate; |
|
92 this.contentLength = contentLength; |
|
93 } |
|
94 |
|
95 @Override |
|
96 public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) { |
|
97 delegate.subscribe(subscriber); |
|
98 } |
|
99 |
|
100 @Override |
|
101 public long contentLength() { return contentLength; /* may be wrong! */ } |
|
102 } |
|
103 |
|
104 // Request body processors that may generate a different number of actual |
|
105 // bytes to that of what is reported through their {@code contentLength}. |
|
106 |
|
107 static class StringRequestBody extends AbstractDelegateRequestBody { |
|
108 StringRequestBody(String body, int additionalLength) { |
|
109 super(HttpRequest.BodyProcessor.fromString(body), |
|
110 body.getBytes(UTF_8).length + additionalLength); |
|
111 } |
|
112 } |
|
113 |
|
114 static class ByteArrayRequestBody extends AbstractDelegateRequestBody { |
|
115 ByteArrayRequestBody(byte[] body, int additionalLength) { |
|
116 super(HttpRequest.BodyProcessor.fromByteArray(body), |
|
117 body.length + additionalLength); |
|
118 } |
|
119 } |
|
120 |
|
121 static class FileRequestBody extends AbstractDelegateRequestBody { |
|
122 FileRequestBody(Path path, int additionalLength) throws IOException { |
|
123 super(HttpRequest.BodyProcessor.fromFile(path), |
|
124 Files.size(path) + additionalLength); |
|
125 } |
|
126 } |
|
127 |
|
128 // --- |
|
129 |
|
130 public static void main(String[] args) throws Exception { |
|
131 try (Server server = new Server()) { |
|
132 URI uri = new URI("http://127.0.0.1:" + server.getPort() + "/"); |
|
133 |
|
134 // sanity |
|
135 success(uri, new StringRequestBody(STRING_BODY, 0)); |
|
136 success(uri, new ByteArrayRequestBody(BYTE_ARRAY_BODY, 0)); |
|
137 success(uri, new FileRequestBody(FILE_BODY, 0)); |
|
138 |
|
139 for (int i=1; i< BODY_OFFSETS.length; i++) { |
|
140 failureBlocking(uri, new StringRequestBody(STRING_BODY, BODY_OFFSETS[i])); |
|
141 failureBlocking(uri, new ByteArrayRequestBody(BYTE_ARRAY_BODY, BODY_OFFSETS[i])); |
|
142 failureBlocking(uri, new FileRequestBody(FILE_BODY, BODY_OFFSETS[i])); |
|
143 |
|
144 failureNonBlocking(uri, new StringRequestBody(STRING_BODY, BODY_OFFSETS[i])); |
|
145 failureNonBlocking(uri, new ByteArrayRequestBody(BYTE_ARRAY_BODY, BODY_OFFSETS[i])); |
|
146 failureNonBlocking(uri, new FileRequestBody(FILE_BODY, BODY_OFFSETS[i])); |
|
147 } |
|
148 } finally { |
|
149 Executor def = defaultClient().executor(); |
|
150 if (def instanceof ExecutorService) { |
|
151 ((ExecutorService)def).shutdownNow(); |
|
152 } |
|
153 } |
|
154 } |
|
155 |
|
156 static void success(URI uri, HttpRequest.BodyProcessor processor) |
|
157 throws Exception |
|
158 { |
|
159 CompletableFuture<HttpResponse<Void>> cf; |
|
160 HttpRequest request = HttpRequest.newBuilder(uri) |
|
161 .POST(processor) |
|
162 .build(); |
|
163 cf = defaultClient().sendAsync(request, discard(null)); |
|
164 |
|
165 HttpResponse<Void> resp = cf.get(30, TimeUnit.SECONDS); |
|
166 out.println("Response code: " + resp.statusCode()); |
|
167 check(resp.statusCode() == 200, "Expected 200, got ", resp.statusCode()); |
|
168 } |
|
169 |
|
170 static void failureNonBlocking(URI uri, HttpRequest.BodyProcessor processor) |
|
171 throws Exception |
|
172 { |
|
173 CompletableFuture<HttpResponse<Void>> cf; |
|
174 HttpRequest request = HttpRequest.newBuilder(uri) |
|
175 .POST(processor) |
|
176 .build(); |
|
177 cf = defaultClient().sendAsync(request, discard(null)); |
|
178 |
|
179 try { |
|
180 HttpResponse<Void> r = cf.get(30, TimeUnit.SECONDS); |
|
181 throw new RuntimeException("Unexpected response: " + r.statusCode()); |
|
182 } catch (TimeoutException x) { |
|
183 throw new RuntimeException("Unexpected timeout", x); |
|
184 } catch (ExecutionException expected) { |
|
185 out.println("Caught expected: " + expected); |
|
186 check(expected.getCause() instanceof IOException, |
|
187 "Expected cause IOException, but got: ", expected.getCause()); |
|
188 } |
|
189 } |
|
190 |
|
191 static void failureBlocking(URI uri, HttpRequest.BodyProcessor processor) |
|
192 throws Exception |
|
193 { |
|
194 HttpRequest request = HttpRequest.newBuilder(uri) |
|
195 .POST(processor) |
|
196 .build(); |
|
197 try { |
|
198 HttpResponse<Void> r = defaultClient().send(request, discard(null)); |
|
199 throw new RuntimeException("Unexpected response: " + r.statusCode()); |
|
200 } catch (IOException expected) { |
|
201 out.println("Caught expected: " + expected); |
|
202 } |
|
203 } |
|
204 |
|
205 static class Server extends Thread implements AutoCloseable { |
|
206 |
|
207 static String RESPONSE = "HTTP/1.1 200 OK\r\n" + |
|
208 "Connection: close\r\n"+ |
|
209 "Content-length: 0\r\n\r\n"; |
|
210 |
|
211 private final ServerSocket ss; |
|
212 private volatile boolean closed; |
|
213 |
|
214 Server() throws IOException { |
|
215 super("Test-Server"); |
|
216 ss = new ServerSocket(0); this.start(); |
|
217 } |
|
218 |
|
219 int getPort() { return ss.getLocalPort(); } |
|
220 |
|
221 @Override |
|
222 public void run() { |
|
223 int count = 0; |
|
224 int offset = 0; |
|
225 |
|
226 while (!closed) { |
|
227 try (Socket s = ss.accept()) { |
|
228 InputStream is = s.getInputStream(); |
|
229 readRequestHeaders(is); |
|
230 byte[] ba = new byte[1024]; |
|
231 |
|
232 int length = BODY_LENGTHS[count % 3]; |
|
233 length += BODY_OFFSETS[offset]; |
|
234 |
|
235 is.readNBytes(ba, 0, length); |
|
236 |
|
237 OutputStream os = s.getOutputStream(); |
|
238 os.write(RESPONSE.getBytes(US_ASCII)); |
|
239 count++; |
|
240 if (count % 6 == 0) // 6 is the number of failure requests per offset |
|
241 offset++; |
|
242 } catch (IOException e) { |
|
243 if (!closed) |
|
244 System.out.println("Unexpected" + e); |
|
245 } |
|
246 } |
|
247 } |
|
248 |
|
249 @Override |
|
250 public void close() { |
|
251 if (closed) |
|
252 return; |
|
253 closed = true; |
|
254 try { |
|
255 ss.close(); |
|
256 } catch (IOException e) { |
|
257 throw new UncheckedIOException("Unexpected", e); |
|
258 } |
|
259 } |
|
260 } |
|
261 |
|
262 static final byte[] requestEnd = new byte[] {'\r', '\n', '\r', '\n' }; |
|
263 |
|
264 // Read until the end of a HTTP request headers |
|
265 static void readRequestHeaders(InputStream is) throws IOException { |
|
266 int requestEndCount = 0, r; |
|
267 while ((r = is.read()) != -1) { |
|
268 if (r == requestEnd[requestEndCount]) { |
|
269 requestEndCount++; |
|
270 if (requestEndCount == 4) { |
|
271 break; |
|
272 } |
|
273 } else { |
|
274 requestEndCount = 0; |
|
275 } |
|
276 } |
|
277 } |
|
278 |
|
279 static int fileSize(Path p) { |
|
280 try { return (int) Files.size(p); } |
|
281 catch (IOException x) { throw new UncheckedIOException(x); } |
|
282 } |
|
283 |
|
284 static boolean check(boolean cond, Object... failedArgs) { |
|
285 if (cond) |
55 return true; |
286 return true; |
56 } |
287 // We are going to fail... |
57 } |
288 StringBuilder sb = new StringBuilder(); |
58 |
289 for (Object o : failedArgs) |
59 static void close(Closeable c) { |
290 sb.append(o); |
60 try { |
291 throw new RuntimeException(sb.toString()); |
61 if (c == null) |
|
62 return; |
|
63 c.close(); |
|
64 } catch (IOException e) {} |
|
65 } |
|
66 |
|
67 public static void main(String[] args) throws Exception { |
|
68 ServerSocket server = new ServerSocket(0); |
|
69 int port = server.getLocalPort(); |
|
70 URI uri = new URI("http://127.0.0.1:" + port + "/"); |
|
71 |
|
72 HttpRequest request; |
|
73 HttpResponse r; |
|
74 Socket s = null; |
|
75 CompletableFuture<HttpResponse> cf1; |
|
76 try { |
|
77 cf1 = HttpRequest.create(uri) |
|
78 .body(new RequestBody()) |
|
79 .GET() |
|
80 .responseAsync(); |
|
81 |
|
82 s = server.accept(); |
|
83 s.getInputStream().readAllBytes(); |
|
84 try (OutputStream os = s.getOutputStream()) { |
|
85 os.write(response.getBytes()); |
|
86 } catch (IOException ee) { |
|
87 } |
|
88 |
|
89 try { |
|
90 r = cf1.get(3, TimeUnit.SECONDS); |
|
91 throw new RuntimeException("Failed"); |
|
92 } catch (TimeoutException e0) { |
|
93 throw new RuntimeException("Failed timeout"); |
|
94 } catch (ExecutionException e) { |
|
95 System.err.println("OK"); |
|
96 } |
|
97 } finally { |
|
98 HttpClient.getDefault().executorService().shutdownNow(); |
|
99 close(s); |
|
100 close(server); |
|
101 } |
|
102 } |
292 } |
103 } |
293 } |