1 /* 2 * Copyright (c) 2015, 2017, 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 import java.io.IOException; 25 import java.io.InputStream; 26 import java.io.OutputStream; 27 import java.io.UncheckedIOException; 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; 34 import java.nio.ByteBuffer; 35 import java.util.ArrayList; 36 import java.util.List; 37 import java.util.concurrent.CompletableFuture; 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 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; 49 import static java.nio.charset.StandardCharsets.US_ASCII; 50 import static jdk.incubator.http.HttpResponse.BodyHandler.discard; 51 import static java.nio.charset.StandardCharsets.UTF_8; 52 53 /** 54 * @test 55 * @bug 8151441 56 * @summary Request body of incorrect (larger or smaller) sizes than that 57 * reported by the body publisher 58 * @run main/othervm ShortRequestBody 59 */ 60 61 public class ShortRequestBody { 62 63 static final Path testSrc = Paths.get(System.getProperty("test.src", ".")); 64 65 // Some body types ( sources ) for testing. 66 static final String STRING_BODY = "Hello world"; 67 static final byte[] BYTE_ARRAY_BODY = new byte[] { 68 (byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE }; 69 static final Path FILE_BODY = testSrc.resolve("docs").resolve("files").resolve("foo.txt"); 70 71 // Body lengths and offsets ( amount to be wrong by ), to make coordination 72 // between client and server easier. 73 static final int[] BODY_LENGTHS = new int[] { STRING_BODY.length(), 74 BYTE_ARRAY_BODY.length, 75 fileSize(FILE_BODY) }; 76 static final int[] BODY_OFFSETS = new int[] { 0, +1, -1, +2, -2, +3, -3 }; 77 78 // A delegating Body Publisher. Subtypes will have a concrete body type. 79 static abstract class AbstractDelegateRequestBody 80 implements HttpRequest.BodyPublisher { 81 82 final HttpRequest.BodyPublisher delegate; 83 final long contentLength; 84 85 AbstractDelegateRequestBody(HttpRequest.BodyPublisher delegate, 86 long contentLength) { 87 this.delegate = delegate; 88 this.contentLength = contentLength; 89 } 90 91 @Override 92 public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) { 93 delegate.subscribe(subscriber); 94 } 95 96 @Override 97 public long contentLength() { return contentLength; /* may be wrong! */ } 98 } 99 100 // Request body Publishers that may generate a different number of actual 101 // bytes to that of what is reported through their {@code contentLength}. 102 103 static class StringRequestBody extends AbstractDelegateRequestBody { 104 StringRequestBody(String body, int additionalLength) { 105 super(HttpRequest.BodyPublisher.fromString(body), 106 body.getBytes(UTF_8).length + additionalLength); 107 } 108 } 109 110 static class ByteArrayRequestBody extends AbstractDelegateRequestBody { 111 ByteArrayRequestBody(byte[] body, int additionalLength) { 112 super(HttpRequest.BodyPublisher.fromByteArray(body), 113 body.length + additionalLength); 114 } 115 } 116 117 static class FileRequestBody extends AbstractDelegateRequestBody { 118 FileRequestBody(Path path, int additionalLength) throws IOException { 119 super(HttpRequest.BodyPublisher.fromFile(path), 120 Files.size(path) + additionalLength); 121 } 122 } 123 124 // --- 125 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 132 try (Server server = new Server()) { 133 for (Supplier<HttpClient> cs : clientSuppliers) { 134 err.println("\n---- next supplier ----\n"); 135 URI uri = new URI("http://127.0.0.1:" + server.getPort() + "/"); 136 137 // sanity ( 6 requests to keep client and server offsets easy to workout ) 138 success(cs, uri, new StringRequestBody(STRING_BODY, 0)); 139 success(cs, uri, new ByteArrayRequestBody(BYTE_ARRAY_BODY, 0)); 140 success(cs, uri, new FileRequestBody(FILE_BODY, 0)); 141 success(cs, uri, new StringRequestBody(STRING_BODY, 0)); 142 success(cs, uri, new ByteArrayRequestBody(BYTE_ARRAY_BODY, 0)); 143 success(cs, uri, new FileRequestBody(FILE_BODY, 0)); 144 145 for (int i = 1; i < BODY_OFFSETS.length; i++) { 146 failureBlocking(cs, uri, new StringRequestBody(STRING_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 } 154 } 155 } 156 } 157 158 static void success(Supplier<HttpClient> clientSupplier, 159 URI uri, 160 HttpRequest.BodyPublisher publisher) 161 throws Exception 162 { 163 CompletableFuture<HttpResponse<Void>> cf; 164 HttpRequest request = HttpRequest.newBuilder(uri) 165 .POST(publisher) 166 .build(); 167 cf = clientSupplier.get().sendAsync(request, discard(null)); 168 169 HttpResponse<Void> resp = cf.get(30, TimeUnit.SECONDS); 170 err.println("Response code: " + resp.statusCode()); 171 check(resp.statusCode() == 200, "Expected 200, got ", resp.statusCode()); 172 } 173 174 static void failureNonBlocking(Supplier<HttpClient> clientSupplier, 175 URI uri, 176 HttpRequest.BodyPublisher publisher) 177 throws Exception 178 { 179 CompletableFuture<HttpResponse<Void>> cf; 180 HttpRequest request = HttpRequest.newBuilder(uri) 181 .POST(publisher) 182 .build(); 183 cf = clientSupplier.get().sendAsync(request, discard(null)); 184 185 try { 186 HttpResponse<Void> r = cf.get(30, TimeUnit.SECONDS); 187 throw new RuntimeException("Unexpected response: " + r.statusCode()); 188 } catch (TimeoutException x) { 189 throw new RuntimeException("Unexpected timeout", x); 190 } catch (ExecutionException expected) { 191 err.println("Caught expected: " + expected); 192 Throwable t = expected.getCause(); 193 check(t instanceof IOException, 194 "Expected cause IOException, but got: ", expected.getCause()); 195 String msg = t.getMessage(); 196 check(msg.contains("Too many") || msg.contains("Too few"), 197 "Expected Too many|Too few, got: ", t); 198 } 199 } 200 201 static void failureBlocking(Supplier<HttpClient> clientSupplier, 202 URI uri, 203 HttpRequest.BodyPublisher publisher) 204 throws Exception 205 { 206 HttpRequest request = HttpRequest.newBuilder(uri) 207 .POST(publisher) 208 .build(); 209 try { 210 HttpResponse<Void> r = clientSupplier.get().send(request, discard(null)); 211 throw new RuntimeException("Unexpected response: " + r.statusCode()); 212 } catch (HttpTimeoutException x) { 213 throw new RuntimeException("Unexpected timeout", x); 214 } catch (IOException 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); 219 } 220 } 221 222 static class Server extends Thread implements AutoCloseable { 223 224 static String RESPONSE = "HTTP/1.1 200 OK\r\n" + 225 "Connection: close\r\n"+ 226 "Content-length: 0\r\n\r\n"; 227 228 private final ServerSocket ss; 229 private volatile boolean closed; 230 231 Server() throws IOException { 232 super("Test-Server"); 233 ss = new ServerSocket(0); this.start(); 234 } 235 236 int getPort() { return ss.getLocalPort(); } 237 238 @Override 239 public void run() { 240 int count = 0; 241 int offset = 0; 242 243 while (!closed) { 244 try (Socket s = ss.accept()) { 245 err.println("Server: got connection"); 246 InputStream is = s.getInputStream(); 247 readRequestHeaders(is); 248 byte[] ba = new byte[1024]; 249 250 int length = BODY_LENGTHS[count % 3]; 251 length += BODY_OFFSETS[offset]; 252 err.println("Server: count=" + count + ", offset=" + offset); 253 err.println("Server: expecting " +length+ " bytes"); 254 int read = is.readNBytes(ba, 0, length); 255 err.println("Server: actually read " + read + " bytes"); 256 257 // Update the counts before replying, to prevent the 258 // client-side racing reset with this thread. 259 count++; 260 if (count % 6 == 0) // 6 is the number of failure requests per 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 279 } catch (IOException e) { 280 if (!closed) 281 System.out.println("Unexpected" + e); 282 } 283 } 284 } 285 286 @Override 287 public void close() { 288 if (closed) 289 return; 290 closed = true; 291 try { 292 ss.close(); 293 } catch (IOException e) { 294 throw new UncheckedIOException("Unexpected", e); 295 } 296 } 297 } 298 299 static final byte[] requestEnd = new byte[] {'\r', '\n', '\r', '\n' }; 300 301 // Read until the end of a HTTP request headers 302 static void readRequestHeaders(InputStream is) throws IOException { 303 int requestEndCount = 0, r; 304 while ((r = is.read()) != -1) { 305 if (r == requestEnd[requestEndCount]) { 306 requestEndCount++; 307 if (requestEndCount == 4) { 308 break; 309 } 310 } else { 311 requestEndCount = 0; 312 } 313 } 314 } 315 316 static int fileSize(Path p) { 317 try { return (int) Files.size(p); } 318 catch (IOException x) { throw new UncheckedIOException(x); } 319 } 320 321 static boolean check(boolean cond, Object... failedArgs) { 322 if (cond) 323 return true; 324 // We are going to fail... 325 StringBuilder sb = new StringBuilder(); 326 for (Object o : failedArgs) 327 sb.append(o); 328 throw new RuntimeException(sb.toString()); 329 } 330 }