Path: blob/master/src/java.net.http/share/classes/java/net/http/HttpRequest.java
41159 views
/*1* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.net.http;2627import java.io.FileNotFoundException;28import java.io.InputStream;29import java.net.URI;30import java.nio.ByteBuffer;31import java.nio.charset.Charset;32import java.nio.charset.StandardCharsets;33import java.nio.file.Files;34import java.nio.file.OpenOption;35import java.nio.file.Path;36import java.time.Duration;37import java.util.Iterator;38import java.util.Objects;39import java.util.Optional;40import java.util.concurrent.Flow;41import java.util.function.BiPredicate;42import java.util.function.Supplier;4344import jdk.internal.net.http.HttpRequestBuilderImpl;45import jdk.internal.net.http.RequestPublishers;4647import static java.nio.charset.StandardCharsets.UTF_8;4849/**50* An HTTP request.51*52* <p> An {@code HttpRequest} instance is built through an {@code HttpRequest}53* {@linkplain HttpRequest.Builder builder}. An {@code HttpRequest} builder54* is obtained from one of the {@link HttpRequest#newBuilder(URI) newBuilder}55* methods. A request's {@link URI}, headers, and body can be set. Request56* bodies are provided through a {@link BodyPublisher BodyPublisher} supplied57* to one of the {@link Builder#POST(BodyPublisher) POST},58* {@link Builder#PUT(BodyPublisher) PUT} or59* {@link Builder#method(String,BodyPublisher) method} methods.60* Once all required parameters have been set in the builder, {@link61* Builder#build() build} will return the {@code HttpRequest}. Builders can be62* copied and modified many times in order to build multiple related requests63* that differ in some parameters.64*65* <p> The following is an example of a GET request that prints the response66* body as a String:67*68* <pre>{@code HttpClient client = HttpClient.newHttpClient();69* HttpRequest request = HttpRequest.newBuilder()70* .uri(URI.create("http://foo.com/"))71* .build();72* client.sendAsync(request, BodyHandlers.ofString())73* .thenApply(HttpResponse::body)74* .thenAccept(System.out::println)75* .join(); }</pre>76*77* <p>The class {@link BodyPublishers BodyPublishers} provides implementations78* of many common publishers. Alternatively, a custom {@code BodyPublisher}79* implementation can be used.80*81* @since 1182*/83public abstract class HttpRequest {8485/**86* Creates an HttpRequest.87*/88protected HttpRequest() {}8990/**91* A builder of {@linkplain HttpRequest HTTP requests}.92*93* <p> Instances of {@code HttpRequest.Builder} are created by calling94* {@link HttpRequest#newBuilder()}, {@link HttpRequest#newBuilder(URI)},95* or {@link HttpRequest#newBuilder(HttpRequest, BiPredicate)}.96*97* <p> The builder can be used to configure per-request state, such as: the98* request URI, the request method (default is GET unless explicitly set),99* specific request headers, etc. Each of the setter methods modifies the100* state of the builder and returns the same instance. The methods are not101* synchronized and should not be called from multiple threads without102* external synchronization. The {@link #build() build} method returns a new103* {@code HttpRequest} each time it is invoked. Once built an {@code104* HttpRequest} is immutable, and can be sent multiple times.105*106* <p> Note, that not all request headers may be set by user code. Some are107* restricted for security reasons and others such as the headers relating108* to authentication, redirection and cookie management may be managed by109* specific APIs rather than through directly user set headers.110*111* @since 11112*/113public interface Builder {114115/**116* Sets this {@code HttpRequest}'s request {@code URI}.117*118* @param uri the request URI119* @return this builder120* @throws IllegalArgumentException if the {@code URI} scheme is not121* supported122*/123public Builder uri(URI uri);124125/**126* Requests the server to acknowledge the request before sending the127* body. This is disabled by default. If enabled, the server is128* requested to send an error response or a {@code 100 Continue}129* response before the client sends the request body. This means the130* request publisher for the request will not be invoked until this131* interim response is received.132*133* @param enable {@code true} if Expect continue to be sent134* @return this builder135*/136public Builder expectContinue(boolean enable);137138/**139* Sets the preferred {@link HttpClient.Version} for this request.140*141* <p> The corresponding {@link HttpResponse} should be checked for the142* version that was actually used. If the version is not set in a143* request, then the version requested will be that of the sending144* {@link HttpClient}.145*146* @param version the HTTP protocol version requested147* @return this builder148*/149public Builder version(HttpClient.Version version);150151/**152* Adds the given name value pair to the set of headers for this request.153* The given value is added to the list of values for that name.154*155* @implNote An implementation may choose to restrict some header names156* or values, as the HTTP Client may determine their value itself.157* For example, "Content-Length", which will be determined by158* the request Publisher. In such a case, an implementation of159* {@code HttpRequest.Builder} may choose to throw an160* {@code IllegalArgumentException} if such a header is passed161* to the builder.162*163* @param name the header name164* @param value the header value165* @return this builder166* @throws IllegalArgumentException if the header name or value is not167* valid, see <a href="https://tools.ietf.org/html/rfc7230#section-3.2">168* RFC 7230 section-3.2</a>, or the header name or value is restricted169* by the implementation.170*/171public Builder header(String name, String value);172173/**174* Adds the given name value pairs to the set of headers for this175* request. The supplied {@code String} instances must alternate as176* header names and header values.177* To add several values to the same name then the same name must178* be supplied with each new value.179*180* @param headers the list of name value pairs181* @return this builder182* @throws IllegalArgumentException if there are an odd number of183* parameters, or if a header name or value is not valid, see184* <a href="https://tools.ietf.org/html/rfc7230#section-3.2">185* RFC 7230 section-3.2</a>, or a header name or value is186* {@linkplain #header(String, String) restricted} by the187* implementation.188*/189public Builder headers(String... headers);190191/**192* Sets a timeout for this request. If the response is not received193* within the specified timeout then an {@link HttpTimeoutException} is194* thrown from {@link HttpClient#send(java.net.http.HttpRequest,195* java.net.http.HttpResponse.BodyHandler) HttpClient::send} or196* {@link HttpClient#sendAsync(java.net.http.HttpRequest,197* java.net.http.HttpResponse.BodyHandler) HttpClient::sendAsync}198* completes exceptionally with an {@code HttpTimeoutException}. The effect199* of not setting a timeout is the same as setting an infinite Duration,200* i.e. block forever.201*202* @param duration the timeout duration203* @return this builder204* @throws IllegalArgumentException if the duration is non-positive205*/206public abstract Builder timeout(Duration duration);207208/**209* Sets the given name value pair to the set of headers for this210* request. This overwrites any previously set values for name.211*212* @param name the header name213* @param value the header value214* @return this builder215* @throws IllegalArgumentException if the header name or value is not valid,216* see <a href="https://tools.ietf.org/html/rfc7230#section-3.2">217* RFC 7230 section-3.2</a>, or the header name or value is218* {@linkplain #header(String, String) restricted} by the219* implementation.220*/221public Builder setHeader(String name, String value);222223/**224* Sets the request method of this builder to GET.225* This is the default.226*227* @return this builder228*/229public Builder GET();230231/**232* Sets the request method of this builder to POST and sets its233* request body publisher to the given value.234*235* @param bodyPublisher the body publisher236*237* @return this builder238*/239public Builder POST(BodyPublisher bodyPublisher);240241/**242* Sets the request method of this builder to PUT and sets its243* request body publisher to the given value.244*245* @param bodyPublisher the body publisher246*247* @return this builder248*/249public Builder PUT(BodyPublisher bodyPublisher);250251/**252* Sets the request method of this builder to DELETE.253*254* @return this builder255*/256public Builder DELETE();257258/**259* Sets the request method and request body of this builder to the260* given values.261*262* @apiNote The {@link BodyPublishers#noBody() noBody} request263* body publisher can be used where no request body is required or264* appropriate. Whether a method is restricted, or not, is265* implementation specific. For example, some implementations may choose266* to restrict the {@code CONNECT} method.267*268* @param method the method to use269* @param bodyPublisher the body publisher270* @return this builder271* @throws IllegalArgumentException if the method name is not272* valid, see <a href="https://tools.ietf.org/html/rfc7230#section-3.1.1">273* RFC 7230 section-3.1.1</a>, or the method is restricted by the274* implementation.275*/276public Builder method(String method, BodyPublisher bodyPublisher);277278/**279* Builds and returns an {@link HttpRequest}.280*281* @return a new {@code HttpRequest}282* @throws IllegalStateException if a URI has not been set283*/284public HttpRequest build();285286/**287* Returns an exact duplicate copy of this {@code Builder} based on288* current state. The new builder can then be modified independently of289* this builder.290*291* @return an exact copy of this builder292*/293public Builder copy();294}295296/**297* Creates an {@code HttpRequest} builder with the given URI.298*299* @param uri the request URI300* @return a new request builder301* @throws IllegalArgumentException if the URI scheme is not supported.302*/303public static HttpRequest.Builder newBuilder(URI uri) {304return new HttpRequestBuilderImpl(uri);305}306307/**308* Creates a {@code Builder} whose initial state is copied from an existing309* {@code HttpRequest}.310*311* <p> This builder can be used to build an {@code HttpRequest}, equivalent312* to the original, while allowing amendment of the request state prior to313* construction - for example, adding additional headers.314*315* <p> The {@code filter} is applied to each header name value pair as they316* are copied from the given request. When completed, only headers that317* satisfy the condition as laid out by the {@code filter} will be present318* in the {@code Builder} returned from this method.319*320* @apiNote321* The following scenarios demonstrate typical use-cases of the filter.322* Given an {@code HttpRequest} <em>request</em>:323* <br><br>324* <ul>325* <li> Retain all headers:326* <pre>{@code HttpRequest.newBuilder(request, (n, v) -> true)}</pre>327*328* <li> Remove all headers:329* <pre>{@code HttpRequest.newBuilder(request, (n, v) -> false)}</pre>330*331* <li> Remove a particular header (e.g. Foo-Bar):332* <pre>{@code HttpRequest.newBuilder(request, (name, value) -> !name.equalsIgnoreCase("Foo-Bar"))}</pre>333* </ul>334*335* @param request the original request336* @param filter a header filter337* @return a new request builder338* @throws IllegalArgumentException if a new builder cannot be seeded from339* the given request (for instance, if the request contains illegal340* parameters)341* @since 16342*/343public static Builder newBuilder(HttpRequest request, BiPredicate<String, String> filter) {344Objects.requireNonNull(request);345Objects.requireNonNull(filter);346347final HttpRequest.Builder builder = HttpRequest.newBuilder();348builder.uri(request.uri());349builder.expectContinue(request.expectContinue());350351// Filter unwanted headers352HttpHeaders headers = HttpHeaders.of(request.headers().map(), filter);353headers.map().forEach((name, values) ->354values.forEach(value -> builder.header(name, value)));355356request.version().ifPresent(builder::version);357request.timeout().ifPresent(builder::timeout);358var method = request.method();359request.bodyPublisher().ifPresentOrElse(360// if body is present, set it361bodyPublisher -> builder.method(method, bodyPublisher),362// otherwise, the body is absent, special case for GET/DELETE,363// or else use empty body364() -> {365switch (method) {366case "GET" -> builder.GET();367case "DELETE" -> builder.DELETE();368default -> builder.method(method, HttpRequest.BodyPublishers.noBody());369}370}371);372return builder;373}374375/**376* Creates an {@code HttpRequest} builder.377*378* @return a new request builder379*/380public static HttpRequest.Builder newBuilder() {381return new HttpRequestBuilderImpl();382}383384/**385* Returns an {@code Optional} containing the {@link BodyPublisher} set on386* this request. If no {@code BodyPublisher} was set in the requests's387* builder, then the {@code Optional} is empty.388*389* @return an {@code Optional} containing this request's {@code BodyPublisher}390*/391public abstract Optional<BodyPublisher> bodyPublisher();392393/**394* Returns the request method for this request. If not set explicitly,395* the default method for any request is "GET".396*397* @return this request's method398*/399public abstract String method();400401/**402* Returns an {@code Optional} containing this request's timeout duration.403* If the timeout duration was not set in the request's builder, then the404* {@code Optional} is empty.405*406* @return an {@code Optional} containing this request's timeout duration407*/408public abstract Optional<Duration> timeout();409410/**411* Returns this request's {@linkplain HttpRequest.Builder#expectContinue(boolean)412* expect continue} setting.413*414* @return this request's expect continue setting415*/416public abstract boolean expectContinue();417418/**419* Returns this request's {@code URI}.420*421* @return this request's URI422*/423public abstract URI uri();424425/**426* Returns an {@code Optional} containing the HTTP protocol version that427* will be requested for this {@code HttpRequest}. If the version was not428* set in the request's builder, then the {@code Optional} is empty.429* In that case, the version requested will be that of the sending430* {@link HttpClient}. The corresponding {@link HttpResponse} should be431* queried to determine the version that was actually used.432*433* @return HTTP protocol version434*/435public abstract Optional<HttpClient.Version> version();436437/**438* The (user-accessible) request headers that this request was (or will be)439* sent with.440*441* @return this request's HttpHeaders442*/443public abstract HttpHeaders headers();444445/**446* Tests this HTTP request instance for equality with the given object.447*448* <p> If the given object is not an {@code HttpRequest} then this449* method returns {@code false}. Two HTTP requests are equal if their URI,450* method, and headers fields are all equal.451*452* <p> This method satisfies the general contract of the {@link453* Object#equals(Object) Object.equals} method.454*455* @param obj the object to which this object is to be compared456* @return {@code true} if, and only if, the given object is an {@code457* HttpRequest} that is equal to this HTTP request458*/459@Override460public final boolean equals(Object obj) {461if (! (obj instanceof HttpRequest))462return false;463HttpRequest that = (HttpRequest)obj;464if (!that.method().equals(this.method()))465return false;466if (!that.uri().equals(this.uri()))467return false;468if (!that.headers().equals(this.headers()))469return false;470return true;471}472473/**474* Computes a hash code for this HTTP request instance.475*476* <p> The hash code is based upon the HTTP request's URI, method, and477* header components, and satisfies the general contract of the478* {@link Object#hashCode Object.hashCode} method.479*480* @return the hash-code value for this HTTP request481*/482public final int hashCode() {483return method().hashCode()484+ uri().hashCode()485+ headers().hashCode();486}487488/**489* A {@code BodyPublisher} converts high-level Java objects into a flow of490* byte buffers suitable for sending as a request body. The class491* {@link BodyPublishers BodyPublishers} provides implementations of many492* common publishers.493*494* <p> The {@code BodyPublisher} interface extends {@link Flow.Publisher495* Flow.Publisher<ByteBuffer>}, which means that a {@code BodyPublisher}496* acts as a publisher of {@linkplain ByteBuffer byte buffers}.497*498* <p> When sending a request that contains a body, the HTTP Client499* subscribes to the request's {@code BodyPublisher} in order to receive the500* flow of outgoing request body data. The normal semantics of {@link501* Flow.Subscriber} and {@link Flow.Publisher} are implemented by the HTTP502* Client and are expected from {@code BodyPublisher} implementations. Each503* outgoing request results in one HTTP Client {@code Subscriber}504* subscribing to the {@code BodyPublisher} in order to provide the sequence505* of byte buffers containing the request body. Instances of {@code506* ByteBuffer} published by the publisher must be allocated by the507* publisher, and must not be accessed after being published to the HTTP508* Client. These subscriptions complete normally when the request body is509* fully sent, and can be canceled or terminated early through error. If a510* request needs to be resent for any reason, then a new subscription is511* created which is expected to generate the same data as before.512*513* <p> A {@code BodyPublisher} that reports a {@linkplain #contentLength()514* content length} of {@code 0} may not be subscribed to by the HTTP Client,515* as it has effectively no data to publish.516*517* @see BodyPublishers518* @since 11519*/520public interface BodyPublisher extends Flow.Publisher<ByteBuffer> {521522/**523* Returns the content length for this request body. May be zero524* if no request body being sent, greater than zero for a fixed525* length content, or less than zero for an unknown content length.526*527* <p> This method may be invoked before the publisher is subscribed to.528* This method may be invoked more than once by the HTTP client529* implementation, and MUST return the same constant value each time.530*531* @return the content length for this request body, if known532*/533long contentLength();534}535536/**537* Implementations of {@link BodyPublisher BodyPublisher} that implement538* various useful publishers, such as publishing the request body from a539* String, or from a file.540*541* <p> The following are examples of using the predefined body publishers to542* convert common high-level Java objects into a flow of data suitable for543* sending as a request body:544*545* <pre>{@code // Request body from a String546* HttpRequest request = HttpRequest.newBuilder()547* .uri(URI.create("https://foo.com/"))548* .header("Content-Type", "text/plain; charset=UTF-8")549* .POST(BodyPublishers.ofString("some body text"))550* .build();551*552* // Request body from a File553* HttpRequest request = HttpRequest.newBuilder()554* .uri(URI.create("https://foo.com/"))555* .header("Content-Type", "application/json")556* .POST(BodyPublishers.ofFile(Paths.get("file.json")))557* .build();558*559* // Request body from a byte array560* HttpRequest request = HttpRequest.newBuilder()561* .uri(URI.create("https://foo.com/"))562* .POST(BodyPublishers.ofByteArray(new byte[] { ... }))563* .build(); }</pre>564*565* @since 11566*/567public static class BodyPublishers {568569private BodyPublishers() { }570571/**572* Returns a request body publisher whose body is retrieved from the573* given {@code Flow.Publisher}. The returned request body publisher574* has an unknown content length.575*576* @apiNote This method can be used as an adapter between {@code577* BodyPublisher} and {@code Flow.Publisher}, where the amount of578* request body that the publisher will publish is unknown.579*580* @param publisher the publisher responsible for publishing the body581* @return a BodyPublisher582*/583public static BodyPublisher584fromPublisher(Flow.Publisher<? extends ByteBuffer> publisher) {585return new RequestPublishers.PublisherAdapter(publisher, -1L);586}587588/**589* Returns a request body publisher whose body is retrieved from the590* given {@code Flow.Publisher}. The returned request body publisher591* has the given content length.592*593* <p> The given {@code contentLength} is a positive number, that594* represents the exact amount of bytes the {@code publisher} must595* publish.596*597* @apiNote This method can be used as an adapter between {@code598* BodyPublisher} and {@code Flow.Publisher}, where the amount of599* request body that the publisher will publish is known.600*601* @param publisher the publisher responsible for publishing the body602* @param contentLength a positive number representing the exact603* amount of bytes the publisher will publish604* @throws IllegalArgumentException if the content length is605* non-positive606* @return a BodyPublisher607*/608public static BodyPublisher609fromPublisher(Flow.Publisher<? extends ByteBuffer> publisher,610long contentLength) {611if (contentLength < 1)612throw new IllegalArgumentException("non-positive contentLength: "613+ contentLength);614return new RequestPublishers.PublisherAdapter(publisher, contentLength);615}616617/**618* Returns a request body publisher whose body is the given {@code619* String}, converted using the {@link StandardCharsets#UTF_8 UTF_8}620* character set.621*622* @param body the String containing the body623* @return a BodyPublisher624*/625public static BodyPublisher ofString(String body) {626return ofString(body, UTF_8);627}628629/**630* Returns a request body publisher whose body is the given {@code631* String}, converted using the given character set.632*633* @param s the String containing the body634* @param charset the character set to convert the string to bytes635* @return a BodyPublisher636*/637public static BodyPublisher ofString(String s, Charset charset) {638return new RequestPublishers.StringPublisher(s, charset);639}640641/**642* A request body publisher that reads its data from an {@link643* InputStream}. A {@link Supplier} of {@code InputStream} is used in644* case the request needs to be repeated, as the content is not buffered.645* The {@code Supplier} may return {@code null} on subsequent attempts,646* in which case the request fails.647*648* @param streamSupplier a Supplier of open InputStreams649* @return a BodyPublisher650*/651// TODO (spec): specify that the stream will be closed652public static BodyPublisher ofInputStream(Supplier<? extends InputStream> streamSupplier) {653return new RequestPublishers.InputStreamPublisher(streamSupplier);654}655656/**657* Returns a request body publisher whose body is the given byte array.658*659* @param buf the byte array containing the body660* @return a BodyPublisher661*/662public static BodyPublisher ofByteArray(byte[] buf) {663return new RequestPublishers.ByteArrayPublisher(buf);664}665666/**667* Returns a request body publisher whose body is the content of the668* given byte array of {@code length} bytes starting from the specified669* {@code offset}.670*671* @param buf the byte array containing the body672* @param offset the offset of the first byte673* @param length the number of bytes to use674* @return a BodyPublisher675* @throws IndexOutOfBoundsException if the sub-range is defined to be676* out of bounds677*/678public static BodyPublisher ofByteArray(byte[] buf, int offset, int length) {679Objects.checkFromIndexSize(offset, length, buf.length);680return new RequestPublishers.ByteArrayPublisher(buf, offset, length);681}682683/**684* A request body publisher that takes data from the contents of a File.685*686* <p> Security manager permission checks are performed in this factory687* method, when the {@code BodyPublisher} is created. Care must be taken688* that the {@code BodyPublisher} is not shared with untrusted code.689*690* @param path the path to the file containing the body691* @return a BodyPublisher692* @throws java.io.FileNotFoundException if the path is not found693* @throws SecurityException if694* {@linkplain Files#newInputStream(Path, OpenOption...)695* opening the file for reading} is denied:696* in the case of the system-default file system provider,697* and a security manager is installed,698* {@link SecurityManager#checkRead(String) checkRead}699* is invoked to check read access to the given file700*/701public static BodyPublisher ofFile(Path path) throws FileNotFoundException {702Objects.requireNonNull(path);703return RequestPublishers.FilePublisher.create(path);704}705706/**707* A request body publisher that takes data from an {@code Iterable}708* of byte arrays. An {@link Iterable} is provided which supplies709* {@link Iterator} instances. Each attempt to send the request results710* in one invocation of the {@code Iterable}.711*712* @param iter an Iterable of byte arrays713* @return a BodyPublisher714*/715public static BodyPublisher ofByteArrays(Iterable<byte[]> iter) {716return new RequestPublishers.IterablePublisher(iter);717}718719/**720* A request body publisher which sends no request body.721*722* @return a BodyPublisher which completes immediately and sends723* no request body.724*/725public static BodyPublisher noBody() {726return new RequestPublishers.EmptyPublisher();727}728729/**730* Returns a {@code BodyPublisher} that publishes a request731* body consisting of the concatenation of the request bodies732* published by a sequence of publishers.733*734* <p> If the sequence is empty an {@linkplain #noBody() empty} publisher735* is returned. Otherwise, if the sequence contains a single element,736* that publisher is returned. Otherwise a <em>concatenation publisher</em>737* is returned.738*739* <p> The request body published by a <em>concatenation publisher</em>740* is logically equivalent to the request body that would have741* been published by concatenating all the bytes of each publisher742* in sequence.743*744* <p> Each publisher is lazily subscribed to in turn,745* until all the body bytes are published, an error occurs, or the746* concatenation publisher's subscription is cancelled.747* The concatenation publisher may be subscribed to more than once,748* which in turn may result in the publishers in the sequence being749* subscribed to more than once.750*751* <p> The concatenation publisher has a known content752* length only if all publishers in the sequence have a known content753* length. The {@link BodyPublisher#contentLength() contentLength}754* reported by the concatenation publisher is computed as follows:755* <ul>756* <li> If any of the publishers reports an <em>{@linkplain757* BodyPublisher#contentLength() unknown}</em> content length,758* or if the sum of the known content lengths would exceed759* {@link Long#MAX_VALUE}, the resulting760* content length is <em>unknown</em>.</li>761* <li> Otherwise, the resulting content length is the sum of the762* known content lengths, a number between763* {@code 0} and {@link Long#MAX_VALUE}, inclusive.</li>764* </ul>765*766* @implNote If the concatenation publisher's subscription is767* {@linkplain Flow.Subscription#cancel() cancelled}, or an error occurs768* while publishing the bytes, not all publishers in the sequence may769* be subscribed to.770*771* @param publishers a sequence of publishers.772* @return An aggregate publisher that publishes a request body773* logically equivalent to the concatenation of all bytes published774* by each publisher in the sequence.775*776* @since 16777*/778public static BodyPublisher concat(BodyPublisher... publishers) {779return RequestPublishers.concat(Objects.requireNonNull(publishers));780}781}782}783784785