Support Custom Headers for Multipart Async Data

This commit makes sure there is no custom Content-Disposition header
before setting one automatically.

This commit also adds a headers(Consumer<HttpHeaders>) method, so that
one can user the nicer methods of HttpHeaders, as opposed to basic
strings.

Issue: SPR-16376
This commit is contained in:
Arjen Poutsma
2018-01-20 15:16:52 +01:00
parent b2ce98e881
commit 646fcc5c2f
3 changed files with 71 additions and 16 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.http.client;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.reactivestreams.Publisher;
@@ -187,6 +188,13 @@ public final class MultipartBodyBuilder {
* @see HttpHeaders#add(String, String)
*/
PartBuilder header(String headerName, String... headerValues);
/**
* Manipulate the part's headers with the given consumer.
* @param headersConsumer a function that consumes the {@code HttpHeaders}
* @return this builder
*/
PartBuilder headers(Consumer<HttpHeaders> headersConsumer);
}
@@ -208,6 +216,13 @@ public final class MultipartBodyBuilder {
return this;
}
@Override
public PartBuilder headers(Consumer<HttpHeaders> headersConsumer) {
Assert.notNull(headersConsumer, "'headersConsumer' must not be null");
headersConsumer.accept(this.headers);
return this;
}
public HttpEntity<?> build() {
return new HttpEntity<>(this.body, this.headers);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -229,12 +229,13 @@ public class MultipartHttpMessageWriter implements HttpMessageWriter<MultiValueM
@SuppressWarnings("unchecked")
private <T> Flux<DataBuffer> encodePart(byte[] boundary, String name, T value) {
MultipartHttpOutputMessage outputMessage = new MultipartHttpOutputMessage(this.bufferFactory, getCharset());
HttpHeaders outputHeaders = outputMessage.getHeaders();
T body;
ResolvableType resolvableType = null;
if (value instanceof HttpEntity) {
HttpEntity<T> httpEntity = (HttpEntity<T>) value;
outputMessage.getHeaders().putAll(httpEntity.getHeaders());
outputHeaders.putAll(httpEntity.getHeaders());
body = httpEntity.getBody();
Assert.state(body != null, "MultipartHttpMessageWriter only supports HttpEntity with body");
@@ -247,24 +248,24 @@ public class MultipartHttpMessageWriter implements HttpMessageWriter<MultiValueM
else {
body = value;
}
if (resolvableType == null) {
resolvableType = ResolvableType.forClass(body.getClass());
}
if (body instanceof Resource) {
outputMessage.getHeaders().setContentDispositionFormData(name, ((Resource) body).getFilename());
}
else if (Resource.class.equals(resolvableType.getRawClass())) {
body = (T) Mono.from((Publisher<?>) body).doOnNext(o -> {
outputMessage.getHeaders().setContentDispositionFormData(name, ((Resource) o).getFilename());
});
}
else {
outputMessage.getHeaders().setContentDispositionFormData(name, null);
if (!outputHeaders.containsKey(HttpHeaders.CONTENT_DISPOSITION)) {
if (body instanceof Resource) {
outputHeaders.setContentDispositionFormData(name, ((Resource) body).getFilename());
}
else if (Resource.class.equals(resolvableType.getRawClass())) {
body = (T) Mono.from((Publisher<?>) body).doOnNext(o -> outputHeaders
.setContentDispositionFormData(name, ((Resource) o).getFilename()));
}
else {
outputHeaders.setContentDispositionFormData(name, null);
}
}
MediaType contentType = outputMessage.getHeaders().getContentType();
MediaType contentType = outputHeaders.getContentType();
final ResolvableType finalBodyType = resolvableType;
Optional<HttpMessageWriter<?>> writer = this.partWriters.stream()