|
|
|
@ -36,19 +36,39 @@ import org.springframework.util.LinkedMultiValueMap; |
|
|
|
|
import org.springframework.util.MultiValueMap; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* A mutable builder for multipart form bodies. For example: |
|
|
|
|
* Builder for the body of a multipart request, producing |
|
|
|
|
* {@code MultiValueMap<String, HttpEntity>}, which can be provided to the |
|
|
|
|
* {@code WebClient} through the {@code syncBody} method. |
|
|
|
|
* |
|
|
|
|
* Examples: |
|
|
|
|
* <pre class="code"> |
|
|
|
|
* |
|
|
|
|
* // Add form field
|
|
|
|
|
* MultipartBodyBuilder builder = new MultipartBodyBuilder(); |
|
|
|
|
* builder.part("form field", "form value"); |
|
|
|
|
* builder.part("form field", "form value").header("foo", "bar"); |
|
|
|
|
* |
|
|
|
|
* // Add file part
|
|
|
|
|
* Resource image = new ClassPathResource("image.jpg"); |
|
|
|
|
* builder.part("image", image).header("Baz", "Qux"); |
|
|
|
|
* builder.part("image", image).header("foo", "bar"); |
|
|
|
|
* |
|
|
|
|
* // Add content (e.g. JSON)
|
|
|
|
|
* Account account = ... |
|
|
|
|
* builder.part("account", account).header("foo", "bar"); |
|
|
|
|
* |
|
|
|
|
* // Add content from Publisher
|
|
|
|
|
* Mono<Account> accountMono = ... |
|
|
|
|
* builder.asyncPart("account", accountMono).header("foo", "bar"); |
|
|
|
|
* |
|
|
|
|
* // Build and use
|
|
|
|
|
* MultiValueMap<String, HttpEntity<?>> multipartBody = builder.build(); |
|
|
|
|
* // use multipartBody with RestTemplate or WebClient
|
|
|
|
|
* |
|
|
|
|
* Mono<Void> result = webClient.post() |
|
|
|
|
* .uri("...") |
|
|
|
|
* .syncBody(multipartBody) |
|
|
|
|
* .retrieve() |
|
|
|
|
* .bodyToMono(Void.class) |
|
|
|
|
* </pre> |
|
|
|
|
|
|
|
|
|
* |
|
|
|
|
* @author Arjen Poutsma |
|
|
|
|
* @author Rossen Stoyanchev |
|
|
|
|
* @since 5.0.2 |
|
|
|
@ -67,7 +87,14 @@ public final class MultipartBodyBuilder { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Add a part from an Object. |
|
|
|
|
* Add a part where the Object may be: |
|
|
|
|
* <ul> |
|
|
|
|
* <li>String -- form field |
|
|
|
|
* <li>{@link org.springframework.core.io.Resource Resource} -- file part |
|
|
|
|
* <li>Object -- content to be encoded (e.g. to JSON) |
|
|
|
|
* <li>HttpEntity -- part content and headers although generally it's |
|
|
|
|
* easier to add headers through the returned builder</li> |
|
|
|
|
* </ul> |
|
|
|
|
* @param name the name of the part to add |
|
|
|
|
* @param part the part data |
|
|
|
|
* @return builder that allows for further customization of part headers |
|
|
|
@ -77,51 +104,53 @@ public final class MultipartBodyBuilder { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Variant of {@link #part(String, Object)} that also accepts a MediaType |
|
|
|
|
* which is used to determine how to encode the part. |
|
|
|
|
* Variant of {@link #part(String, Object)} that also accepts a MediaType. |
|
|
|
|
* @param name the name of the part to add |
|
|
|
|
* @param part the part data |
|
|
|
|
* @param contentType the media type for the part |
|
|
|
|
* @param contentType the media type to help with encoding the part |
|
|
|
|
* @return builder that allows for further customization of part headers |
|
|
|
|
*/ |
|
|
|
|
public PartBuilder part(String name, Object part, @Nullable MediaType contentType) { |
|
|
|
|
Assert.hasLength(name, "'name' must not be empty"); |
|
|
|
|
Assert.notNull(part, "'part' must not be null"); |
|
|
|
|
|
|
|
|
|
if (part instanceof Publisher) { |
|
|
|
|
throw new IllegalArgumentException("Use publisher(String, Publisher, Class) or " + |
|
|
|
|
"publisher(String, Publisher, ParameterizedTypeReference) for adding Publisher parts"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (part instanceof PublisherEntity<?,?>) { |
|
|
|
|
PublisherPartBuilder<?, ?> builder = new PublisherPartBuilder<>((PublisherEntity<?, ?>) part); |
|
|
|
|
if (contentType != null) { |
|
|
|
|
builder.header(HttpHeaders.CONTENT_TYPE, contentType.toString()); |
|
|
|
|
} |
|
|
|
|
this.parts.add(name, builder); |
|
|
|
|
return builder; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Object partBody; |
|
|
|
|
HttpHeaders partHeaders = new HttpHeaders(); |
|
|
|
|
|
|
|
|
|
HttpHeaders partHeaders = null; |
|
|
|
|
if (part instanceof HttpEntity) { |
|
|
|
|
HttpEntity<?> httpEntity = (HttpEntity<?>) part; |
|
|
|
|
partBody = httpEntity.getBody(); |
|
|
|
|
partHeaders.addAll(httpEntity.getHeaders()); |
|
|
|
|
partBody = ((HttpEntity<?>) part).getBody(); |
|
|
|
|
partHeaders = new HttpHeaders(); |
|
|
|
|
partHeaders.putAll(((HttpEntity<?>) part).getHeaders()); |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
partBody = part; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (contentType != null) { |
|
|
|
|
partHeaders.setContentType(contentType); |
|
|
|
|
if (partBody instanceof Publisher) { |
|
|
|
|
throw new IllegalArgumentException( |
|
|
|
|
"Use asyncPart(String, Publisher, Class)" + |
|
|
|
|
" or asyncPart(String, Publisher, ParameterizedTypeReference) or" + |
|
|
|
|
" or MultipartBodyBuilder.PublisherEntity"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DefaultPartBuilder builder = new DefaultPartBuilder(partHeaders, partBody); |
|
|
|
|
if (contentType != null) { |
|
|
|
|
builder.header(HttpHeaders.CONTENT_TYPE, contentType.toString()); |
|
|
|
|
} |
|
|
|
|
this.parts.add(name, builder); |
|
|
|
|
return builder; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Add an asynchronous part with {@link Publisher}-based content. |
|
|
|
|
* Add a part from {@link Publisher} content. |
|
|
|
|
* @param name the name of the part to add |
|
|
|
|
* @param publisher the part contents |
|
|
|
|
* @param elementClass the type of elements contained in the publisher |
|
|
|
@ -132,17 +161,15 @@ public final class MultipartBodyBuilder { |
|
|
|
|
Assert.notNull(publisher, "'publisher' must not be null"); |
|
|
|
|
Assert.notNull(elementClass, "'elementClass' must not be null"); |
|
|
|
|
|
|
|
|
|
HttpHeaders headers = new HttpHeaders(); |
|
|
|
|
PublisherPartBuilder<T, P> builder = new PublisherPartBuilder<>(headers, publisher, elementClass); |
|
|
|
|
PublisherPartBuilder<T, P> builder = new PublisherPartBuilder<>(null, publisher, elementClass); |
|
|
|
|
this.parts.add(name, builder); |
|
|
|
|
return builder; |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Variant of {@link #asyncPart(String, Publisher, Class)} that accepts a |
|
|
|
|
* {@link ParameterizedTypeReference} for the element type, which allows |
|
|
|
|
* specifying generic type information. |
|
|
|
|
* Variant of {@link #asyncPart(String, Publisher, Class)} with a |
|
|
|
|
* {@link ParameterizedTypeReference} for the element type information. |
|
|
|
|
* @param name the name of the part to add |
|
|
|
|
* @param publisher the part contents |
|
|
|
|
* @param typeReference the type of elements contained in the publisher |
|
|
|
@ -155,8 +182,7 @@ public final class MultipartBodyBuilder { |
|
|
|
|
Assert.notNull(publisher, "'publisher' must not be null"); |
|
|
|
|
Assert.notNull(typeReference, "'typeReference' must not be null"); |
|
|
|
|
|
|
|
|
|
HttpHeaders headers = new HttpHeaders(); |
|
|
|
|
PublisherPartBuilder<T, P> builder = new PublisherPartBuilder<>(headers, publisher, typeReference); |
|
|
|
|
PublisherPartBuilder<T, P> builder = new PublisherPartBuilder<>(null, publisher, typeReference); |
|
|
|
|
this.parts.add(name, builder); |
|
|
|
|
return builder; |
|
|
|
|
} |
|
|
|
@ -201,28 +227,36 @@ public final class MultipartBodyBuilder { |
|
|
|
|
|
|
|
|
|
private static class DefaultPartBuilder implements PartBuilder { |
|
|
|
|
|
|
|
|
|
protected final HttpHeaders headers; |
|
|
|
|
@Nullable |
|
|
|
|
protected HttpHeaders headers; |
|
|
|
|
|
|
|
|
|
@Nullable |
|
|
|
|
protected final Object body; |
|
|
|
|
|
|
|
|
|
public DefaultPartBuilder(HttpHeaders headers, @Nullable Object body) { |
|
|
|
|
public DefaultPartBuilder(@Nullable HttpHeaders headers, @Nullable Object body) { |
|
|
|
|
this.headers = headers; |
|
|
|
|
this.body = body; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public PartBuilder header(String headerName, String... headerValues) { |
|
|
|
|
this.headers.addAll(headerName, Arrays.asList(headerValues)); |
|
|
|
|
initHeadersIfNecessary().addAll(headerName, Arrays.asList(headerValues)); |
|
|
|
|
return this; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public PartBuilder headers(Consumer<HttpHeaders> headersConsumer) { |
|
|
|
|
headersConsumer.accept(this.headers); |
|
|
|
|
headersConsumer.accept(initHeadersIfNecessary()); |
|
|
|
|
return this; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private HttpHeaders initHeadersIfNecessary() { |
|
|
|
|
if (this.headers == null) { |
|
|
|
|
this.headers = new HttpHeaders(); |
|
|
|
|
} |
|
|
|
|
return this.headers; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public HttpEntity<?> build() { |
|
|
|
|
return new HttpEntity<>(this.body, this.headers); |
|
|
|
|
} |
|
|
|
@ -233,14 +267,14 @@ public final class MultipartBodyBuilder { |
|
|
|
|
|
|
|
|
|
private final ResolvableType resolvableType; |
|
|
|
|
|
|
|
|
|
public PublisherPartBuilder(HttpHeaders headers, P body, Class<S> elementClass) { |
|
|
|
|
public PublisherPartBuilder(@Nullable HttpHeaders headers, P body, Class<S> elementClass) { |
|
|
|
|
super(headers, body); |
|
|
|
|
this.resolvableType = ResolvableType.forClass(elementClass); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public PublisherPartBuilder(HttpHeaders headers, P body, ParameterizedTypeReference<S> typeReference) { |
|
|
|
|
public PublisherPartBuilder(@Nullable HttpHeaders headers, P body, ParameterizedTypeReference<S> typeRef) { |
|
|
|
|
super(headers, body); |
|
|
|
|
this.resolvableType = ResolvableType.forType(typeReference); |
|
|
|
|
this.resolvableType = ResolvableType.forType(typeRef); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public PublisherPartBuilder(PublisherEntity<S, P> other) { |
|
|
|
@ -265,7 +299,7 @@ public final class MultipartBodyBuilder { |
|
|
|
|
* @param <T> the type contained in the publisher |
|
|
|
|
* @param <P> the publisher |
|
|
|
|
*/ |
|
|
|
|
public static final class PublisherEntity<T, P extends Publisher<T>> extends HttpEntity<P> |
|
|
|
|
static final class PublisherEntity<T, P extends Publisher<T>> extends HttpEntity<P> |
|
|
|
|
implements ResolvableTypeProvider { |
|
|
|
|
|
|
|
|
|
private final ResolvableType resolvableType; |
|
|
|
|