207 * In the second example, the function returns a different processor depending
208 * on the status code.
209 * <pre>
210 * {@code
211 * HttpResponse<Path> resp1 = HttpRequest
212 * .create(URI.create("http://www.foo.com"))
213 * .GET()
214 * .response(
215 * (status, headers) -> status == 200
216 * ? BodyProcessor.asFile(Paths.get("/tmp/f"))
217 * : BodyProcessor.discard(Paths.get("/NULL")));
218 * }
219 * </pre>
220 *
221 * @param <T> the response body type.
222 */
223 @FunctionalInterface
224 public interface BodyHandler<T> {
225
226 /**
227 * Return a {@link BodyProcessor BodyProcessor} considering the given response status
228 * code and headers. This method is always called before the body is read
229 * and its implementation can decide to keep the body and store it somewhere
230 * or else discard it, by returning the {@code BodyProcessor} returned
231 * from {@link BodyProcessor#discard(java.lang.Object) discard()}.
232 *
233 * @param statusCode the HTTP status code received
234 * @param responseHeaders the response headers received
235 * @return
236 */
237 public BodyProcessor<T> apply(int statusCode, HttpHeaders responseHeaders);
238
239 /**
240 * Returns a response body handler which discards the response body and
241 * uses the given value as a replacement for it.
242 *
243 * @param <U> the response body type
244 * @param value the value of U to return as the body
245 * @return
246 */
247 public static <U> BodyHandler<U> discard(U value) {
248 return (status, headers) -> BodyProcessor.discard(value);
249 }
250
251 /**
252 * Returns a {@code BodyHandler<String>} that returns a
253 * {@link BodyProcessor BodyProcessor}{@code <String>} obtained from
254 * {@link BodyProcessor#asString(java.nio.charset.Charset)
255 * BodyProcessor.asString(Charset)}. If a charset is provided, the
256 * body is decoded using it. If charset is {@code null} then the processor
257 * tries to determine the character set from the {@code Content-encoding}
258 * header. If that charset is not supported then
259 * {@link java.nio.charset.StandardCharsets#UTF_8 UTF_8} is used.
260 *
261 * @param charset the name of the charset to interpret the body as. If
262 * {@code null} then charset determined from Content-encoding header
263 * @return a response handler
264 */
265 public static BodyHandler<String> asString(Charset charset) {
266 return (status, headers) -> {
267 if (charset != null) {
268 return BodyProcessor.asString(charset);
269 }
270 return BodyProcessor.asString(charsetFrom(headers));
271 };
272 }
273
274
275 /**
276 * Returns a {@code BodyHandler<Path>} that returns a
277 * {@link BodyProcessor BodyProcessor}{@code <Path>} obtained from
278 * {@link BodyProcessor#asFile(Path) BodyProcessor.asFile(Path)}.
279 * <p>
280 * When the {@code HttpResponse} object is returned, the body has been completely
281 * written to the file, and {@link #body()} returns a reference to its
282 * {@link Path}.
283 *
284 * @param file the file to store the body in
285 * @return a response handler
286 */
287 public static BodyHandler<Path> asFile(Path file) {
288 return (status, headers) -> BodyProcessor.asFile(file);
289 }
290
291 /**
292 * Returns a {@code BodyHandler<Path>} that returns a
293 * {@link BodyProcessor BodyProcessor}<{@link Path}>
294 * where the download directory is specified, but the filename is
295 * obtained from the {@code Content-Disposition} response header. The
296 * {@code Content-Disposition} header must specify the <i>attachment</i> type
297 * and must also contain a
298 * <i>filename</i> parameter. If the filename specifies multiple path
299 * components only the final component is used as the filename (with the
300 * given directory name). When the {@code HttpResponse} object is
301 * returned, the body has been completely written to the file and {@link
302 * #body()} returns a {@code Path} object for the file. The returned {@code Path} is the
303 * combination of the supplied directory name and the file name supplied
304 * by the server. If the destination directory does not exist or cannot
305 * be written to, then the response will fail with an {@link IOException}.
306 *
307 * @param directory the directory to store the file in
308 * @param openOptions open options
309 * @return a response handler
310 */
311 public static BodyHandler<Path> asFileDownload(Path directory, OpenOption... openOptions) {
312 return (status, headers) -> {
313 String dispoHeader = headers.firstValue("Content-Disposition")
314 .orElseThrow(() -> unchecked(new IOException("No Content-Disposition")));
315 if (!dispoHeader.startsWith("attachment;")) {
316 throw unchecked(new IOException("Unknown Content-Disposition type"));
317 }
318 int n = dispoHeader.indexOf("filename=");
319 if (n == -1) {
320 throw unchecked(new IOException("Bad Content-Disposition type"));
321 }
322 int lastsemi = dispoHeader.lastIndexOf(';');
323 String disposition;
324 if (lastsemi < n) {
325 disposition = dispoHeader.substring(n + 9);
326 } else {
327 disposition = dispoHeader.substring(n + 9, lastsemi);
328 }
329 Path file = Paths.get(directory.toString(), disposition);
330 return BodyProcessor.asFile(file, openOptions);
331 };
332 }
333
334 /**
335 * Returns a {@code BodyHandler<Path>} that returns a
336 * {@link BodyProcessor BodyProcessor}{@code <Path>} obtained from
337 * {@link BodyProcessor#asFile(java.nio.file.Path, java.nio.file.OpenOption...)
338 * BodyProcessor.asFile(Path,OpenOption...)}.
339 * <p>
340 * When the {@code HttpResponse} object is returned, the body has been completely
341 * written to the file, and {@link #body()} returns a reference to its
342 * {@link Path}.
343 *
344 * @param file the filename to store the body in
345 * @param openOptions any options to use when opening/creating the file
346 * @return a response handler
347 */
348 public static BodyHandler<Path> asFile(Path file, OpenOption... openOptions) {
349 return (status, headers) -> BodyProcessor.asFile(file, openOptions);
350 }
351
352 /**
353 * Returns a {@code BodyHandler<Void>} that returns a
354 * {@link BodyProcessor BodyProcessor}{@code <Void>} obtained from
355 * {@link BodyProcessor#asByteArrayConsumer(java.util.function.Consumer)
356 * BodyProcessor.asByteArrayConsumer(Consumer)}.
357 * <p>
358 * When the {@code HttpResponse} object is returned, the body has been completely
359 * written to the consumer.
360 *
361 * @param consumer a Consumer to accept the response body
362 * @return a a response handler
363 */
364 public static BodyHandler<Void> asByteArrayConsumer(Consumer<Optional<byte[]>> consumer) {
365 return (status, headers) -> BodyProcessor.asByteArrayConsumer(consumer);
366 }
367
368 /**
369 * Returns a {@code BodyHandler<byte[]>} that returns a
370 * {@link BodyProcessor BodyProcessor}<{@code byte[]}> obtained
371 * from {@link BodyProcessor#asByteArray() BodyProcessor.asByteArray()}.
372 * <p>
373 * When the {@code HttpResponse} object is returned, the body has been completely
374 * written to the byte array.
375 *
376 * @return a response handler
377 */
378 public static BodyHandler<byte[]> asByteArray() {
379 return (status, headers) -> BodyProcessor.asByteArray();
380 }
381
382 /**
383 * Returns a {@code BodyHandler<String>} that returns a
384 * {@link BodyProcessor BodyProcessor}{@code <String>} obtained from
385 * {@link BodyProcessor#asString(java.nio.charset.Charset)
386 * BodyProcessor.asString(Charset)}. The body is
387 * decoded using the character set specified in
388 * the {@code Content-encoding} response header. If there is no such
389 * header, or the character set is not supported, then
390 * {@link java.nio.charset.StandardCharsets#UTF_8 UTF_8} is used.
391 * <p>
392 * When the {@code HttpResponse} object is returned, the body has been completely
393 * written to the string.
394 *
395 * @return a response handler
396 */
397 public static BodyHandler<String> asString() {
398 return (status, headers) -> BodyProcessor.asString(charsetFrom(headers));
399 }
400 }
401
402 /**
403 * A processor for response bodies.
404 * {@Incubating}
405 * <p>
406 * The object acts as a {@link Flow.Subscriber}<{@link ByteBuffer}> to
407 * the HTTP client implementation which publishes ByteBuffers containing the
408 * response body. The processor converts the incoming buffers of data to
409 * some user-defined object type {@code T}.
410 * <p>
411 * The {@link #getBody()} method returns a {@link CompletionStage}{@code <T>}
412 * that provides the response body object. The {@code CompletionStage} must
413 * be obtainable at any time. When it completes depends on the nature
414 * of type {@code T}. In many cases, when {@code T} represents the entire body after being
415 * read then it completes after the body has been read. If {@code T} is a streaming
589 *
590 * [Note] The reason for switching to this callback interface rather
591 * than using CompletableFutures supplied to onRequest() is that there
592 * is a subtle interaction between those CFs and the CF returned from
593 * completion() (or when onComplete() was called formerly). The completion()
594 * CF will not complete until after all of the work done by the onResponse()
595 * calls is done. Whereas if you just create CF's dependent on a supplied
596 * CF (to onRequest()) then the implementation has no visibility of the
597 * dependent CFs and can't guarantee to call onComplete() (or complete
598 * the completion() CF) after the dependent CFs complete.
599 *
600 * @param response the response received
601 */
602 void onResponse(HttpResponse<T> response);
603
604 /**
605 * Called if an error occurs receiving a response. For each request
606 * either one of onResponse() or onError() is guaranteed to be called,
607 * but not both.
608 *
609 * @param request
610 * @param t the Throwable that caused the error
611 */
612 void onError(HttpRequest request, Throwable t);
613
614 /**
615 * Returns a {@link java.util.concurrent.CompletableFuture}{@code <U>}
616 * which completes when the aggregate result object itself is available.
617 * It is expected that the returned {@code CompletableFuture} will depend
618 * on one of the given {@code CompletableFuture<Void}s which themselves complete
619 * after all individual responses associated with the multi response
620 * have completed, or after all push promises have been received.
621 * <p>
622 * @implNote Implementations might follow the pattern shown below
623 * <pre>
624 * {@code
625 * CompletableFuture<U> completion(
626 * CompletableFuture<Void> onComplete,
627 * CompletableFuture<Void> onFinalPushPromise)
628 * {
629 * return onComplete.thenApply((v) -> {
700 * {@code
701 * HttpRequest request = HttpRequest.newBuilder()
702 * .uri(URI.create("https://www.foo.com/"))
703 * .GET()
704 * .build();
705 *
706 * HttpClient client = HttpClient.newHttpClient();
707 *
708 * Map<HttpRequest,CompletableFuture<HttpResponse<String>>> results = client
709 * .sendAsync(request, MultiProcessor.asMap(
710 * (req) -> Optional.of(HttpResponse.BodyHandler.asString())))
711 * .join();
712 * }</pre>
713 * <p>
714 * The lambda in this example is the simplest possible implementation,
715 * where neither the incoming requests are examined, nor the response
716 * headers, and every push that the server sends is accepted. When the
717 * join() call returns, all {@code HttpResponse}s and their associated
718 * body objects are available.
719 *
720 * @param <V>
721 * @param pushHandler a function invoked for each request or push
722 * promise
723 * @return a MultiProcessor
724 */
725 public static <V> MultiProcessor<MultiMapResult<V>,V> asMap(
726 Function<HttpRequest, Optional<HttpResponse.BodyHandler<V>>> pushHandler) {
727
728 return asMap(pushHandler, true);
729 }
730
731 }
732 }
|
207 * In the second example, the function returns a different processor depending
208 * on the status code.
209 * <pre>
210 * {@code
211 * HttpResponse<Path> resp1 = HttpRequest
212 * .create(URI.create("http://www.foo.com"))
213 * .GET()
214 * .response(
215 * (status, headers) -> status == 200
216 * ? BodyProcessor.asFile(Paths.get("/tmp/f"))
217 * : BodyProcessor.discard(Paths.get("/NULL")));
218 * }
219 * </pre>
220 *
221 * @param <T> the response body type.
222 */
223 @FunctionalInterface
224 public interface BodyHandler<T> {
225
226 /**
227 * Returns a {@link BodyProcessor BodyProcessor} considering the given response status
228 * code and headers. This method is always called before the body is read
229 * and its implementation can decide to keep the body and store it somewhere
230 * or else discard it, by returning the {@code BodyProcessor} returned
231 * from {@link BodyProcessor#discard(java.lang.Object) discard()}.
232 *
233 * @param statusCode the HTTP status code received
234 * @param responseHeaders the response headers received
235 * @return a response body handler
236 */
237 public BodyProcessor<T> apply(int statusCode, HttpHeaders responseHeaders);
238
239 /**
240 * Returns a response body handler which discards the response body and
241 * uses the given value as a replacement for it.
242 *
243 * @param <U> the response body type
244 * @param value the value of U to return as the body
245 * @return a response body handler
246 */
247 public static <U> BodyHandler<U> discard(U value) {
248 return (status, headers) -> BodyProcessor.discard(value);
249 }
250
251 /**
252 * Returns a {@code BodyHandler<String>} that returns a
253 * {@link BodyProcessor BodyProcessor}{@code <String>} obtained from
254 * {@link BodyProcessor#asString(java.nio.charset.Charset)
255 * BodyProcessor.asString(Charset)}. If a charset is provided, the
256 * body is decoded using it. If charset is {@code null} then the processor
257 * tries to determine the character set from the {@code Content-encoding}
258 * header. If that charset is not supported then
259 * {@link java.nio.charset.StandardCharsets#UTF_8 UTF_8} is used.
260 *
261 * @param charset the name of the charset to interpret the body as. If
262 * {@code null} then charset determined from Content-encoding header
263 * @return a response body handler
264 */
265 public static BodyHandler<String> asString(Charset charset) {
266 return (status, headers) -> {
267 if (charset != null) {
268 return BodyProcessor.asString(charset);
269 }
270 return BodyProcessor.asString(charsetFrom(headers));
271 };
272 }
273
274
275 /**
276 * Returns a {@code BodyHandler<Path>} that returns a
277 * {@link BodyProcessor BodyProcessor}{@code <Path>} obtained from
278 * {@link BodyProcessor#asFile(Path) BodyProcessor.asFile(Path)}.
279 * <p>
280 * When the {@code HttpResponse} object is returned, the body has been completely
281 * written to the file, and {@link #body()} returns a reference to its
282 * {@link Path}.
283 *
284 * @param file the file to store the body in
285 * @return a response body handler
286 */
287 public static BodyHandler<Path> asFile(Path file) {
288 return (status, headers) -> BodyProcessor.asFile(file);
289 }
290
291 /**
292 * Returns a {@code BodyHandler<Path>} that returns a
293 * {@link BodyProcessor BodyProcessor}<{@link Path}>
294 * where the download directory is specified, but the filename is
295 * obtained from the {@code Content-Disposition} response header. The
296 * {@code Content-Disposition} header must specify the <i>attachment</i> type
297 * and must also contain a
298 * <i>filename</i> parameter. If the filename specifies multiple path
299 * components only the final component is used as the filename (with the
300 * given directory name). When the {@code HttpResponse} object is
301 * returned, the body has been completely written to the file and {@link
302 * #body()} returns a {@code Path} object for the file. The returned {@code Path} is the
303 * combination of the supplied directory name and the file name supplied
304 * by the server. If the destination directory does not exist or cannot
305 * be written to, then the response will fail with an {@link IOException}.
306 *
307 * @param directory the directory to store the file in
308 * @param openOptions open options
309 * @return a response body handler
310 */
311 public static BodyHandler<Path> asFileDownload(Path directory, OpenOption... openOptions) {
312 return (status, headers) -> {
313 String dispoHeader = headers.firstValue("Content-Disposition")
314 .orElseThrow(() -> unchecked(new IOException("No Content-Disposition")));
315 if (!dispoHeader.startsWith("attachment;")) {
316 throw unchecked(new IOException("Unknown Content-Disposition type"));
317 }
318 int n = dispoHeader.indexOf("filename=");
319 if (n == -1) {
320 throw unchecked(new IOException("Bad Content-Disposition type"));
321 }
322 int lastsemi = dispoHeader.lastIndexOf(';');
323 String disposition;
324 if (lastsemi < n) {
325 disposition = dispoHeader.substring(n + 9);
326 } else {
327 disposition = dispoHeader.substring(n + 9, lastsemi);
328 }
329 Path file = Paths.get(directory.toString(), disposition);
330 return BodyProcessor.asFile(file, openOptions);
331 };
332 }
333
334 /**
335 * Returns a {@code BodyHandler<Path>} that returns a
336 * {@link BodyProcessor BodyProcessor}{@code <Path>} obtained from
337 * {@link BodyProcessor#asFile(java.nio.file.Path, java.nio.file.OpenOption...)
338 * BodyProcessor.asFile(Path,OpenOption...)}.
339 * <p>
340 * When the {@code HttpResponse} object is returned, the body has been completely
341 * written to the file, and {@link #body()} returns a reference to its
342 * {@link Path}.
343 *
344 * @param file the filename to store the body in
345 * @param openOptions any options to use when opening/creating the file
346 * @return a response body handler
347 */
348 public static BodyHandler<Path> asFile(Path file, OpenOption... openOptions) {
349 return (status, headers) -> BodyProcessor.asFile(file, openOptions);
350 }
351
352 /**
353 * Returns a {@code BodyHandler<Void>} that returns a
354 * {@link BodyProcessor BodyProcessor}{@code <Void>} obtained from
355 * {@link BodyProcessor#asByteArrayConsumer(java.util.function.Consumer)
356 * BodyProcessor.asByteArrayConsumer(Consumer)}.
357 * <p>
358 * When the {@code HttpResponse} object is returned, the body has been completely
359 * written to the consumer.
360 *
361 * @param consumer a Consumer to accept the response body
362 * @return a response body handler
363 */
364 public static BodyHandler<Void> asByteArrayConsumer(Consumer<Optional<byte[]>> consumer) {
365 return (status, headers) -> BodyProcessor.asByteArrayConsumer(consumer);
366 }
367
368 /**
369 * Returns a {@code BodyHandler<byte[]>} that returns a
370 * {@link BodyProcessor BodyProcessor}<{@code byte[]}> obtained
371 * from {@link BodyProcessor#asByteArray() BodyProcessor.asByteArray()}.
372 * <p>
373 * When the {@code HttpResponse} object is returned, the body has been completely
374 * written to the byte array.
375 *
376 * @return a response body handler
377 */
378 public static BodyHandler<byte[]> asByteArray() {
379 return (status, headers) -> BodyProcessor.asByteArray();
380 }
381
382 /**
383 * Returns a {@code BodyHandler<String>} that returns a
384 * {@link BodyProcessor BodyProcessor}{@code <String>} obtained from
385 * {@link BodyProcessor#asString(java.nio.charset.Charset)
386 * BodyProcessor.asString(Charset)}. The body is
387 * decoded using the character set specified in
388 * the {@code Content-encoding} response header. If there is no such
389 * header, or the character set is not supported, then
390 * {@link java.nio.charset.StandardCharsets#UTF_8 UTF_8} is used.
391 * <p>
392 * When the {@code HttpResponse} object is returned, the body has been completely
393 * written to the string.
394 *
395 * @return a response body handler
396 */
397 public static BodyHandler<String> asString() {
398 return (status, headers) -> BodyProcessor.asString(charsetFrom(headers));
399 }
400 }
401
402 /**
403 * A processor for response bodies.
404 * {@Incubating}
405 * <p>
406 * The object acts as a {@link Flow.Subscriber}<{@link ByteBuffer}> to
407 * the HTTP client implementation which publishes ByteBuffers containing the
408 * response body. The processor converts the incoming buffers of data to
409 * some user-defined object type {@code T}.
410 * <p>
411 * The {@link #getBody()} method returns a {@link CompletionStage}{@code <T>}
412 * that provides the response body object. The {@code CompletionStage} must
413 * be obtainable at any time. When it completes depends on the nature
414 * of type {@code T}. In many cases, when {@code T} represents the entire body after being
415 * read then it completes after the body has been read. If {@code T} is a streaming
589 *
590 * [Note] The reason for switching to this callback interface rather
591 * than using CompletableFutures supplied to onRequest() is that there
592 * is a subtle interaction between those CFs and the CF returned from
593 * completion() (or when onComplete() was called formerly). The completion()
594 * CF will not complete until after all of the work done by the onResponse()
595 * calls is done. Whereas if you just create CF's dependent on a supplied
596 * CF (to onRequest()) then the implementation has no visibility of the
597 * dependent CFs and can't guarantee to call onComplete() (or complete
598 * the completion() CF) after the dependent CFs complete.
599 *
600 * @param response the response received
601 */
602 void onResponse(HttpResponse<T> response);
603
604 /**
605 * Called if an error occurs receiving a response. For each request
606 * either one of onResponse() or onError() is guaranteed to be called,
607 * but not both.
608 *
609 * @param request the main request or subsequent push promise
610 * @param t the Throwable that caused the error
611 */
612 void onError(HttpRequest request, Throwable t);
613
614 /**
615 * Returns a {@link java.util.concurrent.CompletableFuture}{@code <U>}
616 * which completes when the aggregate result object itself is available.
617 * It is expected that the returned {@code CompletableFuture} will depend
618 * on one of the given {@code CompletableFuture<Void}s which themselves complete
619 * after all individual responses associated with the multi response
620 * have completed, or after all push promises have been received.
621 * <p>
622 * @implNote Implementations might follow the pattern shown below
623 * <pre>
624 * {@code
625 * CompletableFuture<U> completion(
626 * CompletableFuture<Void> onComplete,
627 * CompletableFuture<Void> onFinalPushPromise)
628 * {
629 * return onComplete.thenApply((v) -> {
700 * {@code
701 * HttpRequest request = HttpRequest.newBuilder()
702 * .uri(URI.create("https://www.foo.com/"))
703 * .GET()
704 * .build();
705 *
706 * HttpClient client = HttpClient.newHttpClient();
707 *
708 * Map<HttpRequest,CompletableFuture<HttpResponse<String>>> results = client
709 * .sendAsync(request, MultiProcessor.asMap(
710 * (req) -> Optional.of(HttpResponse.BodyHandler.asString())))
711 * .join();
712 * }</pre>
713 * <p>
714 * The lambda in this example is the simplest possible implementation,
715 * where neither the incoming requests are examined, nor the response
716 * headers, and every push that the server sends is accepted. When the
717 * join() call returns, all {@code HttpResponse}s and their associated
718 * body objects are available.
719 *
720 * @param <V> the body type used for all responses
721 * @param pushHandler a function invoked for each request or push
722 * promise
723 * @return a MultiProcessor
724 */
725 public static <V> MultiProcessor<MultiMapResult<V>,V> asMap(
726 Function<HttpRequest, Optional<HttpResponse.BodyHandler<V>>> pushHandler) {
727
728 return asMap(pushHandler, true);
729 }
730
731 }
732 }
|