/* * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.incubator.http; import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; import jdk.incubator.http.ResponseProcessors.MultiFile; import jdk.incubator.http.ResponseProcessors.MultiProcessorImpl; import static jdk.incubator.http.internal.common.Utils.unchecked; import static jdk.incubator.http.internal.common.Utils.charsetFrom; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.Flow; import java.util.function.Consumer; import java.util.function.Function; import javax.net.ssl.SSLParameters; /** * Represents a response to a {@link HttpRequest}. * {@Incubating} * *

A {@code HttpResponse} is available when the response status code and * headers have been received, and typically after the response body has also * been received. This depends on the response body handler provided when * sending the request. In all cases, the response body handler is invoked * before the body is read. This gives applications an opportunity to decide * how to handle the body. * *

Methods are provided in this class for accessing the response headers, * and response body. *

* Response handlers and processors *

* Response bodies are handled at two levels. Application code supplies a response * handler ({@link BodyHandler}) which may examine the response status code * and headers, and which then returns a {@link BodyProcessor} to actually read * (or discard) the body and convert it into some useful Java object type. The handler * can return one of the pre-defined processor types, or a custom processor, or * if the body is to be discarded, it can call {@link BodyProcessor#discard(Object) * BodyProcessor.discard()} and return a processor which discards the response body. * Static implementations of both handlers and processors are provided in * {@link BodyHandler BodyHandler} and {@link BodyProcessor BodyProcessor} respectively. * In all cases, the handler functions provided are convenience implementations * which ignore the supplied status code and * headers and return the relevant pre-defined {@code BodyProcessor}. *

* See {@link BodyHandler} for example usage. * * @param the response body type * @since 9 */ public abstract class HttpResponse { /** * Creates an HttpResponse. */ protected HttpResponse() { } /** * Returns the status code for this response. * * @return the response code */ public abstract int statusCode(); /** * Returns the initial {@link HttpRequest} that initiated the exchange. * * @return the request */ public abstract HttpRequest request(); /** * Returns the final {@link HttpRequest} that was sent on the wire for the * exchange ( may, or may not, be the same as the initial request ). * * @return the request */ public abstract HttpRequest finalRequest(); /** * Returns the received response headers. * * @return the response headers */ public abstract HttpHeaders headers(); /** * Returns the received response trailers, if there are any, when they * become available. For many response processor types this will be at the same * time as the {@code HttpResponse} itself is available. In such cases, the * returned {@code CompletableFuture} will be already completed. * * @return a CompletableFuture of the response trailers (may be empty) */ public abstract CompletableFuture trailers(); /** * Returns the body. Depending on the type of {@code T}, the returned body may * represent the body after it was read (such as {@code byte[]}, or * {@code String}, or {@code Path}) or it may represent an object with * which the body is read, such as an {@link java.io.InputStream}. * * @return the body */ public abstract T body(); /** * Returns the {@link javax.net.ssl.SSLParameters} in effect for this * response. Returns {@code null} if this is not a HTTPS response. * * @return the SSLParameters associated with the response */ public abstract SSLParameters sslParameters(); /** * Returns the {@code URI} that the response was received from. This may be * different from the request {@code URI} if redirection occurred. * * @return the URI of the response */ public abstract URI uri(); /** * Returns the HTTP protocol version that was used for this response. * * @return HTTP protocol version */ public abstract HttpClient.Version version(); /** * A handler for response bodies. * {@Incubating} *

* This is a function that takes two parameters: the response status code, * and the response headers, and which returns a {@link BodyProcessor}. * The function is always called just before the response body is read. Its * implementation may examine the status code or headers and must decide, * whether to accept the response body or discard it, and if accepting it, * exactly how to handle it. *

* Some pre-defined implementations which do not utilize the status code * or headers (meaning the body is always accepted) are defined: *

*

* These implementations return the equivalent {@link BodyProcessor}. * Alternatively, the handler can be used to examine the status code * or headers and return different body processors as appropriate. *

* Examples of handler usage *

* The first example uses one of the predefined handler functions which * ignore the response headers and status, and always process the response * body in the same way. *

     * {@code
     *      HttpResponse resp = HttpRequest
     *              .create(URI.create("http://www.foo.com"))
     *              .GET()
     *              .response(BodyHandler.asFile(Paths.get("/tmp/f")));
     * }
     * 
* Note, that even though these pre-defined handlers ignore the status code * and headers, this information is still accessible from the {@code HttpResponse} * when it is returned. *

* In the second example, the function returns a different processor depending * on the status code. *

     * {@code
     *      HttpResponse resp1 = HttpRequest
     *              .create(URI.create("http://www.foo.com"))
     *              .GET()
     *              .response(
     *                  (status, headers) -> status == 200
     *                      ? BodyProcessor.asFile(Paths.get("/tmp/f"))
     *                      : BodyProcessor.discard(Paths.get("/NULL")));
     * }
     * 
* * @param the response body type. */ @FunctionalInterface public interface BodyHandler { /** * Returns a {@link BodyProcessor BodyProcessor} considering the given response status * code and headers. This method is always called before the body is read * and its implementation can decide to keep the body and store it somewhere * or else discard it, by returning the {@code BodyProcessor} returned * from {@link BodyProcessor#discard(java.lang.Object) discard()}. * * @param statusCode the HTTP status code received * @param responseHeaders the response headers received * @return a response body handler */ public BodyProcessor apply(int statusCode, HttpHeaders responseHeaders); /** * Returns a response body handler which discards the response body and * uses the given value as a replacement for it. * * @param the response body type * @param value the value of U to return as the body * @return a response body handler */ public static BodyHandler discard(U value) { return (status, headers) -> BodyProcessor.discard(value); } /** * Returns a {@code BodyHandler} that returns a * {@link BodyProcessor BodyProcessor}{@code } obtained from * {@link BodyProcessor#asString(java.nio.charset.Charset) * BodyProcessor.asString(Charset)}. If a charset is provided, the * body is decoded using it. If charset is {@code null} then the processor * tries to determine the character set from the {@code Content-encoding} * header. If that charset is not supported then * {@link java.nio.charset.StandardCharsets#UTF_8 UTF_8} is used. * * @param charset the name of the charset to interpret the body as. If * {@code null} then charset determined from Content-encoding header * @return a response body handler */ public static BodyHandler asString(Charset charset) { return (status, headers) -> { if (charset != null) { return BodyProcessor.asString(charset); } return BodyProcessor.asString(charsetFrom(headers)); }; } /** * Returns a {@code BodyHandler} that returns a * {@link BodyProcessor BodyProcessor}{@code } obtained from * {@link BodyProcessor#asFile(Path) BodyProcessor.asFile(Path)}. *

* When the {@code HttpResponse} object is returned, the body has been completely * written to the file, and {@link #body()} returns a reference to its * {@link Path}. * * @param file the file to store the body in * @return a response body handler */ public static BodyHandler asFile(Path file) { return (status, headers) -> BodyProcessor.asFile(file); } /** * Returns a {@code BodyHandler} that returns a * {@link BodyProcessor BodyProcessor}<{@link Path}> * where the download directory is specified, but the filename is * obtained from the {@code Content-Disposition} response header. The * {@code Content-Disposition} header must specify the attachment type * and must also contain a * filename parameter. If the filename specifies multiple path * components only the final component is used as the filename (with the * given directory name). When the {@code HttpResponse} object is * returned, the body has been completely written to the file and {@link * #body()} returns a {@code Path} object for the file. The returned {@code Path} is the * combination of the supplied directory name and the file name supplied * by the server. If the destination directory does not exist or cannot * be written to, then the response will fail with an {@link IOException}. * * @param directory the directory to store the file in * @param openOptions open options * @return a response body handler */ public static BodyHandler asFileDownload(Path directory, OpenOption... openOptions) { return (status, headers) -> { String dispoHeader = headers.firstValue("Content-Disposition") .orElseThrow(() -> unchecked(new IOException("No Content-Disposition"))); if (!dispoHeader.startsWith("attachment;")) { throw unchecked(new IOException("Unknown Content-Disposition type")); } int n = dispoHeader.indexOf("filename="); if (n == -1) { throw unchecked(new IOException("Bad Content-Disposition type")); } int lastsemi = dispoHeader.lastIndexOf(';'); String disposition; if (lastsemi < n) { disposition = dispoHeader.substring(n + 9); } else { disposition = dispoHeader.substring(n + 9, lastsemi); } Path file = Paths.get(directory.toString(), disposition); return BodyProcessor.asFile(file, openOptions); }; } /** * Returns a {@code BodyHandler} that returns a * {@link BodyProcessor BodyProcessor}{@code } obtained from * {@link BodyProcessor#asFile(java.nio.file.Path, java.nio.file.OpenOption...) * BodyProcessor.asFile(Path,OpenOption...)}. *

* When the {@code HttpResponse} object is returned, the body has been completely * written to the file, and {@link #body()} returns a reference to its * {@link Path}. * * @param file the filename to store the body in * @param openOptions any options to use when opening/creating the file * @return a response body handler */ public static BodyHandler asFile(Path file, OpenOption... openOptions) { return (status, headers) -> BodyProcessor.asFile(file, openOptions); } /** * Returns a {@code BodyHandler} that returns a * {@link BodyProcessor BodyProcessor}{@code } obtained from * {@link BodyProcessor#asByteArrayConsumer(java.util.function.Consumer) * BodyProcessor.asByteArrayConsumer(Consumer)}. *

* When the {@code HttpResponse} object is returned, the body has been completely * written to the consumer. * * @param consumer a Consumer to accept the response body * @return a response body handler */ public static BodyHandler asByteArrayConsumer(Consumer> consumer) { return (status, headers) -> BodyProcessor.asByteArrayConsumer(consumer); } /** * Returns a {@code BodyHandler} that returns a * {@link BodyProcessor BodyProcessor}<{@code byte[]}> obtained * from {@link BodyProcessor#asByteArray() BodyProcessor.asByteArray()}. *

* When the {@code HttpResponse} object is returned, the body has been completely * written to the byte array. * * @return a response body handler */ public static BodyHandler asByteArray() { return (status, headers) -> BodyProcessor.asByteArray(); } /** * Returns a {@code BodyHandler} that returns a * {@link BodyProcessor BodyProcessor}{@code } obtained from * {@link BodyProcessor#asString(java.nio.charset.Charset) * BodyProcessor.asString(Charset)}. The body is * decoded using the character set specified in * the {@code Content-encoding} response header. If there is no such * header, or the character set is not supported, then * {@link java.nio.charset.StandardCharsets#UTF_8 UTF_8} is used. *

* When the {@code HttpResponse} object is returned, the body has been completely * written to the string. * * @return a response body handler */ public static BodyHandler asString() { return (status, headers) -> BodyProcessor.asString(charsetFrom(headers)); } } /** * A processor for response bodies. * {@Incubating} *

* The object acts as a {@link Flow.Subscriber}<{@link ByteBuffer}> to * the HTTP client implementation which publishes ByteBuffers containing the * response body. The processor converts the incoming buffers of data to * some user-defined object type {@code T}. *

* The {@link #getBody()} method returns a {@link CompletionStage}{@code } * that provides the response body object. The {@code CompletionStage} must * be obtainable at any time. When it completes depends on the nature * of type {@code T}. In many cases, when {@code T} represents the entire body after being * read then it completes after the body has been read. If {@code T} is a streaming * type such as {@link java.io.InputStream} then it completes before the * body has been read, because the calling code uses it to consume the data. * * @param the response body type */ public interface BodyProcessor extends Flow.Subscriber { /** * Returns a {@code CompletionStage} which when completed will return the * response body object. * * @return a CompletionStage for the response body */ public CompletionStage getBody(); /** * Returns a body processor which stores the response body as a {@code * String} converted using the given {@code Charset}. *

* The {@link HttpResponse} using this processor is available after the * entire response has been read. * * @param charset the character set to convert the String with * @return a body processor */ public static BodyProcessor asString(Charset charset) { return new ResponseProcessors.ByteArrayProcessor<>( bytes -> new String(bytes, charset) ); } /** * Returns a {@code BodyProcessor} which stores the response body as a * byte array. *

* The {@link HttpResponse} using this processor is available after the * entire response has been read. * * @return a body processor */ public static BodyProcessor asByteArray() { return new ResponseProcessors.ByteArrayProcessor<>( Function.identity() // no conversion ); } /** * Returns a {@code BodyProcessor} which stores the response body in a * file opened with the given options and name. The file will be opened * with the given options using * {@link java.nio.channels.FileChannel#open(java.nio.file.Path,java.nio.file.OpenOption...) * FileChannel.open} just before the body is read. Any exception thrown will be returned * or thrown from {@link HttpClient#send(jdk.incubator.http.HttpRequest, * jdk.incubator.http.HttpResponse.BodyHandler) HttpClient::send} * or {@link HttpClient#sendAsync(jdk.incubator.http.HttpRequest, * jdk.incubator.http.HttpResponse.BodyHandler) HttpClient::sendAsync} * as appropriate. *

* The {@link HttpResponse} using this processor is available after the * entire response has been read. * * @param file the file to store the body in * @param openOptions the list of options to open the file with * @return a body processor */ public static BodyProcessor asFile(Path file, OpenOption... openOptions) { return new ResponseProcessors.PathProcessor(file, openOptions); } /** * Returns a {@code BodyProcessor} which provides the incoming body * data to the provided Consumer of {@code Optional}. Each * call to {@link Consumer#accept(java.lang.Object) Consumer.accept()} * will contain a non empty {@code Optional}, except for the final invocation after * all body data has been read, when the {@code Optional} will be empty. *

* The {@link HttpResponse} using this processor is available after the * entire response has been read. * * @param consumer a Consumer of byte arrays * @return a BodyProcessor */ public static BodyProcessor asByteArrayConsumer(Consumer> consumer) { return new ResponseProcessors.ConsumerProcessor(consumer); } /** * Returns a {@code BodyProcessor} which stores the response body in a * file opened with the given name. Has the same effect as calling * {@link #asFile(java.nio.file.Path, java.nio.file.OpenOption...) asFile} * with the standard open options {@code CREATE} and {@code WRITE} *

* The {@link HttpResponse} using this processor is available after the * entire response has been read. * * @param file the file to store the body in * @return a body processor */ public static BodyProcessor asFile(Path file) { return new ResponseProcessors.PathProcessor( file, StandardOpenOption.CREATE, StandardOpenOption.WRITE); } /** * Returns a response processor which discards the response body. The * supplied value is the value that will be returned from * {@link HttpResponse#body()}. * * @param The type of the response body * @param value the value to return from HttpResponse.body() * @return a {@code BodyProcessor} */ public static BodyProcessor discard(U value) { return new ResponseProcessors.NullProcessor<>(Optional.ofNullable(value)); } } /** * A response processor for a HTTP/2 multi response. * {@Incubating} *

* A multi response comprises a main response, and zero or more additional * responses. Each additional response is sent by the server in response to * requests that the server also generates. Additional responses are * typically resources that the server expects the client will need which * are related to the initial request. *

* Note. Instead of implementing this interface, applications should consider * first using the mechanism (built on this interface) provided by * {@link MultiProcessor#asMap(java.util.function.Function, boolean) * MultiProcessor.asMap()} which is a slightly simplified, but * general purpose interface. *

* The server generated requests are also known as push promises. * The server is permitted to send any number of these requests up to the * point where the main response is fully received. Therefore, after * completion of the main response, the final number of additional * responses is known. Additional responses may be canceled, but given that * the server does not wait for any acknowledgment before sending the * response, this must be done quickly to avoid unnecessary data transmission. * *

{@code MultiProcessor}s are parameterized with a type {@code U} which * represents some meaningful aggregate of the responses received. This * would typically be a collection of response or response body objects. * * @param a type representing the aggregated results * @param a type representing all of the response bodies * * @since 9 */ public interface MultiProcessor { /** * Called for the main request and each push promise that is received. * The first call will always be for the main request that was sent * by the caller. This {@link HttpRequest} parameter * represents the initial request or subsequent PUSH_PROMISE. The * implementation must return an {@code Optional} of {@link BodyHandler} for * the response body. Different handlers (of the same type) can be returned * for different pushes within the same multi send. If no handler * (an empty {@code Optional}) is returned, then the push will be canceled. It is * an error to not return a valid {@code BodyHandler} for the initial (main) request. * * @param request the main request or subsequent push promise * * @return an optional body handler */ Optional> onRequest(HttpRequest request); /** * Called for each response received. For each request either one of * onResponse() or onError() is guaranteed to be called, but not both. * * [Note] The reason for switching to this callback interface rather * than using CompletableFutures supplied to onRequest() is that there * is a subtle interaction between those CFs and the CF returned from * completion() (or when onComplete() was called formerly). The completion() * CF will not complete until after all of the work done by the onResponse() * calls is done. Whereas if you just create CF's dependent on a supplied * CF (to onRequest()) then the implementation has no visibility of the * dependent CFs and can't guarantee to call onComplete() (or complete * the completion() CF) after the dependent CFs complete. * * @param response the response received */ void onResponse(HttpResponse response); /** * Called if an error occurs receiving a response. For each request * either one of onResponse() or onError() is guaranteed to be called, * but not both. * * @param request the main request or subsequent push promise * @param t the Throwable that caused the error */ void onError(HttpRequest request, Throwable t); /** * Returns a {@link java.util.concurrent.CompletableFuture}{@code } * which completes when the aggregate result object itself is available. * It is expected that the returned {@code CompletableFuture} will depend * on one of the given {@code CompletableFuture * {@code * CompletableFuture completion( * CompletableFuture onComplete, * CompletableFuture onFinalPushPromise) * { * return onComplete.thenApply((v) -> { * U u = ... instantiate and populate a U instance * return u; * }); * } * } * * * @param onComplete a CompletableFuture which completes after all * responses have been received relating to this multi request. * * @param onFinalPushPromise CompletableFuture which completes after all * push promises have been received. * * @return the aggregate CF response object */ CompletableFuture completion(CompletableFuture onComplete, CompletableFuture onFinalPushPromise); /** * Returns a general purpose handler for multi responses. The aggregated * result object produced by this handler is a * {@code Map>>}. Each * request (both the original user generated request and each server * generated push promise) is returned as a key of the map. The value * corresponding to each key is a * {@code CompletableFuture>}. *

* There are two ways to use these handlers, depending on the value of * the completion parameter. If completion is true, then the * aggregated result will be available after all responses have * themselves completed. If completion is false, then the * aggregated result will be available immediately after the last push * promise was received. In the former case, this implies that all the * CompletableFutures in the map values will have completed. In the * latter case, they may or may not have completed yet. *

* The simplest way to use these handlers is to set completion to * {@code true}, and then all (results) values in the Map will be * accessible without blocking. *

* See {@link #asMap(java.util.function.Function, boolean) * } * for a code sample of using this interface. * * @param the body type used for all responses * @param pushHandler a function invoked for each request or push * promise * @param completion {@code true} if the aggregate CompletableFuture * completes after all responses have been received, or {@code false} * after all push promises received. * * @return a MultiProcessor */ public static MultiProcessor,V> asMap( Function>> pushHandler, boolean completion) { return new MultiProcessorImpl(pushHandler, completion); } /** * Returns a general purpose handler for multi responses. This is a * convenience method which invokes {@link #asMap(java.util.function.Function,boolean) * asMap(Function, true)} meaning that the aggregate result * object completes after all responses have been received. *

* Example usage: *
*

         * {@code
         *          HttpRequest request = HttpRequest.newBuilder()
         *                  .uri(URI.create("https://www.foo.com/"))
         *                  .GET()
         *                  .build();
         *
         *          HttpClient client = HttpClient.newHttpClient();
         *
         *          Map>> results = client
         *              .sendAsync(request, MultiProcessor.asMap(
         *                  (req) -> Optional.of(HttpResponse.BodyHandler.asString())))
         *              .join();
         * }
*

* The lambda in this example is the simplest possible implementation, * where neither the incoming requests are examined, nor the response * headers, and every push that the server sends is accepted. When the * join() call returns, all {@code HttpResponse}s and their associated * body objects are available. * * @param the body type used for all responses * @param pushHandler a function invoked for each request or push * promise * @return a MultiProcessor */ public static MultiProcessor,V> asMap( Function>> pushHandler) { return asMap(pushHandler, true); } } }